diff --git a/examples/iniconfig/README.md b/examples/iniconfig/README.md new file mode 100644 index 0000000..d1c32a4 --- /dev/null +++ b/examples/iniconfig/README.md @@ -0,0 +1,18 @@ +# iniconfig Examples + +Each sub-directory contains a self-contained example. The order in +which the examples are to appear is specified in `order.json` (an +array of directory names in the expected order). + +In each example directory you'll find: + +* `config.toml` - must conform to the specification outlined here: + https://docs.pyscript.net/latest/user-guide/configuration/ This is + parsed and ultimately turned into a JSON representation as part of + the package's API object. +* `setup.py` - Python code for contextual and environmental setup, + NOT SEEN BY THE END USER, but is run before the `code.py` code is + evaluated. Allows us to create useful (IPython) shims, avoid + repeating boilerplate and whatnot. +* `code.py` - the actual code added to the editor which forms the + practical example of using the package. diff --git a/examples/iniconfig/iterate_and_handle_errors/code.py b/examples/iniconfig/iterate_and_handle_errors/code.py new file mode 100644 index 0000000..bf17bb8 --- /dev/null +++ b/examples/iniconfig/iterate_and_handle_errors/code.py @@ -0,0 +1,91 @@ +# --------------------------------------------------------------------- +# Iterating an IniConfig and dealing with malformed input. +# --------------------------------------------------------------------- + +heading("Iterating sections and items") +note( + "An IniConfig is iterable: looping over it yields " + "sections in the order they appeared in the source. Each " + "section in turn yields its (name, value) pairs " + "via .items(). iniconfig also supports multi-line " + "values -- just indent the continuation lines." +) + +ini_source = """ +[fiction] +title = The Left Hand of Darkness +author = Ursula K. Le Guin +tags = sci-fi,classic + +[non_fiction] +title = The Making of the Atomic Bomb +author = Richard Rhodes + +[poetry] +title = Ariel +author = Sylvia Plath +# A multi-line value: indent continuation lines. +notes = + Published posthumously in 1965. + Restored edition followed in 2004. +""" + +shelf = iniconfig.IniConfig("shelf.ini", data=ini_source) + +# Membership tests work on section names. +note(f"Has 'poetry' section? {'poetry' in shelf}") +note(f"Has 'cookbooks' section? {'cookbooks' in shelf}") + +# Walk every section and dump its items as a small HTML list. +rows = [] +for section in shelf: + items_html = "".join( + f"
  • {name} = {value!r}
  • " + for name, value in section.items() + ) + rows.append(f"

    [{section.name}]

    ") +display(HTML("".join(rows)), append=True) + +# --------------------------------------------------------------------- +# Parse errors carry the line number, which makes them easy to +# surface to a user. +# --------------------------------------------------------------------- +heading("Parse errors point at the offending line") +note( + "If the source is malformed, iniconfig raises " + "ParseError with the file label and line number. " + "The example below has a stray key outside any section." +) + +broken_source = """ +[ok] +key = value + +stray_key = oops, no section above me + +[also_ok] +foo = bar +""" + +try: + iniconfig.IniConfig("broken.ini", data=broken_source) +except iniconfig.ParseError as err: + note( + f"Caught ParseError: {err}" + ) + +# Duplicate section names are also rejected. +duplicate_source = """ +[server] +host = a + +[server] +host = b +""" + +try: + iniconfig.IniConfig("dup.ini", data=duplicate_source) +except iniconfig.ParseError as err: + note( + f"Duplicate sections raise too: {err}" + ) diff --git a/examples/iniconfig/iterate_and_handle_errors/config.toml b/examples/iniconfig/iterate_and_handle_errors/config.toml new file mode 100644 index 0000000..60d1a10 --- /dev/null +++ b/examples/iniconfig/iterate_and_handle_errors/config.toml @@ -0,0 +1 @@ +packages = ["iniconfig"] diff --git a/examples/iniconfig/iterate_and_handle_errors/setup.py b/examples/iniconfig/iterate_and_handle_errors/setup.py new file mode 100644 index 0000000..bbc1dee --- /dev/null +++ b/examples/iniconfig/iterate_and_handle_errors/setup.py @@ -0,0 +1,22 @@ +"""Lighter setup for the second example: same names, no IPython shim.""" +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display( + *args, **kwargs, target=__pyscript_display_target__, + ) + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

    {text}

    "), append=True) + + +import iniconfig diff --git a/examples/iniconfig/order.json b/examples/iniconfig/order.json new file mode 100644 index 0000000..73e9434 --- /dev/null +++ b/examples/iniconfig/order.json @@ -0,0 +1,4 @@ +[ + "parse_ini_basics", + "iterate_and_handle_errors" +] diff --git a/examples/iniconfig/parse_ini_basics/code.py b/examples/iniconfig/parse_ini_basics/code.py new file mode 100644 index 0000000..ba72b83 --- /dev/null +++ b/examples/iniconfig/parse_ini_basics/code.py @@ -0,0 +1,60 @@ +""" +A first look at iniconfig: parsing a small INI file in memory. + +iniconfig is a tiny, dependency-free parser that preserves the order +of sections and entries, supports `#` comments, and reports proper +line numbers on errors. See https://github.com/pytest-dev/iniconfig +for the source. + +Normally you'd call `IniConfig("path/to/file.ini")`, which reads the +file from disk. We can also pass the source text directly using the +`data` argument, which is handy when the content lives in memory -- +as it does here in the browser. +""" +from IPython.core.display import display, HTML + +import iniconfig + +heading("A made-up app config") +note( + "Imagine the INI text below lives in app.ini on " + "disk. We'll parse it and pull values out by section and key." +) + +ini_source = """ +# content of app.ini +[server] # web server settings +host = localhost +port = 8080 + +[database] +url = sqlite:///app.db +pool_size = 5 + +[features] +# comma-separated flags, parsed below +enabled = search,export,dark_mode +""" + +# Pass the source via `data=`; the first arg is just a label used in +# error messages. +config = iniconfig.IniConfig("app.ini", data=ini_source) + +# Index a section like a dict, then index a key like a dict. +note(f"Server host: {config['server']['host']}") +note(f"Server port: {config['server']['port']}") +note(f"Database URL: {config['database']['url']}") + +# `get` lets you supply a default and a converter function; perfect +# for splitting CSV-style values or coercing to int. +enabled = config.get( + "features", "enabled", default=[], convert=lambda x: x.split(","), +) +pool = config.get("database", "pool_size", default=1, convert=int) + +note(f"Enabled features: {enabled}") +note(f"Pool size (as int): {pool} (type: {type(pool).__name__})") + +# `get` with a missing key returns the default rather than raising. +missing = config.get("server", "timeout", default=30, convert=int) +note(f"Missing key falls back to default: {missing}") diff --git a/examples/iniconfig/parse_ini_basics/config.toml b/examples/iniconfig/parse_ini_basics/config.toml new file mode 100644 index 0000000..60d1a10 --- /dev/null +++ b/examples/iniconfig/parse_ini_basics/config.toml @@ -0,0 +1 @@ +packages = ["iniconfig"] diff --git a/examples/iniconfig/parse_ini_basics/setup.py b/examples/iniconfig/parse_ini_basics/setup.py new file mode 100644 index 0000000..07879f9 --- /dev/null +++ b/examples/iniconfig/parse_ini_basics/setup.py @@ -0,0 +1,42 @@ +""" +Shim IPython's display API onto PyScript so example code written in a +Jupyter/IPython idiom runs unmodified in the browser. +""" + +import sys +import types +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + """Wrap pyscript.display so output lands in the example target.""" + return _display( + *args, **kwargs, target=__pyscript_display_target__, + ) + + +ipython = types.ModuleType("IPython") +core = types.ModuleType("IPython.core") +core_display = types.ModuleType("IPython.core.display") +core_display.display = display +core_display.HTML = HTML +ipython.core = core +core.display = core_display +ipython.get_ipython = lambda: None +ipython.display = core_display +sys.modules["IPython"] = ipython +sys.modules["IPython.core"] = core +sys.modules["IPython.core.display"] = core_display +sys.modules["IPython.display"] = core_display + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

    {text}

    "), append=True) +