Metadata-Version: 2.5
Name: stixis
Version: 0.1.2
Summary: Spec-driven CSS serializer and validator generated from W3C @webref/css and Web Platform Tests
Project-URL: Homepage, https://github.com/myakura/stixis
Project-URL: Repository, https://github.com/myakura/stixis.git
Project-URL: Issues, https://github.com/myakura/stixis/issues
Author-email: Masataka Yakura <masataka.yakura@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: css,cssom,font-face,pep-750,serializer,validator,webref,wpt
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Code Generators
Classifier: Topic :: Text Processing :: Markup
Classifier: Typing :: Typed
Requires-Python: >=3.14
Description-Content-Type: text/markdown

# Stixis

Stixis (`stixis`) is a CSS serializer and validator for Python 3.14+ with zero runtime dependencies. It generates its grammar tables and `TypedDict` definitions from W3C `@webref/css` (`ed/css/*.json`) and builds its test suite from Web Platform Tests (WPT) and Hypothesis.

## What it does

* **Compiles W3C Value Definition Syntax.** `compile_value_syntax` turns `@webref/css` grammars (`|`, `||`, `&&`, juxtaposition, `?`, `*`, `+`, `#`, `{A,B}`, `[...]!`, `<type [min,max]>`, `<'prop'>`) into a `ValueNode` tree. `serialize_value` formats structured Python values against that tree, and `canonicalize` validates raw CSS token streams.
* **Validates AST nodes on construction.** `FontFace`, `StyleRule`, `AtRule`, `StyleSheet`, `CommentNode`, and `RawDeclaration` are frozen dataclasses (`dataclass(frozen=True, slots=True)`) that check selectors, preludes, descriptors, declarations, and comments in `__post_init__`.
* **Escapes PEP 750 `Template` (`t"..."`) slots by context.** `render_css_template` checks the CSS tokens before each `Interpolation` slot, escapes comments (`/* ... */`), strings, `url(...)`, and numbers accordingly, and validates the rendered tokens against the target `ValueNode` grammar.
* **Handles five CSS microsyntaxes.**
  1. `<urange>`: `parse_urange`, `format_urange`, and `compact_urange` for cascade-shadowing range compaction.
  2. `<opentype-tag>`: `font-feature-settings`, `font-variation-settings`, and `font-language-override`.
  3. `<font-src-list>`: `FontSrc`, `Url`, and `LocalFont`, enforcing `format()` before `tech()`.
  4. `<family-name>` and `<generic-family>`: quotes Python `str` family names by default, requires `generic("serif")` or `Keyword("serif")` for generic families, and matches WPT `font-family-serialization-001.html`.
  5. `{1,2}` descriptor and property ranges: `font-weight`, `font-width` and `font-stretch`, `font-style: oblique`, and metric overrides.
* **Includes a read-only CLI.** `stixis inspect` and `stixis check` exit with `0` for valid input, `1` for a validation error, `2` for a usage error, and `4` for an unknown target.

## Quickstart

```python
from stixis import (
    Deg,
    FontFace,
    FontSrc,
    Pct,
    Px,
    SerializationOptions,
    StyleRule,
    StyleSheet,
    compact_urange,
    dumps,
    generic,
)

if __name__ == "__main__":
    woff2_url = "https://example.com/fonts/noto-sans-jp-slice0.woff2"
    slice_id = "kana"
    size_kb = 18.4

    urange = compact_urange(
        sorted_target_cps=[0x3000, 0x3041, 0x3042, 0x3044],
        forbidden_sorted=[0x3043],
        max_gap=0x80,
    )

    sheet = StyleSheet(
        header_comment="Generated by Stixis",
        rules=[
            FontFace(
                comment=t"Slice [{slice_id}] ({size_kb:.1f} KB)",
                font_family="Noto Sans JP",
                font_style=("oblique", Deg(-10), Deg(20)),
                font_weight=(100, 900),
                font_width=(Pct(75), Pct(125)),
                font_display="swap",
                src=t"url({woff2_url}) format(woff2) tech(variations)",
                unicode_range=urange,
            ),
            StyleRule(
                "body",
                {
                    "font_family": ["Noto Sans JP", generic("sans-serif")],
                    "font_feature_settings": {"liga": True, "dlig": False},
                    "margin": Px(0),
                },
            ),
        ],
    )

    print(dumps(sheet))
    print(dumps(sheet, options=SerializationOptions.cleome()))
```

## CLI usage

```bash
# Inspect an at-rule, property, or <type>
uv run stixis inspect @font-face
uv run stixis inspect font-style --json
uv run stixis inspect "<font-src>"

# Validate and canonicalize CSS values
uv run stixis check font-weight "100 900" --at-rule @font-face
uv run stixis check unicode-range "u+3040-309f" --at-rule @font-face
```

## Development and verification

```bash
uv sync
uv run python codegen/gen_specs.py --check
uv run python codegen/gen_wpt_tests.py --check
uv run pytest
uv run ruff check
```
