diff options
| author | Bobby <[email protected]> | 2026-07-15 15:37:46 +0530 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-07-15 15:37:46 +0530 |
| commit | 119475cb439ea3cbad7c75bdddb2f35033e17202 (patch) | |
| tree | 20d7bf2b8241465ae7eb93ef8eaa90c83ea41338 | |
| parent | 3dcca5d124fb0c5e92995bf5116f34c8794235d8 (diff) | |
| parent | be086a5933d29acb63572c666deb4d5fa23e0f0e (diff) | |
| download | edify-119475cb439ea3cbad7c75bdddb2f35033e17202.tar.xz edify-119475cb439ea3cbad7c75bdddb2f35033e17202.zip | |
feat: snapshot suite, char-class normalization, ReDoS detector, and structured open-frame errors (#281)
Ships the compile-path correctness epic + the build-time validation epic
end to end.
## Snapshot suite
- **Snapshot helper.** `edify/testing/snapshots.py` — an ~80-line
difflib-based helper (`assert_snapshot(actual, path)` +
`SnapshotMismatchError` / `SnapshotMissingError`) with
`EDIFY_UPDATE_SNAPSHOTS=1` regenerate mode. Publicly re-exported through
`edify.testing`.
- **Library snapshots.** Every one of the 223 public library validators
emits a committed `.regex` snapshot under `tests/snapshots/library/`.
Any compile-path change that touches the emitted form surfaces here
first.
- **Doc snapshots.** Every `.. code-block:: python` block across `docs/`
is exec-ed in a fresh edify-populated namespace and its resulting
`Regex` / `Pattern` bodies are snapshotted under
`tests/snapshots/docs/<file-stem>/<line>.regex`. Broken blocks pinned to
planned validators (`email_rfc_5322`, `ipv4`, `ipv6`, `iso_date`, `zip`)
are explicitly deferred to the docs rewrite (epic #81).
- **Edge-case fixtures.** Deeply-nested alternation, combined
lookaround, item-4 named-backref, item-6 ReDoS-flagged constructs,
hex/email/composition — 8 fixtures under `tests/snapshots/fixtures/`. A
parallel test asserts the ReDoS warning fires for the nested-quantifier
fixture.
- **Orphan detection.** A discipline test walks all three snapshot roots
and fails CI if any snapshot has no live source (library Pattern /
registered fixture / discovered code-block). The reverse direction
(source without snapshot) is caught by `SnapshotMissingError` in the
per-source test.
## Compile-path invariants
- **Insertion-order determinism** pinned: `any_of_chars("zyxabc321")`
emits `[zyxabc321]` deterministically across runs; a discipline test
defends against future set/dict-backed rewrites.
- **`to_regex_string() == to_regex().pattern`** pinned per registered
library Pattern (223 parametrized checks). Item 5's regex-module backend
was the change most likely to silently split these — the invariant now
holds them together.
## Char-class escaping normalization (breaking)
- Introduces `edify.compile.escape.escape_for_char_class(value)` — the
minimal correct form. Inside `[...]`, only `\`, `]`, `^` (first
position), and `-` (interior position) are escaped; everything else is a
literal.
- `any_of_chars` and `anything_but_chars` now use the minimal escaper;
`string`, `char`, `anything_but_string` keep the full `re.escape` since
they live outside a class.
- **Before:** `any_of_chars('#?!@$%^&*-')` → `[\#\?!@$%\^\&\*\-]`.
**After:** `[#?!@$%^&*-]`. Match behavior is identical (equivalence
corpus test asserts this).
## AST walker substrate + ReDoS detector
- **`edify.elements.walk.walk_elements`** — the shared depth-first
walker that yields every `BaseElement` in an emission-order tree.
Serialize already had one; extracted, generalized, and reused by ReDoS.
- **Build-time ReDoS detector**
(`edify.compile.redos.warn_on_redos_constructs`) — walks the element
tree at `to_regex()` time and emits `ReDoSWarning` when it finds the
classic `(x+)+` shape (an unbounded quantifier wrapping a single-child
group whose one child is itself an unbounded quantifier). The check is
intentionally conservative — it requires the group's children tuple to
have length one, so composite patterns like `(?:X)*(?:Y*)*` aren't
false-positived.
## Structured open-frame stack in error messages
- `CannotCallSubexpressionError.__init__(open_frames:
tuple[OpenFrameInfo, ...])` — lists every open frame innermost-first,
one line per frame, with its opener call site (`filename:line:col`).
Named captures reproduce the declared name
(`.named_capture("username")`).
- `DanglingQuantifierError.__init__(pending_quantifier_name,
pending_quantifier_call_site)` — names the specific queued quantifier
(`.exactly(3)`) and points at its call site.
- `edify.builder.diagnose.describe_open_frames(state)` — the shared
helper both terminals and subexpression use.
## Breaking changes
- `any_of_chars` and `anything_but_chars` emit the minimal-correct form,
not the over-escaped form. Match behavior is unchanged (equivalence
corpus test verifies), but any caller comparing the emitted string
against a hard-coded expected value with `\.`/`\-` etc. needs to drop
the redundant backslashes.
- `CannotCallSubexpressionError.__init__` now takes
`tuple[OpenFrameInfo, ...]` instead of a bare string. Direct
constructors need updating.
- `DanglingQuantifierError.__init__` accepts optional
`pending_quantifier_name` / `pending_quantifier_call_site` kwargs; the
bare `DanglingQuantifierError()` call still works.
Closes #72, closes #98, closes #99, closes #100, closes #101, closes
#102, closes #103, closes #104, closes #105, closes #106, closes #73,
closes #107, closes #114, closes #192.
320 files changed, 1529 insertions, 58 deletions
diff --git a/edify/builder/diagnose.py b/edify/builder/diagnose.py index c96f8e4..6583747 100644 --- a/edify/builder/diagnose.py +++ b/edify/builder/diagnose.py @@ -15,6 +15,7 @@ from edify.elements.types.groups import ( ) from edify.errors.context import CallerContext from edify.errors.formatting import FixInsertion, Problem +from edify.errors.structure import OpenFrameInfo _END_INSERTION_TEXT = ".end()" _DANGLING_INSERTION_TEXT = ".digit()" @@ -26,6 +27,41 @@ _UNKNOWN_CALL_SITE = CallerContext( ) +def describe_open_frames(state: BuilderState) -> tuple[OpenFrameInfo, ...]: + """Return the innermost-first list of frames still open on ``state``. + + The root frame is excluded; only user-opened frames appear. + """ + stack_without_root = state.stack[1:] + infos = [ + OpenFrameInfo( + kind=_frame_kind(frame), + opened_at=frame.call_site, + ) + for frame in stack_without_root + ] + infos.reverse() + return tuple(infos) + + +def _frame_kind(frame: StackFrame) -> str: + type_node = frame.type_node + if isinstance(type_node, NamedCaptureElement): + return f'named_capture("{type_node.name}")' + return _STATIC_FRAME_KIND_NAMES[type(type_node)] + + +_STATIC_FRAME_KIND_NAMES: dict[type, str] = { + AnyOfElement: "any_of", + GroupElement: "group", + AssertAheadElement: "assert_ahead", + AssertNotAheadElement: "assert_not_ahead", + AssertBehindElement: "assert_behind", + AssertNotBehindElement: "assert_not_behind", + CaptureElement: "capture", +} + + def diagnose_unfinished(state: BuilderState, subject: str) -> Problem | None: """Return a :class:`Problem` for the first unfinished thing in ``state``, or ``None``.""" if len(state.stack) > 1: diff --git a/edify/builder/mixins/chars.py b/edify/builder/mixins/chars.py index 56c1592..a491b3b 100644 --- a/edify/builder/mixins/chars.py +++ b/edify/builder/mixins/chars.py @@ -10,7 +10,7 @@ from __future__ import annotations from typing import Self from edify.builder.types.protocol import BuilderProtocol -from edify.compile.escape import escape_special +from edify.compile.escape import escape_for_char_class, escape_special from edify.elements.types.chars import ( AnyOfCharsElement, AnythingButCharsElement, @@ -60,7 +60,7 @@ class CharsMixin(BuilderProtocol): def any_of_chars(self, characters: str) -> Self: """Return a new builder with an inline ``[characters]`` class appended.""" - escaped_characters = escape_special(characters) + escaped_characters = escape_for_char_class(characters) element = AnyOfCharsElement(value=escaped_characters) new_state = self._state.with_element_added_to_top(element) return self._with_state(new_state) @@ -78,7 +78,7 @@ class CharsMixin(BuilderProtocol): """Return a new builder with an inline ``[^characters]`` negation appended.""" _ensure_is_string("Value", characters) _ensure_non_empty("Value", characters) - escaped_characters = escape_special(characters) + escaped_characters = escape_for_char_class(characters) element = AnythingButCharsElement(value=escaped_characters) new_state = self._state.with_element_added_to_top(element) return self._with_state(new_state) diff --git a/edify/builder/mixins/subexpression.py b/edify/builder/mixins/subexpression.py index f3bcf1c..0ba96f5 100644 --- a/edify/builder/mixins/subexpression.py +++ b/edify/builder/mixins/subexpression.py @@ -16,6 +16,7 @@ from __future__ import annotations from typing import Self +from edify.builder.diagnose import describe_open_frames from edify.builder.merge import MergeContext, merge_element from edify.builder.types.protocol import BuilderProtocol from edify.elements.types.base import BaseElement @@ -91,8 +92,8 @@ def _ensure_fully_specified(expression: BuilderProtocol) -> None: """Raise when ``expression`` still has nested frames open beyond the root.""" if len(expression._state.stack) == 1: return - top_frame_type_name = type(expression._state.top_frame.type_node).__name__ - raise CannotCallSubexpressionError(top_frame_type_name) + open_frames = describe_open_frames(expression._state) + raise CannotCallSubexpressionError(open_frames) def _merge_expression_children( diff --git a/edify/builder/mixins/terminals.py b/edify/builder/mixins/terminals.py index b25873b..10e22f0 100644 --- a/edify/builder/mixins/terminals.py +++ b/edify/builder/mixins/terminals.py @@ -10,11 +10,13 @@ an :class:`edify.result.Regex`. from __future__ import annotations +from edify.builder.diagnose import describe_open_frames from edify.builder.types.engine import Engine from edify.builder.types.flags import Flags from edify.builder.types.protocol import BuilderProtocol from edify.compile.backend import compile_pattern from edify.compile.dispatch import render_element +from edify.compile.redos import warn_on_redos_constructs from edify.elements.types.root import RootElement from edify.errors.quantifier import DanglingQuantifierError from edify.errors.structure import CannotCallSubexpressionError @@ -90,12 +92,14 @@ class TerminalsMixin(BuilderProtocol): if can_cache and self._cached_regex is not None: return self._cached_regex pattern_string = self.to_regex_string() + top_frame_children = tuple(self._state.top_frame.children) + warn_on_redos_constructs(top_frame_children) effective_flags = self._state.flags.with_merged(kwarg_flags) compiled_pattern = compile_pattern(pattern_string, engine, effective_flags) wrapped = Regex( source=pattern_string, compiled=compiled_pattern, - elements=tuple(self._state.top_frame.children), + elements=top_frame_children, engine=engine, ) if can_cache: @@ -107,12 +111,15 @@ def _ensure_fully_specified(builder: BuilderProtocol) -> None: """Raise :class:`CannotCallSubexpressionError` when frames beyond the root remain open.""" if len(builder._state.stack) == 1: return - top_frame_type_name = type(builder._state.top_frame.type_node).__name__ - raise CannotCallSubexpressionError(top_frame_type_name) + open_frames = describe_open_frames(builder._state) + raise CannotCallSubexpressionError(open_frames) def _ensure_no_dangling_quantifier(builder: BuilderProtocol) -> None: """Raise :class:`DanglingQuantifierError` when any frame carries an unconsumed quantifier.""" for frame in builder._state.stack: if frame.quantifier is not None: - raise DanglingQuantifierError() + raise DanglingQuantifierError( + pending_quantifier_name=frame.quantifier_name, + pending_quantifier_call_site=frame.quantifier_call_site, + ) diff --git a/edify/compile/__init__.py b/edify/compile/__init__.py index b92f5b0..eeac406 100644 --- a/edify/compile/__init__.py +++ b/edify/compile/__init__.py @@ -1,4 +1,4 @@ from edify.compile.dispatch import render_element -from edify.compile.escape import escape_special +from edify.compile.escape import escape_for_char_class, escape_special -__all__ = ["escape_special", "render_element"] +__all__ = ["escape_for_char_class", "escape_special", "render_element"] diff --git a/edify/compile/escape.py b/edify/compile/escape.py index b78454c..d69bc59 100644 --- a/edify/compile/escape.py +++ b/edify/compile/escape.py @@ -1,9 +1,23 @@ -"""Escape user-provided string fragments for safe insertion into a regex pattern.""" +"""Escape user-provided string fragments for safe insertion into a regex pattern. + +Two different escape scopes: + +* :func:`escape_special` — for literals that will land *outside* a character + class (``.string()``, ``.char()``, ``.anything_but_string()``). Every regex + metacharacter must be escaped because the fragment is embedded directly + into the concatenation stream. +* :func:`escape_for_char_class` — for literals that will land *inside* + ``[...]``. Only ``\\``, ``]``, ``^`` (first position), and ``-`` + (interior position) need escaping; everything else is a literal inside a + class. This is the minimal correct form. +""" from __future__ import annotations import re +_CHAR_CLASS_ESCAPE_ALWAYS = {"\\", "]"} + def escape_special(value: str) -> str: """Return ``value`` with all regex metacharacters backslash-escaped. @@ -16,3 +30,40 @@ def escape_special(value: str) -> str: directly into a compiled pattern. """ return re.escape(value) + + +def escape_for_char_class(characters: str) -> str: + """Return ``characters`` escaped for insertion inside ``[...]``. + + Escapes exactly the characters that would otherwise carry syntactic + meaning inside a character class: + + * ``\\`` and ``]`` — always. + * ``^`` — only at position 0 (else the whole class would negate). + * ``-`` — only in interior position (position 0 and the final position + are unambiguously literal). + + Every other character passes through untouched. + + Args: + characters: The raw class body supplied by the user. + + Returns: + The escaped fragment, safe to place between ``[`` and ``]``. + """ + if characters == "": + return characters + last_index = len(characters) - 1 + escaped_pieces: list[str] = [] + for position, character in enumerate(characters): + if character in _CHAR_CLASS_ESCAPE_ALWAYS: + escaped_pieces.append("\\" + character) + continue + if character == "^" and position == 0: + escaped_pieces.append("\\^") + continue + if character == "-" and 0 < position < last_index: + escaped_pieces.append("\\-") + continue + escaped_pieces.append(character) + return "".join(escaped_pieces) diff --git a/edify/compile/redos.py b/edify/compile/redos.py new file mode 100644 index 0000000..683f42e --- /dev/null +++ b/edify/compile/redos.py @@ -0,0 +1,91 @@ +"""Build-time ReDoS detection over the element tree. + +Walks a Pattern's element tree and emits :class:`ReDoSWarning` when it +finds constructs known to cause catastrophic backtracking. This is a +uniquely-structural check — impossible to do reliably against a raw +regex string — and it's the reason edify carries an AST at all: the +warning fires when :meth:`to_regex` is called, so the caller sees the +diagnosis before the pattern ever runs against user input. + +The check is intentionally conservative: it flags only the classical +``(x+)+`` shape — an unbounded quantifier wrapping a single-child group +whose one child is itself an unbounded quantifier. That shape is the +one that turns linear input into exponential match time and is almost +never intentional. False positives on legitimate composite patterns +(``[^()]*(?:\\([^()]*\\)[^()]*)*``) are avoided by requiring the +group's children tuple to have length one. +""" + +from __future__ import annotations + +import warnings +from collections.abc import Iterable + +from edify.elements.types.base import BaseElement +from edify.elements.types.groups import GroupElement +from edify.elements.types.quantifiers import ( + AtLeastElement, + OneOrMoreElement, + OneOrMoreLazyElement, + ZeroOrMoreElement, + ZeroOrMoreLazyElement, +) +from edify.elements.walk import walk_elements + +UnboundedQuantifierElement = ( + OneOrMoreElement + | OneOrMoreLazyElement + | ZeroOrMoreElement + | ZeroOrMoreLazyElement + | AtLeastElement +) + + +class ReDoSWarning(UserWarning): + """Warning raised when the compile path detects a catastrophic-backtracking construct.""" + + +def warn_on_redos_constructs(roots: Iterable[BaseElement]) -> None: + """Emit :class:`ReDoSWarning` once per detected ``(x+)+`` shape in ``roots``.""" + for element in walk_elements(roots): + if not isinstance(element, UnboundedQuantifierElement): + continue + inner_quantifier = _extract_nested_bare_unbounded_quantifier(element) + if inner_quantifier is None: + continue + message = ( + f"nested unbounded quantifier detected: " + f"{_display_name_for(element)} wraps a single-child group whose only " + f"child is {_display_name_for(inner_quantifier)} — this shape is " + "vulnerable to catastrophic backtracking (ReDoS). Consider using a " + "possessive quantifier or an atomic group; under engine='regex' the " + "(?>...) atomic group is available." + ) + warnings.warn(message, ReDoSWarning, stacklevel=3) + + +def _extract_nested_bare_unbounded_quantifier( + outer: UnboundedQuantifierElement, +) -> UnboundedQuantifierElement | None: + outer_child = outer.child + if not isinstance(outer_child, GroupElement): + return None + group_children = outer_child.children + if len(group_children) != 1: + return None + only_child = group_children[0] + if not isinstance(only_child, UnboundedQuantifierElement): + return None + return only_child + + +def _display_name_for(element: UnboundedQuantifierElement) -> str: + if isinstance(element, OneOrMoreElement): + return "one_or_more()" + if isinstance(element, OneOrMoreLazyElement): + return "one_or_more_lazy()" + if isinstance(element, ZeroOrMoreElement): + return "zero_or_more()" + if isinstance(element, ZeroOrMoreLazyElement): + return "zero_or_more_lazy()" + return f"at_least({element.times})" diff --git a/edify/elements/walk.py b/edify/elements/walk.py new file mode 100644 index 0000000..5ae5fc5 --- /dev/null +++ b/edify/elements/walk.py @@ -0,0 +1,42 @@ +"""Shared iterator over the internal element tree. + +Provides a single depth-first walker that yields every :class:`BaseElement` +in an emission-order tree, including nested children reachable through +dataclass fields. Callers that need to inspect an entire pattern (ReDoS +detection, serialization, introspection) reuse this instead of hand-rolling +their own traversal. +""" + +from __future__ import annotations + +from collections.abc import Iterable, Iterator +from dataclasses import fields +from typing import cast + +from edify.elements.types.base import BaseElement + + +def walk_elements(roots: Iterable[BaseElement]) -> Iterator[BaseElement]: + """Yield every :class:`BaseElement` reachable from ``roots`` in depth-first emission order. + + Each root is yielded first, then its descendants (in dataclass-field order), + then the next root. + + Args: + roots: An iterable of root elements. + """ + for element in roots: + yield from _walk_single(element) + + +def _walk_single(element: BaseElement) -> Iterator[BaseElement]: + yield element + for spec in fields(element): + value: object = getattr(element, spec.name) + if isinstance(value, BaseElement): + yield from _walk_single(value) + continue + if isinstance(value, tuple): + narrowed_tuple = cast(tuple[BaseElement, ...], value) + for child in narrowed_tuple: + yield from _walk_single(child) diff --git a/edify/errors/quantifier.py b/edify/errors/quantifier.py index 2acc47b..224196d 100644 --- a/edify/errors/quantifier.py +++ b/edify/errors/quantifier.py @@ -2,28 +2,66 @@ from __future__ import annotations +from edify.errors.context import CallerContext from edify.errors.formatting import compose_annotated_message from edify.errors.syntax import EdifySyntaxError class DanglingQuantifierError(EdifySyntaxError): - """Raised when a terminal is called while a quantifier is still pending.""" + """Raised when a terminal is called while a quantifier is still pending. - def __init__(self) -> None: + Args: + pending_quantifier_name: Human-readable name of the pending quantifier, + e.g. ``"exactly(3)"`` or ``"one_or_more()"``. ``None`` when the name + could not be recovered. + pending_quantifier_call_site: Caller context for the chain call that queued + the pending quantifier; ``None`` when the location could not be captured. + """ + + def __init__( + self, + pending_quantifier_name: str | None = None, + pending_quantifier_call_site: CallerContext | None = None, + ) -> None: + summary = _format_dangling_summary(pending_quantifier_name) + note = _format_dangling_note(pending_quantifier_name, pending_quantifier_call_site) message = compose_annotated_message( - summary="dangling quantifier with no operand to apply to", + summary=summary, trigger_hint="terminal called here", - note=( - "a quantifier such as .exactly(n), .one_or_more(), .at_least(n), or " - ".between(a, b) attaches to the next element in the chain; the chain " - "ended before an element was supplied." - ), + note=note, help_line=( "help: append the element the quantifier should apply to " "(e.g. .digit(), .word(), .string('...')) before the terminal call." ), ) super().__init__(message) + self.pending_quantifier_name = pending_quantifier_name + self.pending_quantifier_call_site = pending_quantifier_call_site + + +def _format_dangling_summary(pending_quantifier_name: str | None) -> str: + if pending_quantifier_name is None: + return "dangling quantifier with no operand to apply to" + return f"dangling .{pending_quantifier_name} with no operand to apply to" + + +def _format_dangling_note( + pending_quantifier_name: str | None, + pending_quantifier_call_site: CallerContext | None, +) -> str: + generic_prefix = ( + "a quantifier such as .exactly(n), .one_or_more(), .at_least(n), or " + ".between(a, b) attaches to the next element in the chain; the chain " + "ended before an element was supplied." + ) + if pending_quantifier_name is None or pending_quantifier_call_site is None: + return generic_prefix + location = pending_quantifier_call_site + return ( + f"{generic_prefix}\n" + f"dangling quantifier: .{pending_quantifier_name} — queued at " + f"{location.filename}:{location.lineno}:{location.colno}" + ) class StackedQuantifierError(EdifySyntaxError): diff --git a/edify/errors/structure.py b/edify/errors/structure.py index e55bf3e..1806b13 100644 --- a/edify/errors/structure.py +++ b/edify/errors/structure.py @@ -2,10 +2,27 @@ from __future__ import annotations +from dataclasses import dataclass + +from edify.errors.context import CallerContext from edify.errors.formatting import compose_annotated_message from edify.errors.syntax import EdifySyntaxError +@dataclass(frozen=True) +class OpenFrameInfo: + """One frame in the open-frame stack, named for error rendering. + + Attributes: + kind: Human-readable frame type (``"capture"``, ``"assert_ahead"``, ...). + opened_at: Caller context for the chain call that opened the frame; + ``None`` when the location could not be captured. + """ + + kind: str + opened_at: CallerContext | None + + class CannotEndWhileBuildingRootExpressionError(EdifySyntaxError): """Raised when ``.end()`` is called on a builder whose only open frame is the root.""" @@ -29,20 +46,41 @@ class CannotCallSubexpressionError(EdifySyntaxError): """Raised when ``.subexpression(expression)`` is given an expression with open frames. Args: - current_frame_type: Name of the frame kind that is still open on the subexpression. + open_frames: The stack of frames that remain open on the subexpression, in + innermost-first order. """ - def __init__(self, current_frame_type: str) -> None: + def __init__(self, open_frames: tuple[OpenFrameInfo, ...]) -> None: + rendered_stack = _format_open_frame_stack(open_frames) + innermost_kind = open_frames[0].kind message = compose_annotated_message( summary="cannot merge a subexpression that has an unclosed frame", trigger_hint="subexpression merged here", note=( - f"the subexpression still has an open {current_frame_type} frame; " - "only fully-closed expressions can be merged into another builder." + f"the subexpression still has {len(open_frames)} open frame(s); " + "only fully-closed expressions can be merged into another builder.\n" + f"open frames (innermost first):\n{rendered_stack}" ), help_line=( - f"help: add a matching .end() call to close the {current_frame_type} frame " - "on the subexpression before passing it to .subexpression(...)." + f"help: add a matching .end() call for each open frame — start by closing " + f"the innermost .{innermost_kind}() frame — before passing the subexpression " + "to .subexpression(...)." ), ) super().__init__(message) + self.open_frames = open_frames + + +def _format_open_frame_stack(open_frames: tuple[OpenFrameInfo, ...]) -> str: + rendered_lines: list[str] = [] + for depth, frame_info in enumerate(open_frames, start=1): + rendered_lines.append(_format_open_frame_line(depth, frame_info)) + return "\n".join(rendered_lines) + + +def _format_open_frame_line(depth: int, frame_info: OpenFrameInfo) -> str: + prefix = f" {depth}. .{frame_info.kind}()" + if frame_info.opened_at is None: + return f"{prefix} — opened at <unknown>" + context = frame_info.opened_at + return f"{prefix} — opened at {context.filename}:{context.lineno}:{context.colno}" diff --git a/edify/serialize/load.py b/edify/serialize/load.py index 67d6a39..b897f25 100644 --- a/edify/serialize/load.py +++ b/edify/serialize/load.py @@ -2,9 +2,7 @@ from __future__ import annotations -from collections.abc import Iterator from dataclasses import fields -from typing import cast from edify.builder.types.flags import Flags from edify.builder.types.frame import StackFrame @@ -13,6 +11,7 @@ from edify.elements.types.base import BaseElement from edify.elements.types.captures import CaptureElement, NamedCaptureElement from edify.elements.types.leaves import EndOfInputElement, StartOfInputElement from edify.elements.types.root import RootElement +from edify.elements.walk import walk_elements from edify.errors.serialize import ( IncompatibleSchemaVersionError, MissingSchemaKeyError, @@ -101,29 +100,10 @@ def _flags_from_document(document: dict[str, JSONValue]) -> Flags: def _collect_named_groups(children: tuple[BaseElement, ...]) -> tuple[str, ...]: - return tuple( - element.name - for element in _walk_elements(children) - if isinstance(element, NamedCaptureElement) - ) + walked = walk_elements(children) + return tuple(element.name for element in walked if isinstance(element, NamedCaptureElement)) def _count_capture_groups(children: tuple[BaseElement, ...]) -> int: - return sum( - 1 - for element in _walk_elements(children) - if isinstance(element, CaptureElement | NamedCaptureElement) - ) - - -def _walk_elements(children: tuple[BaseElement, ...]) -> Iterator[BaseElement]: - for element in children: - yield element - for spec in fields(element): - value: object = getattr(element, spec.name) - if isinstance(value, BaseElement): - yield from _walk_elements((value,)) - continue - if isinstance(value, tuple): - narrowed_tuple = cast(tuple[BaseElement, ...], value) - yield from _walk_elements(narrowed_tuple) + walked = walk_elements(children) + return sum(1 for element in walked if isinstance(element, CaptureElement | NamedCaptureElement)) diff --git a/edify/testing/__init__.py b/edify/testing/__init__.py new file mode 100644 index 0000000..3511525 --- /dev/null +++ b/edify/testing/__init__.py @@ -0,0 +1,7 @@ +from edify.testing.snapshots import ( + SnapshotMismatchError, + SnapshotMissingError, + assert_snapshot, +) + +__all__ = ["SnapshotMismatchError", "SnapshotMissingError", "assert_snapshot"] diff --git a/edify/testing/snapshots.py b/edify/testing/snapshots.py new file mode 100644 index 0000000..6cebeb1 --- /dev/null +++ b/edify/testing/snapshots.py @@ -0,0 +1,91 @@ +"""String-snapshot helpers for pattern tests. + +The default flow compares a produced string against a committed reference +file and raises an AssertionError with a unified diff on mismatch. Setting +``EDIFY_UPDATE_SNAPSHOTS=1`` in the environment switches the helper into +regeneration mode: mismatched or missing snapshots are written to disk +instead of raising, so the reference set can be refreshed in one pytest +run. +""" + +from __future__ import annotations + +import difflib +import os +from pathlib import Path + +_UPDATE_ENVIRONMENT_VARIABLE = "EDIFY_UPDATE_SNAPSHOTS" + + +class SnapshotMismatchError(AssertionError): + """Raised when actual output diverges from the committed snapshot.""" + + def __init__(self, snapshot_path: Path, actual: str, expected: str) -> None: + rendered_diff = _render_diff(snapshot_path, expected, actual) + message = ( + f"snapshot mismatch at {snapshot_path}\n\n" + f"{rendered_diff}\n\n" + f"help: re-run pytest with {_UPDATE_ENVIRONMENT_VARIABLE}=1 to accept the new " + "output as the reference." + ) + super().__init__(message) + + +class SnapshotMissingError(AssertionError): + """Raised when the committed snapshot file does not exist.""" + + def __init__(self, snapshot_path: Path) -> None: + message = ( + f"snapshot file missing at {snapshot_path}\n\n" + f"help: re-run pytest with {_UPDATE_ENVIRONMENT_VARIABLE}=1 to create it." + ) + super().__init__(message) + + +def assert_snapshot(actual: str, snapshot_path: Path) -> None: + """Compare ``actual`` against the reference at ``snapshot_path``. + + Args: + actual: The string produced by the code under test. + snapshot_path: Absolute path to the committed reference file. + + Raises: + SnapshotMissingError: when the reference file does not exist and + update-mode is off. + SnapshotMismatchError: when ``actual`` differs from the reference and + update-mode is off. + """ + if _update_mode_enabled(): + _write_snapshot(snapshot_path, actual) + return + if not snapshot_path.exists(): + raise SnapshotMissingError(snapshot_path) + expected = _read_snapshot(snapshot_path) + if actual == expected: + return + raise SnapshotMismatchError(snapshot_path, actual, expected) + + +def _update_mode_enabled() -> bool: + return os.environ.get(_UPDATE_ENVIRONMENT_VARIABLE) == "1" + + +def _write_snapshot(snapshot_path: Path, actual: str) -> None: + snapshot_path.parent.mkdir(parents=True, exist_ok=True) + snapshot_path.write_bytes(actual.encode("utf-8")) + + +def _read_snapshot(snapshot_path: Path) -> str: + return snapshot_path.read_bytes().decode("utf-8") + + +def _render_diff(snapshot_path: Path, expected: str, actual: str) -> str: + expected_lines = expected.splitlines(keepends=True) + actual_lines = actual.splitlines(keepends=True) + diff_iterator = difflib.unified_diff( + expected_lines, + actual_lines, + fromfile=f"{snapshot_path} (snapshot)", + tofile=f"{snapshot_path} (actual)", + ) + return "".join(diff_iterator).rstrip() diff --git a/tests/builder/builder.test.py b/tests/builder/builder.test.py index 82aa1cf..5f96b5c 100644 --- a/tests/builder/builder.test.py +++ b/tests/builder/builder.test.py @@ -404,14 +404,14 @@ def test_end_of_input(): def test_any_of_chars(): expr = RegexBuilder().any_of_chars("aeiou.-") - regex_equality("[aeiou\\.\\-]", expr) - regex_compilation("[aeiou\\.\\-]", expr) + regex_equality("[aeiou.-]", expr) + regex_compilation("[aeiou.-]", expr) def test_anything_but_chars(): expr = RegexBuilder().anything_but_chars("aeiou.-") - regex_equality("[^aeiou\\.\\-]", expr) - regex_compilation("[^aeiou\\.\\-]", expr) + regex_equality("[^aeiou.-]", expr) + regex_compilation("[^aeiou.-]", expr) def test_anything_but_string(): diff --git a/tests/builder/insertion_order.test.py b/tests/builder/insertion_order.test.py new file mode 100644 index 0000000..4feae65 --- /dev/null +++ b/tests/builder/insertion_order.test.py @@ -0,0 +1,27 @@ +"""Pinned property: character-class members preserve insertion order across calls.""" + +from edify import RegexBuilder + + +def test_any_of_chars_preserves_the_exact_insertion_order_across_runs(): + first = RegexBuilder().any_of_chars("zyxabc321").to_regex_string() + second = RegexBuilder().any_of_chars("zyxabc321").to_regex_string() + third = RegexBuilder().any_of_chars("zyxabc321").to_regex_string() + assert first == second == third == "[zyxabc321]" + + +def test_anything_but_chars_preserves_the_exact_insertion_order_across_runs(): + first = RegexBuilder().anything_but_chars("zyxabc321").to_regex_string() + second = RegexBuilder().anything_but_chars("zyxabc321").to_regex_string() + assert first == second == "[^zyxabc321]" + + +def test_any_of_chars_preserves_duplicated_members_in_declaration_order(): + emitted = RegexBuilder().any_of_chars("aabbcc").to_regex_string() + assert emitted == "[aabbcc]" + + +def test_any_of_chars_never_alphabetically_sorts_its_members(): + emitted = RegexBuilder().any_of_chars("cba").to_regex_string() + assert emitted == "[cba]" + assert emitted != "[abc]" diff --git a/tests/compile/escape.test.py b/tests/compile/escape.test.py new file mode 100644 index 0000000..e9ab657 --- /dev/null +++ b/tests/compile/escape.test.py @@ -0,0 +1,115 @@ +"""Tests for the character-class escape normalization. + +The minimal-correct form escapes only ``\\``, ``]``, first-position ``^``, +and interior ``-`` — everything else passes through as a literal. Match +behavior against a representative corpus must stay identical to the +previous over-escaped form. +""" + +import re + +import pytest + +from edify import RegexBuilder + +_METACHARS_UNRELATED_TO_CLASS = "#?!@$%^&*" +_MIXED_CORPUS = [ + "", + "a", + "z", + "#", + "?", + "!", + "@", + "$", + "%", + "^", + "&", + "*", + "-", + ".", + "abc", + "a#b", + "1-2", + " ", + "foo.bar", + "hello world", + "a^b", + "]", + "[a]", + "\\", +] + + +def test_any_of_chars_emits_minimal_form_for_class_safe_metachars(): + emitted = RegexBuilder().any_of_chars(_METACHARS_UNRELATED_TO_CLASS).to_regex_string() + assert emitted == "[#?!@$%^&*]" + + +def test_any_of_chars_escapes_backslash_and_closing_bracket_only(): + emitted = RegexBuilder().any_of_chars("a\\b]c").to_regex_string() + assert emitted == "[a\\\\b\\]c]" + + +def test_any_of_chars_escapes_first_position_caret_only(): + first_caret = RegexBuilder().any_of_chars("^abc").to_regex_string() + later_caret = RegexBuilder().any_of_chars("a^bc").to_regex_string() + assert first_caret == "[\\^abc]" + assert later_caret == "[a^bc]" + + +def test_any_of_chars_escapes_interior_dash_only(): + edge_dashes = RegexBuilder().any_of_chars("-a-").to_regex_string() + interior_dash = RegexBuilder().any_of_chars("a-b").to_regex_string() + assert edge_dashes == "[-a-]" + assert interior_dash == "[a\\-b]" + + +def test_any_of_chars_leaves_dot_and_asterisk_unescaped_inside_a_class(): + emitted = RegexBuilder().any_of_chars(".*").to_regex_string() + assert emitted == "[.*]" + + +def test_anything_but_chars_leading_position_of_body_is_not_caret(): + emitted = RegexBuilder().anything_but_chars("^abc").to_regex_string() + assert emitted == "[^\\^abc]" + + +def test_anything_but_chars_normalizes_the_same_way_as_any_of_chars(): + emitted = RegexBuilder().anything_but_chars("#?!@$%^&*").to_regex_string() + assert emitted == "[^#?!@$%^&*]" + + +def test_string_terminal_still_uses_full_escape_outside_the_class(): + emitted = RegexBuilder().string("a.b").to_regex_string() + assert emitted == "a\\.b" + + +def test_char_terminal_still_uses_full_escape_outside_the_class(): + emitted = RegexBuilder().char(".").to_regex_string() + assert emitted == "\\." + + [email protected]("candidate", _MIXED_CORPUS) +def test_normalized_and_over_escaped_char_classes_match_the_same_inputs(candidate): + normalized_pattern = ( + RegexBuilder().any_of_chars(_METACHARS_UNRELATED_TO_CLASS).to_regex_string() + ) + over_escaped_pattern = f"[{re.escape(_METACHARS_UNRELATED_TO_CLASS)}]" + normalized_hits = re.findall(normalized_pattern, candidate) + over_escaped_hits = re.findall(over_escaped_pattern, candidate) + assert normalized_hits == over_escaped_hits + + [email protected]("candidate", _MIXED_CORPUS) +def test_normalized_and_over_escaped_negated_classes_match_the_same_inputs(candidate): + normalized_pattern = RegexBuilder().anything_but_chars("aeiou.-").to_regex_string() + over_escaped_pattern = "[^aeiou\\.\\-]" + normalized_hits = re.findall(normalized_pattern, candidate) + over_escaped_hits = re.findall(over_escaped_pattern, candidate) + assert normalized_hits == over_escaped_hits + + +def test_any_of_chars_empty_class_still_renders_empty(): + emitted = RegexBuilder().any_of_chars("").to_regex_string() + assert emitted == "[]" diff --git a/tests/compile/redos.test.py b/tests/compile/redos.test.py new file mode 100644 index 0000000..c4e1ba9 --- /dev/null +++ b/tests/compile/redos.test.py @@ -0,0 +1,78 @@ +"""Tests for the build-time ReDoS detector.""" + +import pytest + +from edify import RegexBuilder +from edify.compile.redos import ReDoSWarning + + +def _classic_redos_builder(): + return RegexBuilder().one_or_more().group().one_or_more().digit().end() + + +def test_classic_nested_unbounded_quantifier_triggers_the_warning(): + with pytest.warns(ReDoSWarning, match="nested unbounded quantifier"): + _classic_redos_builder().to_regex() + + +def test_zero_or_more_wrapping_one_or_more_triggers_the_warning(): + builder = RegexBuilder().zero_or_more().group().one_or_more().digit().end() + with pytest.warns(ReDoSWarning): + builder.to_regex() + + +def test_at_least_wrapping_zero_or_more_triggers_the_warning(): + builder = RegexBuilder().at_least(2).group().zero_or_more().word().end() + with pytest.warns(ReDoSWarning): + builder.to_regex() + + +def test_lazy_variants_also_trigger_the_warning(): + builder = RegexBuilder().one_or_more_lazy().group().zero_or_more_lazy().digit().end() + with pytest.warns(ReDoSWarning): + builder.to_regex() + + +def test_warning_message_names_both_quantifiers(): + with pytest.warns(ReDoSWarning) as record: + _classic_redos_builder().to_regex() + message_text = str(record[0].message) + assert "one_or_more()" in message_text + assert "engine='regex'" in message_text + + +def test_bounded_quantifier_does_not_trigger_the_warning(recwarn): + builder = RegexBuilder().exactly(3).group().exactly(2).digit().end() + builder.to_regex() + assert not any(isinstance(warning.message, ReDoSWarning) for warning in recwarn) + + +def test_grouped_quantifier_over_a_composite_group_does_not_trigger(recwarn): + builder = RegexBuilder().one_or_more().group().digit().letter().end() + builder.to_regex() + assert not any(isinstance(warning.message, ReDoSWarning) for warning in recwarn) + + +def test_grouped_quantifier_over_a_single_non_quantifier_child_does_not_trigger(recwarn): + builder = RegexBuilder().one_or_more().group().digit().end() + builder.to_regex() + assert not any(isinstance(warning.message, ReDoSWarning) for warning in recwarn) + + +def test_sequential_unbounded_quantifiers_do_not_trigger_the_warning(recwarn): + builder = RegexBuilder().one_or_more().digit().one_or_more().letter() + builder.to_regex() + assert not any(isinstance(warning.message, ReDoSWarning) for warning in recwarn) + + +def test_warning_only_fires_once_per_construct_within_a_single_terminal_call(): + with pytest.warns(ReDoSWarning) as record: + _classic_redos_builder().to_regex() + redos_records = [w for w in record if isinstance(w.message, ReDoSWarning)] + assert len(redos_records) == 1 + + +def test_terminal_still_returns_the_compiled_regex_after_warning(): + with pytest.warns(ReDoSWarning): + compiled = _classic_redos_builder().to_regex() + assert compiled.source == "(?:\\d+)+" diff --git a/tests/discipline/orphans.test.py b/tests/discipline/orphans.test.py new file mode 100644 index 0000000..a0a3150 --- /dev/null +++ b/tests/discipline/orphans.test.py @@ -0,0 +1,131 @@ +"""Orphan detection — every committed snapshot must trace back to a live source. + +Runs three checks, one per snapshot root: + +* ``tests/snapshots/library/<name>.regex`` — must correspond to a + :class:`edify.Pattern` exported from :mod:`edify.library`. +* ``tests/snapshots/fixtures/<name>.regex`` — must correspond to a fixture + registered in ``tests/fixtures/edge_cases.test.py``. +* ``tests/snapshots/docs/<file>/<block>.regex`` — must correspond to a + ``.. code-block:: python`` block at that line in that RST. + +The reverse direction (a source without a snapshot) is caught by +``SnapshotMissingError`` inside the per-source test; adding a new source +without generating its snapshot fails the direct test, not this one. +""" + +from pathlib import Path + +import edify.library as library_module +from edify import Pattern + +_REPO_ROOT = Path(__file__).parent.parent.parent +_SNAPSHOT_ROOT = _REPO_ROOT / "tests" / "snapshots" +_LIBRARY_SNAPSHOT_ROOT = _SNAPSHOT_ROOT / "library" +_FIXTURES_SNAPSHOT_ROOT = _SNAPSHOT_ROOT / "fixtures" +_DOCS_SNAPSHOT_ROOT = _SNAPSHOT_ROOT / "docs" +_DOCS_SOURCE_ROOT = _REPO_ROOT / "docs" +_CODE_BLOCK_HEADER = ".. code-block:: python" + + +def test_every_library_snapshot_has_a_registered_pattern(): + snapshot_stems = {path.stem for path in _LIBRARY_SNAPSHOT_ROOT.glob("*.regex")} + pattern_names = _library_pattern_names() + orphaned_snapshots = snapshot_stems - pattern_names + assert orphaned_snapshots == set(), ( + f"library snapshot files without a registered Pattern in edify.library: " + f"{sorted(orphaned_snapshots)}" + ) + + +def test_every_fixture_snapshot_has_a_registered_fixture(): + snapshot_stems = {path.stem for path in _FIXTURES_SNAPSHOT_ROOT.glob("*.regex")} + fixture_names = _registered_fixture_names() + orphaned_snapshots = snapshot_stems - fixture_names + assert orphaned_snapshots == set(), ( + f"fixture snapshot files without a registered fixture in " + f"tests/fixtures/edge_cases.test.py: {sorted(orphaned_snapshots)}" + ) + + +def test_every_doc_snapshot_has_a_matching_rst_code_block(): + snapshot_pairs = _collect_doc_snapshot_pairs() + doc_block_pairs = _collect_doc_code_block_pairs() + orphaned_snapshots = snapshot_pairs - doc_block_pairs + assert orphaned_snapshots == set(), ( + f"doc snapshot files without a matching .. code-block:: python: " + f"{sorted(orphaned_snapshots)}" + ) + + +def _library_pattern_names() -> set[str]: + names: set[str] = set() + for export_name in dir(library_module): + if export_name.startswith("_"): + continue + value = getattr(library_module, export_name) + if isinstance(value, Pattern): + names.add(export_name) + return names + + +def _registered_fixture_names() -> set[str]: + fixture_source = (_REPO_ROOT / "tests" / "fixtures" / "edge_cases.test.py").read_text() + return _fixture_names_from_source(fixture_source) + + +def _fixture_names_from_source(source: str) -> set[str]: + names: set[str] = set() + for line in source.splitlines(): + stripped = line.strip() + if not stripped.startswith('("'): + continue + closing_quote_index = stripped.find('"', 2) + if closing_quote_index == -1: + continue + names.add(stripped[2:closing_quote_index]) + return names + + +def _collect_doc_snapshot_pairs() -> set[tuple[str, int]]: + pairs: set[tuple[str, int]] = set() + for snapshot in _DOCS_SNAPSHOT_ROOT.rglob("*.regex"): + relative_stem = snapshot.parent.relative_to(_DOCS_SNAPSHOT_ROOT).as_posix() + block_start = int(snapshot.stem) + pairs.add((relative_stem, block_start)) + return pairs + + +def _collect_doc_code_block_pairs() -> set[tuple[str, int]]: + pairs: set[tuple[str, int]] = set() + for rst_path in _DOCS_SOURCE_ROOT.rglob("*.rst"): + relative_stem = rst_path.relative_to(_DOCS_SOURCE_ROOT).with_suffix("").as_posix() + for block_start in _code_block_start_lines(rst_path): + pairs.add((relative_stem, block_start)) + return pairs + + +def _code_block_start_lines(rst_path: Path) -> list[int]: + starts: list[int] = [] + lines = rst_path.read_text().splitlines() + line_index = 0 + while line_index < len(lines): + if lines[line_index].strip() != _CODE_BLOCK_HEADER: + line_index += 1 + continue + body_start = _first_content_line(lines, line_index + 1) + if body_start is not None: + starts.append(body_start) + line_index += 1 + return starts + + +def _first_content_line(lines: list[str], from_index: int) -> int | None: + line_index = from_index + while line_index < len(lines) and lines[line_index].strip() == "": + line_index += 1 + if line_index >= len(lines): + return None + if lines[line_index].startswith(" ") or lines[line_index].startswith("\t"): + return line_index + return None diff --git a/tests/docs/snapshots.test.py b/tests/docs/snapshots.test.py new file mode 100644 index 0000000..75523ee --- /dev/null +++ b/tests/docs/snapshots.test.py @@ -0,0 +1,143 @@ +"""Snapshot suite for every ``.. code-block:: python`` block shipped in the docs. + +Each block is executed in a fresh edify-populated namespace; any ``Regex`` +or ``Pattern`` bound to a top-level name has its ``.to_regex_string()`` +snapshotted under ``tests/snapshots/docs/<file-stem>/<block-index>.regex``. +The snapshot pins the docs against silent drift: an intentional compile-path +change surfaces every doc example the change touches so their prose stays +truthful. +""" + +import re +import sys +from pathlib import Path + +import pytest + +import edify +import edify.library as edify_library +from edify import Pattern +from edify.result import Regex +from edify.testing import assert_snapshot + +_ON_PYPY = hasattr(sys, "pypy_version_info") + +_REPO_ROOT = Path(__file__).parent.parent.parent +_DOCS_ROOT = _REPO_ROOT / "docs" +_SNAPSHOT_ROOT = _REPO_ROOT / "tests" / "snapshots" / "docs" +_CODE_BLOCK_HEADER = ".. code-block:: python" + + +def _collect_python_code_blocks(rst_path: Path): + lines = rst_path.read_text().splitlines() + line_index = 0 + while line_index < len(lines): + stripped = lines[line_index].strip() + if stripped != _CODE_BLOCK_HEADER: + line_index += 1 + continue + block_start, block_lines = _consume_indented_block(lines, line_index + 1) + if block_lines: + yield block_start, "\n".join(block_lines) + line_index = block_start + len(block_lines) + + +def _consume_indented_block(lines, start_index): + block_lines: list[str] = [] + line_index = start_index + while line_index < len(lines) and lines[line_index].strip() == "": + line_index += 1 + if line_index >= len(lines): + return line_index, block_lines + indent_prefix = lines[line_index][: len(lines[line_index]) - len(lines[line_index].lstrip())] + if indent_prefix == "": + return line_index, block_lines + while line_index < len(lines): + current_line = lines[line_index] + if current_line.strip() == "": + block_lines.append("") + line_index += 1 + continue + if not current_line.startswith(indent_prefix): + break + block_lines.append(current_line[len(indent_prefix) :]) + line_index += 1 + return line_index - len(block_lines), block_lines + + +def _discover_blocks(): + for rst_path in sorted(_DOCS_ROOT.rglob("*.rst")): + for block_start, block_source in _collect_python_code_blocks(rst_path): + relative_stem = rst_path.relative_to(_DOCS_ROOT).with_suffix("") + yield rst_path, block_start, block_source, relative_stem + + +def _snapshot_bodies_for_block(namespace: dict) -> str: + interesting_pairs = [] + for identifier, value in namespace.items(): + if identifier.startswith("_") or identifier in {"edify", "Pattern", "Regex"}: + continue + if isinstance(value, Regex): + interesting_pairs.append((identifier, "regex", value.source)) + elif isinstance(value, Pattern): + interesting_pairs.append((identifier, "pattern", value.to_regex_string())) + interesting_pairs.sort() + rendered_lines = [f"{name} :: {kind} :: {body}" for name, kind, body in interesting_pairs] + return "\n".join(rendered_lines) + ("\n" if rendered_lines else "") + + +DISCOVERED_BLOCKS = list(_discover_blocks()) + +_BLOCKS_DEFERRED_TO_DOCS_REWRITE = frozenset( + { + ("built-in/index", 46), + ("built-in/index", 83), + ("built-in/index", 98), + ("built-in/index", 142), + ("built-in/index", 175), + ("built-in/index", 184), + ("built-in/index", 936), + ("regex-builder/builder/index", 396), + } +) + +_BLOCKS_SKIPPED_ON_PYPY = frozenset( + { + ("regex-builder/flags/index", 38), + } +) + + + ("rst_path", "block_start", "block_source", "relative_stem"), + DISCOVERED_BLOCKS, + ids=[ + f"{relative_stem}:{block_start}" for _, block_start, _, relative_stem in DISCOVERED_BLOCKS + ], +) +def test_doc_code_block_produces_the_snapshotted_regex( + rst_path, block_start, block_source, relative_stem +): + stem_string = str(relative_stem) + if (stem_string, block_start) in _BLOCKS_DEFERRED_TO_DOCS_REWRITE: + pytest.skip("doc block references validators / kwargs slated for the docs rewrite") + if _ON_PYPY and (stem_string, block_start) in _BLOCKS_SKIPPED_ON_PYPY: + pytest.skip("doc block hits PyPy's re.DEBUG upstream disassembler bug") + namespace = _prepared_exec_namespace() + exec(compile(block_source, str(rst_path), "exec"), namespace) + rendered = _snapshot_bodies_for_block(namespace) + snapshot_path = _SNAPSHOT_ROOT / stem_string / f"{block_start:04d}.regex" + assert_snapshot(rendered, snapshot_path) + + +def _prepared_exec_namespace() -> dict[str, object]: + namespace: dict[str, object] = {"edify": edify, "Pattern": Pattern, "Regex": Regex, "re": re} + for edify_export in dir(edify): + if edify_export.startswith("_"): + continue + namespace[edify_export] = getattr(edify, edify_export) + for library_export in dir(edify_library): + if library_export.startswith("_"): + continue + namespace[library_export] = getattr(edify_library, library_export) + return namespace diff --git a/tests/errors/errors.test.py b/tests/errors/errors.test.py index 632045d..82eb8fe 100644 --- a/tests/errors/errors.test.py +++ b/tests/errors/errors.test.py @@ -24,6 +24,7 @@ from edify.errors.naming import ( from edify.errors.structure import ( CannotCallSubexpressionError, CannotEndWhileBuildingRootExpressionError, + OpenFrameInfo, ) @@ -176,10 +177,26 @@ def test_cannot_end_while_building_root_expression(): def test_cannot_call_subexpression(): - error = CannotCallSubexpressionError("capture") + frames = (OpenFrameInfo(kind="capture", opened_at=None),) + error = CannotCallSubexpressionError(frames) text = str(error) assert "cannot merge a subexpression that has an unclosed frame" in text - assert "capture" in text + assert "1 open frame" in text + assert ".capture()" in text + + +def test_cannot_call_subexpression_lists_every_open_frame_in_the_stack(): + frames = ( + OpenFrameInfo(kind="capture", opened_at=None), + OpenFrameInfo(kind="assert_ahead", opened_at=None), + OpenFrameInfo(kind="group", opened_at=None), + ) + error = CannotCallSubexpressionError(frames) + text = str(error) + assert "3 open frame" in text + assert "1. .capture()" in text + assert "2. .assert_ahead()" in text + assert "3. .group()" in text def test_every_annotated_error_message_starts_with_error_prefix(): @@ -202,7 +219,7 @@ def test_every_annotated_error_message_starts_with_error_prefix(): CannotCreateDuplicateNamedGroupError("x"), NamedGroupDoesNotExistError("x"), CannotEndWhileBuildingRootExpressionError(), - CannotCallSubexpressionError("capture"), + CannotCallSubexpressionError((OpenFrameInfo(kind="capture", opened_at=None),)), ] for error in errors: text = str(error) diff --git a/tests/errors/frames.test.py b/tests/errors/frames.test.py new file mode 100644 index 0000000..b4077ba --- /dev/null +++ b/tests/errors/frames.test.py @@ -0,0 +1,69 @@ +"""Tests for the structured open-frame stack surfaced by CannotCallSubexpressionError.""" + +import pytest + +from edify import RegexBuilder +from edify.errors.structure import CannotCallSubexpressionError + + +def test_single_open_capture_frame_is_named_in_the_error_message(): + unfinished = RegexBuilder().capture().digit() + with pytest.raises(CannotCallSubexpressionError) as excinfo: + unfinished.to_regex_string() + text = str(excinfo.value) + assert "1 open frame" in text + assert "1. .capture()" in text + + +def test_nested_open_frames_are_listed_innermost_first(): + unfinished = RegexBuilder().capture().group().assert_ahead().digit() + with pytest.raises(CannotCallSubexpressionError) as excinfo: + unfinished.to_regex_string() + text = str(excinfo.value) + assert "3 open frame" in text + innermost_index = text.index("1. .assert_ahead()") + middle_index = text.index("2. .group()") + outermost_index = text.index("3. .capture()") + assert innermost_index < middle_index < outermost_index + + +def test_named_capture_frame_reports_the_declared_group_name(): + unfinished = RegexBuilder().named_capture("username").letter() + with pytest.raises(CannotCallSubexpressionError) as excinfo: + unfinished.to_regex_string() + text = str(excinfo.value) + assert '.named_capture("username")' in text + + +def test_open_frames_carry_a_file_line_column_pointer_when_the_context_is_captured(): + unfinished = RegexBuilder().assert_behind().digit() + with pytest.raises(CannotCallSubexpressionError) as excinfo: + unfinished.to_regex_string() + text = str(excinfo.value) + assert "opened at" in text + assert __file__ in text or "frames.test.py" in text + + +def test_subexpression_lists_open_frames_from_the_argument_not_the_receiver(): + unfinished = RegexBuilder().named_capture("group").digit() + parent = RegexBuilder() + with pytest.raises(CannotCallSubexpressionError) as excinfo: + parent.subexpression(unfinished) + assert 'named_capture("group")' in str(excinfo.value) + + +def test_dangling_quantifier_reports_the_specific_pending_quantifier_name(): + from edify.errors.quantifier import DanglingQuantifierError + + with pytest.raises(DanglingQuantifierError) as excinfo: + RegexBuilder().exactly(3).to_regex_string() + text = str(excinfo.value) + assert ".exactly(3)" in text + + +def test_dangling_quantifier_falls_back_to_generic_summary_when_name_unknown(): + from edify.errors.quantifier import DanglingQuantifierError + + error = DanglingQuantifierError() + text = str(error) + assert "dangling quantifier" in text diff --git a/tests/fixtures/edge_cases.test.py b/tests/fixtures/edge_cases.test.py new file mode 100644 index 0000000..18200b6 --- /dev/null +++ b/tests/fixtures/edge_cases.test.py @@ -0,0 +1,130 @@ +"""Snapshot suite for the compile-path edge-case fixtures. + +Each fixture builds a small, intentionally-tricky pattern and pins the +emitted regex string. The fixtures are the ones most likely to silently +drift when the compile path is refactored: deeply nested alternation, +combined lookaround, item-4 named backreferences, and constructs that +:mod:`edify` flags for ReDoS. +""" + +from pathlib import Path + +import pytest + +from edify import Pattern, RegexBuilder +from edify.testing import assert_snapshot + +_SNAPSHOT_ROOT = Path(__file__).parent.parent / "snapshots" / "fixtures" + + +def _deeply_nested_alternation(): + inner_alt = RegexBuilder().any_of("abc", "def", "ghi") + outer_alt = RegexBuilder().any_of("first", "second", "third") + return ( + RegexBuilder() + .start_of_input() + .subexpression(inner_alt) + .string("-") + .subexpression(outer_alt) + .end_of_input() + ) + + +def _combined_lookaround_positive_and_negative(): + return ( + RegexBuilder() + .assert_ahead() + .digit() + .end() + .assert_not_ahead() + .string("0") + .end() + .assert_behind() + .string("$") + .end() + .digit() + .digit() + .digit() + ) + + +def _named_backreference_pair(): + return ( + RegexBuilder() + .named_capture("open") + .any_of("[", "(", "{") + .end() + .zero_or_more() + .word() + .named_back_reference("open") + ) + + +def _nested_unbounded_quantifier_redos(): + return RegexBuilder().one_or_more().group().one_or_more().digit().end() + + +def _alternation_with_overlapping_prefixes_redos(): + return RegexBuilder().one_or_more().any_of("aa", "aaa", "aaaa") + + +def _hex_color_pattern(): + return ( + RegexBuilder() + .start_of_input() + .string("#") + .any_of_chars("0123456789abcdefABCDEF") + .exactly(6) + .word() + .end_of_input() + ) + + +def _email_shape_pattern(): + return ( + RegexBuilder() + .start_of_input() + .one_or_more() + .any_of_chars("a-zA-Z0-9._%+-") + .string("@") + .one_or_more() + .any_of_chars("a-zA-Z0-9.-") + .string(".") + .at_least(2) + .letter() + .end_of_input() + ) + + +def _pattern_composition_via_use(): + reusable = Pattern().at_least(2).letter() + return RegexBuilder().start_of_input().subexpression(reusable).end_of_input() + + +_FIXTURES = [ + ("deeply_nested_alternation", _deeply_nested_alternation), + ("combined_lookaround", _combined_lookaround_positive_and_negative), + ("named_backreference_pair", _named_backreference_pair), + ("nested_unbounded_quantifier_redos", _nested_unbounded_quantifier_redos), + ("alternation_with_overlapping_prefixes_redos", _alternation_with_overlapping_prefixes_redos), + ("hex_color_pattern", _hex_color_pattern), + ("email_shape_pattern", _email_shape_pattern), + ("pattern_composition_via_use", _pattern_composition_via_use), +] + + + ("fixture_name", "builder_factory"), _FIXTURES, ids=[name for name, _ in _FIXTURES] +) +def test_edge_case_fixture_emits_the_snapshotted_regex(fixture_name, builder_factory): + builder = builder_factory() + emitted = builder.to_regex_string() + snapshot_path = _SNAPSHOT_ROOT / f"{fixture_name}.regex" + assert_snapshot(emitted, snapshot_path) + + +def test_nested_unbounded_quantifier_fixture_raises_the_redos_warning(): + from edify.compile.redos import ReDoSWarning + + with pytest.warns(ReDoSWarning): + _nested_unbounded_quantifier_redos().to_regex() diff --git a/tests/library/invariants.test.py b/tests/library/invariants.test.py new file mode 100644 index 0000000..a153d2f --- /dev/null +++ b/tests/library/invariants.test.py @@ -0,0 +1,28 @@ +"""Cross-cutting invariants that must hold for every registered library Pattern.""" + +import pytest + +import edify.library as library_module +from edify import Pattern + + +def _registered_patterns(): + for name in sorted(dir(library_module)): + if name.startswith("_"): + continue + value = getattr(library_module, name) + if isinstance(value, Pattern): + yield name, value + + +REGISTERED_PATTERNS = list(_registered_patterns()) + + [email protected](("name", "pattern"), REGISTERED_PATTERNS, ids=lambda item: item) +def test_to_regex_string_matches_the_compiled_pattern_source(name, pattern): + emitted_source = pattern.to_regex_string() + compiled_source = pattern.to_regex().source + assert emitted_source == compiled_source, ( + f"library pattern {name!r} emits {emitted_source!r} but its compiled Regex reports " + f"{compiled_source!r} — the terminals have drifted apart." + ) diff --git a/tests/library/snapshots.test.py b/tests/library/snapshots.test.py new file mode 100644 index 0000000..05ff721 --- /dev/null +++ b/tests/library/snapshots.test.py @@ -0,0 +1,36 @@ +"""Snapshot suite for every public library validator. + +Each :class:`edify.Pattern` exposed by :mod:`edify.library` has a committed +``.to_regex_string()`` snapshot under ``tests/snapshots/library/<name>.regex``. +Set ``EDIFY_UPDATE_SNAPSHOTS=1`` to regenerate the corpus in one pytest run +after an intentional compile-path change. +""" + +from pathlib import Path + +import pytest + +import edify.library as library_module +from edify import Pattern +from edify.testing import assert_snapshot + +_SNAPSHOT_ROOT = Path(__file__).parent.parent / "snapshots" / "library" + + +def _registered_patterns(): + for name in sorted(dir(library_module)): + if name.startswith("_"): + continue + value = getattr(library_module, name) + if isinstance(value, Pattern): + yield name, value + + +REGISTERED_PATTERNS = list(_registered_patterns()) + + [email protected](("name", "pattern"), REGISTERED_PATTERNS, ids=lambda item: item) +def test_library_pattern_emits_the_snapshotted_regex(name, pattern): + snapshot_path = _SNAPSHOT_ROOT / f"{name}.regex" + emitted = pattern.to_regex_string() + assert_snapshot(emitted, snapshot_path) diff --git a/tests/snapshots/docs/built-in/index/0028.regex b/tests/snapshots/docs/built-in/index/0028.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/0028.regex diff --git a/tests/snapshots/docs/built-in/index/0034.regex b/tests/snapshots/docs/built-in/index/0034.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/0034.regex diff --git a/tests/snapshots/docs/built-in/index/0064.regex b/tests/snapshots/docs/built-in/index/0064.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/0064.regex diff --git a/tests/snapshots/docs/built-in/index/0115.regex b/tests/snapshots/docs/built-in/index/0115.regex Binary files differnew file mode 100644 index 0000000..bfea61d --- /dev/null +++ b/tests/snapshots/docs/built-in/index/0115.regex diff --git a/tests/snapshots/docs/built-in/index/0128.regex b/tests/snapshots/docs/built-in/index/0128.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/0128.regex diff --git a/tests/snapshots/docs/built-in/index/0160.regex b/tests/snapshots/docs/built-in/index/0160.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/0160.regex diff --git a/tests/snapshots/docs/built-in/index/0199.regex b/tests/snapshots/docs/built-in/index/0199.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/0199.regex diff --git a/tests/snapshots/docs/built-in/index/0957.regex b/tests/snapshots/docs/built-in/index/0957.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/0957.regex diff --git a/tests/snapshots/docs/built-in/index/0983.regex b/tests/snapshots/docs/built-in/index/0983.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/0983.regex diff --git a/tests/snapshots/docs/built-in/index/1001.regex b/tests/snapshots/docs/built-in/index/1001.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/1001.regex diff --git a/tests/snapshots/docs/built-in/index/1017.regex b/tests/snapshots/docs/built-in/index/1017.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/built-in/index/1017.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0019.regex b/tests/snapshots/docs/regex-builder/builder/index/0019.regex Binary files differnew file mode 100644 index 0000000..3a6abd5 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0019.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0044.regex b/tests/snapshots/docs/regex-builder/builder/index/0044.regex Binary files differnew file mode 100644 index 0000000..fd99ebb --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0044.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0059.regex b/tests/snapshots/docs/regex-builder/builder/index/0059.regex Binary files differnew file mode 100644 index 0000000..8f6a9c7 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0059.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0080.regex b/tests/snapshots/docs/regex-builder/builder/index/0080.regex Binary files differnew file mode 100644 index 0000000..f8428de --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0080.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0103.regex b/tests/snapshots/docs/regex-builder/builder/index/0103.regex Binary files differnew file mode 100644 index 0000000..1829d24 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0103.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0120.regex b/tests/snapshots/docs/regex-builder/builder/index/0120.regex Binary files differnew file mode 100644 index 0000000..9cd58c7 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0120.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0139.regex b/tests/snapshots/docs/regex-builder/builder/index/0139.regex Binary files differnew file mode 100644 index 0000000..df37fb5 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0139.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0156.regex b/tests/snapshots/docs/regex-builder/builder/index/0156.regex Binary files differnew file mode 100644 index 0000000..b460d45 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0156.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0175.regex b/tests/snapshots/docs/regex-builder/builder/index/0175.regex Binary files differnew file mode 100644 index 0000000..0c33843 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0175.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0189.regex b/tests/snapshots/docs/regex-builder/builder/index/0189.regex Binary files differnew file mode 100644 index 0000000..9186a50 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0189.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0201.regex b/tests/snapshots/docs/regex-builder/builder/index/0201.regex Binary files differnew file mode 100644 index 0000000..3734389 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0201.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0216.regex b/tests/snapshots/docs/regex-builder/builder/index/0216.regex Binary files differnew file mode 100644 index 0000000..75e1c54 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0216.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0232.regex b/tests/snapshots/docs/regex-builder/builder/index/0232.regex Binary files differnew file mode 100644 index 0000000..495a4a2 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0232.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0248.regex b/tests/snapshots/docs/regex-builder/builder/index/0248.regex Binary files differnew file mode 100644 index 0000000..d51fcf6 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0248.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0265.regex b/tests/snapshots/docs/regex-builder/builder/index/0265.regex Binary files differnew file mode 100644 index 0000000..d62f468 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0265.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0293.regex b/tests/snapshots/docs/regex-builder/builder/index/0293.regex Binary files differnew file mode 100644 index 0000000..a7833cd --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0293.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0318.regex b/tests/snapshots/docs/regex-builder/builder/index/0318.regex Binary files differnew file mode 100644 index 0000000..7b80739 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0318.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0347.regex b/tests/snapshots/docs/regex-builder/builder/index/0347.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0347.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0371.regex b/tests/snapshots/docs/regex-builder/builder/index/0371.regex Binary files differnew file mode 100644 index 0000000..c8c8e22 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0371.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0421.regex b/tests/snapshots/docs/regex-builder/builder/index/0421.regex Binary files differnew file mode 100644 index 0000000..d16376a --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0421.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0445.regex b/tests/snapshots/docs/regex-builder/builder/index/0445.regex Binary files differnew file mode 100644 index 0000000..3a62450 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0445.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0469.regex b/tests/snapshots/docs/regex-builder/builder/index/0469.regex Binary files differnew file mode 100644 index 0000000..7d9fa46 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0469.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0493.regex b/tests/snapshots/docs/regex-builder/builder/index/0493.regex Binary files differnew file mode 100644 index 0000000..470123c --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0493.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0514.regex b/tests/snapshots/docs/regex-builder/builder/index/0514.regex Binary files differnew file mode 100644 index 0000000..ad1e9ea --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0514.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0535.regex b/tests/snapshots/docs/regex-builder/builder/index/0535.regex Binary files differnew file mode 100644 index 0000000..8b71c9d --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0535.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0554.regex b/tests/snapshots/docs/regex-builder/builder/index/0554.regex Binary files differnew file mode 100644 index 0000000..7098f9c --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0554.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0573.regex b/tests/snapshots/docs/regex-builder/builder/index/0573.regex Binary files differnew file mode 100644 index 0000000..b77a507 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0573.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0592.regex b/tests/snapshots/docs/regex-builder/builder/index/0592.regex Binary files differnew file mode 100644 index 0000000..29e3ccc --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0592.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0611.regex b/tests/snapshots/docs/regex-builder/builder/index/0611.regex Binary files differnew file mode 100644 index 0000000..d115de0 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0611.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0630.regex b/tests/snapshots/docs/regex-builder/builder/index/0630.regex Binary files differnew file mode 100644 index 0000000..14e843f --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0630.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0649.regex b/tests/snapshots/docs/regex-builder/builder/index/0649.regex Binary files differnew file mode 100644 index 0000000..5dbd654 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0649.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0668.regex b/tests/snapshots/docs/regex-builder/builder/index/0668.regex Binary files differnew file mode 100644 index 0000000..1526551 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0668.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0687.regex b/tests/snapshots/docs/regex-builder/builder/index/0687.regex Binary files differnew file mode 100644 index 0000000..51fc73e --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0687.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0706.regex b/tests/snapshots/docs/regex-builder/builder/index/0706.regex Binary files differnew file mode 100644 index 0000000..5a1cd83 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0706.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0725.regex b/tests/snapshots/docs/regex-builder/builder/index/0725.regex Binary files differnew file mode 100644 index 0000000..ffec79c --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0725.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0744.regex b/tests/snapshots/docs/regex-builder/builder/index/0744.regex Binary files differnew file mode 100644 index 0000000..9321e99 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0744.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0762.regex b/tests/snapshots/docs/regex-builder/builder/index/0762.regex Binary files differnew file mode 100644 index 0000000..3c348f5 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0762.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0780.regex b/tests/snapshots/docs/regex-builder/builder/index/0780.regex Binary files differnew file mode 100644 index 0000000..b76ca12 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0780.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0798.regex b/tests/snapshots/docs/regex-builder/builder/index/0798.regex Binary files differnew file mode 100644 index 0000000..09c76e6 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0798.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0816.regex b/tests/snapshots/docs/regex-builder/builder/index/0816.regex Binary files differnew file mode 100644 index 0000000..2404b1d --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0816.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0834.regex b/tests/snapshots/docs/regex-builder/builder/index/0834.regex Binary files differnew file mode 100644 index 0000000..cd4745e --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0834.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0852.regex b/tests/snapshots/docs/regex-builder/builder/index/0852.regex Binary files differnew file mode 100644 index 0000000..cd83a68 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0852.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0892.regex b/tests/snapshots/docs/regex-builder/builder/index/0892.regex Binary files differnew file mode 100644 index 0000000..57c8680 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0892.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0913.regex b/tests/snapshots/docs/regex-builder/builder/index/0913.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0913.regex diff --git a/tests/snapshots/docs/regex-builder/builder/index/0942.regex b/tests/snapshots/docs/regex-builder/builder/index/0942.regex Binary files differnew file mode 100644 index 0000000..a75fda3 --- /dev/null +++ b/tests/snapshots/docs/regex-builder/builder/index/0942.regex diff --git a/tests/snapshots/docs/regex-builder/flags/index/0026.regex b/tests/snapshots/docs/regex-builder/flags/index/0026.regex Binary files differnew file mode 100644 index 0000000..2404b1d --- /dev/null +++ b/tests/snapshots/docs/regex-builder/flags/index/0026.regex diff --git a/tests/snapshots/docs/regex-builder/flags/index/0038.regex b/tests/snapshots/docs/regex-builder/flags/index/0038.regex Binary files differnew file mode 100644 index 0000000..2404b1d --- /dev/null +++ b/tests/snapshots/docs/regex-builder/flags/index/0038.regex diff --git a/tests/snapshots/docs/regex-builder/flags/index/0051.regex b/tests/snapshots/docs/regex-builder/flags/index/0051.regex Binary files differnew file mode 100644 index 0000000..2404b1d --- /dev/null +++ b/tests/snapshots/docs/regex-builder/flags/index/0051.regex diff --git a/tests/snapshots/docs/regex-builder/flags/index/0065.regex b/tests/snapshots/docs/regex-builder/flags/index/0065.regex Binary files differnew file mode 100644 index 0000000..2404b1d --- /dev/null +++ b/tests/snapshots/docs/regex-builder/flags/index/0065.regex diff --git a/tests/snapshots/docs/regex-builder/flags/index/0080.regex b/tests/snapshots/docs/regex-builder/flags/index/0080.regex Binary files differnew file mode 100644 index 0000000..2404b1d --- /dev/null +++ b/tests/snapshots/docs/regex-builder/flags/index/0080.regex diff --git a/tests/snapshots/docs/regex-builder/flags/index/0094.regex b/tests/snapshots/docs/regex-builder/flags/index/0094.regex Binary files differnew file mode 100644 index 0000000..2404b1d --- /dev/null +++ b/tests/snapshots/docs/regex-builder/flags/index/0094.regex diff --git a/tests/snapshots/docs/upgrading/0.3-to-1.0/0018.regex b/tests/snapshots/docs/upgrading/0.3-to-1.0/0018.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/upgrading/0.3-to-1.0/0018.regex diff --git a/tests/snapshots/docs/upgrading/0.3-to-1.0/0037.regex b/tests/snapshots/docs/upgrading/0.3-to-1.0/0037.regex Binary files differnew file mode 100644 index 0000000..4ba98bc --- /dev/null +++ b/tests/snapshots/docs/upgrading/0.3-to-1.0/0037.regex diff --git a/tests/snapshots/fixtures/alternation_with_overlapping_prefixes_redos.regex b/tests/snapshots/fixtures/alternation_with_overlapping_prefixes_redos.regex new file mode 100644 index 0000000..8d3f7b5 --- /dev/null +++ b/tests/snapshots/fixtures/alternation_with_overlapping_prefixes_redos.regex @@ -0,0 +1 @@ +(?:aa|aaa|aaaa)+
\ No newline at end of file diff --git a/tests/snapshots/fixtures/combined_lookaround.regex b/tests/snapshots/fixtures/combined_lookaround.regex new file mode 100644 index 0000000..baa7e8f --- /dev/null +++ b/tests/snapshots/fixtures/combined_lookaround.regex @@ -0,0 +1 @@ +(?=\d)(?!0)(?<=\$)\d\d\d
\ No newline at end of file diff --git a/tests/snapshots/fixtures/deeply_nested_alternation.regex b/tests/snapshots/fixtures/deeply_nested_alternation.regex new file mode 100644 index 0000000..0f2c26b --- /dev/null +++ b/tests/snapshots/fixtures/deeply_nested_alternation.regex @@ -0,0 +1 @@ +^(?:abc|def|ghi)\-(?:first|second|third)$
\ No newline at end of file diff --git a/tests/snapshots/fixtures/email_shape_pattern.regex b/tests/snapshots/fixtures/email_shape_pattern.regex new file mode 100644 index 0000000..bd17d4d --- /dev/null +++ b/tests/snapshots/fixtures/email_shape_pattern.regex @@ -0,0 +1 @@ +^[a\-zA\-Z0\-9._%+-]+@[a\-zA\-Z0\-9.-]+\.[a-zA-Z]{2,}$
\ No newline at end of file diff --git a/tests/snapshots/fixtures/hex_color_pattern.regex b/tests/snapshots/fixtures/hex_color_pattern.regex new file mode 100644 index 0000000..c2d63c0 --- /dev/null +++ b/tests/snapshots/fixtures/hex_color_pattern.regex @@ -0,0 +1 @@ +^\#[0123456789abcdefABCDEF]\w{6}$
\ No newline at end of file diff --git a/tests/snapshots/fixtures/named_backreference_pair.regex b/tests/snapshots/fixtures/named_backreference_pair.regex new file mode 100644 index 0000000..1e3fe4a --- /dev/null +++ b/tests/snapshots/fixtures/named_backreference_pair.regex @@ -0,0 +1 @@ +(?P<open>[\[\(\{])\w*(?P=open)
\ No newline at end of file diff --git a/tests/snapshots/fixtures/nested_unbounded_quantifier_redos.regex b/tests/snapshots/fixtures/nested_unbounded_quantifier_redos.regex new file mode 100644 index 0000000..e5aaf8b --- /dev/null +++ b/tests/snapshots/fixtures/nested_unbounded_quantifier_redos.regex @@ -0,0 +1 @@ +(?:\d+)+
\ No newline at end of file diff --git a/tests/snapshots/fixtures/pattern_composition_via_use.regex b/tests/snapshots/fixtures/pattern_composition_via_use.regex new file mode 100644 index 0000000..70b3c7c --- /dev/null +++ b/tests/snapshots/fixtures/pattern_composition_via_use.regex @@ -0,0 +1 @@ +^[a-zA-Z]{2,}$
\ No newline at end of file diff --git a/tests/snapshots/library/abnf.regex b/tests/snapshots/library/abnf.regex new file mode 100644 index 0000000..5603337 --- /dev/null +++ b/tests/snapshots/library/abnf.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9_\-<>:=\|\*\+\?\(\)\[\]\{\}\.'"/;,]){4,65536}$
\ No newline at end of file diff --git a/tests/snapshots/library/address.regex b/tests/snapshots/library/address.regex new file mode 100644 index 0000000..53dbd11 --- /dev/null +++ b/tests/snapshots/library/address.regex @@ -0,0 +1 @@ +^\d+\s+(?:\s|[A-Za-z0-9.,'\-#/])+$
\ No newline at end of file diff --git a/tests/snapshots/library/age.regex b/tests/snapshots/library/age.regex new file mode 100644 index 0000000..e2638b9 --- /dev/null +++ b/tests/snapshots/library/age.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9\+/=_\-\.:]){16,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/aircraft.regex b/tests/snapshots/library/aircraft.regex new file mode 100644 index 0000000..61da29a --- /dev/null +++ b/tests/snapshots/library/aircraft.regex @@ -0,0 +1 @@ +^[A-Z]{1,2}\-?[A-Z0-9]{1,5}$
\ No newline at end of file diff --git a/tests/snapshots/library/alpha.regex b/tests/snapshots/library/alpha.regex new file mode 100644 index 0000000..a1e4509 --- /dev/null +++ b/tests/snapshots/library/alpha.regex @@ -0,0 +1 @@ +^[a-zA-Z]+$
\ No newline at end of file diff --git a/tests/snapshots/library/alphanumeric.regex b/tests/snapshots/library/alphanumeric.regex new file mode 100644 index 0000000..6cf8d66 --- /dev/null +++ b/tests/snapshots/library/alphanumeric.regex @@ -0,0 +1 @@ +^[a-zA-Z0-9]+$
\ No newline at end of file diff --git a/tests/snapshots/library/altitude.regex b/tests/snapshots/library/altitude.regex new file mode 100644 index 0000000..c2a29b2 --- /dev/null +++ b/tests/snapshots/library/altitude.regex @@ -0,0 +1 @@ +^\-?\d+(?:\.\d+)?\s?(?:ft|km|mi|[m])?$
\ No newline at end of file diff --git a/tests/snapshots/library/antlr.regex b/tests/snapshots/library/antlr.regex new file mode 100644 index 0000000..e9b265e --- /dev/null +++ b/tests/snapshots/library/antlr.regex @@ -0,0 +1 @@ +^grammar\s+[a-zA-Z][a-zA-Z0-9_]*;.*$
\ No newline at end of file diff --git a/tests/snapshots/library/apache.regex b/tests/snapshots/library/apache.regex new file mode 100644 index 0000000..19ae6bb --- /dev/null +++ b/tests/snapshots/library/apache.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+=\?\&\#:%\~]{2,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/apikey.regex b/tests/snapshots/library/apikey.regex new file mode 100644 index 0000000..94b0e63 --- /dev/null +++ b/tests/snapshots/library/apikey.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_-]{20,128}$
\ No newline at end of file diff --git a/tests/snapshots/library/arn.regex b/tests/snapshots/library/arn.regex new file mode 100644 index 0000000..cefdc2a --- /dev/null +++ b/tests/snapshots/library/arn.regex @@ -0,0 +1 @@ +^arn:[a-z\-]+:[a-z0-9\-]+:[a-z0-9\-]*:\d*:.+$
\ No newline at end of file diff --git a/tests/snapshots/library/arxiv.regex b/tests/snapshots/library/arxiv.regex new file mode 100644 index 0000000..2d52c1a --- /dev/null +++ b/tests/snapshots/library/arxiv.regex @@ -0,0 +1 @@ +^(?:\d{4}\.\d{4,5}(?:v\d+)?|[a-z]{2,10}(?:\.[A-Z]{2})?/\d{7}(?:v\d+)?)$
\ No newline at end of file diff --git a/tests/snapshots/library/ascii.regex b/tests/snapshots/library/ascii.regex new file mode 100644 index 0000000..6730c0b --- /dev/null +++ b/tests/snapshots/library/ascii.regex @@ -0,0 +1 @@ +^[ -~]+$
\ No newline at end of file diff --git a/tests/snapshots/library/asin.regex b/tests/snapshots/library/asin.regex new file mode 100644 index 0000000..4b34df5 --- /dev/null +++ b/tests/snapshots/library/asin.regex @@ -0,0 +1 @@ +^[A-Z0-9]{10}$
\ No newline at end of file diff --git a/tests/snapshots/library/atom.regex b/tests/snapshots/library/atom.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/atom.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/avro.regex b/tests/snapshots/library/avro.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/avro.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/barcode.regex b/tests/snapshots/library/barcode.regex new file mode 100644 index 0000000..11fe7e0 --- /dev/null +++ b/tests/snapshots/library/barcode.regex @@ -0,0 +1 @@ +^[A-Z0-9]{6,48}$
\ No newline at end of file diff --git a/tests/snapshots/library/base.regex b/tests/snapshots/library/base.regex new file mode 100644 index 0000000..57871a4 --- /dev/null +++ b/tests/snapshots/library/base.regex @@ -0,0 +1 @@ +^(?:[0-9A-Fa-f]+|[A-Z2-7]+=*|[1-9A-HJ-NP-Za-km-z]+|[A-Za-z0-9\+/]+=*|[A-Za-z0-9_\-]+)$
\ No newline at end of file diff --git a/tests/snapshots/library/bearer.regex b/tests/snapshots/library/bearer.regex new file mode 100644 index 0000000..56cce08 --- /dev/null +++ b/tests/snapshots/library/bearer.regex @@ -0,0 +1 @@ +^Bearer [A-Za-z0-9._-]+$
\ No newline at end of file diff --git a/tests/snapshots/library/bearing.regex b/tests/snapshots/library/bearing.regex new file mode 100644 index 0000000..eede435 --- /dev/null +++ b/tests/snapshots/library/bearing.regex @@ -0,0 +1 @@ +^(?:360(?:\.0+)?|(?:3[0-5]\d|[1-2]\d\d|\d{1,2})(?:\.\d+)?)°?$
\ No newline at end of file diff --git a/tests/snapshots/library/bic.regex b/tests/snapshots/library/bic.regex new file mode 100644 index 0000000..5fea907 --- /dev/null +++ b/tests/snapshots/library/bic.regex @@ -0,0 +1 @@ +^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}(?:[A-Z0-9]{3})?$
\ No newline at end of file diff --git a/tests/snapshots/library/blood.regex b/tests/snapshots/library/blood.regex new file mode 100644 index 0000000..148cbbc --- /dev/null +++ b/tests/snapshots/library/blood.regex @@ -0,0 +1 @@ +^(?:(?:AB|[ABO]))[+-]$
\ No newline at end of file diff --git a/tests/snapshots/library/bnf.regex b/tests/snapshots/library/bnf.regex new file mode 100644 index 0000000..5603337 --- /dev/null +++ b/tests/snapshots/library/bnf.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9_\-<>:=\|\*\+\?\(\)\[\]\{\}\.'"/;,]){4,65536}$
\ No newline at end of file diff --git a/tests/snapshots/library/bump.regex b/tests/snapshots/library/bump.regex new file mode 100644 index 0000000..7fe8bdd --- /dev/null +++ b/tests/snapshots/library/bump.regex @@ -0,0 +1 @@ +^(?:major|minor|patch|premajor|preminor|prepatch|prerelease|release)$
\ No newline at end of file diff --git a/tests/snapshots/library/captcha.regex b/tests/snapshots/library/captcha.regex new file mode 100644 index 0000000..19ae6bb --- /dev/null +++ b/tests/snapshots/library/captcha.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+=\?\&\#:%\~]{2,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/card.regex b/tests/snapshots/library/card.regex new file mode 100644 index 0000000..6313318 --- /dev/null +++ b/tests/snapshots/library/card.regex @@ -0,0 +1 @@ +^\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{1,7}$
\ No newline at end of file diff --git a/tests/snapshots/library/cargo.regex b/tests/snapshots/library/cargo.regex new file mode 100644 index 0000000..dc907b1 --- /dev/null +++ b/tests/snapshots/library/cargo.regex @@ -0,0 +1 @@ +^[a-zA-Z][a-zA-Z0-9_\-]{0,63}(?:@\d+(?:\.\d+){0,3}(?:[-.+][a-zA-Z0-9\.\-]+)?)?$
\ No newline at end of file diff --git a/tests/snapshots/library/certificate.regex b/tests/snapshots/library/certificate.regex new file mode 100644 index 0000000..e2638b9 --- /dev/null +++ b/tests/snapshots/library/certificate.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9\+/=_\-\.:]){16,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/challenge.regex b/tests/snapshots/library/challenge.regex new file mode 100644 index 0000000..b7faca2 --- /dev/null +++ b/tests/snapshots/library/challenge.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_-]{16,128}$
\ No newline at end of file diff --git a/tests/snapshots/library/charset.regex b/tests/snapshots/library/charset.regex new file mode 100644 index 0000000..fe88a5c --- /dev/null +++ b/tests/snapshots/library/charset.regex @@ -0,0 +1 @@ +^[a-zA-Z][a-zA-Z0-9_\+\.\-]{1,39}$
\ No newline at end of file diff --git a/tests/snapshots/library/checksum.regex b/tests/snapshots/library/checksum.regex new file mode 100644 index 0000000..9f6aed9 --- /dev/null +++ b/tests/snapshots/library/checksum.regex @@ -0,0 +1 @@ +^[a-fA-F0-9]{8,128}$
\ No newline at end of file diff --git a/tests/snapshots/library/cidr.regex b/tests/snapshots/library/cidr.regex new file mode 100644 index 0000000..21e454b --- /dev/null +++ b/tests/snapshots/library/cidr.regex @@ -0,0 +1 @@ +^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}/(?:3[0-2]|[12]?\d)|(?:[0-9a-fA-F]{1,4}:){0,7}[0-9a-fA-F]{1,4}/(?:12[0-8]|1[01]\d|[1-9]?\d))$
\ No newline at end of file diff --git a/tests/snapshots/library/codec.regex b/tests/snapshots/library/codec.regex new file mode 100644 index 0000000..aeafca3 --- /dev/null +++ b/tests/snapshots/library/codec.regex @@ -0,0 +1 @@ +^[a-zA-Z][a-zA-Z0-9_\.\-]{1,29}$
\ No newline at end of file diff --git a/tests/snapshots/library/color.regex b/tests/snapshots/library/color.regex new file mode 100644 index 0000000..1a78673 --- /dev/null +++ b/tests/snapshots/library/color.regex @@ -0,0 +1 @@ +(?:^\#(?:[0-9A-Fa-f]{3,4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$|^rgba?\(\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?(?:\s*,\s*(?:\d|[\.])+)?\s*\)$|^hsla?\(\s*\d{1,3}(?:deg)?\s*,\s*\d{1,3}%\s*,\s*\d{1,3}%(?:\s*,\s*(?:\d|[\.])+)?\s*\)$|^[a-zA-Z]{3,20}$)
\ No newline at end of file diff --git a/tests/snapshots/library/component.regex b/tests/snapshots/library/component.regex new file mode 100644 index 0000000..99bdf80 --- /dev/null +++ b/tests/snapshots/library/component.regex @@ -0,0 +1 @@ +^(?:@[a-z0-9][a-z0-9\-]*/)?[a-z0-9][a-z0-9\._\-]{0,213}@\d+(?:\.\d+){0,3}(?:[-.+][a-zA-Z0-9\.\-]+)?$
\ No newline at end of file diff --git a/tests/snapshots/library/cookie.regex b/tests/snapshots/library/cookie.regex new file mode 100644 index 0000000..3c9e1ab --- /dev/null +++ b/tests/snapshots/library/cookie.regex @@ -0,0 +1 @@ +^[a-zA-Z0-9_\-]+=(?:(?!(?:\s|[;])).)+$
\ No newline at end of file diff --git a/tests/snapshots/library/coordinate.regex b/tests/snapshots/library/coordinate.regex new file mode 100644 index 0000000..7a49f7a --- /dev/null +++ b/tests/snapshots/library/coordinate.regex @@ -0,0 +1 @@ +^\-?(?:90(?:\.0+)?|[0-8]?\d(?:\.\d+)?)\s*,\s*\-?(?:180(?:\.0+)?|(?:1[0-7]\d|[0-9]?\d)(?:\.\d+)?)$
\ No newline at end of file diff --git a/tests/snapshots/library/cron.regex b/tests/snapshots/library/cron.regex new file mode 100644 index 0000000..80b24e1 --- /dev/null +++ b/tests/snapshots/library/cron.regex @@ -0,0 +1 @@ +^(?:@(?:(?:annually|yearly|monthly|weekly|daily|hourly|reboot))|(?:[\*\?0-9/,\-]+\s+){4,5}[\*\?0-9/,\-]+)$
\ No newline at end of file diff --git a/tests/snapshots/library/crypto.regex b/tests/snapshots/library/crypto.regex new file mode 100644 index 0000000..2b8beea --- /dev/null +++ b/tests/snapshots/library/crypto.regex @@ -0,0 +1 @@ +^[A-Z0-9]{3,10}$
\ No newline at end of file diff --git a/tests/snapshots/library/csp.regex b/tests/snapshots/library/csp.regex new file mode 100644 index 0000000..dad51a5 --- /dev/null +++ b/tests/snapshots/library/csp.regex @@ -0,0 +1 @@ +^[a-z\-]+\s+(?:(?!;).)+(?:;\s*[a-z\-]+\s+(?:(?!;).)+)*;?$
\ No newline at end of file diff --git a/tests/snapshots/library/csr.regex b/tests/snapshots/library/csr.regex new file mode 100644 index 0000000..e2638b9 --- /dev/null +++ b/tests/snapshots/library/csr.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9\+/=_\-\.:]){16,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/csrf.regex b/tests/snapshots/library/csrf.regex new file mode 100644 index 0000000..5314f5b --- /dev/null +++ b/tests/snapshots/library/csrf.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_-]{32,128}$
\ No newline at end of file diff --git a/tests/snapshots/library/csv.regex b/tests/snapshots/library/csv.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/csv.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/currency.regex b/tests/snapshots/library/currency.regex new file mode 100644 index 0000000..c13f934 --- /dev/null +++ b/tests/snapshots/library/currency.regex @@ -0,0 +1 @@ +^[A-Z]{3}$
\ No newline at end of file diff --git a/tests/snapshots/library/cusip.regex b/tests/snapshots/library/cusip.regex new file mode 100644 index 0000000..6b9cb42 --- /dev/null +++ b/tests/snapshots/library/cusip.regex @@ -0,0 +1 @@ +^[A-Z0-9]{9}$
\ No newline at end of file diff --git a/tests/snapshots/library/date.regex b/tests/snapshots/library/date.regex new file mode 100644 index 0000000..dcdaa2d --- /dev/null +++ b/tests/snapshots/library/date.regex @@ -0,0 +1 @@ +^(?:\d{1,2}/\d{1,2}/\d{4}|\d{4}\-\d{2}\-\d{2}|\d{2}\-\d{2}\-\d{4}|\d{4}/\d{2}/\d{2}|\d{1,2}\.\d{1,2}\.\d{4}|\d{4}\.\d{2}\.\d{2}|\d{8})$
\ No newline at end of file diff --git a/tests/snapshots/library/datetime.regex b/tests/snapshots/library/datetime.regex new file mode 100644 index 0000000..decfe4c --- /dev/null +++ b/tests/snapshots/library/datetime.regex @@ -0,0 +1 @@ +^(?:\d{4}\-\d{2}\-\d{2}[Tt ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:(?:[+-]\d{2}:?\d{2}|[Zz]))?|\d{4}\d{2}\d{2}[Tt]\d{2}\d{2}\d{2}(?:(?:[+-]\d{4}|[Zz]))?)$
\ No newline at end of file diff --git a/tests/snapshots/library/der.regex b/tests/snapshots/library/der.regex new file mode 100644 index 0000000..e2638b9 --- /dev/null +++ b/tests/snapshots/library/der.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9\+/=_\-\.:]){16,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/dicom.regex b/tests/snapshots/library/dicom.regex new file mode 100644 index 0000000..3e88b2c --- /dev/null +++ b/tests/snapshots/library/dicom.regex @@ -0,0 +1 @@ +^\d+(?:\.\d+)+$
\ No newline at end of file diff --git a/tests/snapshots/library/did.regex b/tests/snapshots/library/did.regex new file mode 100644 index 0000000..e62c684 --- /dev/null +++ b/tests/snapshots/library/did.regex @@ -0,0 +1 @@ +^did:[a-z0-9]+:.+$
\ No newline at end of file diff --git a/tests/snapshots/library/digest.regex b/tests/snapshots/library/digest.regex new file mode 100644 index 0000000..bc8a892 --- /dev/null +++ b/tests/snapshots/library/digest.regex @@ -0,0 +1 @@ +^(?:(?:sha256|sha512|sha1|md5|blake2[bs]?))[:-][a-fA-F0-9]{32,128}$
\ No newline at end of file diff --git a/tests/snapshots/library/docker.regex b/tests/snapshots/library/docker.regex new file mode 100644 index 0000000..ad5905e --- /dev/null +++ b/tests/snapshots/library/docker.regex @@ -0,0 +1 @@ +^(?:(?:[a-z0-9\.\-]+(?::\d+)?/)?[a-z0-9]+(?:[._-][a-z0-9]+)*)(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)*(?::[a-zA-Z0-9_][a-zA-Z0-9\._\-]{0,127})?(?:@sha256:[a-f0-9]{64})?$
\ No newline at end of file diff --git a/tests/snapshots/library/docx.regex b/tests/snapshots/library/docx.regex new file mode 100644 index 0000000..10c8c17 --- /dev/null +++ b/tests/snapshots/library/docx.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/]{1,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/doi.regex b/tests/snapshots/library/doi.regex new file mode 100644 index 0000000..f999d1c --- /dev/null +++ b/tests/snapshots/library/doi.regex @@ -0,0 +1 @@ +^10\.\d{4,9}/[\-\._;\(\)/:A-Za-z0-9]+$
\ No newline at end of file diff --git a/tests/snapshots/library/domain.regex b/tests/snapshots/library/domain.regex new file mode 100644 index 0000000..fac164f --- /dev/null +++ b/tests/snapshots/library/domain.regex @@ -0,0 +1 @@ +^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$
\ No newline at end of file diff --git a/tests/snapshots/library/dosage.regex b/tests/snapshots/library/dosage.regex new file mode 100644 index 0000000..1e20a36 --- /dev/null +++ b/tests/snapshots/library/dosage.regex @@ -0,0 +1 @@ +^\d+(?:\.\d+)?\s?(?:mg|kg|ml|mcg|iu|[gl])(?:/(?:kg|day|dose))?$
\ No newline at end of file diff --git a/tests/snapshots/library/duration.regex b/tests/snapshots/library/duration.regex new file mode 100644 index 0000000..3fee4d7 --- /dev/null +++ b/tests/snapshots/library/duration.regex @@ -0,0 +1 @@ +^P(?=.)(?:\d+(?:\.\d+)?Y)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?W)?(?:\d+(?:\.\d+)?D)?(?:T(?=\d)(?:\d+(?:\.\d+)?H)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?S)?)?$
\ No newline at end of file diff --git a/tests/snapshots/library/ebnf.regex b/tests/snapshots/library/ebnf.regex new file mode 100644 index 0000000..5603337 --- /dev/null +++ b/tests/snapshots/library/ebnf.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9_\-<>:=\|\*\+\?\(\)\[\]\{\}\.'"/;,]){4,65536}$
\ No newline at end of file diff --git a/tests/snapshots/library/ein.regex b/tests/snapshots/library/ein.regex new file mode 100644 index 0000000..954dcdd --- /dev/null +++ b/tests/snapshots/library/ein.regex @@ -0,0 +1 @@ +^\d{2}\-\d{7}$
\ No newline at end of file diff --git a/tests/snapshots/library/email.regex b/tests/snapshots/library/email.regex new file mode 100644 index 0000000..9de9ad1 --- /dev/null +++ b/tests/snapshots/library/email.regex @@ -0,0 +1 @@ +^(?:(?:[a-z0-9!\#\$%\&'\*\+/=\?\^_`\{\|\}\~\-])+(?:\.(?:[a-z0-9!\#\$%\&'\*\+/=\?\^_`\{\|\}\~\-])+)*@(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?|(?:(?:[a-z0-9!\#\$%\&'\*\+/=\?\^_`\{\|\}\~\-])+(?:\.(?:[a-z0-9!\#\$%\&'\*\+/=\?\^_`\{\|\}\~\-])+)*|"(?:(?:[-\\-!#-[]-]|\\[- \\-]))*")@(?:(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|[a-z0-9\-]*[a-z0-9]:(?:(?:[-\\-!-ZS-]|\\[- \\-]))+)\]))$
\ No newline at end of file diff --git a/tests/snapshots/library/emoji.regex b/tests/snapshots/library/emoji.regex new file mode 100644 index 0000000..d6fee58 --- /dev/null +++ b/tests/snapshots/library/emoji.regex @@ -0,0 +1 @@ +^[🌀-☀-➿]+$
\ No newline at end of file diff --git a/tests/snapshots/library/encoding.regex b/tests/snapshots/library/encoding.regex new file mode 100644 index 0000000..fe88a5c --- /dev/null +++ b/tests/snapshots/library/encoding.regex @@ -0,0 +1 @@ +^[a-zA-Z][a-zA-Z0-9_\+\.\-]{1,39}$
\ No newline at end of file diff --git a/tests/snapshots/library/epoch.regex b/tests/snapshots/library/epoch.regex new file mode 100644 index 0000000..a812e41 --- /dev/null +++ b/tests/snapshots/library/epoch.regex @@ -0,0 +1 @@ +^\-?\d{1,10}$
\ No newline at end of file diff --git a/tests/snapshots/library/epub.regex b/tests/snapshots/library/epub.regex new file mode 100644 index 0000000..10c8c17 --- /dev/null +++ b/tests/snapshots/library/epub.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/]{1,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/extension.regex b/tests/snapshots/library/extension.regex new file mode 100644 index 0000000..98eba7a --- /dev/null +++ b/tests/snapshots/library/extension.regex @@ -0,0 +1 @@ +^\.[a-zA-Z0-9]{1,10}$
\ No newline at end of file diff --git a/tests/snapshots/library/favicon.regex b/tests/snapshots/library/favicon.regex Binary files differnew file mode 100644 index 0000000..120fe27 --- /dev/null +++ b/tests/snapshots/library/favicon.regex diff --git a/tests/snapshots/library/fax.regex b/tests/snapshots/library/fax.regex new file mode 100644 index 0000000..c7664dd --- /dev/null +++ b/tests/snapshots/library/fax.regex @@ -0,0 +1 @@ +^\+?\d{1,4}?(?:\s|[\-\.])?\(?\d{1,3}?\)?(?:\s|[\-\.])?\d{1,4}(?:\s|[\-\.])?\d{1,4}(?:\s|[\-\.])?\d{1,9}$
\ No newline at end of file diff --git a/tests/snapshots/library/filename.regex b/tests/snapshots/library/filename.regex Binary files differnew file mode 100644 index 0000000..41a024d --- /dev/null +++ b/tests/snapshots/library/filename.regex diff --git a/tests/snapshots/library/filter.regex b/tests/snapshots/library/filter.regex new file mode 100644 index 0000000..c44a86b --- /dev/null +++ b/tests/snapshots/library/filter.regex @@ -0,0 +1 @@ +^(?:blur|brightness|contrast|grayscale|hue\-rotate|invert|opacity|saturate|sepia|drop\-shadow)\([^)]+\)$
\ No newline at end of file diff --git a/tests/snapshots/library/flight.regex b/tests/snapshots/library/flight.regex new file mode 100644 index 0000000..4cb8094 --- /dev/null +++ b/tests/snapshots/library/flight.regex @@ -0,0 +1 @@ +^[A-Z]{2}\d{1,4}[A-Z]?$
\ No newline at end of file diff --git a/tests/snapshots/library/fraction.regex b/tests/snapshots/library/fraction.regex new file mode 100644 index 0000000..7b8bb4a --- /dev/null +++ b/tests/snapshots/library/fraction.regex @@ -0,0 +1 @@ +^\-?(?:\d+\s+)?\d+/\d+$
\ No newline at end of file diff --git a/tests/snapshots/library/geohash.regex b/tests/snapshots/library/geohash.regex new file mode 100644 index 0000000..8bffb62 --- /dev/null +++ b/tests/snapshots/library/geohash.regex @@ -0,0 +1 @@ +^[0-9bcdefghjkmnpqrstuvwxyz]{1,12}$
\ No newline at end of file diff --git a/tests/snapshots/library/git.regex b/tests/snapshots/library/git.regex new file mode 100644 index 0000000..e01570c --- /dev/null +++ b/tests/snapshots/library/git.regex @@ -0,0 +1 @@ +^[a-f0-9]{7,40}$
\ No newline at end of file diff --git a/tests/snapshots/library/glob.regex b/tests/snapshots/library/glob.regex Binary files differnew file mode 100644 index 0000000..07f0321 --- /dev/null +++ b/tests/snapshots/library/glob.regex diff --git a/tests/snapshots/library/gradient.regex b/tests/snapshots/library/gradient.regex new file mode 100644 index 0000000..fbf69ac --- /dev/null +++ b/tests/snapshots/library/gradient.regex @@ -0,0 +1 @@ +^(?:linear|radial|conic)\-gradient\([^()]*(?:\([^()]*\)[^()]*)*\)$
\ No newline at end of file diff --git a/tests/snapshots/library/graphql.regex b/tests/snapshots/library/graphql.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/graphql.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/gtin.regex b/tests/snapshots/library/gtin.regex new file mode 100644 index 0000000..49ce8e0 --- /dev/null +++ b/tests/snapshots/library/gtin.regex @@ -0,0 +1 @@ +^(?:\d{8}|\d{12}|\d{13}|\d{14})$
\ No newline at end of file diff --git a/tests/snapshots/library/guid.regex b/tests/snapshots/library/guid.regex new file mode 100644 index 0000000..b3b223f --- /dev/null +++ b/tests/snapshots/library/guid.regex @@ -0,0 +1 @@ +^\{?[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}\}?$
\ No newline at end of file diff --git a/tests/snapshots/library/hal.regex b/tests/snapshots/library/hal.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/hal.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/handle.regex b/tests/snapshots/library/handle.regex new file mode 100644 index 0000000..66aad02 --- /dev/null +++ b/tests/snapshots/library/handle.regex @@ -0,0 +1 @@ +^@[a-zA-Z0-9_]{1,30}$
\ No newline at end of file diff --git a/tests/snapshots/library/hash.regex b/tests/snapshots/library/hash.regex new file mode 100644 index 0000000..9f6aed9 --- /dev/null +++ b/tests/snapshots/library/hash.regex @@ -0,0 +1 @@ +^[a-fA-F0-9]{8,128}$
\ No newline at end of file diff --git a/tests/snapshots/library/hdf5.regex b/tests/snapshots/library/hdf5.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/hdf5.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/hmac.regex b/tests/snapshots/library/hmac.regex new file mode 100644 index 0000000..eca0ba1 --- /dev/null +++ b/tests/snapshots/library/hmac.regex @@ -0,0 +1 @@ +^[0-9a-fA-F]{32,128}$
\ No newline at end of file diff --git a/tests/snapshots/library/hostname.regex b/tests/snapshots/library/hostname.regex new file mode 100644 index 0000000..0404c94 --- /dev/null +++ b/tests/snapshots/library/hostname.regex @@ -0,0 +1 @@ +^(?=.{1,253}$)(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$
\ No newline at end of file diff --git a/tests/snapshots/library/htaccess.regex b/tests/snapshots/library/htaccess.regex new file mode 100644 index 0000000..19ae6bb --- /dev/null +++ b/tests/snapshots/library/htaccess.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+=\?\&\#:%\~]{2,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/html.regex b/tests/snapshots/library/html.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/html.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/humans.regex b/tests/snapshots/library/humans.regex new file mode 100644 index 0000000..19ae6bb --- /dev/null +++ b/tests/snapshots/library/humans.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+=\?\&\#:%\~]{2,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/iata.regex b/tests/snapshots/library/iata.regex new file mode 100644 index 0000000..128cc16 --- /dev/null +++ b/tests/snapshots/library/iata.regex @@ -0,0 +1 @@ +^[A-Z]{2,3}$
\ No newline at end of file diff --git a/tests/snapshots/library/iban.regex b/tests/snapshots/library/iban.regex new file mode 100644 index 0000000..11efbae --- /dev/null +++ b/tests/snapshots/library/iban.regex @@ -0,0 +1 @@ +^[A-Z]{2}\d{2}[A-Z0-9]{1,30}$
\ No newline at end of file diff --git a/tests/snapshots/library/icao.regex b/tests/snapshots/library/icao.regex new file mode 100644 index 0000000..e602cdb --- /dev/null +++ b/tests/snapshots/library/icao.regex @@ -0,0 +1 @@ +^[A-Z]{3,4}$
\ No newline at end of file diff --git a/tests/snapshots/library/iccid.regex b/tests/snapshots/library/iccid.regex new file mode 100644 index 0000000..0ba451b --- /dev/null +++ b/tests/snapshots/library/iccid.regex @@ -0,0 +1 @@ +^\d{19,22}$
\ No newline at end of file diff --git a/tests/snapshots/library/image.regex b/tests/snapshots/library/image.regex new file mode 100644 index 0000000..ad5905e --- /dev/null +++ b/tests/snapshots/library/image.regex @@ -0,0 +1 @@ +^(?:(?:[a-z0-9\.\-]+(?::\d+)?/)?[a-z0-9]+(?:[._-][a-z0-9]+)*)(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)*(?::[a-zA-Z0-9_][a-zA-Z0-9\._\-]{0,127})?(?:@sha256:[a-f0-9]{64})?$
\ No newline at end of file diff --git a/tests/snapshots/library/imei.regex b/tests/snapshots/library/imei.regex new file mode 100644 index 0000000..698dfa9 --- /dev/null +++ b/tests/snapshots/library/imei.regex @@ -0,0 +1 @@ +^\d{15}$
\ No newline at end of file diff --git a/tests/snapshots/library/imo.regex b/tests/snapshots/library/imo.regex new file mode 100644 index 0000000..813a08d --- /dev/null +++ b/tests/snapshots/library/imo.regex @@ -0,0 +1 @@ +^IMO\d{7}$
\ No newline at end of file diff --git a/tests/snapshots/library/ini.regex b/tests/snapshots/library/ini.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/ini.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/integer.regex b/tests/snapshots/library/integer.regex new file mode 100644 index 0000000..41efab1 --- /dev/null +++ b/tests/snapshots/library/integer.regex @@ -0,0 +1 @@ +^[+-]?\d+$
\ No newline at end of file diff --git a/tests/snapshots/library/interval.regex b/tests/snapshots/library/interval.regex new file mode 100644 index 0000000..421dee4 --- /dev/null +++ b/tests/snapshots/library/interval.regex @@ -0,0 +1 @@ +^\d{4}\-\d{2}\-\d{2}[Tt ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:(?:[+-]\d{2}:?\d{2}|[Zz]))?/(?:\d{4}\-\d{2}\-\d{2}[Tt ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:(?:[+-]\d{2}:?\d{2}|[Zz]))?|P(?=.)(?:\d+(?:\.\d+)?Y)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?W)?(?:\d+(?:\.\d+)?D)?(?:T(?=\d)(?:\d+(?:\.\d+)?H)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?S)?)?)$
\ No newline at end of file diff --git a/tests/snapshots/library/ip.regex b/tests/snapshots/library/ip.regex new file mode 100644 index 0000000..8b242c2 --- /dev/null +++ b/tests/snapshots/library/ip.regex @@ -0,0 +1 @@ +^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?:(?:(?:[0-9a-fA-F]){1,4}:){7}(?:[0-9a-fA-F]){1,4}|(?:(?:[0-9a-fA-F]){1,4}:){1,7}:|(?:(?:[0-9a-fA-F]){1,4}:){1,6}:(?:[0-9a-fA-F]){1,4}|(?:(?:[0-9a-fA-F]){1,4}:){1,5}(?::(?:[0-9a-fA-F]){1,4}){1,2}|(?:(?:[0-9a-fA-F]){1,4}:){1,4}(?::(?:[0-9a-fA-F]){1,4}){1,3}|(?:(?:[0-9a-fA-F]){1,4}:){1,3}(?::(?:[0-9a-fA-F]){1,4}){1,4}|(?:(?:[0-9a-fA-F]){1,4}:){1,2}(?::(?:[0-9a-fA-F]){1,4}){1,5}|(?:[0-9a-fA-F]){1,4}:(?:(?::(?:[0-9a-fA-F]){1,4}){1,6})|:(?:(?:(?::(?:[0-9a-fA-F]){1,4}){1,7}|[:]))|fe80:(?::(?:[0-9a-fA-F]){0,4}){0,4}%[0-9a-zA-Z]+|::(?:ffff(?::0{1,4})?:)?(?:(?:25[0-5]|(?:(?:2[0-4]|1?\d))?\d)\.){3}(?:25[0-5]|(?:(?:2[0-4]|1?\d))?\d)|(?:(?:[0-9a-fA-F]){1,4}:){1,4}:(?:(?:25[0-5]|(?:(?:2[0-4]|1?\d))?\d)\.){3}(?:25[0-5]|(?:(?:2[0-4]|1?\d))?\d)))$
\ No newline at end of file diff --git a/tests/snapshots/library/isbn.regex b/tests/snapshots/library/isbn.regex new file mode 100644 index 0000000..9f4075c --- /dev/null +++ b/tests/snapshots/library/isbn.regex @@ -0,0 +1 @@ +(?:^(?:\d[- ]?){9}(?:\d|[Xx])$|^(?:\d[- ]?){12}\d$)
\ No newline at end of file diff --git a/tests/snapshots/library/isin.regex b/tests/snapshots/library/isin.regex new file mode 100644 index 0000000..a3c7b42 --- /dev/null +++ b/tests/snapshots/library/isin.regex @@ -0,0 +1 @@ +^[A-Z]{2}[A-Z0-9]{9}\d$
\ No newline at end of file diff --git a/tests/snapshots/library/issn.regex b/tests/snapshots/library/issn.regex new file mode 100644 index 0000000..98898d4 --- /dev/null +++ b/tests/snapshots/library/issn.regex @@ -0,0 +1 @@ +^\d{4}\-\d{3}(?:\d|[Xx])$
\ No newline at end of file diff --git a/tests/snapshots/library/itin.regex b/tests/snapshots/library/itin.regex new file mode 100644 index 0000000..3d025b6 --- /dev/null +++ b/tests/snapshots/library/itin.regex @@ -0,0 +1 @@ +^9\d{2}\-(?:5\d|6[0-5]|7\d|8[0-8]|9[0-2]|9[4-9])\-\d{4}$
\ No newline at end of file diff --git a/tests/snapshots/library/json.regex b/tests/snapshots/library/json.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/json.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/jsonapi.regex b/tests/snapshots/library/jsonapi.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/jsonapi.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/jwt.regex b/tests/snapshots/library/jwt.regex new file mode 100644 index 0000000..1cb5316 --- /dev/null +++ b/tests/snapshots/library/jwt.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
\ No newline at end of file diff --git a/tests/snapshots/library/keyring.regex b/tests/snapshots/library/keyring.regex new file mode 100644 index 0000000..e2638b9 --- /dev/null +++ b/tests/snapshots/library/keyring.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9\+/=_\-\.:]){16,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/lei.regex b/tests/snapshots/library/lei.regex new file mode 100644 index 0000000..5af6936 --- /dev/null +++ b/tests/snapshots/library/lei.regex @@ -0,0 +1 @@ +^[A-Z0-9]{20}$
\ No newline at end of file diff --git a/tests/snapshots/library/locale.regex b/tests/snapshots/library/locale.regex new file mode 100644 index 0000000..fbb7dbb --- /dev/null +++ b/tests/snapshots/library/locale.regex @@ -0,0 +1 @@ +^[a-z]{2,3}(?:[_-][A-Z]{2})?(?:\.[a-zA-Z0-9\-]+)?(?:@[a-zA-Z0-9]+)?$
\ No newline at end of file diff --git a/tests/snapshots/library/mac.regex b/tests/snapshots/library/mac.regex new file mode 100644 index 0000000..cd424ba --- /dev/null +++ b/tests/snapshots/library/mac.regex @@ -0,0 +1 @@ +^(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$
\ No newline at end of file diff --git a/tests/snapshots/library/makefile.regex b/tests/snapshots/library/makefile.regex new file mode 100644 index 0000000..b4b27c3 --- /dev/null +++ b/tests/snapshots/library/makefile.regex @@ -0,0 +1 @@ +^\.?[a-zA-Z][a-zA-Z0-9\._\-]*(?:\s+[a-zA-Z][a-zA-Z0-9\._\-]*)*\s*:.*$
\ No newline at end of file diff --git a/tests/snapshots/library/manifest.regex b/tests/snapshots/library/manifest.regex new file mode 100644 index 0000000..19ae6bb --- /dev/null +++ b/tests/snapshots/library/manifest.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+=\?\&\#:%\~]{2,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/medical.regex b/tests/snapshots/library/medical.regex new file mode 100644 index 0000000..baf2b6d --- /dev/null +++ b/tests/snapshots/library/medical.regex @@ -0,0 +1 @@ +^(?:\d{6,18}|[A-TV-Z]\d[A-Z0-9](?:\.[A-Z0-9]{1,4})?|\d{10}|\d{1,7}\-\d)$
\ No newline at end of file diff --git a/tests/snapshots/library/meid.regex b/tests/snapshots/library/meid.regex new file mode 100644 index 0000000..2301792 --- /dev/null +++ b/tests/snapshots/library/meid.regex @@ -0,0 +1 @@ +^[0-9A-F]{14}$
\ No newline at end of file diff --git a/tests/snapshots/library/mfa.regex b/tests/snapshots/library/mfa.regex new file mode 100644 index 0000000..e9f4fe8 --- /dev/null +++ b/tests/snapshots/library/mfa.regex @@ -0,0 +1 @@ +^\d{6,8}$
\ No newline at end of file diff --git a/tests/snapshots/library/mgrs.regex b/tests/snapshots/library/mgrs.regex new file mode 100644 index 0000000..da7bba1 --- /dev/null +++ b/tests/snapshots/library/mgrs.regex @@ -0,0 +1 @@ +^\d{1,2}[C-HJ-NP-X][A-Z]{2}(?:\d{2}|\d{4}|\d{6}|\d{8}|\d{10})$
\ No newline at end of file diff --git a/tests/snapshots/library/mimetype.regex b/tests/snapshots/library/mimetype.regex new file mode 100644 index 0000000..757298f --- /dev/null +++ b/tests/snapshots/library/mimetype.regex @@ -0,0 +1 @@ +^[a-zA-Z][a-zA-Z0-9!\#\$\&\-\^_\.\+]*/[a-zA-Z][a-zA-Z0-9!\#\$\&\-\^_\.\+]*$
\ No newline at end of file diff --git a/tests/snapshots/library/mmsi.regex b/tests/snapshots/library/mmsi.regex new file mode 100644 index 0000000..cb08452 --- /dev/null +++ b/tests/snapshots/library/mmsi.regex @@ -0,0 +1 @@ +^\d{9}$
\ No newline at end of file diff --git a/tests/snapshots/library/mnemonic.regex b/tests/snapshots/library/mnemonic.regex new file mode 100644 index 0000000..6c20a6d --- /dev/null +++ b/tests/snapshots/library/mnemonic.regex @@ -0,0 +1 @@ +^(?:[a-z]+ ){11,23}[a-z]+$
\ No newline at end of file diff --git a/tests/snapshots/library/mobi.regex b/tests/snapshots/library/mobi.regex new file mode 100644 index 0000000..10c8c17 --- /dev/null +++ b/tests/snapshots/library/mobi.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/]{1,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/mpn.regex b/tests/snapshots/library/mpn.regex new file mode 100644 index 0000000..db8b18d --- /dev/null +++ b/tests/snapshots/library/mpn.regex @@ -0,0 +1 @@ +^[A-Z0-9][A-Z0-9\-_\.]{1,63}$
\ No newline at end of file diff --git a/tests/snapshots/library/msgpack.regex b/tests/snapshots/library/msgpack.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/msgpack.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/natural.regex b/tests/snapshots/library/natural.regex new file mode 100644 index 0000000..41df73d --- /dev/null +++ b/tests/snapshots/library/natural.regex @@ -0,0 +1 @@ +^[1-9]\d*$
\ No newline at end of file diff --git a/tests/snapshots/library/nginx.regex b/tests/snapshots/library/nginx.regex new file mode 100644 index 0000000..19ae6bb --- /dev/null +++ b/tests/snapshots/library/nginx.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+=\?\&\#:%\~]{2,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/nonce.regex b/tests/snapshots/library/nonce.regex new file mode 100644 index 0000000..6f12a7c --- /dev/null +++ b/tests/snapshots/library/nonce.regex @@ -0,0 +1 @@ +^[A-Za-z0-9\+/=_\-]{16,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/number.regex b/tests/snapshots/library/number.regex new file mode 100644 index 0000000..743f037 --- /dev/null +++ b/tests/snapshots/library/number.regex @@ -0,0 +1 @@ +^(?:[+-]?\d+|[+-]?\d+\.\d+|[+-]?\.\d+|[+-]?\d+\.\d*[eE][+-]?\d+|[+-]?\d+[eE][+-]?\d+|0[xX][0-9a-fA-F]+|0[oO][0-7]+|0[bB][01]+|[+-]?\d+(?:\.\d+)?[+-]\d+(?:\.\d+)?[jJi])$
\ No newline at end of file diff --git a/tests/snapshots/library/numeric.regex b/tests/snapshots/library/numeric.regex new file mode 100644 index 0000000..665d92c --- /dev/null +++ b/tests/snapshots/library/numeric.regex @@ -0,0 +1 @@ +^\d+$
\ No newline at end of file diff --git a/tests/snapshots/library/oauth.regex b/tests/snapshots/library/oauth.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/oauth.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/odt.regex b/tests/snapshots/library/odt.regex new file mode 100644 index 0000000..10c8c17 --- /dev/null +++ b/tests/snapshots/library/odt.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/]{1,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/offset.regex b/tests/snapshots/library/offset.regex new file mode 100644 index 0000000..0b7cc67 --- /dev/null +++ b/tests/snapshots/library/offset.regex @@ -0,0 +1 @@ +^(?:[+-](?:0\d|1[0-4]):?[0-5]\d|[Z])$
\ No newline at end of file diff --git a/tests/snapshots/library/openapi.regex b/tests/snapshots/library/openapi.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/openapi.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/openid.regex b/tests/snapshots/library/openid.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/openid.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/orc.regex b/tests/snapshots/library/orc.regex new file mode 100644 index 0000000..6e06a79 --- /dev/null +++ b/tests/snapshots/library/orc.regex @@ -0,0 +1 @@ +^ORC.*$
\ No newline at end of file diff --git a/tests/snapshots/library/orcid.regex b/tests/snapshots/library/orcid.regex new file mode 100644 index 0000000..b80cabc --- /dev/null +++ b/tests/snapshots/library/orcid.regex @@ -0,0 +1 @@ +^\d{4}\-\d{4}\-\d{4}\-\d{3}(?:\d|[X])$
\ No newline at end of file diff --git a/tests/snapshots/library/ordinal.regex b/tests/snapshots/library/ordinal.regex new file mode 100644 index 0000000..3e573ff --- /dev/null +++ b/tests/snapshots/library/ordinal.regex @@ -0,0 +1 @@ +^\d+(?:(?:st|nd|rd|th))$
\ No newline at end of file diff --git a/tests/snapshots/library/otp.regex b/tests/snapshots/library/otp.regex new file mode 100644 index 0000000..2310308 --- /dev/null +++ b/tests/snapshots/library/otp.regex @@ -0,0 +1 @@ +(?:^\d{6,8}$|^[A-Z0-9]{6,8}$)
\ No newline at end of file diff --git a/tests/snapshots/library/package.regex b/tests/snapshots/library/package.regex new file mode 100644 index 0000000..a7dc1f6 --- /dev/null +++ b/tests/snapshots/library/package.regex @@ -0,0 +1 @@ +^(?:@[a-z0-9][a-z0-9\-]*/)?[a-z0-9][a-z0-9\._\-]{0,213}$
\ No newline at end of file diff --git a/tests/snapshots/library/pager.regex b/tests/snapshots/library/pager.regex new file mode 100644 index 0000000..504218d --- /dev/null +++ b/tests/snapshots/library/pager.regex @@ -0,0 +1 @@ +^\d{4,10}$
\ No newline at end of file diff --git a/tests/snapshots/library/palette.regex b/tests/snapshots/library/palette.regex new file mode 100644 index 0000000..a011f9d --- /dev/null +++ b/tests/snapshots/library/palette.regex @@ -0,0 +1 @@ +^(?:(?:\#[0-9A-Fa-f]{3,8})|[a-zA-Z]{3,20})(?:\s*,\s*(?:(?:\#[0-9A-Fa-f]{3,8})|[a-zA-Z]{3,20})){1,15}$
\ No newline at end of file diff --git a/tests/snapshots/library/parquet.regex b/tests/snapshots/library/parquet.regex new file mode 100644 index 0000000..5f6b0b3 --- /dev/null +++ b/tests/snapshots/library/parquet.regex @@ -0,0 +1 @@ +^PAR1.*$
\ No newline at end of file diff --git a/tests/snapshots/library/passkey.regex b/tests/snapshots/library/passkey.regex new file mode 100644 index 0000000..5da3fc6 --- /dev/null +++ b/tests/snapshots/library/passkey.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_-]{22,512}$
\ No newline at end of file diff --git a/tests/snapshots/library/password.regex b/tests/snapshots/library/password.regex new file mode 100644 index 0000000..d1c63d3 --- /dev/null +++ b/tests/snapshots/library/password.regex @@ -0,0 +1 @@ +(?:)
\ No newline at end of file diff --git a/tests/snapshots/library/path.regex b/tests/snapshots/library/path.regex Binary files differnew file mode 100644 index 0000000..41b80f8 --- /dev/null +++ b/tests/snapshots/library/path.regex diff --git a/tests/snapshots/library/pdf.regex b/tests/snapshots/library/pdf.regex new file mode 100644 index 0000000..10c8c17 --- /dev/null +++ b/tests/snapshots/library/pdf.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/]{1,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/peg.regex b/tests/snapshots/library/peg.regex new file mode 100644 index 0000000..5603337 --- /dev/null +++ b/tests/snapshots/library/peg.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9_\-<>:=\|\*\+\?\(\)\[\]\{\}\.'"/;,]){4,65536}$
\ No newline at end of file diff --git a/tests/snapshots/library/pem.regex b/tests/snapshots/library/pem.regex new file mode 100644 index 0000000..e2638b9 --- /dev/null +++ b/tests/snapshots/library/pem.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9\+/=_\-\.:]){16,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/percentage.regex b/tests/snapshots/library/percentage.regex new file mode 100644 index 0000000..7389873 --- /dev/null +++ b/tests/snapshots/library/percentage.regex @@ -0,0 +1 @@ +^\-?\d+(?:\.\d+)?\s?%$
\ No newline at end of file diff --git a/tests/snapshots/library/pest.regex b/tests/snapshots/library/pest.regex new file mode 100644 index 0000000..5603337 --- /dev/null +++ b/tests/snapshots/library/pest.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9_\-<>:=\|\*\+\?\(\)\[\]\{\}\.'"/;,]){4,65536}$
\ No newline at end of file diff --git a/tests/snapshots/library/pgp.regex b/tests/snapshots/library/pgp.regex new file mode 100644 index 0000000..e2638b9 --- /dev/null +++ b/tests/snapshots/library/pgp.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9\+/=_\-\.:]){16,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/phone.regex b/tests/snapshots/library/phone.regex new file mode 100644 index 0000000..609f322 --- /dev/null +++ b/tests/snapshots/library/phone.regex @@ -0,0 +1 @@ +^(?:\+?\d{1,4}?(?:\s|[\-\.])?\(?\d{1,3}?\)?(?:\s|[\-\.])?\d{1,4}(?:\s|[\-\.])?\d{1,4}(?:\s|[\-\.])?\d{1,9}|\d{2,4})$
\ No newline at end of file diff --git a/tests/snapshots/library/pin.regex b/tests/snapshots/library/pin.regex new file mode 100644 index 0000000..2aadeb2 --- /dev/null +++ b/tests/snapshots/library/pin.regex @@ -0,0 +1 @@ +^\d{4,12}$
\ No newline at end of file diff --git a/tests/snapshots/library/place.regex b/tests/snapshots/library/place.regex new file mode 100644 index 0000000..2388960 --- /dev/null +++ b/tests/snapshots/library/place.regex @@ -0,0 +1 @@ +^[a-zA-Z][A-Za-z \.,'\-]{1,99}$
\ No newline at end of file diff --git a/tests/snapshots/library/plate.regex b/tests/snapshots/library/plate.regex new file mode 100644 index 0000000..5e7ced9 --- /dev/null +++ b/tests/snapshots/library/plate.regex @@ -0,0 +1 @@ +^[A-Z0-9]{1,3}[- ]?[A-Z0-9]{1,4}$
\ No newline at end of file diff --git a/tests/snapshots/library/plus.regex b/tests/snapshots/library/plus.regex new file mode 100644 index 0000000..1ff22f5 --- /dev/null +++ b/tests/snapshots/library/plus.regex @@ -0,0 +1 @@ +^[23456789CFGHJMPQRVWX]{2,8}\+[23456789CFGHJMPQRVWX]{2,3}(?:\s+.+)?$
\ No newline at end of file diff --git a/tests/snapshots/library/pmc.regex b/tests/snapshots/library/pmc.regex new file mode 100644 index 0000000..054d7c6 --- /dev/null +++ b/tests/snapshots/library/pmc.regex @@ -0,0 +1 @@ +^PMC\d{1,9}$
\ No newline at end of file diff --git a/tests/snapshots/library/pmid.regex b/tests/snapshots/library/pmid.regex new file mode 100644 index 0000000..11dcb2a --- /dev/null +++ b/tests/snapshots/library/pmid.regex @@ -0,0 +1 @@ +^\d{1,8}$
\ No newline at end of file diff --git a/tests/snapshots/library/port.regex b/tests/snapshots/library/port.regex new file mode 100644 index 0000000..31f4234 --- /dev/null +++ b/tests/snapshots/library/port.regex @@ -0,0 +1 @@ +(?:^6553[0-5]$|^655[0-2]\d$|^65[0-4]\d{2}$|^6[0-4]\d{3}$|^[1-5]\d{4}$|^[1-9]\d{0,3}$|^0$)
\ No newline at end of file diff --git a/tests/snapshots/library/postal.regex b/tests/snapshots/library/postal.regex new file mode 100644 index 0000000..fde788d --- /dev/null +++ b/tests/snapshots/library/postal.regex @@ -0,0 +1 @@ +^(?:(?:(?:120|122))\d{2}|(?:NL\-)?\d{4}\s*[A-Z]{2}|(?:(?:[AC-FHKNPRTV-Y]\d{2}|D6W))[ -]?[0-9AC-FHKNPRTV-Y]{4}|(?:GIR 0AA|(?:[A-Za-z]\d{1,2}|[A-Za-z][A-HJ-Ya-hj-y]\d{1,2}|[A-Za-z]\d[A-Za-z]|[A-Za-z][A-HJ-Ya-hj-y]\d(?:[A-Za-z])?)\s?\d(?:[A-Za-z]){2})|96950|971\d{2}|972\d{2}|973\d{2}|974\d{2}|976\d{2}|980\d{2}|987\d{2}|988\d{2}|AI\-2640|BB\d{5}|FIQQ 1ZZ|GX11 1AA|LT\-\d{5}|LV\-\d{4}|MD\-?\d{4}|MSR \d{4}|STHL 1ZZ|TKCA 1ZZ|VC\d{4}|VG\d{4}|WS\d{4}|\d{2}\-\d{3}|\d{3}\s\d{2}|\d{3}|\d{3}(?:\-\d{2})?|\d{3}\-\d{4}|\d{4}\s\d{4}|\d{4}|\d{4}(?:\-[A-Z])?|\d{4}\-\d{3}|\d{5}|\d{5}(?:\-\d{4})?|\d{5}\-\d{3}|(?:\d{5}|\d{7})|\d{6}|\d{7}|[A-Z]\d[A-Z]\s?\d[A-Z]\d|[A-Z]\d{3}|[A-Z]\d{4}[A-Z]{3}|[A-Z]{2}\s\d{5}|[A-Z]{2}\d\-\d{4}|[A-Z]{2}\d{2}\s\d{3}|[A-Z]{2}\d{2}|[A-Z]{2}\d{4}|[A-Z]{3}\s\d{4}|\d{5}(?:[-](?:\s|[\-])\d{4})?|(?!0)\d{6})$
\ No newline at end of file diff --git a/tests/snapshots/library/pptx.regex b/tests/snapshots/library/pptx.regex new file mode 100644 index 0000000..10c8c17 --- /dev/null +++ b/tests/snapshots/library/pptx.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/]{1,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/printable.regex b/tests/snapshots/library/printable.regex Binary files differnew file mode 100644 index 0000000..1a32025 --- /dev/null +++ b/tests/snapshots/library/printable.regex diff --git a/tests/snapshots/library/protobuf.regex b/tests/snapshots/library/protobuf.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/protobuf.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/ptr.regex b/tests/snapshots/library/ptr.regex new file mode 100644 index 0000000..2fe090e --- /dev/null +++ b/tests/snapshots/library/ptr.regex @@ -0,0 +1 @@ +^(?:(?:\d{1,3}\.){4}in\-addr\.arpa\.?|(?:[0-9a-fA-F]\.){32}ip6\.arpa\.?)$
\ No newline at end of file diff --git a/tests/snapshots/library/ratio.regex b/tests/snapshots/library/ratio.regex new file mode 100644 index 0000000..e6de6b8 --- /dev/null +++ b/tests/snapshots/library/ratio.regex @@ -0,0 +1 @@ +^\d+:\d+$
\ No newline at end of file diff --git a/tests/snapshots/library/readme.regex b/tests/snapshots/library/readme.regex new file mode 100644 index 0000000..10c8c17 --- /dev/null +++ b/tests/snapshots/library/readme.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/]{1,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/ref.regex b/tests/snapshots/library/ref.regex new file mode 100644 index 0000000..ccc006c --- /dev/null +++ b/tests/snapshots/library/ref.regex @@ -0,0 +1 @@ +^(?:[a-f0-9]{7,40}|refs/(?:(?:heads|tags|remotes))/(?:(?!(?:\s|[~^:?*[\\])).)+|(?!(?:\s|[~^:?*[\\/])).(?:(?!(?:\s|[~^:?*[\\])).){0,127})$
\ No newline at end of file diff --git a/tests/snapshots/library/refresh.regex b/tests/snapshots/library/refresh.regex new file mode 100644 index 0000000..aadc171 --- /dev/null +++ b/tests/snapshots/library/refresh.regex @@ -0,0 +1 @@ +^[A-Za-z0-9._\-~+/=]{32,512}$
\ No newline at end of file diff --git a/tests/snapshots/library/regex.regex b/tests/snapshots/library/regex.regex new file mode 100644 index 0000000..d1c63d3 --- /dev/null +++ b/tests/snapshots/library/regex.regex @@ -0,0 +1 @@ +(?:)
\ No newline at end of file diff --git a/tests/snapshots/library/robots.regex b/tests/snapshots/library/robots.regex new file mode 100644 index 0000000..19ae6bb --- /dev/null +++ b/tests/snapshots/library/robots.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+=\?\&\#:%\~]{2,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/roman.regex b/tests/snapshots/library/roman.regex new file mode 100644 index 0000000..71b7922 --- /dev/null +++ b/tests/snapshots/library/roman.regex @@ -0,0 +1 @@ +^M{0,3}(?:(?:CM|CD|D?C{0,3}))(?:(?:XC|XL|L?X{0,3}))(?:(?:IX|IV|V?I{0,3}))$
\ No newline at end of file diff --git a/tests/snapshots/library/routing.regex b/tests/snapshots/library/routing.regex new file mode 100644 index 0000000..cb08452 --- /dev/null +++ b/tests/snapshots/library/routing.regex @@ -0,0 +1 @@ +^\d{9}$
\ No newline at end of file diff --git a/tests/snapshots/library/rss.regex b/tests/snapshots/library/rss.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/rss.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/rtf.regex b/tests/snapshots/library/rtf.regex new file mode 100644 index 0000000..76fca38 --- /dev/null +++ b/tests/snapshots/library/rtf.regex @@ -0,0 +1 @@ +^\{\\rtf\d?.*$
\ No newline at end of file diff --git a/tests/snapshots/library/saml.regex b/tests/snapshots/library/saml.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/saml.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/scientific.regex b/tests/snapshots/library/scientific.regex new file mode 100644 index 0000000..9083fb5 --- /dev/null +++ b/tests/snapshots/library/scientific.regex @@ -0,0 +1 @@ +^[+-]?\d+(?:\.\d+)?[eE][+-]?\d+$
\ No newline at end of file diff --git a/tests/snapshots/library/script.regex b/tests/snapshots/library/script.regex new file mode 100644 index 0000000..63c114f --- /dev/null +++ b/tests/snapshots/library/script.regex @@ -0,0 +1 @@ +^(?:[A-Za-zÀ-ɏ]+|[Ѐ-ӿ]+|[Ͱ-Ͽ]+|[一-鿿-ヿ가-]+|[-ۿ]+|[-]+|[ऀ-ॿ]+)$
\ No newline at end of file diff --git a/tests/snapshots/library/secret.regex b/tests/snapshots/library/secret.regex new file mode 100644 index 0000000..4e05bdc --- /dev/null +++ b/tests/snapshots/library/secret.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_-]{16,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/sedol.regex b/tests/snapshots/library/sedol.regex new file mode 100644 index 0000000..83999af --- /dev/null +++ b/tests/snapshots/library/sedol.regex @@ -0,0 +1 @@ +^[B-DF-HJ-NP-TV-XY-Z0-9]{6}\d$
\ No newline at end of file diff --git a/tests/snapshots/library/semver.regex b/tests/snapshots/library/semver.regex new file mode 100644 index 0000000..b878d26 --- /dev/null +++ b/tests/snapshots/library/semver.regex @@ -0,0 +1 @@ +^(?P<major>(?:[1-9]\d*|[0]))\.(?P<minor>(?:[1-9]\d*|[0]))\.(?P<patch>(?:[1-9]\d*|[0]))(?:\-(?P<prerelease>(?:[1-9]\d*|\d*[a-zA-Z\-][0-9a-zA-Z\-]*|[0])(?:\.(?:[1-9]\d*|\d*[a-zA-Z\-][0-9a-zA-Z\-]*|[0]))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z\-]+(?:\.[0-9a-zA-Z\-]+)*))?$
\ No newline at end of file diff --git a/tests/snapshots/library/session.regex b/tests/snapshots/library/session.regex new file mode 100644 index 0000000..b7faca2 --- /dev/null +++ b/tests/snapshots/library/session.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_-]{16,128}$
\ No newline at end of file diff --git a/tests/snapshots/library/shebang.regex b/tests/snapshots/library/shebang.regex new file mode 100644 index 0000000..899503e --- /dev/null +++ b/tests/snapshots/library/shebang.regex @@ -0,0 +1 @@ +^\#!/(?:usr/)?(?:(?:bin|sbin|local))/(?:env\s+)?[a-zA-Z0-9\._\+/\-]+$
\ No newline at end of file diff --git a/tests/snapshots/library/signature.regex b/tests/snapshots/library/signature.regex new file mode 100644 index 0000000..5953cda --- /dev/null +++ b/tests/snapshots/library/signature.regex @@ -0,0 +1 @@ +^[A-Za-z0-9\+/=_\-]{64,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/signing.regex b/tests/snapshots/library/signing.regex new file mode 100644 index 0000000..db3063b --- /dev/null +++ b/tests/snapshots/library/signing.regex @@ -0,0 +1 @@ +^[A-Za-z0-9+/=_-]{32,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/sitemap.regex b/tests/snapshots/library/sitemap.regex new file mode 100644 index 0000000..19ae6bb --- /dev/null +++ b/tests/snapshots/library/sitemap.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+=\?\&\#:%\~]{2,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/sku.regex b/tests/snapshots/library/sku.regex new file mode 100644 index 0000000..4fdd21e --- /dev/null +++ b/tests/snapshots/library/sku.regex @@ -0,0 +1 @@ +^[A-Za-z0-9-_./]{4,20}$
\ No newline at end of file diff --git a/tests/snapshots/library/slug.regex b/tests/snapshots/library/slug.regex new file mode 100644 index 0000000..8f483d5 --- /dev/null +++ b/tests/snapshots/library/slug.regex @@ -0,0 +1 @@ +^[a-z0-9]+(?:\-[a-z0-9]+)*$
\ No newline at end of file diff --git a/tests/snapshots/library/soap.regex b/tests/snapshots/library/soap.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/soap.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/socket.regex b/tests/snapshots/library/socket.regex new file mode 100644 index 0000000..36ef08c --- /dev/null +++ b/tests/snapshots/library/socket.regex @@ -0,0 +1 @@ +^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|\[[0-9a-fA-F:]+\]|[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*):\d{1,5}$
\ No newline at end of file diff --git a/tests/snapshots/library/sortcode.regex b/tests/snapshots/library/sortcode.regex new file mode 100644 index 0000000..e2c3aaf --- /dev/null +++ b/tests/snapshots/library/sortcode.regex @@ -0,0 +1 @@ +(?:^\d{2}\-\d{2}\-\d{2}$|^\d{6}$)
\ No newline at end of file diff --git a/tests/snapshots/library/ssh.regex b/tests/snapshots/library/ssh.regex new file mode 100644 index 0000000..e2638b9 --- /dev/null +++ b/tests/snapshots/library/ssh.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9\+/=_\-\.:]){16,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/ssn.regex b/tests/snapshots/library/ssn.regex new file mode 100644 index 0000000..45dabbc --- /dev/null +++ b/tests/snapshots/library/ssn.regex @@ -0,0 +1 @@ +^(?!(?:666|000|9\d{2}))\d{3}\-(?!00)\d{2}\-(?!0{4})\d{4}$
\ No newline at end of file diff --git a/tests/snapshots/library/sso.regex b/tests/snapshots/library/sso.regex new file mode 100644 index 0000000..dff8aed --- /dev/null +++ b/tests/snapshots/library/sso.regex @@ -0,0 +1 @@ +^[A-Za-z0-9+/=_\-.]{20,2048}$
\ No newline at end of file diff --git a/tests/snapshots/library/subdomain.regex b/tests/snapshots/library/subdomain.regex new file mode 100644 index 0000000..b51e39e --- /dev/null +++ b/tests/snapshots/library/subdomain.regex @@ -0,0 +1 @@ +^[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]$
\ No newline at end of file diff --git a/tests/snapshots/library/subnet.regex b/tests/snapshots/library/subnet.regex new file mode 100644 index 0000000..284d1f0 --- /dev/null +++ b/tests/snapshots/library/subnet.regex @@ -0,0 +1 @@ +^(?:255|254|252|248|240|224|192|128|[0])\.(?:255|254|252|248|240|224|192|128|[0])\.(?:255|254|252|248|240|224|192|128|[0])\.(?:255|254|252|248|240|224|192|128|[0])$
\ No newline at end of file diff --git a/tests/snapshots/library/svg.regex b/tests/snapshots/library/svg.regex new file mode 100644 index 0000000..10c8c17 --- /dev/null +++ b/tests/snapshots/library/svg.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/]{1,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/swagger.regex b/tests/snapshots/library/swagger.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/swagger.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/swatch.regex b/tests/snapshots/library/swatch.regex new file mode 100644 index 0000000..d3d4f11 --- /dev/null +++ b/tests/snapshots/library/swatch.regex @@ -0,0 +1 @@ +^(?:(?:\#[0-9A-Fa-f]{3,8})|[a-zA-Z]{3,20})$
\ No newline at end of file diff --git a/tests/snapshots/library/tex.regex b/tests/snapshots/library/tex.regex new file mode 100644 index 0000000..818db67 --- /dev/null +++ b/tests/snapshots/library/tex.regex @@ -0,0 +1 @@ +^\\documentclass(?:\[[^\]]*\])?\{[^}]+\}.*$
\ No newline at end of file diff --git a/tests/snapshots/library/time.regex b/tests/snapshots/library/time.regex new file mode 100644 index 0000000..753d9d4 --- /dev/null +++ b/tests/snapshots/library/time.regex @@ -0,0 +1 @@ +^(?:(?:2[0-3]|[01]?\d):[0-5]\d(?::[0-5]\d(?:\.\d{1,6})?)?|(?:1[0-2]|0?[1-9]):[0-5]\d(?::[0-5]\d)?\s?[AaPp][Mm])$
\ No newline at end of file diff --git a/tests/snapshots/library/timestamp.regex b/tests/snapshots/library/timestamp.regex new file mode 100644 index 0000000..17e8f86 --- /dev/null +++ b/tests/snapshots/library/timestamp.regex @@ -0,0 +1 @@ +^\-?\d{10,13}$
\ No newline at end of file diff --git a/tests/snapshots/library/timezone.regex b/tests/snapshots/library/timezone.regex new file mode 100644 index 0000000..ee45ed2 --- /dev/null +++ b/tests/snapshots/library/timezone.regex @@ -0,0 +1 @@ +^(?:[A-Z][a-zA-Z_\+\-]+(?:/[A-Z][a-zA-Z_\+\-]+)+|(?:UTC|GMT|UT|[Z])|[A-Z]{2,5})$
\ No newline at end of file diff --git a/tests/snapshots/library/tin.regex b/tests/snapshots/library/tin.regex new file mode 100644 index 0000000..a2a44f9 --- /dev/null +++ b/tests/snapshots/library/tin.regex @@ -0,0 +1 @@ +(?:^(?!(?:666|000|9\d{2}))\d{3}\-(?!00)\d{2}\-(?!0{4})\d{4}$|^\d{2}\-\d{7}$|^9\d{2}\-(?:5\d|6[0-5]|7\d|8[0-8]|9[0-2]|9[4-9])\-\d{4}$)
\ No newline at end of file diff --git a/tests/snapshots/library/tld.regex b/tests/snapshots/library/tld.regex new file mode 100644 index 0000000..7b44a44 --- /dev/null +++ b/tests/snapshots/library/tld.regex @@ -0,0 +1 @@ +^[a-zA-Z]{2,63}$
\ No newline at end of file diff --git a/tests/snapshots/library/token.regex b/tests/snapshots/library/token.regex new file mode 100644 index 0000000..b43fe98 --- /dev/null +++ b/tests/snapshots/library/token.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\-.]{24,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/toml.regex b/tests/snapshots/library/toml.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/toml.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/tsv.regex b/tests/snapshots/library/tsv.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/tsv.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/unicode.regex b/tests/snapshots/library/unicode.regex Binary files differnew file mode 100644 index 0000000..1a32025 --- /dev/null +++ b/tests/snapshots/library/unicode.regex diff --git a/tests/snapshots/library/uri.regex b/tests/snapshots/library/uri.regex new file mode 100644 index 0000000..1870d58 --- /dev/null +++ b/tests/snapshots/library/uri.regex @@ -0,0 +1 @@ +^[a-zA-Z][a-zA-Z0-9\+\.\-]*:\S+$
\ No newline at end of file diff --git a/tests/snapshots/library/url.regex b/tests/snapshots/library/url.regex new file mode 100644 index 0000000..e672564 --- /dev/null +++ b/tests/snapshots/library/url.regex @@ -0,0 +1 @@ +^(?:https?://)?(?:www\.)?[\-a-zA-Z0-9@:%\._\+\~\#=]{1,256}\.[a-zA-Z0-9\(\)]{1,6}\b[\-a-zA-Z0-9\(\)@:%_\+\.\~\#\?\&/=]*$
\ No newline at end of file diff --git a/tests/snapshots/library/useragent.regex b/tests/snapshots/library/useragent.regex new file mode 100644 index 0000000..ce6825b --- /dev/null +++ b/tests/snapshots/library/useragent.regex @@ -0,0 +1 @@ +^[A-Za-z0-9/\.\-\(\) ;\+_,:]{4,1024}$
\ No newline at end of file diff --git a/tests/snapshots/library/username.regex b/tests/snapshots/library/username.regex new file mode 100644 index 0000000..4e5c432 --- /dev/null +++ b/tests/snapshots/library/username.regex @@ -0,0 +1 @@ +^[a-zA-Z0-9][a-zA-Z0-9_.-]{2,29}$
\ No newline at end of file diff --git a/tests/snapshots/library/uuid.regex b/tests/snapshots/library/uuid.regex new file mode 100644 index 0000000..7697f0f --- /dev/null +++ b/tests/snapshots/library/uuid.regex @@ -0,0 +1 @@ +^[0-9a-f]{8}\-[0-9a-f]{4}\-[0-5][0-9a-f]{3}\-[089ab][0-9a-f]{3}\-[0-9a-f]{12}$
\ No newline at end of file diff --git a/tests/snapshots/library/vat.regex b/tests/snapshots/library/vat.regex new file mode 100644 index 0000000..b05c315 --- /dev/null +++ b/tests/snapshots/library/vat.regex @@ -0,0 +1 @@ +^[A-Z]{2}\d{6,12}$
\ No newline at end of file diff --git a/tests/snapshots/library/vehicle.regex b/tests/snapshots/library/vehicle.regex new file mode 100644 index 0000000..22d7bab --- /dev/null +++ b/tests/snapshots/library/vehicle.regex @@ -0,0 +1 @@ +^[A-Z0-9][A-Z0-9\- ]{3,17}$
\ No newline at end of file diff --git a/tests/snapshots/library/version.regex b/tests/snapshots/library/version.regex new file mode 100644 index 0000000..1276869 --- /dev/null +++ b/tests/snapshots/library/version.regex @@ -0,0 +1 @@ +^v?\d+(?:\.\d+){0,3}(?:[-.+][a-zA-Z0-9\.\-]+)?$
\ No newline at end of file diff --git a/tests/snapshots/library/vin.regex b/tests/snapshots/library/vin.regex new file mode 100644 index 0000000..602b901 --- /dev/null +++ b/tests/snapshots/library/vin.regex @@ -0,0 +1 @@ +^[A-HJ-NPR-Z0-9]{17}$
\ No newline at end of file diff --git a/tests/snapshots/library/wallet.regex b/tests/snapshots/library/wallet.regex new file mode 100644 index 0000000..76cba2c --- /dev/null +++ b/tests/snapshots/library/wallet.regex @@ -0,0 +1 @@ +^(?:[13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-z0-9]{25,89}|0x[a-fA-F0-9]{40}|[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}|D[5-9A-HJ-NP-U][1-9A-HJ-NP-Za-km-z]{32}|X[1-9A-HJ-NP-Za-km-z]{33})$
\ No newline at end of file diff --git a/tests/snapshots/library/webauthn.regex b/tests/snapshots/library/webauthn.regex new file mode 100644 index 0000000..95d1108 --- /dev/null +++ b/tests/snapshots/library/webauthn.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_-]{43,512}$
\ No newline at end of file diff --git a/tests/snapshots/library/webhook.regex b/tests/snapshots/library/webhook.regex new file mode 100644 index 0000000..5ce9b78 --- /dev/null +++ b/tests/snapshots/library/webhook.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{3,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/word.regex b/tests/snapshots/library/word.regex new file mode 100644 index 0000000..dd924e9 --- /dev/null +++ b/tests/snapshots/library/word.regex @@ -0,0 +1 @@ +^\w+$
\ No newline at end of file diff --git a/tests/snapshots/library/x509.regex b/tests/snapshots/library/x509.regex new file mode 100644 index 0000000..e2638b9 --- /dev/null +++ b/tests/snapshots/library/x509.regex @@ -0,0 +1 @@ +^(?:\s|[A-Za-z0-9\+/=_\-\.:]){16,4096}$
\ No newline at end of file diff --git a/tests/snapshots/library/xlsx.regex b/tests/snapshots/library/xlsx.regex new file mode 100644 index 0000000..10c8c17 --- /dev/null +++ b/tests/snapshots/library/xlsx.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/]{1,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/xml.regex b/tests/snapshots/library/xml.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/xml.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/yaml.regex b/tests/snapshots/library/yaml.regex new file mode 100644 index 0000000..d9c50db --- /dev/null +++ b/tests/snapshots/library/yaml.regex @@ -0,0 +1 @@ +^[A-Za-z0-9_\.\-/\+]{2,256}$
\ No newline at end of file diff --git a/tests/snapshots/library/year.regex b/tests/snapshots/library/year.regex new file mode 100644 index 0000000..e7d130f --- /dev/null +++ b/tests/snapshots/library/year.regex @@ -0,0 +1 @@ +^\d{4}$
\ No newline at end of file diff --git a/tests/testing/snapshots.test.py b/tests/testing/snapshots.test.py new file mode 100644 index 0000000..c2d8676 --- /dev/null +++ b/tests/testing/snapshots.test.py @@ -0,0 +1,90 @@ +"""Tests for the snapshot helper in :mod:`edify.testing.snapshots`.""" + +from pathlib import Path + +import pytest + +from edify.testing import SnapshotMismatchError, SnapshotMissingError, assert_snapshot + +_UPDATE_ENVIRONMENT_VARIABLE = "EDIFY_UPDATE_SNAPSHOTS" + + +def test_assert_snapshot_passes_when_actual_matches_committed_reference(tmp_path, monkeypatch): + monkeypatch.delenv(_UPDATE_ENVIRONMENT_VARIABLE, raising=False) + snapshot_path = tmp_path / "identical.snapshot" + snapshot_path.write_text("hello\n") + assert_snapshot("hello\n", snapshot_path) + + +def test_assert_snapshot_raises_snapshot_mismatch_when_content_differs(tmp_path, monkeypatch): + monkeypatch.delenv(_UPDATE_ENVIRONMENT_VARIABLE, raising=False) + snapshot_path = tmp_path / "drift.snapshot" + snapshot_path.write_text("expected\n") + with pytest.raises(SnapshotMismatchError) as excinfo: + assert_snapshot("actual\n", snapshot_path) + text = str(excinfo.value) + assert "snapshot mismatch" in text + assert "-expected" in text + assert "+actual" in text + assert "EDIFY_UPDATE_SNAPSHOTS=1" in text + + +def test_assert_snapshot_raises_snapshot_missing_when_reference_absent(tmp_path, monkeypatch): + monkeypatch.delenv(_UPDATE_ENVIRONMENT_VARIABLE, raising=False) + absent_snapshot = tmp_path / "never_written.snapshot" + with pytest.raises(SnapshotMissingError, match="snapshot file missing"): + assert_snapshot("any actual value", absent_snapshot) + + +def test_update_mode_writes_the_snapshot_when_the_file_is_missing(tmp_path, monkeypatch): + monkeypatch.setenv(_UPDATE_ENVIRONMENT_VARIABLE, "1") + snapshot_path = tmp_path / "created.snapshot" + assert_snapshot("fresh content\n", snapshot_path) + assert snapshot_path.read_text() == "fresh content\n" + + +def test_update_mode_overwrites_the_snapshot_on_drift(tmp_path, monkeypatch): + monkeypatch.setenv(_UPDATE_ENVIRONMENT_VARIABLE, "1") + snapshot_path = tmp_path / "regenerated.snapshot" + snapshot_path.write_text("stale\n") + assert_snapshot("current\n", snapshot_path) + assert snapshot_path.read_text() == "current\n" + + +def test_update_mode_is_off_when_env_variable_is_set_to_other_value(tmp_path, monkeypatch): + monkeypatch.setenv(_UPDATE_ENVIRONMENT_VARIABLE, "0") + snapshot_path = tmp_path / "no_write.snapshot" + with pytest.raises(SnapshotMissingError): + assert_snapshot("actual", snapshot_path) + + +def test_update_mode_creates_missing_parent_directories(tmp_path, monkeypatch): + monkeypatch.setenv(_UPDATE_ENVIRONMENT_VARIABLE, "1") + snapshot_path = tmp_path / "nested" / "dir" / "leaf.snapshot" + assert_snapshot("value", snapshot_path) + assert snapshot_path.read_text() == "value" + + +def test_snapshot_mismatch_error_names_the_snapshot_path(tmp_path, monkeypatch): + monkeypatch.delenv(_UPDATE_ENVIRONMENT_VARIABLE, raising=False) + snapshot_path = tmp_path / "named.snapshot" + snapshot_path.write_text("was") + with pytest.raises(SnapshotMismatchError) as excinfo: + assert_snapshot("is", snapshot_path) + assert str(snapshot_path) in str(excinfo.value) + + +def test_snapshot_missing_error_is_an_assertion_error_subclass(): + assert issubclass(SnapshotMissingError, AssertionError) + + +def test_snapshot_mismatch_error_is_an_assertion_error_subclass(): + assert issubclass(SnapshotMismatchError, AssertionError) + + +def test_snapshot_helper_is_reachable_via_edify_testing_namespace(tmp_path): + from edify import testing + + snapshot_path: Path = tmp_path / "namespace.snapshot" + snapshot_path.write_text("ok\n") + testing.assert_snapshot("ok\n", snapshot_path) |
