aboutsummaryrefslogtreecommitdiff
AgeCommit message (Collapse)AuthorFilesLines
2026-07-10feat: regex introspection API — explain, verbose export, ASCII + SVG ↵Bobby58-366/+6010
visualize + annotated errors (#272) Ships the regex introspection API on the `Regex` wrapper — three new methods that turn a compiled pattern into something a human can read, plus a full retrofit of every error message to a shape that shows the caller *where* the failure happened and *how* to fix it. ## `Regex.explain()` Plain-English bullet list of what the pattern accepts, followed by a small set of concrete accepted strings. ``` - The text must start with either "http" or "https". - Then the text must have "://". - Then the text must have one or more letters, digits, or underscores. Text this pattern accepts: http://ab1 https://b1_c http://1_c2d ``` ## `Regex.to_verbose_string()` An `re.VERBOSE`-compatible export where every fragment is annotated inline. Copy-pasting the output into `re.compile(..., re.VERBOSE)` compiles to the same pattern. ``` (?P<year> # begin group named "year" \d{4} # exactly 4 ) # end group named "year" ``` ## `Regex.visualize(format='ascii')` ASCII railroad diagram — `START` and `END` boxes flanking the pattern, arrows between elements, fork/merge junctions for alternation, dashed-caption blocks for captures and lookarounds. ``` +--------+ +--->| "cat" |----+ | +--------+ | | | +-------+ | +--------+ | +-----+ | START |------>+--->| "dog" |----+-->| END | +-------+ | +--------+ | +-----+ | | | +--------+ | +--->| "fish" |----+ +--------+ ``` ## `Regex.visualize(format='svg', engine='graphviz')` SVG rendered by Graphviz. Junction-point fork/merge for alternation, dashed rounded clusters for captures/lookarounds, folded two-line node labels for quantifiers (e.g. `digit\n(one or more)`). Requires the optional `graphviz` extra; the missing-dependency error names the install command. ## Annotated error messages Every exception across `edify.errors` — anchors, captures, input, internal, naming, quantifier, structure — now emits a message that names the offending call in the caller's source, explains the invariant that was violated, and prescribes the fix: ``` error: start_of_input has already been added to this pattern --> user_code.py:14:26 | 14 | pattern = RegexBuilder().start_of_input().digit().start_of_input() | ^^^^^^^^^^^^^^^^ second start_of_input added here | = note: a pattern can carry at most one start_of_input anchor; the earlier .start_of_input() call already set it. help: remove the duplicate .start_of_input() call from the chain. ``` Caller source location is captured via `sys._getframe()` walking to the first non-edify frame and `co_positions()` for precise column spans. ## Other landings - `Regex` retains the AST elements the builder produced; every introspection method operates on the AST directly rather than parsing the emitted regex string. - Lazy compile cache on `BuilderCore` — a builder's `to_regex()` result is memoised so `.match()` / `.search()` / `.explain()` share one compiled pattern. - `Regex.__getattr__` delegates any attribute not on the wrapper to the underlying `re.Pattern`, so `.pattern`, `.flags`, `.groups`, `.groupindex` continue to work. - `Regex.__eq__` / `Regex.__hash__` raise when called on an unfinished builder, pointing at both the compare/hash call site and the still-open frame or dangling quantifier. - Property test extended: every recursively-built composition of quantifiers, groups, captures, named captures, and subexpressions emits its expected regex exactly, no fragment dropped or duplicated.
2026-07-10chore: apply ruff format and drop unused import in new context testsnatsuoto1-7/+2
2026-07-10test: cover diagnose fallback branches and caller-context position fallbacksnatsuoto2-0/+217
2026-07-10ci: install graphviz system package for SVG-rendering testsnatsuoto2-0/+4
2026-07-10chore: declare graphviz optional dependency and add to dev groupnatsuoto2-0/+22
2026-07-10chore: apply ruff lint and format fixesnatsuoto13-147/+66
2026-07-10docs: strip design narrative from module docstrings, keep API contractnatsuoto13-80/+11
2026-07-10test(builder): extend Hypothesis property test to cover groups, captures, ↵natsuoto1-29/+182
subexpressions (#111)
2026-07-10feat(result): Regex wrapper with AST, delegation, and introspection API ↵natsuoto15-17/+4145
(#133, #145, #146, #147, #148)
2026-07-10feat(builder): lazy Regex compile cache and __eq__/__hash__ diagnose ↵natsuoto5-32/+316
unfinished builders (#127, #137)
2026-07-10feat(builder): capture caller source location on chain methodsnatsuoto4-59/+128
2026-07-10feat(errors): annotated error messages with caller-context pointersnatsuoto14-149/+1071
2026-07-01feat!: Regex result wrapper on .to_regex(), plus flag kwargs at the terminal ↵Bobby9-8/+311
(#270) Reshapes what `.to_regex()` returns and takes, so pattern compilation becomes a first-class edify value. ## `Regex` result wrapper class `edify.Regex` is a composition wrapper over `re.Pattern` (which is a CPython C type and cannot be subclassed). It exposes: - `.source` — the emitted regex string. - `.compiled` — the underlying `re.Pattern` for callers that need identity checks, `isinstance` checks, or direct interop with libraries typed on `re.Pattern`. - Eight direct delegates — `.match()`, `.search()`, `.fullmatch()`, `.findall()`, `.finditer()`, `.sub()`, `.subn()`, `.split()` — mirroring the `re.Pattern` query surface. - Value equality on `(source, flags)` plus `__hash__`. ## `to_regex()` now returns `Regex` The terminal returns `edify.Regex` instead of raw `re.Pattern`. The existing eight query methods continue to work via delegation. `.match()` / `.search()` / … on builders (via `MatcherMixin`) still work because they go through the same delegation. **Breaking:** `isinstance(x, re.Pattern)` and `re.Pattern`-typed annotations on values returned from `.to_regex()` break. Callers that need the raw pattern read `.compiled`. ## Flag kwargs on `.to_regex(...)` `.to_regex()` now accepts six keyword-only flag arguments — `ascii_only`, `debug`, `ignore_case`, `multiline`, `dotall`, `verbose` — that OR-merge into the flag snapshot the builder already carries. Passing `ignore_case=True` at the terminal is equivalent to calling `.ignore_case()` mid-chain. Kwargs never turn a chain-set flag off; the terminal is the natural home for pattern-global settings. ## Example ```python from edify import RegexBuilder email = RegexBuilder().start_of_input().one_or_more().letter().end_of_input() regex = email.to_regex(ignore_case=True) regex.source # ^[a-zA-Z]+$ regex.match("Hello") # <re.Match ...> regex.compiled # <re.Pattern object; flags=re.IGNORECASE> ``` Closes #133 Closes #134 Closes #128
2026-07-01fix(ci): skip debug=True regression test on PyPy (upstream IndexError bug)natsuoto1-0/+9
2026-07-01fix(ci): reorder edify re-exports; drop pypy-fragile capsys check on debug=Truenatsuoto2-5/+4
2026-07-01feat: terminal flag kwargs on .to_regex(ignore_case=..., multiline=..., ↵natsuoto2-2/+89
dotall=...)
2026-07-01feat!: to_regex() returns the Regex wrapper instead of raw re.Patternnatsuoto8-16/+22
2026-07-01feat: Regex result wrapper class composing over re.Patternnatsuoto3-0/+202
2026-07-01fix!: quantifier validation — dangling and stacked quantifiers raise; ↵夏音 / natsuoto.exe7-1/+238
Hypothesis property gate (#269) Tightens quantifier semantics so the builder can no longer silently drop a quantifier from the emitted regex, and locks the invariant in with a Hypothesis property test. ## Dangling quantifier now raises `RegexBuilder().exactly(3).to_regex_string()` used to emit `^$` — the quantifier had no operand, so it was silently discarded. It now raises `DanglingQuantifierError` at emit time. ```python RegexBuilder().exactly(3).to_regex_string() # edify.errors.quantifier.DanglingQuantifierError: # Dangling quantifier with no operand. Append an element (e.g. .digit()) before compiling. ``` ## Stacked quantifiers now raise `RegexBuilder().one_or_more().exactly(3).digit()` used to emit `\d{3}` — the second quantifier overwrote the first, silently dropping `one_or_more`. It now raises `StackedQuantifierError` at chain-call time. ```python RegexBuilder().one_or_more().exactly(3).digit() # edify.errors.quantifier.StackedQuantifierError: # Cannot stack a quantifier on top of another pending quantifier. # Add an operand between the two quantifiers or drop one. ``` ## Property assertion Hypothesis-driven test in `tests/builder/properties.test.py` — for any list of `(quantifier method, element method)` pairs, the emitted regex is exactly the concatenation of `<element><suffix>` fragments in order. `hypothesis>=6.100` added to the `dev` dependency group. ## Breaking Any chain that previously silently emitted a wrong-but-parseable regex now raises. Correct chains are unaffected. Closes #109 Closes #110 Closes #111
2026-07-01test: Hypothesis property assertion — no chain silently drops a quantifiernatsuoto3-0/+97
2026-07-01chore: consolidate quantifier error tests into tests/errors/quantifier.test.pynatsuoto3-47/+41
2026-07-01fix!: stacked quantifiers raise StackedQuantifierError at chain timenatsuoto2-1/+51
2026-07-01fix!: dangling quantifier raises DanglingQuantifierError at emitnatsuoto3-0/+96
2026-07-01feat: builder ergonomics — __repr__, __eq__/__hash__, .fork()/.copy() (#268)夏音 / natsuoto.exe4-3/+183
Three builder-ergonomics wins so `RegexBuilder` / `Pattern` behave like proper Python values in interactive shells, dicts, and comparisons. ## `__repr__` shows the pattern-so-far Replaces the useless default `<edify.builder.builder.RegexBuilder object at 0x...>` with `<RegexBuilder '^\d+'>`. Builders whose frames are still open render as `<RegexBuilder '<unclosed>'>` so the repr never raises. ## `__eq__` / `__hash__` — value equality Two builders are equal when their underlying immutable `BuilderState` matches; the hash derives from the same state. Builders become safe as dict keys, set members, or assertion targets. `RegexBuilder` and `Pattern` share every mixin below the class name, so a builder and pattern with identical state compare equal. **Breaking:** identity-based `==` on builders becomes value-based. Only code that intentionally relied on identity equality is affected. ## `.fork()` / `.copy()` — explicit branch construction Chaining already yields implicit forking (immutable state means `root.digit()` and `root.word()` share nothing after the split); `.fork()` and `.copy()` are aliases that surface the property via autocomplete and signal branching intent in code. ## Example ```python from edify import RegexBuilder builder = RegexBuilder().start_of_input().exactly(4).digit().end_of_input() repr(builder) # <RegexBuilder '^\d{4}$'> RegexBuilder().digit() == RegexBuilder().digit() # True lookup = {RegexBuilder().digit(): "hit"} # hashable, dict-key safe root = RegexBuilder().digit() branch = root.fork().word() # explicit branch off root ``` Closes #136 Closes #137 Closes #139
2026-07-01test: cover the __repr__ fallback for BuilderCore subclasses without ↵natsuoto1-0/+9
TerminalsMixin
2026-07-01feat: .fork() and .copy() explicit aliases for branch constructionnatsuoto3-1/+61
2026-07-01feat!: __eq__ and __hash__ value equality on the buildernatsuoto2-2/+63
2026-07-01feat: __repr__ shows the pattern-so-far on the buildernatsuoto2-3/+53
2026-07-01feat: convenience char classes .letter() .uppercase() .lowercase() ↵夏音 / natsuoto.exe9-1/+143
.alphanumeric() (#267) Adds four convenience ASCII character-class shortcuts so callers don't have to spell out `.range("a","z").range("A","Z")` for the common cases. ## What is new - **Chain methods** on `ClassesMixin` — `.letter()` → `[a-zA-Z]`, `.uppercase()` → `[A-Z]`, `.lowercase()` → `[a-z]`, `.alphanumeric()` → `[a-zA-Z0-9]`. - **Leaf elements** — `LetterElement`, `UppercaseElement`, `LowercaseElement`, `AlphanumericElement` in `edify.elements.types.leaves`, joined into `LeafElement` and `Element` unions and rendered by `edify.compile.leaves.render_leaf`. - **Module constants** — `LETTER`, `UPPERCASE`, `LOWERCASE`, `ALPHANUMERIC` at both `edify.pattern` and the top-level `edify` namespace. ## Example ```python from edify import LETTER, ALPHANUMERIC, START, END, one_or_more username = START + one_or_more(ALPHANUMERIC) + END LETTER.test("Q") # True LETTER.test("4") # False ``` Follows the same dedicated-leaf pattern the existing `.digit()` / `.word()` / `.whitespace_char()` methods use. Closes #132
2026-07-01feat: convenience char classes .letter() .uppercase() .lowercase() ↵natsuoto9-1/+143
.alphanumeric()
2026-07-01feat: varargs .one_of() and .any_of(*literals) chain-level shorthand (#266)夏音 / natsuoto.exe4-13/+172
Adds a varargs shorthand for literal alternation on both fluent surfaces (`RegexBuilder` and `Pattern`): - **`.any_of(*literals: str)`** — dual-mode overload. Zero args keeps the existing frame-opener behavior (`.any_of().string("cat").string("dog").end()`). One or more string args skips the frame dance and appends the alternation directly. - **`.one_of(*literals: str)`** — new method, always the varargs form. Requires at least one literal (raises `MustBeAtLeastOneLiteralError` on zero). Literals go through the same escape + `CharElement`/`StringElement` branching that `.char()` / `.string()` use, so the compile-path fusion into `[...]` for single-character sets still applies: ```python RegexBuilder().any_of("+", "-").to_regex_string() # [\+\-] RegexBuilder().one_of("cat", "dog", "fish").to_regex_string() # (?:cat|dog|fish) ``` The pre-existing top-level factory `any_of(*operands: Pattern)` from #263 stays untouched — it takes `Pattern` operands, not literal strings, and serves the compositional (humre-style) API rather than the chain-level shorthand this PR delivers. Closes #131
2026-07-01feat: varargs .one_of() and .any_of(*literals) chain-level shorthandnatsuoto4-13/+172
2026-07-01feat: .at_most(n) quantifier closes the symmetry gap with ↵夏音 / natsuoto.exe11-0/+70
at_least/exactly/between (#265) Adds `.at_most(n)` on both `RegexBuilder`/`Pattern` and as an `at_most(n, operand)` factory. Closes the symmetry gap in the quantifier lineup — we had `exactly(n)`, `at_least(n)`, and `between(lo, hi)`, but no `{0,n}` shorthand. ## What is new - `AtMostElement(times, child)` in the elements union, rendered as `{0,times}` by the quantifier suffix dispatch. - `.at_most(count)` chain method on `QuantifiersMixin` — validates `count > 0` (matches the `at_least` contract). - `at_most(count, operand)` free-function factory in the humre-style API. - Re-exported through `edify.pattern` and top-level `edify`. ## Example ```python from edify import DIGIT, at_most, Pattern at_most(3, DIGIT).to_regex_string() # \d{0,3} Pattern().at_most(3).digit().to_regex_string() # \d{0,3} ``` Closes #130
2026-07-01feat: .at_most(n) quantifier closes the symmetry gap with ↵natsuoto11-0/+70
at_least/exactly/between
2026-07-01feat: .test() boolean shortcut on Pattern and RegexBuilder (#264)夏音 / natsuoto.exe2-2/+41
Adds the last convenience terminal from #127: `.test(string) -> bool` on both `Pattern` and `RegexBuilder`. ## What is new - `test(string, pos=0, endpos=sys.maxsize) -> bool` on `MatcherMixin` — returns `True` when the pattern matches anywhere in the input, else `False`. Semantically `self.to_regex().search(...) is not None`. ## Why PR #263 already delivered `.match`, `.search`, `.fullmatch`, `.findall`, `.finditer`, `.sub`, `.subn`, `.split` from the #127 wishlist. `.test` — a boolean shortcut idiomatic in Ruby/humre/JS — was the only piece missing. ## Example ```python from edify import DIGIT, START, END, exactly zip_code = START + exactly(5, DIGIT) + END zip_code.test("10001") # True zip_code.test("nope") # False ``` Closes #127
2026-07-01feat: .test() boolean shortcut on Pattern and RegexBuildernatsuoto2-2/+41
2026-07-01feat: humre-style functional API — operators, constants, factories, ↵夏音 / natsuoto.exe28-11/+1312
matcher (#263) Delivers the humre-style parallel API on top of the fluent chain, closing the operator-algebra epic (#122, #123, #124). ## What is new - **Operators** — `+` (concatenation) and `|` (alternation) on both `Pattern` and `RegexBuilder`, preserving anchors and flags from either operand. - **Constants (15)** — `START`, `END`, `WORD_BOUNDARY`, `NON_WORD_BOUNDARY`, `DIGIT`, `NON_DIGIT`, `WORD`, `NON_WORD`, `WHITESPACE`, `NON_WHITESPACE`, `ANY_CHAR`, `NEW_LINE`, `CARRIAGE_RETURN`, `TAB`, `NULL_BYTE`. - **Value factories (7)** — `string`, `char`, `range_of`, `chars`, `nonchars`, `nonstring`, `nonrange`. - **Quantifier factories (9)** — `optional`, `zero_or_more` (+ lazy), `one_or_more` (+ lazy), `exactly`, `at_least`, `between` (+ lazy). - **Grouping factories (6)** — `group`, `capture`, `named_capture`, `back_reference`, `named_back_reference`, `any_of` (variadic alternation). - **Assertion factories (4)** — `assert_ahead`, `assert_not_ahead`, `assert_behind`, `assert_not_behind`. - **Terminals on `Pattern`** — `to_regex_string()` and `to_regex()` now live on `Pattern` (previously builder-only). - **Matcher proxies** — `.match()`, `.search()`, `.fullmatch()`, `.findall()`, `.finditer()`, `.sub()`, `.subn()`, `.split()` on both `Pattern` and `RegexBuilder`, delegating to `re.Pattern`. - **New error** — `MustBeAtLeastTwoOperandsError` guards `any_of`. ## What it unlocks ```python from edify import START, END, DIGIT, exactly zip_code = START + exactly(5, DIGIT) + END zip_code.match("10001") zip_code.fullmatch("100") ``` Zero `Pattern()` boilerplate, full IDE completion, typed exception surface, and seamless interop with the fluent chain (`Pattern().exactly(5).digit()`). Closes #122 Closes #123 Closes #124
2026-07-01feat: humre-style functional API — operators, constants, factories, matchernatsuoto28-11/+1312
2026-07-01feat: add Pattern class and .use() composition (#261)Bobby8-23/+178
## Summary Closes #125 — the ``Pattern`` class, a named reusable regex fragment that composes with any builder via ``.subexpression()`` or ``.use()``. Closes #126 — the ``.use(pattern)`` chain method, the ergonomic alias for the common-case ``.subexpression(pattern)`` (drops flag / anchor overrides, keeps sensible defaults). ## Design **``Pattern``** is the same fluent surface as ``RegexBuilder`` minus the terminals. It shares every chain-method mixin (anchors, assertions, captures, chain, chars, classes, flags, groups, quantifiers, subexpression). It deliberately does **not** carry ``to_regex`` / ``to_regex_string`` — a pattern is a fragment, and fragments compose upward through ``.use()`` rather than compile themselves. To emit a regex from a pattern, embed it in a builder: ``RegexBuilder().use(my_pattern).to_regex()``. **``BuilderCore``** (new) holds the immutable-state plumbing that both ``RegexBuilder`` and ``Pattern`` need — the ``_state`` attribute, the ``__init__``, and the clone-and-replace ``_with_state`` helper. Both concrete classes now inherit ``BuilderCore`` instead of duplicating three lines each. **``.use(pattern)``** lives on ``SubexpressionMixin`` alongside ``.subexpression``. It calls ``self.subexpression(pattern)`` with the common-case defaults, so ``Pattern`` and ``RegexBuilder`` both get it for free. Direct ``.subexpression`` calls remain available when you need the ``namespace`` / ``ignore_flags`` / ``ignore_start_and_end`` overrides. ## Fanout Closing this unblocks the operator algebra (#122, #123, #124), every validator-to-callable-Pattern migration (#177–#190), the AST walker (#143), and every building-block atom (#172–#176).
2026-07-01feat: add Pattern class and .use() composition (closes #125, #126)natsuoto8-23/+178
2026-06-30chore!: package reorg + uv/ruff/pytest/hatchling toolchain + Python 3.11 ↵Bobby144-2308/+5890
floor (#255) ## Summary Closes #68 — package reorganization to a single-purpose, dir-per-concern layout (no `src/`). Closes #69 — toolchain migration to `uv` + `ruff` + `pytest` + `hatchling` + `hatch-vcs`, driven entirely from `pyproject.toml`. Closes #253. Closes #259 — drop `.editorconfig`; LF / trim / final-newline / Python indent now enforced by `mixed-line-ending` (added in fixup) + existing pre-commit hooks + `ruff format`.— Python floor bump to 3.11; drops `py39` / `py310` / `pypy310` from the CI matrix. The three issues are paired by construction (dropping `src/` requires the new build backend's package discovery, and the 3.11 floor lets the new layout use `typing.Self`, frozen dataclasses, `match`/`case`, and Python-3.11 idioms throughout). ## Highlights **Layout** — `edify/builder/` (RegexBuilder + 11 mixin files + `types/` for state/flags/frame/protocol), `edify/elements/types/` (sealed dataclass union: 16 leaves, 7 char-shaped, 4 captures, 7 groups, 9 quantifiers, RootElement), `edify/compile/` (per-family render functions + dispatch, plus `quantifier/` subpackage for suffix + apply), `edify/validate/`, `edify/errors/` (typed exception hierarchy), `edify/library/` (per-validator files in family folders: `email/`, `ip/`, `date/`, plus flat single-validator files), `pattern/`, `serialize/`, `result/` skeletons. **Toolchain** — replaces `tox` / `virtualenv` / `flake8` / `isort` / `black` / `setup.py` / `setup.cfg` / `pytest.ini` / `.coveragerc` / `.bumpversion.cfg` / `MANIFEST.in` / `ci/` / `tests.local.sh` with `pyproject.toml`-driven `uv` + `ruff` + `pytest` + `hatchling` + `hatch-vcs`. CI workflow rewritten to `uv sync --frozen` + `uv run pytest`; required-status-check names preserved (`check`, `docs`, `py311 (ubuntu)`, `py312 (ubuntu)`, `py313 (ubuntu)`, `py314 (ubuntu)`, `pypy311 (ubuntu)`). Pre-commit replaced with `ruff` hooks, all hook repos pinned by commit SHA. **Tests** — reorganised under `tests/builder/` and `tests/library/<family>/` using the `*.test.py` naming shape. 173 tests, 100% line coverage, ruff lint + format clean, `uv build` produces sdist + wheel cleanly. ## Behaviour changes versus 0.3 These will get their own upgrade-guide entries when the migration tooling lands (#80): - `to_regex_string()` now returns the bare pattern (no `/.../` wrap). - `to_regex()` on a named back-reference compiles successfully via `(?P=name)` instead of raising. - Builder errors raise typed `EdifyError` subclasses (`MustBeAStringError`, `StartInputAlreadyDefinedError`, etc.) instead of bare `Exception` — catch via `except EdifyError`. - Internal helpers (`apply_quantifier`, `frame_creating_element`, `evaluate`, `state`, etc.) removed outright per the 0.3 → 1.0 stub-free policy.
2026-06-30chore: gitignore edify/version.py (hatch-vcs build artefact)natsuoto1-0/+1
2026-06-30fix(ci): untrack hatch-vcs-generated edify/version.pynatsuoto1-24/+0
2026-06-30fix(ci): cover version-fallback branch deterministically via extracted helpernatsuoto2-12/+15
2026-06-30fix(ci): rewrite coverage.yml on uv + codecov-action (closes #260)natsuoto1-18/+16
2026-06-30fix(ci): drop hatch-vcs version-file generation so check stays cleannatsuoto1-4/+0
`hatch-vcs` was generating `edify/version.py` at every build with an unsorted `__all__`, which `ruff check` (RUF022) flagged on CI. The file is unused — `edify/__init__.py` resolves `__version__` via `importlib.metadata.version("edify")` directly — so dropping the generation entirely is the right move. Removed the corresponding coverage `omit` (no longer needed). Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-30chore: drop .editorconfig — replaced by mixed-line-ending hook + ruff formatnatsuoto2-20/+2
LF / trim-trailing-whitespace / final-newline / Python 4-space indent / UTF-8 guarantees are now enforced by `mixed-line-ending` (added here), `trailing-whitespace` + `end-of-file-fixer` (already in pre-commit), and `ruff format` (Python). YAML 2-space indent is the only rule .editorconfig provided that no other tool enforces; the three YAML files in the repo are correctly indented and reviewer attention is the gate. Closes #259 Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-26chore!: package reorg, toolchain migration to uv+ruff+pytest+hatchling, ↵natsuoto143-2270/+5896
Python 3.11 floor Drops the `src/` layout. The package lives at `edify/` at repo root, split into single-purpose subpackages: `builder/` (mixins composing RegexBuilder), `elements/` (sealed dataclass AST), `compile/` (per-family render functions plus dispatch), `validate/`, `errors/` (typed exception hierarchy), and `library/` (per-validator files in family folders). Pattern, serialize, and result subpackages ship as skeletons. Replaces tox / virtualenv / flake8+isort+black / setup.py+setup.cfg / pytest.ini / .coveragerc / .bumpversion.cfg / MANIFEST.in / ci/ / tests.local.sh with a unified uv + ruff + pytest + hatchling + hatch-vcs stack driven entirely from pyproject.toml. CI workflow rewritten to `uv sync --frozen` + `uv run pytest` per matrix entry; required-status- check names preserved for `check`, `docs`, `py3X (ubuntu)`, and `pypy311 (ubuntu)`. `py39` / `py310` / `pypy310` dropped (3.11 floor). Pre-commit replaced with ruff hooks (lint + format), all hook repos SHA-pinned to commit SHAs. Tests reorganised under `tests/builder/` and `tests/library/<family>/` with the `*.test.py` naming shape. 173 tests pass, 100% line coverage. Behaviour changes versus 0.3 (each will get a documented upgrade-guide entry once #80 lands): - `to_regex_string()` returns the bare pattern (no `/.../` wrap) - `to_regex()` on `named_back_reference` compiles successfully via `(?P=name)` instead of raising - builder errors raise typed `EdifyError` subclasses instead of bare `Exception` (catch via `except EdifyError`) - internal helpers (`apply_quantifier`, `frame_creating_element`, `evaluate`, `state`, etc.) removed outright per the 0.3 → 1.0 stub-free policy Closes #68 Closes #69 Closes #253 Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-25chore: gitignore design_docs/ working directory (#66)夏音 / natsuoto.exe1-0/+3
## Summary Adds `design_docs/` to `.gitignore`. The directory is a local working space for collaborative design notes between Bobby and Claude (currently holds the 1.0 design doc) — its contents are intentionally local-only, not user-facing documentation. Without this line, the contents are at risk of being accidentally tracked or committed during routine `git add` operations. The single commit on this branch (`49297e1`) was authored and GPG-signed locally; the PR just lands it on `main` via the standard issue+PR flow. Closes #65
2026-06-25feat: add design_docs to .gitignorenatsuoto1-0/+3