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}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) + + +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 inapp.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) +