Skip to content

cuere 0.2.0

Latest

Choose a tag to compare

@github-actions github-actions released this 18 Jun 09:20
v0.2.0
453e381

Added

  • A documentation site built with zensical (the new
    zensical.toml format) and Material for MkDocs: a landing page, a Quickstart,
    a "Rendering & scanning" guide, an auto-generated CLI reference
    (mkdocs-typer2) and API reference (mkdocstrings), and the existing
    cookbook/standards pages, wired into a docs extra
    (uv sync --extra docs). Every public symbol's docstring now carries a
    usage example. Closes #10, #11, #12, #13.

  • A Docs CI workflow (.github/workflows/docs.yml) that builds the
    documentation site with zensical build --strict as a gate on every pull
    request to main, and deploys it to GitHub Pages on push to main. The
    README gains a documentation badge and a link to the published site at
    https://ivananishchuk.github.io/cuere/. Closes #15.

  • An animated terminal demo in the README, recorded with
    vhs from a checked-in
    examples/demo.tape showing a basic render, --invert, a wallet URI with
    --optimize-uri, and --mode ansi. A scripts/render_demo.py helper
    re-records it locally (uv run python scripts/render_demo.py). Closes #16.

  • Terminal color customization for ansi mode. render / show (and
    cuere.rich.QRCode, plus the CLI --dark / --light flags) now take dark
    and light colors for the dark modules and the light ground. A Color is a
    name (one of the 16 standard ANSI colors), a 256-palette index (an int or its
    string form), or a truecolor value (#rrggbb / #rgb hex, an (r, g, b)
    tuple, or its "r,g,b" string); the defaults stay the theme-proof
    black-on-white (palette 16 on 231). Colors are ansi-only — passing one to
    half / block raises the new ColorError (a CuereError) — and show
    drops them along with its NO_COLOR / non-tty fallback. Adds the exported
    Color type alias. Documented in a new docs/colors.md, including the
    scanner-contrast caveats. Closes #26.

  • A pytest-benchmark performance suite (benchmarks/) and an informational
    Benchmark CI job that records encode and render timings as a JSON artifact.
    It covers QRMatrix.encode across payload sizes, each render mode
    (half / block / ansi), render_svg, render_bytes (PNG), and the
    high-level render() / render_bytes() paths. The suite is kept out of the
    test run (not in testpaths, so coverage is unaffected) and runs only via the
    benchmark job or uv run pytest benchmarks/ --benchmark-only. There is no
    regression threshold yet — it is the foundation for one later. Closes #22.

  • File and bytes export beyond the terminal. render_bytes() returns the
    encoded bytes for a named OutputFormat, and save() writes them to a path
    or binary stream — inferring the format from a .txt / .svg / .png suffix
    when one is not given. Three formats ship: text (the terminal glyph
    rendering, honoring mode / invert), svg (a standalone vector document,
    also exposed directly as render_svg()), and png (a raster image that needs
    the optional cuere[image] extra — Pillow, imported lazily so import cuere
    never pulls it in). scale sets pixels-per-module for svg / png. Unknown
    formats raise UnknownFormatError; a missing optional dependency raises
    MissingDependencyError (both CuereErrors). The CLI gains
    --output FORMAT[:PATH] (PATH - or omitted means stdout; the default stays
    terminal output) and --scale. Also adds the SupportsWriteBytes sink
    protocol. Documented in a new docs/output-formats.md and the cookbook.
    Closes #23, #24, #25.

  • lightning_uri() — a typed lightning: URI builder for bech32 Lightning
    payloads (BOLT11 invoices lnbc…, LNURL lnurl1…, BOLT12 offers lno1…).
    The payload is validated structurally (a single-case run of ASCII
    alphanumerics beginning ln) without a checksum verify, mirroring
    bitcoin_uri; invalid input raises WalletURIError. A lowercase result
    composes with optimize_uri for a smaller QR. Documented in
    docs/cookbook/wallet-uris.md and a new docs/lightning-uri.md.

  • SchemeCase and scheme_case() — a public, typed classifier for how
    optimize_uri treats a URI's scheme: INSENSITIVE (bech32 — bitcoin: /
    lightning: — may be uppercased), SIGNIFICANT (case carries meaning —
    ethereum: EIP-55, wc: WalletConnect — never folded), or UNKNOWN
    (unrecognized scheme, also never folded). optimize_uri now gates on this, so
    wc: is recognized explicitly as case-significant rather than incidentally
    passed through.

  • ethereum_uri() and erc20_transfer_uri() — typed
    EIP-681 ethereum: request builders.
    ethereum_uri(address, *, value=None, chain_id=None, gas_limit=None, gas_price=None) builds a native payment (value in wei, matching the
    spec); erc20_transfer_uri(token, *, to, amount, ...) builds the ERC-20
    transfer(address,uint256) form (amount in the token's base units).
    Addresses are validated structurally as 0x + 40 hex digits with their
    EIP-55 checksum case preserved (so these URIs must never go through
    optimize_uri); numeric arguments are positive uint256 integers. Invalid
    input raises WalletURIError. Documented in docs/cookbook/wallet-uris.md.

  • docs/bip-21.md and docs/eip-681.md — condensed summaries of the wallet-URI
    standards cuere implements (BIP-21/BIP-173 for bitcoin_uri; EIP-681/EIP-55/
    EIP-155 and ERC-20 transfer for the ethereum builders).

  • bitcoin_uri() — a typed BIP-21 bitcoin: payment-request builder
    (bitcoin_uri(address, *, amount=None, label=None, message=None)). It
    validates the address alphabet, renders amount as a plain BTC decimal
    (Decimal/int/str; positive, finite, satoshi precision — never float),
    and percent-encodes label/message. Returns a plain str that composes
    with optimize_uri and render/show. Invalid input raises the new
    WalletURIError (a CuereError). Documented in a new docs/cookbook/wallet-uris.md.

  • Mutation testing with mutmut (dev dependency, run locally — not in CI),
    configured in pyproject.toml and documented in docs/mutation-testing.md,
    with the set of accepted equivalent mutants recorded there. Includes a mypy
    type-check pass (mutmut-mypy.ini) that catches type-error mutants without a
    runtime test, and a no-shrink hypothesis profile for deterministic runs.

  • Behavioral regression tests closing the gaps mutation testing surfaced:
    exact error/warning messages (WidthError, invalid render mode), encode-option
    pass-through in render/fits/rich.QRCode, block-mode width/height in
    fits/show/QRCode.__rich_measure__, the show exact-fit boundary,
    per-row Rich segments, and first-colon URI scheme parsing in optimize_uri.

  • tests/test_types.py: assert_type regression tests pinning the inferred
    types of the public API.

  • zizmor GitHub Actions security audit: dev dependency, a local pre-commit
    hook (offline), and a CI workflow running online audits with SARIF upload to
    code scanning.

  • .github/settings.yml (repository-settings app): security_and_analysis
    (secret scanning + push protection + Dependabot security updates), branch
    protection on main, and the PyPI/TestPyPI environments — as code.

  • .github/CODEOWNERS.

  • gitleaks now uploads its SARIF to code scanning.

  • .coderabbit.yaml — the CodeRabbit review configuration, pinned in-repo
    rather than left to dashboard defaults: assertive profile, auto-review on pull
    requests targeting main, path filters that skip generated files (uv.lock,
    build output, golden fixtures), and house-style review instructions.
    CodeRabbit's bundled ruff and gitleaks are disabled there since CI already
    enforces both. CodeRabbit remains a best-effort second opinion; /code-review
    and CI are the gate.

  • .well-known/security.txt (RFC 9116) — machine-readable security contact.

  • Contributor governance: CONTRIBUTING.md, CODE_OF_CONDUCT.md, issue
    templates (bug / feature / question) and a pull-request template.

  • SupportsWrite — a narrow text-sink protocol (any object with write(str)),
    exported from cuere. It is what show(out=...) now accepts.

  • Test-hardening pass: edge-case and property coverage for empty/whitespace
    payloads, near-maximal payloads, all four EC levels (encoding + monotonic
    version growth), the reachable Micro QR variants (M2–M4, plus the M1-skip and
    the rejected H/oversized cases), extreme borders, that pure render()
    keeps ANSI color regardless of NO_COLOR, and round-trip strategies that
    exercise the renderers against real encoder output (not only synthetic bit
    patterns).

Changed

  • The cookbook moved into a docs/cookbook/ directory: the payment-request
    recipes now live in docs/cookbook/wallet-uris.md and the file/bytes export
    recipes in docs/cookbook/exporting.md (the flat docs/cookbook.md is gone).
    The wallet page now embeds the rendered terminal QR for every recipe, adds a
    standalone WalletConnect (wc:) rendering recipe, and explains why cuere
    encodes at error-correction level L. A scripts/render_docs_qr.py script
    regenerates that embedded art (run with --check in CI to catch drift).
  • optimize_uri() is refactored onto the new scheme_case(); its behavior is
    unchanged (case-insensitive, lowercase, no-query URIs are uppercased;
    everything else is returned verbatim), but ethereum: and wc: are now
    deliberately classified as case-significant rather than relying on the
    unknown-scheme default.
  • Dropped from __future__ import annotations and the if TYPE_CHECKING:
    gating of cheap stdlib/intra-package imports — those are now plain runtime
    imports. Removed the flake8-type-checking (TCH) ruff ruleset, which had
    been mechanically forcing both patterns plus quoted typing.cast types. The
    lazy import of rich/typer is unaffected (still enforced by cuere/__init__
    not importing them, guarded by a test).
  • DRY'd the encode options across the high-level API: render/fits/show/
    rich.QRCode/coerce now take **options: Unpack[EncodeOptions] (one
    EncodeOptions TypedDict) plus a shared Encodable type alias, so the option
    set and its defaults live once in QRMatrix.encode. Calls like
    render(data, border=2, error="H") are unchanged. Also centralized the ANSI
    color values shared by render and rich.
  • show(out=...) is typed as the narrow SupportsWrite protocol instead of
    typing.IO[str], matching what it actually needs (a single write; isatty
    is probed defensively). Backward compatible — every IO[str] already
    satisfies it — and it removes the double-cast workaround the tests needed to
    pass a write-only stream.
  • mypy: made strict_equality and warn_redundant_casts explicit in the
    config (both already implied by strict = true).

Fixed

  • The README's animated demo GIF now uses an absolute, tag-pinned URL so it
    renders on the PyPI project page (PyPI serves the long description off-repo, so
    relative image paths 404 there).
  • Embedded half-block QR art now scans from the rendered documentation site.
    The Material/zensical theme styled code blocks with line-height: 1.4, which
    injected a horizontal sliver between every glyph row and split the codes
    vertically, and the syntax highlighter stripped the art's blank quiet-zone
    rows. A scoped docs/stylesheets/qr.css (wired via extra_css) forces
    line-height: 1, restores the top/bottom quiet zone, and pins black-on-white
    so the codes read in both the light and dark themes; art blocks opt in with a
    { .qr } fence info string. The homepage hero QR — previously corrupted (no
    quiet zone, rows truncated by the trailing-whitespace hook) — is re-rendered
    with a full quiet zone and excluded from that hook. The QR-art generator is
    generalized from the cookbook to all docs pages and renamed
    scripts/render_docs_qr.py, so its --check drift guard now also covers the
    homepage. The CSS also squares the module aspect ratio (scaleX), which HTML's
    monospace metrics otherwise stretch ~20% wider than tall. Closes #68.
  • The CLI now reports a clean error: … (exit 1) instead of an uncaught
    traceback when --input or stdin holds non-UTF-8 bytes (UnicodeDecodeError
    is a ValueError, so it previously slipped past the (CuereError, OSError)
    handler).
  • Corrected mismatched action version comments (e.g. setup-uv was labelled
    v4.2.0 but pinned to v8.2.0; CodeQL v3.27.5v4.36.2; gitleaks
    v2.3.6v3.0.0). The pinned SHAs are unchanged; the comments now match.

Security

  • The release workflow now asserts the built package version matches the
    release tag before publishing, so a forgotten version bump cannot publish a
    mislabeled artifact.
  • SECURITY.md now documents how to verify a release's sigstore signature and
    SLSA provenance, not only the GitHub build-provenance attestation.
  • Hardened the release build against cache poisoning (enable-cache: false
    on the publish build's setup-uv).

What's Changed

New Contributors

Full Changelog: v0.1.0...v0.2.0