stixis 0.1.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.
Files changed (40) hide show
  1. stixis-0.1.0/.agents/docs/grammar-and-codegen.md +85 -0
  2. stixis-0.1.0/.agents/docs/microsyntaxes-and-cssom.md +53 -0
  3. stixis-0.1.0/.gitignore +14 -0
  4. stixis-0.1.0/AGENTS.md +33 -0
  5. stixis-0.1.0/LICENSE +21 -0
  6. stixis-0.1.0/PKG-INFO +118 -0
  7. stixis-0.1.0/README.md +95 -0
  8. stixis-0.1.0/codegen/check_upstream.py +996 -0
  9. stixis-0.1.0/codegen/gen_specs.py +706 -0
  10. stixis-0.1.0/codegen/gen_wpt_tests.py +599 -0
  11. stixis-0.1.0/codegen/spec_patches.toml +106 -0
  12. stixis-0.1.0/codegen/upstream.json +29 -0
  13. stixis-0.1.0/pyproject.toml +68 -0
  14. stixis-0.1.0/skills/stixis/SKILL.md +101 -0
  15. stixis-0.1.0/src/stixis/__init__.py +93 -0
  16. stixis-0.1.0/src/stixis/_generated/__init__.py +1 -0
  17. stixis-0.1.0/src/stixis/_generated/specs.py +2688 -0
  18. stixis-0.1.0/src/stixis/_generated/types.py +88 -0
  19. stixis-0.1.0/src/stixis/_generated/units.py +28 -0
  20. stixis-0.1.0/src/stixis/cli.py +338 -0
  21. stixis-0.1.0/src/stixis/errors.py +34 -0
  22. stixis-0.1.0/src/stixis/microsyntax/__init__.py +1 -0
  23. stixis-0.1.0/src/stixis/microsyntax/family_name.py +163 -0
  24. stixis-0.1.0/src/stixis/microsyntax/font_src.py +238 -0
  25. stixis-0.1.0/src/stixis/microsyntax/opentype.py +268 -0
  26. stixis-0.1.0/src/stixis/microsyntax/ranges.py +388 -0
  27. stixis-0.1.0/src/stixis/microsyntax/urange.py +241 -0
  28. stixis-0.1.0/src/stixis/nodes.py +864 -0
  29. stixis-0.1.0/src/stixis/options.py +61 -0
  30. stixis-0.1.0/src/stixis/primitives.py +440 -0
  31. stixis-0.1.0/src/stixis/py.typed +0 -0
  32. stixis-0.1.0/src/stixis/syntax.py +2712 -0
  33. stixis-0.1.0/tests/_generated/__init__.py +1 -0
  34. stixis-0.1.0/tests/_generated/test_wpt_css_fonts.py +478 -0
  35. stixis-0.1.0/tests/_generated/test_wpt_css_properties.py +2721 -0
  36. stixis-0.1.0/tests/_generated/test_wpt_font_face.py +162 -0
  37. stixis-0.1.0/tests/_generated/test_wpt_font_family.py +73 -0
  38. stixis-0.1.0/tests/_generated/test_wpt_urange.py +124 -0
  39. stixis-0.1.0/tests/test_api_and_cli.py +661 -0
  40. stixis-0.1.0/tests/test_grammar_hypothesis.py +298 -0
@@ -0,0 +1,85 @@
1
+ # Grammar Compiler and Code Generation Guide
2
+
3
+ ## Overview
4
+
5
+ Stixis separates build-time specification ingestion (`codegen/`) from its zero-dependency runtime kernel (`src/stixis/syntax.py`). Both structured Python values (`serialize_value`) and raw CSS token streams (`canonicalize`) are evaluated against the same compiled `ValueNode` combinator tree.
6
+
7
+ ## Value Definition Syntax (`src/stixis/syntax.py`)
8
+
9
+ `compile_value_syntax(syntax_str)` compiles W3C CSS Value Definition Syntax strings (`css-values-4` §5) into an immutable `ValueNode` hierarchy:
10
+
11
+ * `KeywordNode(name)` and `DelimNode(delim)`: Literal keywords (case-insensitively matched, serialized in ASCII lowercase) and punctuation delimiters (`,`, `/`, `:`).
12
+ * `PrimitiveNode(kind, min_val, max_val, min_unit, max_unit)`: Terminal and microsyntax types (`<number>`, `<integer>`, `<length>`, `<angle>`, `<time>`, `<frequency>`, `<resolution>`, `<percentage>`, `<string>`, `<url>`, `<ident>`, `<custom-ident>`, `<dashed-ident>`, `<urange>`, `<opentype-tag>`, `<feature-tag-value>`, `<family-name>`, `<generic-family>`, `<font-src>`, `<font-src-list>`, `<font-format>`, `<font-tech>`, `<hex-color>`, `<declaration-value>`, `<any-value>`), including bracketed range constraints such as `<number [1,1000]>` or `<angle [-90deg,90deg]>`.
13
+ * `RefNode(name, is_property, min_val, max_val)`: Non-terminal references (`<color>`) and property references (`<'font-width'>`), resolved lazily via `lookup_value_node` with cycle protection (`_active_refs`).
14
+ * `FunctionNode(name, arg)`: Typed CSS functions (`generic(...)`, `palette-mix(...)`, `fit-content(...)`).
15
+ * Combinators:
16
+ * `JuxtaposeNode(items)`: Ordered sequence `A B`.
17
+ * `AllOfNode(items)`: Unordered conjunction `A && B`. Matches branches in any input order (allowing optional `MultiplierNode(min_count=0)` branches to be omitted) and serializes matched branches in the canonical grammar order `0 .. k-1`.
18
+ * `AnyOrderNode(items)`: Unordered inclusive-or `A || B`. Requires at least one branch and serializes matched branches in canonical grammar order `0 .. k-1`.
19
+ * `ExclusiveOrNode(items)`: Ordered choice `A | B`.
20
+ * `MultiplierNode(child, min_count, max_count, comma_separated)`: Multipliers `?` (`0..1`), `*` (`0..∞`), `+` (`1..∞`), `{A,B}`, `#` (`comma_separated=True`), and `#{A,B}`.
21
+ * `NonEmptyGroupNode(child)`: Required group `[ ... ]!`.
22
+
23
+ ### PEP 750 `Template` Rendering (`render_css_template`)
24
+
25
+ When `serialize_value`, `StyleRule`, `AtRule`, `FontFace`, or `StyleSheet` receives a Python 3.14+ `string.templatelib.Template` (`t"..."`), `render_css_template` inspects the lexical context preceding each `Interpolation` slot:
26
+
27
+ 1. **Inside a CSS comment (`/* ... */`)**: Replaces any `*/` in the interpolated value with `* /` so untrusted text cannot prematurely close the comment and inject rules.
28
+ 2. **Inside a quoted string (`"..."` or `'...'`)**: Escapes characters using CSSOM string rules without adding outer quotes.
29
+ 3. **Inside `url(...)`**: Escapes the URL string via `serialize_string` so spaces, quotes, and parentheses cannot break out of `url(...)`.
30
+ 4. **Top-level value slot**: Formats `int`/`float` via `serialize_number`, `Dimension`/`Percentage`/`Url`/`LocalFont`/`Keyword` via their primitive serializers, and plain `str` via `serialize_string` (unless `!r` conversion is specified), then passes the assembled CSS string through `evaluate_canonicalize` for full `ValueNode` grammar validation.
31
+
32
+ ## Declarative Spec Patches (`codegen/spec_patches.toml`)
33
+
34
+ W3C `@webref/css` (`ed/css/*.json`) has prose-defined aliases and delta specifications (`css-fonts-5.json`, `css-values-5.json`, `css-color-5.json`). Never hardcode spec overrides inside `codegen/gen_specs.py`; declare them in `codegen/spec_patches.toml`:
35
+
36
+ * `[support]`: Controls browser implementation filtering (`default_min_engines = 2`, `allow_properties = [...]`, `exclude_specs = [...]`) using engine counts (`0..3` across Blink, Gecko, WebKit) extracted from `@mdn/browser-compat-data` (`PROPERTY_ENGINE_COUNTS` in `src/stixis/_generated/specs.py`). At runtime, callers can lower the threshold with `SerializationOptions(min_engines=1)` / `SerializationOptions.cleome(min_engines=1)` or enable specific single-engine properties via `SerializationOptions(extra_properties=frozenset({"..."}))`.
37
+ * `[extra_units]`: Units defined in prose or pending upstream extraction (such as container query length units `cqw`..`cqmax` and speech units `dB`, `st`).
38
+ * `[delta_merges]`: Explicit ordering of base and delta JSON files so delta descriptors (`@font-face/size-adjust`, two-value `ascent-override`, `<font-features-tech>` with `avar2`) extend or override base definitions cleanly.
39
+ * `[values]`: Non-terminal syntax aliases defined in prose (`<font-src-list> = "<font-src>#"`, `<palette-identifier> = "<dashed-ident>"`, `<counter-name> = "<custom-ident>"`).
40
+ * `[atrule_preludes]`: Prelude grammars validated by `AtRule` (such as `@font-palette-values = "<dashed-ident>"`, `@property = "<custom-property-name>#"`, `@layer = "<layer-name>? | <layer-name>#"`).
41
+ * `[atrule_descriptors."@font-face"]`: Prose-defined descriptor aliases (`font-stretch = "auto | <'font-width'>{1,2}"`).
42
+ * `[properties]`: Property syntax overrides where a draft omits prose keywords (`font-synthesis` with `oblique-only`, `font-stretch = "<'font-width'>"`).
43
+
44
+ After editing `codegen/spec_patches.toml`, run:
45
+
46
+ ```bash
47
+ uv run python codegen/gen_specs.py
48
+ uv run python codegen/gen_specs.py --check
49
+ ```
50
+
51
+ ## WPT Test Generation (`codegen/gen_wpt_tests.py`)
52
+
53
+ `codegen/gen_wpt_tests.py` extracts test vectors from local WPT HTML files into `tests/_generated/`:
54
+
55
+ * `test_wpt_urange.py`: All 94 valid and invalid `<urange>` vectors from `css-syntax/urange-parsing.html`.
56
+ * `test_wpt_font_family.py`: `<family-name>` and `<generic-family>` quoting and normalization vectors from `cssom/font-family-serialization-001.html` and `css-fonts/support/font-family-keywords.js`.
57
+ * `test_wpt_font_face.py`: `@font-face` descriptor tests from `css-fonts/parsing/font-face-*.html`.
58
+ * `test_wpt_css_fonts.py`: All property parsing vectors from `css-fonts/parsing/*-{valid,invalid}.html`.
59
+ * `test_wpt_css_properties.py`: General CSS property vectors across `css-*/parsing/*-{valid,invalid}.html`.
60
+
61
+ Always verify that generated files are synchronized before committing:
62
+
63
+ ```bash
64
+ uv run python codegen/gen_wpt_tests.py --check
65
+ uv run pytest
66
+ ```
67
+
68
+ ## Upstream Sync & Agent Triage (`codegen/check_upstream.py` & `codegen/upstream.json`)
69
+
70
+ `codegen/upstream.json` records the baseline commit SHAs and generated counts for `w3c/webref` (`ed/css`) and `web-platform-tests/wpt` (`css`).
71
+
72
+ When upstream repositories advance (or when `.github/workflows/upstream-watch.yml` opens or updates an `upstream-sync` GitHub Issue), run `codegen/check_upstream.py` to classify what changed:
73
+
74
+ 1. **Auto-applicable updates**: New or modified properties, descriptors, units, or WPT vectors that compile and pass with the existing `src/stixis/syntax.py` kernel and `codegen/spec_patches.toml`.
75
+ 2. **Action-required items**:
76
+ * **Unresolved non-terminals (`<new-type>`)**: Add prose aliases to `[values]` in `codegen/spec_patches.toml`, or add a microsyntax handler under `src/stixis/microsyntax/`.
77
+ * **Failing WPT vectors**: Inspect the target grammar with `uv run stixis inspect <target> --json`, reproduce with `uv run stixis check <target> <raw_css>`, and adjust `codegen/spec_patches.toml` or `src/stixis/syntax.py`.
78
+
79
+ After resolving any action items, regenerate all tables, tests, and baseline metadata:
80
+
81
+ ```bash
82
+ uv run python codegen/gen_specs.py --webref-dir <path-to-webref-ed-css>
83
+ uv run python codegen/gen_wpt_tests.py --wpt-dir <path-to-wpt-css>
84
+ uv run python codegen/check_upstream.py --webref-dir <path-to-webref-ed-css> --wpt-dir <path-to-wpt-css> --update-baseline
85
+ ```
@@ -0,0 +1,53 @@
1
+ # CSSOM Serialization and Microsyntaxes Guide
2
+
3
+ ## CSSOM Primitive Serialization (`src/stixis/primitives.py`)
4
+
5
+ All primitive serializers implement W3C CSSOM §2.1 (`Common Serializing Idioms`) and §6.7.2 (`Serializing CSS Values`):
6
+
7
+ * **Identifiers (`serialize_ident`, `serialize_custom_ident`, `serialize_dashed_ident`)**:
8
+ * Replaces `U+0000` with `U+FFFD`.
9
+ * Escapes `U+0001`..`U+001F` and `U+007F` as hex code points (`\1f ` with trailing space).
10
+ * Escapes leading ASCII digits (`0`..`9`) or leading hyphen followed by a digit (`-[0-9]`) as hex code points (`\31 `), and lone `-` as `\-`.
11
+ * `serialize_custom_ident` rejects CSS-wide keywords (`initial`, `inherit`, `unset`, `default`, `revert`, `revert-layer`, `revert-rule`) and any grammar-specific forbidden keywords.
12
+ * `serialize_dashed_ident` requires a leading `--` (`len >= 2`, including `--`).
13
+ * **Strings and URLs (`serialize_string`, `serialize_url`)**:
14
+ * Wraps in `options.quote_char` (`"` by default and in `.cssom()`, `'` in `.cleome()`).
15
+ * Escapes `U+0001`..`U+001F` and `U+007F` as hex code points, and escapes `options.quote_char` and `\` with a backslash.
16
+ * `serialize_url` formats `url(...)` with a quoted inner string.
17
+ * **Numbers, Integers, Percentages, and Dimensions (`serialize_number`, `serialize_integer`, `serialize_percentage`, `serialize_dimension`)**:
18
+ * Rejects `bool` (`isinstance(v, bool)`) and non-finite floats (`nan`, `inf`, `-inf`).
19
+ * Formats numbers as shortest decimal strings without scientific notation, rounded to at most 6 fractional digits with trailing zeros stripped (`400.0` -> `"400"`, `-0.0` -> `"0"`).
20
+ * Enforces bracketed bounds (`[min, max]`) and validates units against `src/stixis/_generated/units.py`, normalizing unit names to their canonical lowercase form (except `Hz`/`kHz` or `Q` where specified, and `dB`/`st`).
21
+
22
+ ## Five Microsyntaxes (`src/stixis/microsyntax/`)
23
+
24
+ ### 1. `<urange>` and Cascade Shadowing Compaction (`urange.py`)
25
+
26
+ * `parse_urange(text)` implements CSS Syntax Level 3 §8 and passes all 94 cases in WPT `urange-parsing.html`. Because CSS tokenizers absorb `U+...` into `ident`, `number` (including scientific notation like `u+1e3`), or `dimension` tokens, `parse_urange` strips comments first while verifying that no comment separated the leading `u`/`U` from `+` (rejecting `u/**/0` while accepting `u/**/+/**/0`).
27
+ * `compact_urange(sorted_target_cps, forbidden_sorted, max_gap=0x80)` merges adjacent ranges when `gap <= max_gap` **only** if `bisect_right` confirms that no code point in `forbidden_sorted` lies inside `(prev_end, next_start)`. This minimizes `unicode-range` string length without shadowing glyphs in subsequent `@font-face` slices.
28
+
29
+ ### 2. `<opentype-tag>` and Feature/Variation Settings (`opentype.py`)
30
+
31
+ * `validate_opentype_tag` enforces exactly 4 printable ASCII bytes (`0x20`..`0x7E`), escaping `"` and `\`.
32
+ * `serialize_font_feature_settings` accepts `Mapping[str, int | bool | str]` or `Sequence[tuple[str, int | bool | str]]`. Per CSSOM shortest serialization, value `1` / `True` / `"on"` is omitted (`"liga"`), `0` / `False` / `"off"` serializes as `0` (`"dlig" 0`), and integers `>= 2` serialize as `"swsh" 2`.
33
+ * `serialize_font_variation_settings` accepts `Mapping[str, int | float]` or `Sequence[tuple[str, int | float]]` and formats each pair as `"tag" <number>`.
34
+ * `serialize_font_language_override` validates `normal` or a 1..4 printable ASCII string and strips trailing spaces per WPT `font-language-override-valid.html`.
35
+
36
+ ### 3. `<font-src-list>` (`font_src.py`)
37
+
38
+ * Enforces WPT `font-face-src-tech.html`, `font-face-src-format.html`, and `font-face-src-local.html`:
39
+ * `format()` must precede `tech()`; reversed `tech(...) format(...)` is rejected.
40
+ * `tech()` accepts only comma-separated `<font-tech>` keywords (serialized in ASCII lowercase) and rejects quoted strings.
41
+ * `format()` accepts `<font-format>` keywords (`woff2`, `woff`, `opentype`, `truetype`, `collection`, `embedded-opentype`, `svg`) or strings; `options.quote_font_format` controls whether keywords are emitted bare (`format(woff2)`) or quoted (`format('woff2')` in `.cleome()`).
42
+ * `local()` rejects single-word unquoted CSS-wide keywords (`local(inherit)`), accepts multi-word unquoted names containing CSS-wide keywords (`local(default A)`), and accepts quoted CSS-wide keywords (`local("inherit")`).
43
+
44
+ ### 4. `<family-name>` and `<generic-family>` (`family_name.py`)
45
+
46
+ * In `serialize_value`, a Python `str` is always treated as `<family-name>` (never `<generic-family>`). Use `generic("serif")`, `GenericFamily("serif")`, or `Keyword("serif")` for generic families.
47
+ * With `DEFAULT_OPTIONS` (`always_quote_family_name=True`), `<family-name>` is always quoted.
48
+ * With `SerializationOptions.cssom()` (`always_quote_family_name=False`), valid `<custom-ident>+` names serialize unquoted (`Times New Roman`), while names starting with a digit (`"34J"`), containing multiple spaces (`"A B"`), or colliding case-insensitively with `<generic-family>` or CSS-wide keywords (`"Serif"`, `"inherit"`) remain quoted per WPT `font-family-serialization-001.html`.
49
+
50
+ ### 5. `{1,2}` Descriptor and Property Ranges (`ranges.py`)
51
+
52
+ * Supports single values and 2-tuples `(lo, hi)` for `font-weight` (`[1, 1000]`), `font-width` / `font-stretch` (`[0%, ∞]`), `font-style` (`oblique <angle [-90deg,90deg]>{1,2}`), and metric overrides (`ascent-override`, `descent-override`, `line-gap-override`).
53
+ * In property context (`is_descriptor=False`), `oblique 0deg` normalizes to `normal` when `options.normalize_zero_oblique=True`.
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ .venv/
5
+ venv/
6
+ build/
7
+ dist/
8
+ *.egg-info/
9
+ .pytest_cache/
10
+ .hypothesis/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+ .upstream/
14
+ .DS_Store
stixis-0.1.0/AGENTS.md ADDED
@@ -0,0 +1,33 @@
1
+ # Stixis
2
+
3
+ Stixis (`stixis`) is a zero-dependency, spec-driven CSS serializer and validator for Python 3.14+ that generates its grammar tables and `TypedDict` types from W3C `@webref/css` and its conformance test suites from Web Platform Tests (WPT) and Hypothesis.
4
+
5
+ ## Commands
6
+
7
+ * Install dependencies: `uv sync`
8
+ * Run test suite: `uv run pytest`
9
+ * Lint and format check: `uv run ruff check`
10
+ * Regenerate spec tables and types: `uv run python codegen/gen_specs.py`
11
+ * Verify generated spec tables are current: `uv run python codegen/gen_specs.py --check`
12
+ * Regenerate WPT test suite: `uv run python codegen/gen_wpt_tests.py --wpt-dir <path-to-wpt-css>`
13
+ * Verify generated WPT tests are current: `uv run python codegen/gen_wpt_tests.py --check`
14
+ * Diff upstream `@webref/css` & WPT and emit an agent Issue report: `uv run python codegen/check_upstream.py [--webref-dir <dir>] [--wpt-dir <dir>] [--min-engines 0..3] [--format markdown|json] [--update-baseline]`
15
+ * Inspect CSS syntax or `@font-face` descriptors: `uv run stixis inspect <target> [--json]`
16
+ * Validate and canonicalize a CSS value: `uv run stixis check <target> <value> [--at-rule <name>] [--min-engines 0..3]`
17
+
18
+ ## Architecture & Invariants
19
+
20
+ * **Zero runtime dependencies**: Everything under `src/stixis/` must use only the Python 3.14+ standard library (`string.templatelib`, `dataclasses`, `bisect`, `math`, `re`, `argparse`, `json`).
21
+ * **Never hand-edit generated files**: `src/stixis/_generated/` (`units.py`, `specs.py`, `types.py`) and `tests/_generated/` (`test_wpt_*.py`) are produced by `codegen/gen_specs.py` and `codegen/gen_wpt_tests.py`. Put grammar fixes, prose aliases, and `[support]` thresholds in `codegen/spec_patches.toml` and rerun the generators.
22
+ * **Multi-engine stability gate with 1-engine toggle**: By default (`SerializationOptions(min_engines=2)`), Stixis enables properties implemented in 2+ core browser engines (Blink, Gecko, WebKit) plus `ALWAYS_ALLOWED_PROPERTIES` in `codegen/spec_patches.toml`, avoiding single-engine spec churn. Pass `SerializationOptions(min_engines=1)` (or `SerializationOptions.cleome(min_engines=1)`), `extra_properties=frozenset({"prop-name"})`, or add to `[support] allow_properties` in `codegen/spec_patches.toml` to enable 1-engine features on demand.
23
+ * **Eager validation on AST nodes**: `StyleRule`, `AtRule`, `FontFace`, `StyleSheet`, `CommentNode`, and `RawDeclaration` (`src/stixis/nodes.py`) are frozen dataclasses (`dataclass(frozen=True, slots=True)`) that validate all selectors, preludes, descriptors, declarations, and comments inside `__post_init__`.
24
+ * **Value vs. raw CSS boundary**:
25
+ * `serialize_value(target, value)` serializes structured Python values (`int`, `float`, `tuple`, `list`, `dict`, `set`, `Dimension`, `Pct`, `Deg`, `Url`, `LocalFont`, `FontSrc`, `Keyword`, `GenericFamily`, and PEP 750 `Template`). A plain Python `str` passed to `<family-name>` or `font-family` is always treated as a font family name (quoted by default); generic families require `generic("serif")` or `Keyword("serif")`.
26
+ * `canonicalize(target, css_text)` tokenizes and validates raw CSS value text against the compiled `ValueNode` grammar and emits canonical CSSOM text.
27
+ * **Entrypoint guard**: Any runnable Python snippet in `README.md` or any script using `multiprocessing` must wrap top-level execution in `if __name__ == "__main__":`.
28
+ * **CLI contract**: `src/stixis/cli.py` is strictly read-only, never branches on agent-detection environment variables, never mixes `nargs="*"`/`"+"` optional flags with positional arguments, and exits with `0` (valid), `1` (validation error), `2` (usage error), or `4` (unknown target).
29
+
30
+ ## Domain Guides
31
+
32
+ * Read [.agents/docs/grammar-and-codegen.md](.agents/docs/grammar-and-codegen.md) when modifying the Value Definition Syntax parser/evaluator (`src/stixis/syntax.py`), adding spec overrides in `codegen/spec_patches.toml`, or updating `codegen/gen_specs.py` and `codegen/gen_wpt_tests.py`.
33
+ * Read [.agents/docs/microsyntaxes-and-cssom.md](.agents/docs/microsyntaxes-and-cssom.md) when modifying CSSOM primitive serialization (`src/stixis/primitives.py`), `SerializationOptions` presets (`src/stixis/options.py`), or the five microsyntax modules (`src/stixis/microsyntax/`).
stixis-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Masataka Yakura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
stixis-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.5
2
+ Name: stixis
3
+ Version: 0.1.0
4
+ Summary: Spec-driven CSS serializer and validator generated from W3C @webref/css and Web Platform Tests
5
+ Project-URL: Homepage, https://github.com/myakura/stixis
6
+ Project-URL: Repository, https://github.com/myakura/stixis.git
7
+ Project-URL: Issues, https://github.com/myakura/stixis/issues
8
+ Author-email: Masataka Yakura <myakura.web@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: css,cssom,font-face,pep-750,serializer,validator,webref,wpt
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Internet :: WWW/HTTP
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Classifier: Topic :: Text Processing :: Markup
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.14
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Stixis
25
+
26
+ **Stixis** (`stixis`) is a spec-driven, zero-dependency CSS serializer and validator for Python 3.14+ that generates its grammar tables and `TypedDict` definitions from W3C `@webref/css` (`ed/css/*.json`) and its conformance test suites from Web Platform Tests (WPT) and Hypothesis.
27
+
28
+ ## Highlights
29
+
30
+ * **100% `@webref/css` property & descriptor coverage**: Compiles W3C Value Definition Syntax (`|`, `||`, `&&`, juxtaposition, `?`, `*`, `+`, `#`, `{A,B}`, `[...]!`, `<type [min,max]>`, `<'prop'>`) into a dual-mode `ValueNode` evaluator that serializes structured Python objects (`serialize_value`) and canonicalizes raw CSS token streams (`canonicalize`).
31
+ * **Eagerly validated immutable AST nodes**: `FontFace`, `StyleRule`, `AtRule`, `StyleSheet`, `CommentNode`, and `RawDeclaration` (`dataclass(frozen=True, slots=True)`) validate selectors, preludes, descriptors, and declarations inside `__post_init__`.
32
+ * **First-class Python 3.14+ PEP 750 `Template` (`t"..."`) support**: Separates static CSS syntax from dynamic `Interpolation` slots, applying context-aware escaping inside comments (`/* ... */`), strings, `url(...)`, and numeric slots before validating the resulting token stream against the `ValueNode` grammar.
33
+ * **Five built-in CSS microsyntaxes**:
34
+ 1. `<urange>` (`parse_urange`, `format_urange`, and `bisect`-based cascade-shadowing range compaction `compact_urange`).
35
+ 2. `<opentype-tag>` (`font-feature-settings`, `font-variation-settings`, `font-language-override`).
36
+ 3. `<font-src-list>` (`FontSrc`, `Url`, `LocalFont` with `format()` -> `tech()` ordering and keyword validation).
37
+ 4. `<family-name>` & `<generic-family>` (safe default quoting of Python `str` family names, explicit `generic("serif")` / `Keyword("serif")`, and WPT `font-family-serialization-001.html` conformance).
38
+ 5. `{1,2}` descriptor and property ranges (`font-weight`, `font-width` / `font-stretch`, `font-style: oblique`, metric overrides).
39
+ * **Agent-first read-only CLI**: `stixis inspect` and `stixis check` with deterministic exit codes (`0` valid, `1` invalid value, `2` usage error, `4` unknown target).
40
+
41
+ ## Quickstart
42
+
43
+ ```python
44
+ from stixis import (
45
+ Deg,
46
+ FontFace,
47
+ FontSrc,
48
+ Pct,
49
+ Px,
50
+ SerializationOptions,
51
+ StyleRule,
52
+ StyleSheet,
53
+ compact_urange,
54
+ dumps,
55
+ generic,
56
+ )
57
+
58
+ if __name__ == "__main__":
59
+ woff2_url = "https://example.com/fonts/noto-sans-jp-slice0.woff2"
60
+ slice_id = "kana"
61
+ size_kb = 18.4
62
+
63
+ urange = compact_urange(
64
+ sorted_target_cps=[0x3000, 0x3041, 0x3042, 0x3044],
65
+ forbidden_sorted=[0x3043],
66
+ max_gap=0x80,
67
+ )
68
+
69
+ sheet = StyleSheet(
70
+ header_comment="Generated by Stixis",
71
+ rules=[
72
+ FontFace(
73
+ comment=t"Slice [{slice_id}] ({size_kb:.1f} KB)",
74
+ font_family="Noto Sans JP",
75
+ font_style=("oblique", Deg(-10), Deg(20)),
76
+ font_weight=(100, 900),
77
+ font_width=(Pct(75), Pct(125)),
78
+ font_display="swap",
79
+ src=t"url({woff2_url}) format(woff2) tech(variations)",
80
+ unicode_range=urange,
81
+ ),
82
+ StyleRule(
83
+ "body",
84
+ {
85
+ "font_family": ["Noto Sans JP", generic("sans-serif")],
86
+ "font_feature_settings": {"liga": True, "dlig": False},
87
+ "margin": Px(0),
88
+ },
89
+ ),
90
+ ],
91
+ )
92
+
93
+ print(dumps(sheet))
94
+ print(dumps(sheet, options=SerializationOptions.cleome()))
95
+ ```
96
+
97
+ ## CLI Usage
98
+
99
+ ```bash
100
+ # Inspect an at-rule, property, or <type>
101
+ uv run stixis inspect @font-face
102
+ uv run stixis inspect font-style --json
103
+ uv run stixis inspect "<font-src>"
104
+
105
+ # Validate and canonicalize CSS values
106
+ uv run stixis check font-weight "100 900" --at-rule @font-face
107
+ uv run stixis check unicode-range "u+3040-309f" --at-rule @font-face
108
+ ```
109
+
110
+ ## Development & Verification
111
+
112
+ ```bash
113
+ uv sync
114
+ uv run python codegen/gen_specs.py --check
115
+ uv run python codegen/gen_wpt_tests.py --check
116
+ uv run pytest
117
+ uv run ruff check
118
+ ```
stixis-0.1.0/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Stixis
2
+
3
+ **Stixis** (`stixis`) is a spec-driven, zero-dependency CSS serializer and validator for Python 3.14+ that generates its grammar tables and `TypedDict` definitions from W3C `@webref/css` (`ed/css/*.json`) and its conformance test suites from Web Platform Tests (WPT) and Hypothesis.
4
+
5
+ ## Highlights
6
+
7
+ * **100% `@webref/css` property & descriptor coverage**: Compiles W3C Value Definition Syntax (`|`, `||`, `&&`, juxtaposition, `?`, `*`, `+`, `#`, `{A,B}`, `[...]!`, `<type [min,max]>`, `<'prop'>`) into a dual-mode `ValueNode` evaluator that serializes structured Python objects (`serialize_value`) and canonicalizes raw CSS token streams (`canonicalize`).
8
+ * **Eagerly validated immutable AST nodes**: `FontFace`, `StyleRule`, `AtRule`, `StyleSheet`, `CommentNode`, and `RawDeclaration` (`dataclass(frozen=True, slots=True)`) validate selectors, preludes, descriptors, and declarations inside `__post_init__`.
9
+ * **First-class Python 3.14+ PEP 750 `Template` (`t"..."`) support**: Separates static CSS syntax from dynamic `Interpolation` slots, applying context-aware escaping inside comments (`/* ... */`), strings, `url(...)`, and numeric slots before validating the resulting token stream against the `ValueNode` grammar.
10
+ * **Five built-in CSS microsyntaxes**:
11
+ 1. `<urange>` (`parse_urange`, `format_urange`, and `bisect`-based cascade-shadowing range compaction `compact_urange`).
12
+ 2. `<opentype-tag>` (`font-feature-settings`, `font-variation-settings`, `font-language-override`).
13
+ 3. `<font-src-list>` (`FontSrc`, `Url`, `LocalFont` with `format()` -> `tech()` ordering and keyword validation).
14
+ 4. `<family-name>` & `<generic-family>` (safe default quoting of Python `str` family names, explicit `generic("serif")` / `Keyword("serif")`, and WPT `font-family-serialization-001.html` conformance).
15
+ 5. `{1,2}` descriptor and property ranges (`font-weight`, `font-width` / `font-stretch`, `font-style: oblique`, metric overrides).
16
+ * **Agent-first read-only CLI**: `stixis inspect` and `stixis check` with deterministic exit codes (`0` valid, `1` invalid value, `2` usage error, `4` unknown target).
17
+
18
+ ## Quickstart
19
+
20
+ ```python
21
+ from stixis import (
22
+ Deg,
23
+ FontFace,
24
+ FontSrc,
25
+ Pct,
26
+ Px,
27
+ SerializationOptions,
28
+ StyleRule,
29
+ StyleSheet,
30
+ compact_urange,
31
+ dumps,
32
+ generic,
33
+ )
34
+
35
+ if __name__ == "__main__":
36
+ woff2_url = "https://example.com/fonts/noto-sans-jp-slice0.woff2"
37
+ slice_id = "kana"
38
+ size_kb = 18.4
39
+
40
+ urange = compact_urange(
41
+ sorted_target_cps=[0x3000, 0x3041, 0x3042, 0x3044],
42
+ forbidden_sorted=[0x3043],
43
+ max_gap=0x80,
44
+ )
45
+
46
+ sheet = StyleSheet(
47
+ header_comment="Generated by Stixis",
48
+ rules=[
49
+ FontFace(
50
+ comment=t"Slice [{slice_id}] ({size_kb:.1f} KB)",
51
+ font_family="Noto Sans JP",
52
+ font_style=("oblique", Deg(-10), Deg(20)),
53
+ font_weight=(100, 900),
54
+ font_width=(Pct(75), Pct(125)),
55
+ font_display="swap",
56
+ src=t"url({woff2_url}) format(woff2) tech(variations)",
57
+ unicode_range=urange,
58
+ ),
59
+ StyleRule(
60
+ "body",
61
+ {
62
+ "font_family": ["Noto Sans JP", generic("sans-serif")],
63
+ "font_feature_settings": {"liga": True, "dlig": False},
64
+ "margin": Px(0),
65
+ },
66
+ ),
67
+ ],
68
+ )
69
+
70
+ print(dumps(sheet))
71
+ print(dumps(sheet, options=SerializationOptions.cleome()))
72
+ ```
73
+
74
+ ## CLI Usage
75
+
76
+ ```bash
77
+ # Inspect an at-rule, property, or <type>
78
+ uv run stixis inspect @font-face
79
+ uv run stixis inspect font-style --json
80
+ uv run stixis inspect "<font-src>"
81
+
82
+ # Validate and canonicalize CSS values
83
+ uv run stixis check font-weight "100 900" --at-rule @font-face
84
+ uv run stixis check unicode-range "u+3040-309f" --at-rule @font-face
85
+ ```
86
+
87
+ ## Development & Verification
88
+
89
+ ```bash
90
+ uv sync
91
+ uv run python codegen/gen_specs.py --check
92
+ uv run python codegen/gen_wpt_tests.py --check
93
+ uv run pytest
94
+ uv run ruff check
95
+ ```