barefootjs 0.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: barefootjs
3
+ Version: 0.18.0
4
+ Summary: Python runtime for the @barefootjs/jinja adapter
5
+ Author-email: kobaken <kentafly88@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/piconic-ai/barefootjs
8
+ Project-URL: Repository, https://github.com/piconic-ai/barefootjs
9
+ Keywords: jinja,jinja2,barefoot,ssr,templates
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: jinja2>=3.1
@@ -0,0 +1 @@
1
+ 0.18.0
@@ -0,0 +1,57 @@
1
+ """barefootjs -- Python runtime for the @barefootjs/jinja adapter.
2
+
3
+ Python port of the Perl `BarefootJS` runtime (`packages/adapter-perl`),
4
+ targeting Jinja2 as the template engine (mirroring `packages/adapter-xslate`'s
5
+ Text::Xslate backend). Generated `.jinja` templates call the `BarefootJS`
6
+ instance as a `bf` object: `{{ bf.scope_attr() }}`, `{{ bf.json(x) }}`,
7
+ `{{ bf.spread_attrs(bag) }}`.
8
+
9
+ Quick start::
10
+
11
+ from barefootjs import BarefootJS, JinjaBackend
12
+
13
+ backend = JinjaBackend(paths=["templates"])
14
+ bf = BarefootJS(None, {"backend": backend})
15
+ html = backend.render_named("counter", bf, {"count": 0})
16
+
17
+ Package layout (mirrors the Perl distribution's module layering):
18
+
19
+ runtime.py -- the engine-agnostic `bf` object (port of BarefootJS.pm)
20
+ evaluator.py -- ParsedExpr evaluator for the `*_eval` helpers
21
+ (port of BarefootJS/Evaluator.pm)
22
+ search_params.py -- searchParams() SSR reader (port of
23
+ BarefootJS/SearchParams.pm)
24
+ backend_jinja.py -- the Jinja2 engine backend (port of
25
+ BarefootJS::Backend::Xslate)
26
+
27
+ Only dependency: `jinja2` (which brings in `markupsafe`). Everything else is
28
+ stdlib.
29
+
30
+ Run the test suite with:
31
+
32
+ python3 -m unittest discover -s packages/adapter-jinja/python/tests -t packages/adapter-jinja/python
33
+
34
+ (equivalently, from `packages/adapter-jinja/python`:
35
+ `python3 -m unittest discover -s tests`.)
36
+ """
37
+
38
+ from .backend_jinja import JinjaBackend, default_json_encoder
39
+ from .evaluator import eval_json
40
+ from .evaluator import evaluate as evaluate_expr
41
+ from .runtime import BarefootJS, jinja_ident, js_bool_str, js_number, js_string, js_truthy, mangle_ident
42
+ from .search_params import SearchParams
43
+
44
+ __all__ = [
45
+ "BarefootJS",
46
+ "JinjaBackend",
47
+ "SearchParams",
48
+ "default_json_encoder",
49
+ "eval_json",
50
+ "evaluate_expr",
51
+ "jinja_ident",
52
+ "mangle_ident",
53
+ "js_bool_str",
54
+ "js_number",
55
+ "js_string",
56
+ "js_truthy",
57
+ ]
@@ -0,0 +1,125 @@
1
+ """Python port of packages/adapter-xslate/lib/BarefootJS/Backend/Xslate.pm.
2
+
3
+ Jinja2 rendering backend for the `barefootjs` runtime.
4
+
5
+ The engine-agnostic runtime logic (the JS-compat value helpers, array/string
6
+ methods, hydration markers, child rendering) lives in `runtime.BarefootJS`.
7
+ This backend supplies the four engine-specific operations the runtime
8
+ delegates to, targeting Jinja2 syntax:
9
+
10
+ encode_json(data) -> JSON string (injectable encoder)
11
+ mark_raw(str) -> a value Jinja emits verbatim (no re-escaping)
12
+ materialize(value) -> resolve a captured-children value to a string
13
+ render_named(name, bf, vars) -> render `<name>.jinja` with `bf` + vars bound
14
+
15
+ Pair it with the `@barefootjs/jinja` compile-time adapter, which emits Jinja2
16
+ `.jinja` templates that call the runtime as a `bf` object: `{{ bf.scope_attr()
17
+ }}`, `{{ bf.json(x) }}`, `{{ bf.spread_attrs(bag) }}`. Jinja auto-escapes
18
+ `{{ ... }}` interpolations (the Environment is built with `autoescape=True`);
19
+ helpers that emit markup return `mark_raw` (`markupsafe.Markup`) values so
20
+ they render unescaped, mirroring Kolon's `| mark_raw` / Mojo EP's `<%==` vs
21
+ `<%=` distinction.
22
+
23
+ Unlike a web-framework plugin, this has no dependency on a specific web
24
+ framework: a plain `jinja2.Environment` renders templates from a path, so it
25
+ runs under any WSGI app (Flask, Plack-equivalent) or none at all.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import json as _json
31
+ import math
32
+ from typing import Any, Callable, Optional, Sequence
33
+
34
+ from jinja2 import ChainableUndefined, Environment, FileSystemLoader
35
+ from markupsafe import Markup
36
+
37
+ from .runtime import jinja_ident
38
+
39
+
40
+ def _prepare_for_json(value: Any) -> Any:
41
+ """Recursively replace non-finite floats with `None` so they encode as
42
+ JSON `null` -- matches `JSON.stringify(NaN)` / `JSON.stringify(Infinity)`
43
+ at ANY depth (a strictly more complete carve-out than the Go/Perl
44
+ backends' documented top-level-only handling, and still spec-compliant:
45
+ the template-helpers.md `json` entry only requires the top level).
46
+ Anything else JSON can't encode (cycles, non-serialisable objects) is
47
+ left for `json.dumps` to raise on -- the failure policy mirrors Go/Perl:
48
+ user-data marshalling bubbles errors loudly rather than silently
49
+ producing an empty payload."""
50
+ if isinstance(value, float):
51
+ if math.isnan(value) or math.isinf(value):
52
+ return None
53
+ return value
54
+ if isinstance(value, dict):
55
+ return {k: _prepare_for_json(v) for k, v in value.items()}
56
+ if isinstance(value, (list, tuple)):
57
+ return [_prepare_for_json(v) for v in value]
58
+ return value
59
+
60
+
61
+ def default_json_encoder(data: Any) -> str:
62
+ """`sort_keys=True` keeps key order deterministic (matching the Xslate
63
+ backend's `JSON::PP->canonical` -- see `render.t`'s "canonical key
64
+ order" assertion, ported verbatim in this package's render test).
65
+ `separators=(',', ':')` matches `JSON.stringify`'s compact form."""
66
+ return _json.dumps(
67
+ _prepare_for_json(data), separators=(",", ":"), sort_keys=True, allow_nan=False
68
+ )
69
+
70
+
71
+ class JinjaBackend:
72
+ """Jinja2 rendering backend. Accepts a pre-built `Environment`, or builds
73
+ one from `paths` (a list of template directories) plus optional
74
+ `environment_options`. `json_encoder` overrides the default canonical
75
+ (sorted-key) encoder."""
76
+
77
+ def __init__(
78
+ self,
79
+ *,
80
+ env: Optional[Environment] = None,
81
+ paths: Optional[Sequence[str]] = None,
82
+ json_encoder: Optional[Callable[[Any], str]] = None,
83
+ environment_options: Optional[dict] = None,
84
+ ):
85
+ self._json_encoder = json_encoder or default_json_encoder
86
+ if env is not None:
87
+ self._env = env
88
+ else:
89
+ options = dict(environment_options or {})
90
+ options.setdefault("autoescape", True)
91
+ options.setdefault("undefined", ChainableUndefined)
92
+ self._env = Environment(loader=FileSystemLoader(list(paths or [])), **options)
93
+
94
+ @property
95
+ def env(self) -> Environment:
96
+ return self._env
97
+
98
+ def encode_json(self, data: Any) -> str:
99
+ return self._json_encoder(data)
100
+
101
+ def mark_raw(self, s: Any) -> Markup:
102
+ """Mark a string as already-safe so Jinja emits it verbatim (no
103
+ auto-escape)."""
104
+ return Markup("" if s is None else s)
105
+
106
+ def materialize(self, value: Any) -> Any:
107
+ """JSX children captured by the adapter resolve to a string here.
108
+ Jinja's `{% set children %}...{% endset %}` set-block already
109
+ produces a rendered `Markup` string directly (unlike Mojo's `begin
110
+ %>...<% end`, which yields a CODE ref) -- `materialize` still
111
+ supports a callable for parity with the Perl port's contract and any
112
+ lazy-render composition a caller builds on top of this backend."""
113
+ return value() if callable(value) else value
114
+
115
+ def render_named(self, template_name: str, child_bf: Any, variables: Optional[dict]) -> str:
116
+ """Render `<name>.jinja` with `child_bf` bound as the `bf` variable
117
+ for the nested render, plus the supplied template vars. Keyword
118
+ mangling (`jinja_ident`) is applied here -- the one point every
119
+ props dict is turned into template variables -- so a prop literally
120
+ named e.g. `class` or `none` doesn't collide with a Jinja/Python
121
+ reserved word."""
122
+ template = self._env.get_template(f"{template_name}.jinja")
123
+ mangled = {jinja_ident(k): v for k, v in (variables or {}).items()}
124
+ mangled["bf"] = child_bf
125
+ return template.render(**mangled)