Skip to content
myakuraPublic

About

Spec-driven CSS serializer and validator generated from W3C @webref/css and Web Platform Tests

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

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), derives property browser-support counts from @mdn/browser-compat-data (css/properties/*.json), and builds its test suite from Web Platform Tests (WPT) and Hypothesis.

What it does

  • Serializes and canonicalizes CSS values against W3C Value Definition Syntax (serialize_value, canonicalize, and the CLI). serialize_value formats structured Python values and PEP 750 Template strings, and canonicalize validates and normalizes raw CSS token streams (including relative color syntax and context-free CSS Values 4 math simplification) against @webref/css grammars. All three entry points resolve standard CSS properties, custom properties (--*), @at-rule descriptors (@font-face/src or at_rule="@font-face"), and <type> syntax targets; unknown <...> targets are rejected with StixisValidationError (exit 4 on the CLI).
  • Gates experimental CSS properties by engine support (SerializationOptions(min_engines=...) and --min-engines). By default (SerializationOptions(min_engines=2)), Stixis enables CSS properties shipped unprefixed in at least 2 core browser engines (Blink, Gecko, WebKit) according to @mdn/browser-compat-data, plus an explicit allowlist in codegen/spec_patches.toml. Pass SerializationOptions(min_engines=1), extra_properties=frozenset({"prop-name"}), or --min-engines 1 (0..3) on the CLI to opt into single-engine properties.
  • Validates AST nodes and primitives on construction. FontFace, StyleRule, AtRule, StyleSheet, CommentNode, RawDeclaration, FontSrc, and Url are frozen, hashable dataclasses (dataclass(frozen=True, slots=True)) that validate selectors, preludes, descriptors, declarations, comments, and URL modifiers in __post_init__, snapshot mutable container inputs into immutable tuples, and raise StixisValidationError or StixisSyntaxError on invalid input.
  • Escapes PEP 750 Template (t"...") slots by context (serialize_value and AST nodes). Passing a Template to serialize_value or an AST node field (FontFace, StyleRule, AtRule, CommentNode) escapes comments (/* ... */), strings, url(...), and numbers according to the surrounding CSS token context and validates the rendered tokens against the target 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.

Only the top-level stixis package is public; submodules such as stixis._syntax, stixis._grammar, stixis.values, stixis.stylesheet, and stixis.api are internal and not part of the public API.

Quickstart

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

# 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 (--min-engines accepts 0..3)
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
uv run stixis check hanging-punctuation "first" --min-engines 1

Development and verification

codegen/gen_wpt_tests.py generates the WPT parsing and serialization test modules in tests/_generated/.

uv sync
uv run python codegen/gen_specs.py --check --webref-dir .upstream/webref/ed/css --bcd-dir .upstream/bcd/css
uv run python codegen/gen_wpt_tests.py --check --wpt-dir .upstream/wpt/css
uv run pytest
uv run ruff check

About

Spec-driven CSS serializer and validator generated from W3C @webref/css and Web Platform Tests

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages