aboutsummaryrefslogtreecommitdiff
path: root/uv.lock
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-07-10 16:59:00 +0530
committerGitHub <[email protected]>2026-07-10 16:59:00 +0530
commit189bcdabe3865a0d52aea580d3fdbcc1cd48e896 (patch)
tree8db00421de426b98a86d4b65665c71be7adcf0bf /uv.lock
parentc757ea1fea447df3ed69e74b407ca9710a9f85e0 (diff)
parent174eb521850550830d97994719ca3d09c467cb5a (diff)
downloadedify-189bcdabe3865a0d52aea580d3fdbcc1cd48e896.tar.xz
edify-189bcdabe3865a0d52aea580d3fdbcc1cd48e896.zip
feat: regex introspection API — explain, verbose export, ASCII + SVG 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.
Diffstat (limited to 'uv.lock')
-rw-r--r--uv.lock18
1 files changed, 18 insertions, 0 deletions
diff --git a/uv.lock b/uv.lock
index 9e67009..ccd1eb1 100644
--- a/uv.lock
+++ b/uv.lock
@@ -233,8 +233,14 @@ wheels = [
name = "edify"
source = { editable = "." }
+[package.optional-dependencies]
+graphviz = [
+ { name = "graphviz" },
+]
+
[package.dev-dependencies]
dev = [
+ { name = "graphviz" },
{ name = "hypothesis" },
{ name = "pytest" },
{ name = "pytest-cov" },
@@ -247,9 +253,12 @@ docs = [
]
[package.metadata]
+requires-dist = [{ name = "graphviz", marker = "extra == 'graphviz'", specifier = ">=0.20" }]
+provides-extras = ["graphviz"]
[package.metadata.requires-dev]
dev = [
+ { name = "graphviz", specifier = ">=0.20" },
{ name = "hypothesis", specifier = ">=6.100" },
{ name = "pytest", specifier = ">=8.0" },
{ name = "pytest-cov", specifier = ">=5.0" },
@@ -261,6 +270,15 @@ docs = [
]
[[package]]
+name = "graphviz"
+version = "0.21"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" },
+]
+
+[[package]]
name = "hypothesis"
version = "6.155.7"
source = { registry = "https://pypi.org/simple" }