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.
- stixis-0.1.0/.agents/docs/grammar-and-codegen.md +85 -0
- stixis-0.1.0/.agents/docs/microsyntaxes-and-cssom.md +53 -0
- stixis-0.1.0/.gitignore +14 -0
- stixis-0.1.0/AGENTS.md +33 -0
- stixis-0.1.0/LICENSE +21 -0
- stixis-0.1.0/PKG-INFO +118 -0
- stixis-0.1.0/README.md +95 -0
- stixis-0.1.0/codegen/check_upstream.py +996 -0
- stixis-0.1.0/codegen/gen_specs.py +706 -0
- stixis-0.1.0/codegen/gen_wpt_tests.py +599 -0
- stixis-0.1.0/codegen/spec_patches.toml +106 -0
- stixis-0.1.0/codegen/upstream.json +29 -0
- stixis-0.1.0/pyproject.toml +68 -0
- stixis-0.1.0/skills/stixis/SKILL.md +101 -0
- stixis-0.1.0/src/stixis/__init__.py +93 -0
- stixis-0.1.0/src/stixis/_generated/__init__.py +1 -0
- stixis-0.1.0/src/stixis/_generated/specs.py +2688 -0
- stixis-0.1.0/src/stixis/_generated/types.py +88 -0
- stixis-0.1.0/src/stixis/_generated/units.py +28 -0
- stixis-0.1.0/src/stixis/cli.py +338 -0
- stixis-0.1.0/src/stixis/errors.py +34 -0
- stixis-0.1.0/src/stixis/microsyntax/__init__.py +1 -0
- stixis-0.1.0/src/stixis/microsyntax/family_name.py +163 -0
- stixis-0.1.0/src/stixis/microsyntax/font_src.py +238 -0
- stixis-0.1.0/src/stixis/microsyntax/opentype.py +268 -0
- stixis-0.1.0/src/stixis/microsyntax/ranges.py +388 -0
- stixis-0.1.0/src/stixis/microsyntax/urange.py +241 -0
- stixis-0.1.0/src/stixis/nodes.py +864 -0
- stixis-0.1.0/src/stixis/options.py +61 -0
- stixis-0.1.0/src/stixis/primitives.py +440 -0
- stixis-0.1.0/src/stixis/py.typed +0 -0
- stixis-0.1.0/src/stixis/syntax.py +2712 -0
- stixis-0.1.0/tests/_generated/__init__.py +1 -0
- stixis-0.1.0/tests/_generated/test_wpt_css_fonts.py +478 -0
- stixis-0.1.0/tests/_generated/test_wpt_css_properties.py +2721 -0
- stixis-0.1.0/tests/_generated/test_wpt_font_face.py +162 -0
- stixis-0.1.0/tests/_generated/test_wpt_font_family.py +73 -0
- stixis-0.1.0/tests/_generated/test_wpt_urange.py +124 -0
- stixis-0.1.0/tests/test_api_and_cli.py +661 -0
- 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`.
|
stixis-0.1.0/.gitignore
ADDED
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
|
+
```
|