Skip to content

Commit d8a2637

Browse files
committed
allow example to be organized into folders
1 parent dece554 commit d8a2637

File tree

13 files changed

+331
-177
lines changed

13 files changed

+331
-177
lines changed

docs/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ RUN pip install -e .[all]
3131
# --------------
3232
ADD docs/main.py ./docs/
3333
ADD docs/source ./docs/source
34+
ADD examples ./examples
3435

3536
RUN pip install -r requirements/build-docs.txt
3637
RUN sphinx-build -b html docs/source docs/build
@@ -40,4 +41,4 @@ RUN sphinx-build -b html docs/source docs/build
4041
ENV PORT 5000
4142
ENV IDOM_DEBUG_MODE=1
4243
ENV IDOM_CHECK_VDOM_SPEC=0
43-
CMD python docs/main.py
44+
CMD python -c "import docs; docs.run()"

docs/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .app import run
2+
3+
4+
__all__ = ["run"]

docs/app.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
from logging import getLogger
3+
from pathlib import Path
4+
5+
from sanic import Sanic, response
6+
7+
from idom.config import IDOM_WED_MODULES_DIR
8+
from idom.server.sanic import PerClientStateServer
9+
from idom.widgets import multiview
10+
11+
from .examples import load_examples
12+
13+
14+
HERE = Path(__file__).parent
15+
IDOM_MODEL_SERVER_URL_PREFIX = "/_idom"
16+
17+
logger = getLogger(__name__)
18+
19+
20+
IDOM_MODEL_SERVER_URL_PREFIX = "/_idom"
21+
22+
23+
def run():
24+
app = make_app()
25+
26+
PerClientStateServer(
27+
make_examples_component(),
28+
{
29+
"redirect_root_to_index": False,
30+
"url_prefix": IDOM_MODEL_SERVER_URL_PREFIX,
31+
},
32+
app,
33+
)
34+
35+
app.run(
36+
host="0.0.0.0",
37+
port=int(os.environ.get("PORT", 5000)),
38+
workers=int(os.environ.get("WEB_CONCURRENCY", 1)),
39+
debug=bool(int(os.environ.get("DEBUG", "0"))),
40+
)
41+
42+
43+
def make_app():
44+
app = Sanic(__name__)
45+
app.static("/docs", str(HERE / "build"))
46+
app.static("/_modules", str(IDOM_WED_MODULES_DIR.current))
47+
48+
@app.route("/")
49+
async def forward_to_index(request):
50+
return response.redirect("/docs/index.html")
51+
52+
return app
53+
54+
55+
def make_examples_component():
56+
mount, component = multiview()
57+
58+
for example_name, example_component in load_examples():
59+
mount.add(example_name, example_component)
60+
61+
return component

docs/examples.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from traceback import format_exc
5+
from typing import Callable, Iterator
6+
7+
import idom
8+
from idom import ComponentType
9+
10+
11+
HERE = Path(__file__)
12+
EXAMPLES_DIR = HERE.parent / "source" / "_examples"
13+
RUN_IDOM = idom.run
14+
15+
16+
def load_examples() -> Iterator[tuple[str, Callable[[], ComponentType]]]:
17+
for file in EXAMPLES_DIR.rglob("*.py"):
18+
yield get_example_name_from_file(file), load_one_example(file)
19+
20+
21+
def all_example_names() -> set[str]:
22+
return {get_example_name_from_file(file) for file in EXAMPLES_DIR.rglob("*.py")}
23+
24+
25+
def load_one_example(file_or_name: Path | str) -> Callable[[], ComponentType]:
26+
if isinstance(file_or_name, str):
27+
file = get_py_example_file_by_name(file_or_name)
28+
else:
29+
file = file_or_name
30+
31+
if not file.exists():
32+
raise FileNotFoundError(str(file))
33+
34+
captured_component_constructor = None
35+
36+
def capture_component(component_constructor):
37+
nonlocal captured_component_constructor
38+
captured_component_constructor = component_constructor
39+
40+
idom.run = capture_component
41+
try:
42+
code = compile(file.read_text(), str(file.absolute()), "exec")
43+
exec(code, {})
44+
except Exception:
45+
return _make_error_display()
46+
finally:
47+
idom.run = RUN_IDOM
48+
49+
if captured_component_constructor is None:
50+
return _make_example_did_not_run(str(file))
51+
else:
52+
return captured_component_constructor
53+
54+
55+
def get_example_name_from_file(file: Path) -> str:
56+
return ".".join(file.relative_to(EXAMPLES_DIR).with_suffix("").parts)
57+
58+
59+
def get_py_example_file_by_name(name: str) -> Path:
60+
return EXAMPLES_DIR.joinpath(*name.split(".")).with_suffix(".py")
61+
62+
63+
def get_js_example_file_by_name(name: str) -> Path:
64+
return EXAMPLES_DIR.joinpath(*name.split(".")).with_suffix(".js")
65+
66+
67+
def _make_example_did_not_run(example_name):
68+
@idom.component
69+
def ExampleDidNotRun():
70+
return idom.html.code(f"Example {example_name} did not run")
71+
72+
return ExampleDidNotRun
73+
74+
75+
def _make_error_display():
76+
@idom.component
77+
def ShowError():
78+
return idom.html.pre(format_exc())
79+
80+
return ShowError

docs/main.py

Lines changed: 0 additions & 82 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from idom import component, html, run
2+
3+
4+
@component
5+
def App():
6+
return html.div(
7+
html.h1("My Todo List"),
8+
html.ul(
9+
html.li("Build a cool new app"),
10+
html.li("Share it with the world!"),
11+
),
12+
)
13+
14+
15+
run(App)

docs/source/_exts/widget_example.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
from sphinx.util.docutils import SphinxDirective
77
from sphinx_design.tabs import TabSetDirective
88

9+
from docs.examples import get_js_example_file_by_name, get_py_example_file_by_name
910

10-
here = Path(__file__).parent
11-
examples = here.parent / "_examples"
11+
12+
HERE = Path(__file__)
13+
EXAMPLES_DIR = HERE.parent.parent / "_examples"
1214

1315

1416
class WidgetExample(SphinxDirective):
@@ -29,16 +31,16 @@ def run(self):
2931
live_example_is_default_tab = "result-is-default-tab" in self.options
3032
activate_result = "activate-result" in self.options
3133

32-
py_ex_path = examples / f"{example_name}.py"
34+
py_ex_path = get_py_example_file_by_name(example_name)
3335
if not py_ex_path.exists():
3436
src_file, line_num = self.get_source_info()
3537
raise ValueError(
3638
f"Missing example file named {py_ex_path} referenced by document {src_file}:{line_num}"
3739
)
3840

3941
labeled_tab_items = {
40-
"python": _literal_include_py(
41-
name=example_name,
42+
"python": _literal_include(
43+
name=str(py_ex_path.relative_to(EXAMPLES_DIR)),
4244
linenos=show_linenos,
4345
),
4446
"result": _interactive_widget(
@@ -53,9 +55,10 @@ def run(self):
5355
"result": "▶️ Result",
5456
}
5557

56-
if (examples / f"{example_name}.js").exists():
57-
labeled_tab_items["javascript"] = _literal_include_js(
58-
name=example_name,
58+
js_ex_path = get_js_example_file_by_name(example_name)
59+
if js_ex_path.exists():
60+
labeled_tab_items["javascript"] = _literal_include(
61+
name=str(js_ex_path.relative_to(EXAMPLES_DIR)),
5962
linenos=show_linenos,
6063
)
6164

@@ -94,20 +97,17 @@ def _make_tab_items(labeled_content_tuples):
9497
return _string_to_nested_lines(tab_items)
9598

9699

97-
def _literal_include_py(name, linenos):
98-
return _literal_include_template.format(
99-
name=name,
100-
ext="py",
101-
language="python",
102-
linenos=":linenos:" if linenos else "",
103-
)
104-
100+
def _literal_include(name, linenos):
101+
if name.endswith(".py"):
102+
language = "python"
103+
elif name.endswith(".js"):
104+
language = "javascript"
105+
else:
106+
raise ValueError("Unknown extension type")
105107

106-
def _literal_include_js(name, linenos):
107108
return _literal_include_template.format(
108109
name=name,
109-
ext="js",
110-
language="javascript",
110+
language=language,
111111
linenos=":linenos:" if linenos else "",
112112
)
113113

@@ -133,7 +133,7 @@ def _interactive_widget(name, with_activate_button):
133133

134134

135135
_literal_include_template = """
136-
.. literalinclude:: /_examples/{name}.{ext}
136+
.. literalinclude:: /_examples/{name}
137137
:language: {language}
138138
{linenos}
139139
"""

0 commit comments

Comments
 (0)