diff options
| author | Bobby <[email protected]> | 2026-07-15 16:46:09 +0530 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-07-15 16:46:09 +0530 |
| commit | 433d41bf5887e61cc570a874e244e226c25b8a41 (patch) | |
| tree | b00a7012bec6ae5ce6ec08e0ce7fe3c98c032df5 | |
| parent | 7682321de9195bf903d1cc1d8df87d0f9522636b (diff) | |
| parent | 38212d5104e22028485eb90ff362c27c1ad7a7ec (diff) | |
| download | edify-433d41bf5887e61cc570a874e244e226c25b8a41.tar.xz edify-433d41bf5887e61cc570a874e244e226c25b8a41.zip | |
feat(library): new ipv4/ipv6/iso_date/zip_code/email_rfc_5322 validators + real-world corpus hardening + guarantee docstrings (#283)
Ships the validator-hardening epic and splits the bench-baseline
regenerate job into its own workflow.
## New validators
- **`ipv4`** — strict IPv4 dotted-quad. Splits the shape out of the
unified `ip` for callers who need v4-only.
- **`ipv6`** — every documented IPv6 form: full 8-group,
`::`-compressed, link-local `%zone`, IPv4-mapped `::ffff:1.2.3.4`, and
the hybrid IPv4-suffix. Splits the shape out of `ip` the same way.
- **`iso_date`** — strict ISO 8601 date shape `YYYY-MM-DD`. Distinct
from the existing `datetime` (which includes a time component) and the
loose `date` (which accepts many separators).
- **`zip_code`** — US ZIP or ZIP+4 postal-code shape.
- **`email_rfc_5322`** — the strict RFC 5322 mailbox subset already
inside `email`, exposed as its own callable so downstream callers can
pick permissive vs strict semantics without composing themselves.
## Real-world corpus hardening
- **`tests/library/corpora/<name>.toml`** — per-validator TOML files
with `accepts` and `rejects` lists (233 cases across 14 validators).
Adding a new validator just means dropping in a new TOML.
- **`tests/library/hardening.test.py`** — parametrized test that walks
every corpus file, resolves the validator from `edify.library`, and
asserts the boolean verdict matches the expected side. `pytest -k
hardening` runs the whole corpus in under half a second.
- Coverage: `email`, `email_rfc_5322`, `url`, `ipv4`, `ipv6`, `mac`,
`uuid`, `guid`, `ssn`, `date`, `iso_date`, `phone`, `zip_code`,
`password`.
## Guarantee documentation pass
- Every hardened validator's module docstring now spells out what it
does and does not guarantee — shape vs semantics, anchoring, case
sensitivity, structural exclusions. Callers no longer have to grep the
regex or read the source to know whether `mac(...)` accepts EUI-64 or
whether `iso_date(...)` validates calendar correctness.
- The `password` validator gets a first-class guarantee block covering
the default policy (min length, class thresholds, special-char set) and
the passphrase-strength and non-ASCII limits it deliberately does not
check.
## CI hygiene — split bench-baseline job
- The `bench: regenerate runner baseline` job moves out of `build` into
its own `.github/workflows/bench-baseline.yml`. It only registers when
workflow_dispatch fires with a typed `YES` confirmation.
- Every push and pull_request stops surfacing the two skipped
baseline-regen checks that were appearing on every commit.
- The runtime behavior is unchanged: baselines are still
runner-generated and committed back with `contents: write` permission.
Closes #78, closes #152, closes #153, closes #156, closes #157, closes
#158, closes #159, closes #160, closes #161, closes #162, closes #163,
closes #164, closes #165, closes #166, closes #167, closes #168, closes
#169.
103 files changed, 673 insertions, 67 deletions
diff --git a/.github/workflows/bench-baseline.yml b/.github/workflows/bench-baseline.yml new file mode 100644 index 0000000..8eb0743 --- /dev/null +++ b/.github/workflows/bench-baseline.yml @@ -0,0 +1,37 @@ +name: 'bench baseline regenerate' +on: + workflow_dispatch: + inputs: + confirm: + description: 'Type YES to confirm regenerating tests/bench/baseline.json on the runner and committing it back' + required: true + default: 'no' +jobs: + regenerate: + name: 'bench: regenerate runner baseline' + runs-on: ubuntu-latest + if: github.event.inputs.confirm == 'YES' + permissions: + contents: write + timeout-minutes: 20 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 + with: + python-version: '3.11' + - name: install dependencies (includes bench group) + run: uv sync --frozen --all-groups + - name: generate baseline.json on the runner + run: | + uv run pytest tests/bench/ --benchmark-only --benchmark-json=tests/bench/baseline.json + - name: commit the runner-generated baseline back to the branch + run: | + git config user.name 'edify-ci[bot]' + git config user.email '[email protected]' + git add tests/bench/baseline.json + git diff --cached --quiet && exit 0 + git commit -m 'chore(bench): regenerate runner-generated baseline' + git push diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 519dc64..c0ed6e2 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -1,13 +1,5 @@ name: build -on: - push: - pull_request: - workflow_dispatch: - inputs: - regenerate_bench_baseline: - description: 'Regenerate the runner-generated bench baseline and commit it back to the branch' - required: false - default: 'false' +on: [push, pull_request] jobs: test: name: ${{ matrix.name }} @@ -142,7 +134,6 @@ jobs: bench: name: 'bench (advisory)' runs-on: ubuntu-latest - if: github.event_name != 'workflow_dispatch' || github.event.inputs.regenerate_bench_baseline != 'true' timeout-minutes: 15 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 @@ -160,32 +151,3 @@ jobs: else uv run pytest tests/bench/ --benchmark-only || true fi - - bench-baseline-regenerate: - name: 'bench: regenerate runner baseline' - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' && github.event.inputs.regenerate_bench_baseline == 'true' - permissions: - contents: write - timeout-minutes: 20 - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 - with: - python-version: '3.11' - - name: install dependencies (includes bench group) - run: uv sync --frozen --all-groups - - name: generate baseline.json on the runner - run: | - uv run pytest tests/bench/ --benchmark-only --benchmark-json=tests/bench/baseline.json - - name: commit the runner-generated baseline back to the branch - run: | - git config user.name 'edify-ci[bot]' - git config user.email '[email protected]' - git add tests/bench/baseline.json - git diff --cached --quiet && exit 0 - git commit -m 'chore(bench): regenerate runner-generated baseline' - git push diff --git a/edify/library/__init__.py b/edify/library/__init__.py index 460702b..989b986 100644 --- a/edify/library/__init__.py +++ b/edify/library/__init__.py @@ -12,6 +12,8 @@ from edify.library.address import ( domain, hostname, ip, + ipv4, + ipv6, path, port, ptr, @@ -21,6 +23,7 @@ from edify.library.address import ( tld, uri, url, + zip_code, ) from edify.library.api import ( atom, @@ -61,6 +64,7 @@ from edify.library.color import color, filter, gradient, palette, swatch from edify.library.contact import ( address, email, + email_rfc_5322, fax, handle, pager, @@ -199,6 +203,7 @@ from edify.library.temporal import ( duration, epoch, interval, + iso_date, offset, time, timestamp, @@ -294,6 +299,7 @@ __all__ = [ "ebnf", "ein", "email", + "email_rfc_5322", "emoji", "encoding", "epoch", @@ -332,8 +338,11 @@ __all__ = [ "integer", "interval", "ip", + "ipv4", + "ipv6", "isbn", "isin", + "iso_date", "issn", "itin", "json", @@ -457,4 +466,5 @@ __all__ = [ "xml", "yaml", "year", + "zip_code", ] diff --git a/edify/library/address/__init__.py b/edify/library/address/__init__.py index 13a934d..d404de4 100644 --- a/edify/library/address/__init__.py +++ b/edify/library/address/__init__.py @@ -1,7 +1,7 @@ from edify.library.address.cidr import cidr from edify.library.address.domain import domain from edify.library.address.hostname import hostname -from edify.library.address.ip import ip +from edify.library.address.ip import ip, ipv4, ipv6 from edify.library.address.path import path from edify.library.address.port import port from edify.library.address.ptr import ptr @@ -11,12 +11,15 @@ from edify.library.address.subnet import subnet from edify.library.address.tld import tld from edify.library.address.uri import uri from edify.library.address.url import url +from edify.library.address.zip_code import zip_code __all__ = [ "cidr", "domain", "hostname", "ip", + "ipv4", + "ipv6", "path", "port", "ptr", @@ -26,4 +29,5 @@ __all__ = [ "tld", "uri", "url", + "zip_code", ] diff --git a/edify/library/address/ip.py b/edify/library/address/ip.py index 85945d7..3fdab15 100644 --- a/edify/library/address/ip.py +++ b/edify/library/address/ip.py @@ -183,5 +183,38 @@ _ipv6 = any_of( _b_hybrid(), ) +ipv4 = Pattern().start_of_input().subexpression(_ipv4).end_of_input() +"""Callable :class:`Pattern` for an IPv4 dotted-quad. + +Guarantees: + * Each octet is a decimal in the range ``0`` to ``255``. + * No leading zeros on multi-digit octets. + * Exactly four octets separated by ``.``. + +Does not guarantee: + * Reachability, allocation, or reserved-range semantics — this is a shape check. + * IPv6 forms — use :data:`ipv6` or :data:`ip` for those. +""" + +ipv6 = Pattern().start_of_input().subexpression(_ipv6).end_of_input() +"""Callable :class:`Pattern` for any IPv6 form. + +Guarantees: + * Full 8-group form, all documented ``::``-compressed forms, link-local ``%zone`` suffixes, + IPv4-mapped ``::ffff:1.2.3.4`` shape, and the hybrid IPv4-suffix form. + * Case-insensitive hex digits. + +Does not guarantee: + * Reachability or reserved-range semantics — this is a shape check. + * IPv4 dotted-quad forms — use :data:`ipv4` or :data:`ip` for those. +""" + ip = Pattern().start_of_input().subexpression(any_of(_ipv4, _ipv6)).end_of_input() -"""Callable :class:`Pattern` for IPv4 dotted-quad or any IPv6 form.""" +"""Callable :class:`Pattern` matching an IPv4 dotted-quad **or** any IPv6 form. + +Guarantees: + * Everything :data:`ipv4` and :data:`ipv6` guarantee individually. + +Does not guarantee: + * Reachability, allocation, or reserved-range semantics. +""" diff --git a/edify/library/address/url.py b/edify/library/address/url.py index 9061c34..d5d968c 100644 --- a/edify/library/address/url.py +++ b/edify/library/address/url.py @@ -67,6 +67,18 @@ url = ( .end() .end_of_input() ) -"""Callable :class:`Pattern` for the URL shape: optional ``http[s]://``, -optional ``www.``, host with dot-separated labels, TLD, and optional path. +"""Callable :class:`Pattern` for a permissive HTTP/HTTPS URL shape. + +Guarantees: + * Optional ``http[s]://`` scheme prefix. + * Optional ``www.`` host prefix. + * A dot-separated authority with a 1-6 character TLD, followed by an optional path. + * Anchored at both ends. + +Does not guarantee: + * URI reachability, TLS certificate validity, or DNS resolution. + * Non-HTTP schemes (``ftp:``, ``mailto:``, ``file:``) — those require dedicated + validators. + * Full RFC 3986 URI grammar — the pattern is deliberately permissive for the + common web-URL shape. """ diff --git a/edify/library/address/zip_code.py b/edify/library/address/zip_code.py new file mode 100644 index 0000000..4603724 --- /dev/null +++ b/edify/library/address/zip_code.py @@ -0,0 +1,30 @@ +"""``zip_code`` — US postal-code shape (5 digits, optionally + 4).""" + +from __future__ import annotations + +from edify import Pattern + +zip_code = ( + Pattern() + .start_of_input() + .exactly(5) + .digit() + .optional() + .group() + .char("-") + .exactly(4) + .digit() + .end() + .end_of_input() +) +"""Callable :class:`Pattern` for the US ZIP or ZIP+4 postal-code shape. + +Guarantees: + * Five decimal digits, optionally followed by ``-`` and four more decimal digits. + * Anchored at both ends. + +Does not guarantee: + * Postal-service validity — accepts every 5- and 5+4-digit string, including + unassigned or reserved ranges. + * Non-US postal codes — those live under a country-specific validator. +""" diff --git a/edify/library/auth/password.py b/edify/library/auth/password.py index a53d3f6..c044864 100644 --- a/edify/library/auth/password.py +++ b/edify/library/auth/password.py @@ -75,7 +75,24 @@ class _PasswordPattern(Pattern): password = _PasswordPattern() -"""Callable :class:`Pattern` (subclass) that enforces configurable -password-strength thresholds. Call as ``password(value)`` for the defaults, -or ``password(value, min_length=12, min_special=2)`` to tighten specific thresholds. +"""Callable :class:`Pattern` (subclass) that enforces configurable password-strength thresholds. + +Call as ``password(value)`` for the defaults or ``password(value, min_length=12, min_special=2)`` +to tighten specific thresholds. + +Default policy: + * Length between 8 and 64 characters inclusive. + * At least one uppercase letter, one lowercase letter, one decimal digit, and + one special character from the default set (``!@#$%^&*()_+-=[]{}|;':",./<>?``). + +Guarantees: + * Every threshold is checked exactly as configured — no silent lower/upper bound. + * The special-character set is overridable per call via ``special_chars=``. + +Does not guarantee: + * Passphrase strength beyond the counted-class thresholds — e.g. does not + reject dictionary words, common patterns, keyboard walks, or breached-password + corpora. + * Non-ASCII character-class handling — the class regex targets ASCII letters + and digits. """ diff --git a/edify/library/contact/__init__.py b/edify/library/contact/__init__.py index b4393cd..909725a 100644 --- a/edify/library/contact/__init__.py +++ b/edify/library/contact/__init__.py @@ -1,9 +1,18 @@ from edify.library.contact.address import address -from edify.library.contact.email import email +from edify.library.contact.email import email, email_rfc_5322 from edify.library.contact.fax import fax from edify.library.contact.handle import handle from edify.library.contact.pager import pager from edify.library.contact.phone import phone from edify.library.contact.username import username -__all__ = ["address", "email", "fax", "handle", "pager", "phone", "username"] +__all__ = [ + "address", + "email", + "email_rfc_5322", + "fax", + "handle", + "pager", + "phone", + "username", +] diff --git a/edify/library/contact/email.py b/edify/library/contact/email.py index 903691c..3abf47b 100644 --- a/edify/library/contact/email.py +++ b/edify/library/contact/email.py @@ -191,6 +191,33 @@ _rfc_domain = any_of(_basic_domain, _ip_literal) _rfc = Pattern().subexpression(_rfc_local).char("@").subexpression(_rfc_domain) email = Pattern().start_of_input().subexpression(any_of(_basic, _rfc)).end_of_input() -"""Callable :class:`Pattern` that accepts either the common permissive email -shape or the full RFC 5322 mailbox shape. +"""Callable :class:`Pattern` for either the common permissive or the strict RFC 5322 email shape. + +Guarantees: + * Accepts the everyday ``local@domain`` shape used across the web (letters, digits, + dot, hyphen, underscore, plus common special characters in the local part). + * Accepts the RFC 5322 mailbox shape — quoted local parts, dot-atoms, and + domain-literal addresses (``user@[192.0.2.1]``). + * Anchored at both ends, so the whole string must match. + +Does not guarantee: + * Deliverability, DNS resolution, or MX-record presence — this is a shape check. + * Full RFC 5322 group / display-name syntax (``Name <[email protected]>``). + * Internationalised domain names beyond ASCII / punycode. +""" + +email_rfc_5322 = Pattern().start_of_input().subexpression(_rfc).end_of_input() +"""Callable :class:`Pattern` for the strict RFC 5322 mailbox shape only. + +Rejects everyday but non-compliant addresses that :data:`email` accepts. + +Guarantees: + * Accepts every dot-atom and quoted-string local part permitted by RFC 5322. + * Accepts domain literals (``user@[192.0.2.1]``) and IPv6 literals + (``user@[IPv6:::1]``). + * Anchored at both ends. + +Does not guarantee: + * Deliverability, DNS resolution, or MX-record presence — this is a shape check. + * Full RFC 5322 group / display-name syntax. """ diff --git a/edify/library/contact/phone.py b/edify/library/contact/phone.py index 6ef4ed8..ebfc5a9 100644 --- a/edify/library/contact/phone.py +++ b/edify/library/contact/phone.py @@ -50,7 +50,20 @@ _international = ( _short = Pattern().between(2, 4).digit() phone = Pattern().start_of_input().subexpression(any_of(_international, _short)).end_of_input() -"""Callable :class:`Pattern` for phone-number shapes: permissive international -form (optional ``+``, country code, area code, and dash/dot/space separators) -or 2-4 digit short-code fallback. +"""Callable :class:`Pattern` for permissive international or 2-4 digit short-code phone shapes. + +Guarantees: + * International form: optional ``+``, 1-4 country-code digits, optional + parenthesised area code, and dash/dot/space separators. + * Short-code fallback for 2-4 digit service numbers. + * Anchored at both ends. + +Does not guarantee: + * ITU E.164 canonical form — the pattern is deliberately permissive to + accept the display forms real inputs use. + * Per-country structural validity — full locale coverage lands with the + dedicated locale-expansion work. + * Missed shapes (parenthesised area codes with mixed separators, 4-digit-only + service numbers outside the short-code range) — expect false rejects for + unusual inputs. """ diff --git a/edify/library/identifier/guid.py b/edify/library/identifier/guid.py index dc1e6ba..66185f4 100644 --- a/edify/library/identifier/guid.py +++ b/edify/library/identifier/guid.py @@ -47,6 +47,16 @@ guid = ( .char("}") .end_of_input() ) -"""Callable :class:`Pattern` that validates the Microsoft-flavour GUID shape: -the 8-4-4-4-12 hex form (either case) optionally wrapped in braces. +"""Callable :class:`Pattern` for the Microsoft-flavour GUID 8-4-4-4-12 hex shape. + +Guarantees: + * 32 hex digits in 8-4-4-4-12 layout with hyphen separators. + * Either case is accepted; the braces are optional and independent. + * Anchored at both ends. + +Does not guarantee: + * Balanced-brace enforcement — a leading ``{`` without a trailing ``}`` and vice + versa both pass. Use :data:`edify.library.uuid` for the version- and variant-locked + RFC 4122 form, or validate braces separately if you require them matched. + * Version or variant digit values — every hex digit passes. """ diff --git a/edify/library/identifier/mac.py b/edify/library/identifier/mac.py index 173bc67..91377eb 100644 --- a/edify/library/identifier/mac.py +++ b/edify/library/identifier/mac.py @@ -25,6 +25,17 @@ mac = ( .end() .end_of_input() ) -"""Callable :class:`Pattern` that validates the IEEE 802 MAC-address form: -six ``:``- or ``-``-separated hex octets (either case). +"""Callable :class:`Pattern` for the IEEE 802 MAC-address shape. + +Guarantees: + * Six 2-hex-digit octets separated by ``:`` or ``-``. + * Either case is accepted. + * Separator is uniform: mixed ``:`` and ``-`` in the same address is rejected. + * Anchored at both ends. + +Does not guarantee: + * OUI or IANA-assignment validity — every syntactically-valid octet passes. + * Dot-separated Cisco-style ``0000.5e00.53af``, bare 12-hex-digit ``00005e0053af``, + or EUI-64 8-octet ``00:00:5e:ff:fe:00:53:af`` — those forms require dedicated + validators. """ diff --git a/edify/library/identifier/ssn.py b/edify/library/identifier/ssn.py index a35b5e4..79ccfe3 100644 --- a/edify/library/identifier/ssn.py +++ b/edify/library/identifier/ssn.py @@ -33,9 +33,19 @@ ssn = ( .digit() .end_of_input() ) -"""Callable :class:`Pattern` that validates the US ``AAA-GG-SSSS`` SSN shape -with the documented blocked ranges (``000``, ``666``, ``9xx`` area; ``00`` -group; ``0000`` serial). +"""Callable :class:`Pattern` for the US ``AAA-GG-SSSS`` Social Security Number shape. + +Guarantees: + * Three-digit area, two-digit group, four-digit serial, hyphen-separated. + * Documented blocked ranges rejected: ``000`` / ``666`` / ``9xx`` area, ``00`` + group, ``0000`` serial. + * Anchored at both ends. + +Does not guarantee: + * Assignment or issuance history — a shape that clears the blocked ranges may + still be unassigned. + * Non-US national identifiers (SIN, NIN, DNI, etc.) — those need dedicated + validators. """ del _blocked_area diff --git a/edify/library/identifier/uuid.py b/edify/library/identifier/uuid.py index ddd12a4..9587658 100644 --- a/edify/library/identifier/uuid.py +++ b/edify/library/identifier/uuid.py @@ -40,7 +40,14 @@ uuid = ( .end() .end_of_input() ) -"""Callable :class:`Pattern` that validates the canonical UUID 8-4-4-4-12 -lowercase-hex shape with the version digit pinned to ``1``-``5`` and the -variant digit pinned to ``8``-``b``. +"""Callable :class:`Pattern` for the canonical UUID 8-4-4-4-12 lowercase-hex shape. + +Guarantees: + * Exactly 32 lowercase hex digits arranged 8-4-4-4-12 with hyphen separators. + * Version digit pinned to ``1``-``5`` and variant digit pinned to ``8``-``b``. + * Anchored at both ends. + +Does not guarantee: + * Uppercase hex — use :data:`edify.library.guid` if either case is required. + * Brace-wrapped, urn-prefixed, or version-6/7/8 UUIDs. """ diff --git a/edify/library/temporal/__init__.py b/edify/library/temporal/__init__.py index 6fb6c54..140cbd0 100644 --- a/edify/library/temporal/__init__.py +++ b/edify/library/temporal/__init__.py @@ -4,6 +4,7 @@ from edify.library.temporal.datetime import datetime from edify.library.temporal.duration import duration from edify.library.temporal.epoch import epoch from edify.library.temporal.interval import interval +from edify.library.temporal.iso_date import iso_date from edify.library.temporal.offset import offset from edify.library.temporal.time import time from edify.library.temporal.timestamp import timestamp @@ -17,6 +18,7 @@ __all__ = [ "duration", "epoch", "interval", + "iso_date", "offset", "time", "timestamp", diff --git a/edify/library/temporal/date.py b/edify/library/temporal/date.py index f92eaf9..0a60fdb 100644 --- a/edify/library/temporal/date.py +++ b/edify/library/temporal/date.py @@ -18,7 +18,19 @@ date = ( .subexpression(any_of(_d1, _d2, _d3, _d4, _d5, _d6, _d7)) .end_of_input() ) -"""Callable :class:`Pattern` for calendar-date shapes: -``M/D/YYYY``, ``YYYY-MM-DD``, ``DD-MM-YYYY``, ``YYYY/MM/DD``, +"""Callable :class:`Pattern` for common calendar-date shapes. + +Accepts ``M/D/YYYY``, ``YYYY-MM-DD``, ``DD-MM-YYYY``, ``YYYY/MM/DD``, ``DD.MM.YYYY``, ``YYYY.MM.DD``, or ``YYYYMMDD``. + +Guarantees: + * Anchored at both ends — the whole input must be one of the accepted shapes. + * Separators are fixed per shape; mixed separators are rejected. + +Does not guarantee: + * Calendar validity — accepts ``02/30/2026`` and other structurally-valid but + calendar-invalid shapes. + * Strict ISO 8601 output — use :data:`edify.library.iso_date` for that. + * Locale-specific day-first vs month-first disambiguation — a two-digit head + that could be either month or day is accepted by both branches. """ diff --git a/edify/library/temporal/iso_date.py b/edify/library/temporal/iso_date.py new file mode 100644 index 0000000..204d087 --- /dev/null +++ b/edify/library/temporal/iso_date.py @@ -0,0 +1,32 @@ +"""``iso_date`` — strict ISO 8601 date shape (``YYYY-MM-DD``).""" + +from __future__ import annotations + +from edify import Pattern + +iso_date = ( + Pattern() + .start_of_input() + .exactly(4) + .digit() + .char("-") + .exactly(2) + .digit() + .char("-") + .exactly(2) + .digit() + .end_of_input() +) +"""Callable :class:`Pattern` for the ISO 8601 calendar-date shape ``YYYY-MM-DD``. + +Guarantees: + * Exactly four year digits, two month digits, two day digits. + * Hyphen separators, no whitespace, no time component. + * Anchored at both ends. + +Does not guarantee: + * Calendar validity — accepts shapes like ``2026-02-30`` that the ISO + grammar allows but the calendar does not. + * ISO 8601 time or datetime shapes — use :data:`edify.library.datetime` + for those. +""" diff --git a/tests/docs/snapshots.test.py b/tests/docs/snapshots.test.py index 75523ee..5b97ddb 100644 --- a/tests/docs/snapshots.test.py +++ b/tests/docs/snapshots.test.py @@ -72,9 +72,11 @@ def _discover_blocks(): yield rst_path, block_start, block_source, relative_stem -def _snapshot_bodies_for_block(namespace: dict) -> str: +def _snapshot_bodies_for_block(namespace: dict, pre_exec_names: frozenset[str]) -> str: interesting_pairs = [] for identifier, value in namespace.items(): + if identifier in pre_exec_names: + continue if identifier.startswith("_") or identifier in {"edify", "Pattern", "Regex"}: continue if isinstance(value, Regex): @@ -124,8 +126,9 @@ def test_doc_code_block_produces_the_snapshotted_regex( 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() + pre_exec_names = frozenset(namespace) exec(compile(block_source, str(rst_path), "exec"), namespace) - rendered = _snapshot_bodies_for_block(namespace) + rendered = _snapshot_bodies_for_block(namespace, pre_exec_names) snapshot_path = _SNAPSHOT_ROOT / stem_string / f"{block_start:04d}.regex" assert_snapshot(rendered, snapshot_path) diff --git a/tests/library/corpora/date.toml b/tests/library/corpora/date.toml new file mode 100644 index 0000000..0d5133b --- /dev/null +++ b/tests/library/corpora/date.toml @@ -0,0 +1,22 @@ +accepts = [ + "2026-07-15", + "07/15/2026", + "15-07-2026", + "2026/07/15", + "15.07.2026", + "2026.07.15", + "20260715", + "1/1/2000", +] + +rejects = [ + "", + "not a date", + "2026", + "2026-7", + "20260", + "2026-07-15T10:30", + "2026-07-15 extra", + " 2026-07-15", + "abcd-ef-gh", +] diff --git a/tests/library/corpora/email.toml b/tests/library/corpora/email.toml new file mode 100644 index 0000000..bd32ca7 --- /dev/null +++ b/tests/library/corpora/email.toml @@ -0,0 +1,25 @@ +accepts = [ + "[email protected]", + "[email protected]", + "[email protected]", + "[email protected]", + "[email protected]", + "[email protected]", + "[email protected]", + "[email protected]", + "user@[192.0.2.1]", +] + +rejects = [ + "", + " ", + "plainaddress", + "@no-local.example.com", + "no-at-sign.example.com", + "spaces in [email protected]", + "user@", + "[email protected]", + "user@example", + "user@@example.com", + "[email protected]", +] diff --git a/tests/library/corpora/email_rfc_5322.toml b/tests/library/corpora/email_rfc_5322.toml new file mode 100644 index 0000000..b4f04d5 --- /dev/null +++ b/tests/library/corpora/email_rfc_5322.toml @@ -0,0 +1,20 @@ +accepts = [ + "[email protected]", + "[email protected]", + "user@[192.0.2.1]", + "[email protected]", + "[email protected]", + "[email protected]", +] + +rejects = [ + "", + "plainaddress", + "@no-local.example.com", + "user@", + "user@@example.com", + "unquoted [email protected]", + "user@example", + "[email protected]", + "[email protected]", +] diff --git a/tests/library/corpora/guid.toml b/tests/library/corpora/guid.toml new file mode 100644 index 0000000..fa5d7d9 --- /dev/null +++ b/tests/library/corpora/guid.toml @@ -0,0 +1,16 @@ +accepts = [ + "01234567-89ab-cdef-0123-456789abcdef", + "{01234567-89AB-CDEF-0123-456789ABCDEF}", + "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE", + "12345678-90ab-cdef-1234-567890abcdef", + "{aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee}", +] + +rejects = [ + "", + "not-a-guid", + "012345678-9ab-cdef-0123-456789abcdef", + "01234567-89ab-cdef-0123-456789abcde", + "01234567-89ab-cdef-0123-456789abcdefg", + "01234567-89abcdef-0123-456789abcdef", +] diff --git a/tests/library/corpora/ipv4.toml b/tests/library/corpora/ipv4.toml new file mode 100644 index 0000000..9883b48 --- /dev/null +++ b/tests/library/corpora/ipv4.toml @@ -0,0 +1,27 @@ +accepts = [ + "0.0.0.0", + "127.0.0.1", + "192.168.1.1", + "10.0.0.1", + "172.16.254.1", + "255.255.255.255", + "8.8.8.8", + "1.1.1.1", +] + +rejects = [ + "", + "256.0.0.1", + "300.1.1.1", + "1.2.3", + "1.2.3.4.5", + "1.2.3.4.", + ".1.2.3.4", + "01.2.3.4", + "1.2.3.04", + "1.2.3.4 ", + " 1.2.3.4", + "999.999.999.999", + "not.an.ip.addr", + "a.b.c.d", +] diff --git a/tests/library/corpora/ipv6.toml b/tests/library/corpora/ipv6.toml new file mode 100644 index 0000000..30f4908 --- /dev/null +++ b/tests/library/corpora/ipv6.toml @@ -0,0 +1,24 @@ +accepts = [ + "::", + "::1", + "2001:db8::", + "2001:db8::1", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "2001:db8:85a3::8a2e:370:7334", + "fe80::1", + "::ffff:192.0.2.1", + "2001:db8::192.0.2.1", + "fe80::1%eth0", +] + +rejects = [ + "", + "not an ipv6", + "192.0.2.1", + "2001:db8:::1", + "2001:db8::gggg", + "2001:db8::1::2", + "2001:db8:85a3:0000:0000:8a2e:0370:7334:extra", + ":", + "1:2:3:4:5:6:7", +] diff --git a/tests/library/corpora/iso_date.toml b/tests/library/corpora/iso_date.toml new file mode 100644 index 0000000..5f4b41e --- /dev/null +++ b/tests/library/corpora/iso_date.toml @@ -0,0 +1,21 @@ +accepts = [ + "2026-07-15", + "1999-01-01", + "2000-12-31", + "2026-02-30", + "0001-01-01", + "9999-12-31", +] + +rejects = [ + "", + "07/15/2026", + "2026-7-15", + "2026-07-5", + "26-07-15", + "2026-07-15T00:00:00", + "2026-07-15Z", + " 2026-07-15", + "2026-07-15 ", + "abcd-ef-gh", +] diff --git a/tests/library/corpora/mac.toml b/tests/library/corpora/mac.toml new file mode 100644 index 0000000..08bd777 --- /dev/null +++ b/tests/library/corpora/mac.toml @@ -0,0 +1,21 @@ +accepts = [ + "00:00:5e:00:53:af", + "00-00-5e-00-53-af", + "AA:BB:CC:DD:EE:FF", + "aa:bb:cc:dd:ee:ff", + "01:23:45:67:89:AB", + "de-ad-be-ef-00-01", +] + +rejects = [ + "", + "00:00:5e:00:53", + "00:00:5e:00:53:af:00", + "0:0:5e:0:53:af", + "gg:hh:ii:jj:kk:ll", + "00:00:5e:00:53:zz", + "00.00.5e.00.53.af", + "00 00 5e 00 53 af", + " 00:00:5e:00:53:af", + "00:00:5e:00:53:af ", +] diff --git a/tests/library/corpora/password.toml b/tests/library/corpora/password.toml new file mode 100644 index 0000000..3861a7b --- /dev/null +++ b/tests/library/corpora/password.toml @@ -0,0 +1,21 @@ +accepts = [ + "Password1!", + "S3cure#Pass", + "Abcdef1@", + "LongerPassword2$", + "T3sting.Password", + "Str0ng!Pass", +] + +rejects = [ + "", + "short", + "password", + "PASSWORD", + "Password", + "Password1", + "password1!", + "PASSWORD1!", + "12345678", + "!!!!!!!!", +] diff --git a/tests/library/corpora/phone.toml b/tests/library/corpora/phone.toml new file mode 100644 index 0000000..7abe2f7 --- /dev/null +++ b/tests/library/corpora/phone.toml @@ -0,0 +1,17 @@ +accepts = [ + "+1 555 123 4567", + "+44 20 7946 0958", + "555-123-4567", + "555.123.4567", + "+1-555-123-4567", + "5551234567", + "+81 3-1234-5678", +] + +rejects = [ + "", + "not a phone", + "abc-def-ghij", + " ", + "555--123--4567", +] diff --git a/tests/library/corpora/ssn.toml b/tests/library/corpora/ssn.toml new file mode 100644 index 0000000..81b0610 --- /dev/null +++ b/tests/library/corpora/ssn.toml @@ -0,0 +1,23 @@ +accepts = [ + "100-22-3333", + "555-44-6666", + "111-22-3333", + "213-45-6789", + "123-45-6789", +] + +rejects = [ + "", + "000-22-3333", + "666-22-3333", + "900-22-3333", + "999-22-3333", + "123-00-4567", + "123-45-0000", + "12-34-5678", + "123-4-5678", + "123-45-678", + "123 45 6789", + "1234-56-789", + "abc-de-fghi", +] diff --git a/tests/library/corpora/url.toml b/tests/library/corpora/url.toml new file mode 100644 index 0000000..4ac3b6c --- /dev/null +++ b/tests/library/corpora/url.toml @@ -0,0 +1,23 @@ +accepts = [ + "http://example.com", + "https://example.com", + "https://www.example.com", + "https://sub.example.com/path/to/resource", + "https://example.com/path?query=value&other=1", + "https://example.com:8080/path", + "http://example.co.uk", + "https://example.com/path#fragment", + "http://192.0.2.1", + "https://example.com/~user/", +] + +rejects = [ + "", + "not a url", + "example", + "://missing-scheme", + "http://", + "javascript:alert(1)", + " http://example.com", + "http://example .com", +] diff --git a/tests/library/corpora/uuid.toml b/tests/library/corpora/uuid.toml new file mode 100644 index 0000000..546ab8d --- /dev/null +++ b/tests/library/corpora/uuid.toml @@ -0,0 +1,22 @@ +accepts = [ + "01234567-89ab-1cde-8f01-23456789abcd", + "01234567-89ab-2cde-9f01-23456789abcd", + "01234567-89ab-4cde-8f01-23456789abcd", + "01234567-89ab-5cde-9f01-23456789abcd", + "550e8400-e29b-41d4-a716-446655440000", + "6ba7b810-9dad-11d1-80b4-00c04fd430c8", + "0f8fad5b-d9cb-469f-a165-70867728950e", +] + +rejects = [ + "", + "not-a-uuid", + "01234567-89ab-6cde-8f01-23456789abcd", + "01234567-89ab-1cde-cf01-23456789abcd", + "0123456789ab1cde8f0123456789abcd", + "01234567-89ab-1cde-8f01-23456789abc", + "01234567-89ab-1cde-8f01-23456789abcde", + "01234567-89ab-1cde-8f01-23456789abcg", + " 01234567-89ab-1cde-8f01-23456789abcd", + "01234567-89ab-1cde-8f01-23456789abcd ", +] diff --git a/tests/library/corpora/zip_code.toml b/tests/library/corpora/zip_code.toml new file mode 100644 index 0000000..3320840 --- /dev/null +++ b/tests/library/corpora/zip_code.toml @@ -0,0 +1,21 @@ +accepts = [ + "00000", + "12345", + "99999", + "12345-6789", + "90210-1234", + "00501-0001", +] + +rejects = [ + "", + "1234", + "123456", + "12345-", + "12345-67", + "12345-67890", + "abcde", + "12345 6789", + " 12345", + "12345 ", +] diff --git a/tests/library/hardening.test.py b/tests/library/hardening.test.py new file mode 100644 index 0000000..0c8305a --- /dev/null +++ b/tests/library/hardening.test.py @@ -0,0 +1,57 @@ +"""Real-world corpus hardening tests for every hand-picked library validator. + +Each corpus file under ``tests/library/corpora/<name>.toml`` lists ``accepts`` +and ``rejects`` — strings the named validator must accept or reject exactly. +Adding a new validator only means dropping a new TOML file next to the others; +the parametrization discovers it at collection time. +""" + +from __future__ import annotations + +import tomllib +from pathlib import Path + +import pytest + +import edify.library as library_module +from edify import Pattern + +_CORPUS_ROOT = Path(__file__).parent / "corpora" + + +def _corpus_cases(): + for corpus_path in sorted(_CORPUS_ROOT.glob("*.toml")): + validator_name = corpus_path.stem + validator = getattr(library_module, validator_name) + if not isinstance(validator, Pattern): + continue + parsed = tomllib.loads(corpus_path.read_text()) + for accept_input in parsed.get("accepts", []): + yield validator_name, validator, "accept", accept_input + for reject_input in parsed.get("rejects", []): + yield validator_name, validator, "reject", reject_input + + +_CASES = list(_corpus_cases()) + + + ("validator_name", "validator", "expected_verdict", "input_string"), + _CASES, + ids=[ + f"{name}-{verdict}-{index}" + for index, (name, _validator, verdict, _input_string) in enumerate(_CASES) + ], +) +def test_library_validator_matches_the_committed_corpus( + validator_name, validator, expected_verdict, input_string +): + observed = validator(input_string) + if expected_verdict == "accept": + assert observed is True, ( + f"validator {validator_name!r} rejected {input_string!r} but corpus expects accept" + ) + else: + assert observed is False, ( + f"validator {validator_name!r} accepted {input_string!r} but corpus expects reject" + ) diff --git a/tests/snapshots/docs/built-in/index/0028.regex b/tests/snapshots/docs/built-in/index/0028.regex Binary files differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/0028.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/0034.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/0064.regex +++ 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 differindex bfea61d..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/0115.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/0128.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/0160.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/0199.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/0957.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/0983.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/1001.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/built-in/index/1017.regex +++ 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 differindex 3a6abd5..e69de29 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0019.regex +++ 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 differindex fd99ebb..2da4cc7 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0044.regex +++ 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 differindex 8f6a9c7..ce62c83 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0059.regex +++ 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 differindex f8428de..102648a 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0080.regex +++ 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 differindex 1829d24..e1fa18e 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0103.regex +++ 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 differindex 9cd58c7..4506fef 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0120.regex +++ 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 differindex df37fb5..c88ce12 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0139.regex +++ 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 differindex b460d45..0c6af77 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0156.regex +++ 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 differindex 0c33843..c15c7e5 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0175.regex +++ 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 differindex 9186a50..f7ac50f 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0189.regex +++ 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 differindex 3734389..58060dc 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0201.regex +++ 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 differindex 75e1c54..3de05f2 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0216.regex +++ 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 differindex 495a4a2..b2daf0f 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0232.regex +++ 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 differindex d51fcf6..eb99a63 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0248.regex +++ 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 differindex d62f468..48048d5 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0265.regex +++ 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 differindex a7833cd..b8c3cf2 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0293.regex +++ 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 differindex 7b80739..f732ed4 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0318.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0347.regex +++ 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 differindex c8c8e22..f10e97e 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0371.regex +++ 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 differindex d16376a..c130394 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0421.regex +++ 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 differindex 3a62450..cb4c442 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0445.regex +++ 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 differindex 7d9fa46..37ed6e9 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0469.regex +++ 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 differindex 470123c..657ffbd 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0493.regex +++ 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 differindex ad1e9ea..ae0718c 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0514.regex +++ 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 differindex 8b71c9d..265763b 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0535.regex +++ 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 differindex 7098f9c..3f1635a 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0554.regex +++ 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 differindex b77a507..3fc175f 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0573.regex +++ 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 differindex 29e3ccc..5888bc7 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0592.regex +++ 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 differindex d115de0..5c873e9 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0611.regex +++ 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 differindex 14e843f..b37ced0 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0630.regex +++ 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 differindex 5dbd654..7c6294d 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0649.regex +++ 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 differindex 1526551..3f444ff 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0668.regex +++ 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 differindex 51fc73e..0be6e37 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0687.regex +++ 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 differindex 5a1cd83..3d6b76b 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0706.regex +++ 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 differindex ffec79c..fe1cd7f 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0725.regex +++ 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 differindex 9321e99..d878e68 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0744.regex +++ 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 differindex 3c348f5..9cc8684 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0762.regex +++ 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 differindex b76ca12..d9e3092 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0780.regex +++ 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 differindex 09c76e6..f0ed1dd 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0798.regex +++ 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 differindex 2404b1d..d1882c9 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0816.regex +++ 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 differindex cd4745e..43c0cb8 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0834.regex +++ 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 differindex cd83a68..8c19bb1 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0852.regex +++ 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 differindex 57c8680..a3f699e 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0892.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0913.regex +++ 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 differindex a75fda3..4d14633 100644 --- a/tests/snapshots/docs/regex-builder/builder/index/0942.regex +++ 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 differindex 2404b1d..d1882c9 100644 --- a/tests/snapshots/docs/regex-builder/flags/index/0026.regex +++ 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 differindex 2404b1d..d1882c9 100644 --- a/tests/snapshots/docs/regex-builder/flags/index/0038.regex +++ 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 differindex 2404b1d..d1882c9 100644 --- a/tests/snapshots/docs/regex-builder/flags/index/0051.regex +++ 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 differindex 2404b1d..d1882c9 100644 --- a/tests/snapshots/docs/regex-builder/flags/index/0065.regex +++ 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 differindex 2404b1d..d1882c9 100644 --- a/tests/snapshots/docs/regex-builder/flags/index/0080.regex +++ 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 differindex 2404b1d..d1882c9 100644 --- a/tests/snapshots/docs/regex-builder/flags/index/0094.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/upgrading/0.3-to-1.0/0018.regex +++ 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 differindex 4ba98bc..e69de29 100644 --- a/tests/snapshots/docs/upgrading/0.3-to-1.0/0037.regex +++ b/tests/snapshots/docs/upgrading/0.3-to-1.0/0037.regex diff --git a/tests/snapshots/library/email_rfc_5322.regex b/tests/snapshots/library/email_rfc_5322.regex new file mode 100644 index 0000000..6557f12 --- /dev/null +++ b/tests/snapshots/library/email_rfc_5322.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])?|\[(?:(?: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/ipv4.regex b/tests/snapshots/library/ipv4.regex new file mode 100644 index 0000000..13e689f --- /dev/null +++ b/tests/snapshots/library/ipv4.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}$
\ No newline at end of file diff --git a/tests/snapshots/library/ipv6.regex b/tests/snapshots/library/ipv6.regex new file mode 100644 index 0000000..a1636ac --- /dev/null +++ b/tests/snapshots/library/ipv6.regex @@ -0,0 +1 @@ +^(?:(?:(?:[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/iso_date.regex b/tests/snapshots/library/iso_date.regex new file mode 100644 index 0000000..85341c2 --- /dev/null +++ b/tests/snapshots/library/iso_date.regex @@ -0,0 +1 @@ +^\d{4}\-\d{2}\-\d{2}$
\ No newline at end of file diff --git a/tests/snapshots/library/zip_code.regex b/tests/snapshots/library/zip_code.regex new file mode 100644 index 0000000..84a7b93 --- /dev/null +++ b/tests/snapshots/library/zip_code.regex @@ -0,0 +1 @@ +^\d{5}(?:\-\d{4})?$
\ No newline at end of file |
