aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornatsuoto <[email protected]>2026-07-11 16:53:44 +0530
committernatsuoto <[email protected]>2026-07-11 16:53:44 +0530
commitef0d136cb9c6517c39bf0d693663034791eb0508 (patch)
tree0ad5e77fe456b4a52349e9858d31acdaf17cf2dd
parentdf4322c63b2937f803d1f35705f73db200b6dec7 (diff)
downloadedify-ef0d136cb9c6517c39bf0d693663034791eb0508.tar.xz
edify-ef0d136cb9c6517c39bf0d693663034791eb0508.zip
refactor(library): rewrite software/ validators to fluent Pattern() chain
-rw-r--r--edify/library/software/cargo.py43
-rw-r--r--edify/library/software/checksum.py16
-rw-r--r--edify/library/software/component.py59
-rw-r--r--edify/library/software/digest.py26
-rw-r--r--edify/library/software/docker.py94
-rw-r--r--edify/library/software/git.py15
-rw-r--r--edify/library/software/image.py94
-rw-r--r--edify/library/software/makefile.py42
-rw-r--r--edify/library/software/package.py37
-rw-r--r--edify/library/software/ref.py71
-rw-r--r--edify/library/software/semver.py90
-rw-r--r--edify/library/software/version.py32
12 files changed, 562 insertions, 57 deletions
diff --git a/edify/library/software/cargo.py b/edify/library/software/cargo.py
index ebaa269..c64e587 100644
--- a/edify/library/software/cargo.py
+++ b/edify/library/software/cargo.py
@@ -1,10 +1,45 @@
-"""``cargo`` — Rust Cargo crate identifier shape (``name`` or ``name@version``)."""
+"""``cargo`` — Cargo crate identifier shape."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-cargo = RegexBackedPattern(
- r"^[a-zA-Z][a-zA-Z0-9_-]{0,63}(?:@\d+(?:\.\d+){0,3}(?:[-.+][a-zA-Z0-9.\-]+)?)?$"
+cargo = (
+ Pattern()
+ .start_of_input()
+ .letter()
+ .between(0, 63)
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char("_")
+ .char("-")
+ .end()
+ .optional()
+ .group()
+ .char("@")
+ .one_or_more()
+ .digit()
+ .between(0, 3)
+ .group()
+ .char(".")
+ .one_or_more()
+ .digit()
+ .end()
+ .optional()
+ .group()
+ .any_of_chars("-.+")
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char(".")
+ .char("-")
+ .end()
+ .end()
+ .end()
+ .end_of_input()
)
"""Callable :class:`Pattern` for a Cargo crate identifier."""
diff --git a/edify/library/software/checksum.py b/edify/library/software/checksum.py
index ed0cfad..aaa6de2 100644
--- a/edify/library/software/checksum.py
+++ b/edify/library/software/checksum.py
@@ -1,8 +1,18 @@
-"""``checksum`` — hex checksum shape (CRC through SHA)."""
+"""``checksum`` — hex checksum shape (any common hash width)."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-checksum = RegexBackedPattern(r"^[a-fA-F0-9]{8,128}$")
+checksum = (
+ Pattern()
+ .start_of_input()
+ .between(8, 128)
+ .any_of()
+ .range("a", "f")
+ .range("A", "F")
+ .range("0", "9")
+ .end()
+ .end_of_input()
+)
"""Callable :class:`Pattern` for a hex checksum (any common hash width)."""
diff --git a/edify/library/software/component.py b/edify/library/software/component.py
index e1227b9..ffaff4e 100644
--- a/edify/library/software/component.py
+++ b/edify/library/software/component.py
@@ -1,11 +1,60 @@
-"""``component`` — versioned software-component identifier (``name@version``)."""
+"""``component`` — versioned component identifier ``name@version`` shape."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-component = RegexBackedPattern(
- r"^(?:@[a-z0-9][a-z0-9-]*/)?[a-z0-9][a-z0-9._-]{0,213}"
- r"@\d+(?:\.\d+){0,3}(?:[-.+][a-zA-Z0-9.\-]+)?$"
+component = (
+ Pattern()
+ .start_of_input()
+ .optional()
+ .group()
+ .char("@")
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .zero_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .char("-")
+ .end()
+ .char("/")
+ .end()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .between(0, 213)
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .char(".")
+ .char("_")
+ .char("-")
+ .end()
+ .char("@")
+ .one_or_more()
+ .digit()
+ .between(0, 3)
+ .group()
+ .char(".")
+ .one_or_more()
+ .digit()
+ .end()
+ .optional()
+ .group()
+ .any_of_chars("-.+")
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char(".")
+ .char("-")
+ .end()
+ .end()
+ .end_of_input()
)
"""Callable :class:`Pattern` for a versioned component identifier ``name@version``."""
diff --git a/edify/library/software/digest.py b/edify/library/software/digest.py
index 66c895f..69f3d65 100644
--- a/edify/library/software/digest.py
+++ b/edify/library/software/digest.py
@@ -1,8 +1,28 @@
-"""``digest`` — content-addressable digest shape (``algorithm:hex``)."""
+"""``digest`` — content-addressable digest shape."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-digest = RegexBackedPattern(r"^(?:sha256|sha512|sha1|md5|blake2[bs]?)(?::|-)[a-fA-F0-9]{32,128}$")
+digest = (
+ Pattern()
+ .start_of_input()
+ .group()
+ .any_of()
+ .string("sha256")
+ .string("sha512")
+ .string("sha1")
+ .string("md5")
+ .subexpression(Pattern().string("blake2").optional().any_of_chars("bs"))
+ .end()
+ .end()
+ .any_of_chars(":-")
+ .between(32, 128)
+ .any_of()
+ .range("a", "f")
+ .range("A", "F")
+ .range("0", "9")
+ .end()
+ .end_of_input()
+)
"""Callable :class:`Pattern` for a content-addressable digest."""
diff --git a/edify/library/software/docker.py b/edify/library/software/docker.py
index 2b9d6f3..f76ad59 100644
--- a/edify/library/software/docker.py
+++ b/edify/library/software/docker.py
@@ -1,13 +1,91 @@
-"""``docker`` — Docker image reference shape."""
+"""``docker`` — container image reference shape."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-docker = RegexBackedPattern(
- r"^(?:(?:[a-z0-9.\-]+(?::\d+)?/)?[a-z0-9]+(?:[._\-][a-z0-9]+)*)"
- r"(?:/[a-z0-9]+(?:[._\-][a-z0-9]+)*)*"
- r"(?::[a-zA-Z0-9_][a-zA-Z0-9._\-]{0,127})?"
- r"(?:@sha256:[a-f0-9]{64})?$"
+docker = (
+ Pattern()
+ .start_of_input()
+ .group()
+ .optional()
+ .group()
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .char(".")
+ .char("-")
+ .end()
+ .optional()
+ .group()
+ .char(":")
+ .one_or_more()
+ .digit()
+ .end()
+ .char("/")
+ .end()
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .zero_or_more()
+ .group()
+ .any_of_chars("._-")
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .end()
+ .end()
+ .zero_or_more()
+ .group()
+ .char("/")
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .zero_or_more()
+ .group()
+ .any_of_chars("._-")
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .end()
+ .end()
+ .optional()
+ .group()
+ .char(":")
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char("_")
+ .end()
+ .between(0, 127)
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char(".")
+ .char("_")
+ .char("-")
+ .end()
+ .end()
+ .optional()
+ .group()
+ .string("@sha256:")
+ .exactly(64)
+ .any_of()
+ .range("a", "f")
+ .range("0", "9")
+ .end()
+ .end()
+ .end_of_input()
)
-"""Callable :class:`Pattern` for a Docker image reference."""
+"""Callable :class:`Pattern` for a docker image reference."""
diff --git a/edify/library/software/git.py b/edify/library/software/git.py
index 17d75d3..67a580d 100644
--- a/edify/library/software/git.py
+++ b/edify/library/software/git.py
@@ -1,8 +1,17 @@
-"""``git`` — 40-character git SHA-1 hash shape."""
+"""``git`` — git commit SHA shape (7-40 hex characters)."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-git = RegexBackedPattern(r"^[a-f0-9]{7,40}$")
+git = (
+ Pattern()
+ .start_of_input()
+ .between(7, 40)
+ .any_of()
+ .range("a", "f")
+ .range("0", "9")
+ .end()
+ .end_of_input()
+)
"""Callable :class:`Pattern` for a git commit SHA (7-40 hex characters)."""
diff --git a/edify/library/software/image.py b/edify/library/software/image.py
index 7708774..9ac7471 100644
--- a/edify/library/software/image.py
+++ b/edify/library/software/image.py
@@ -1,13 +1,91 @@
-"""``image`` — container-image reference (alias-friendly wrapper around docker)."""
+"""``image`` — container image reference shape."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-image = RegexBackedPattern(
- r"^(?:(?:[a-z0-9.\-]+(?::\d+)?/)?[a-z0-9]+(?:[._\-][a-z0-9]+)*)"
- r"(?:/[a-z0-9]+(?:[._\-][a-z0-9]+)*)*"
- r"(?::[a-zA-Z0-9_][a-zA-Z0-9._\-]{0,127})?"
- r"(?:@sha256:[a-f0-9]{64})?$"
+image = (
+ Pattern()
+ .start_of_input()
+ .group()
+ .optional()
+ .group()
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .char(".")
+ .char("-")
+ .end()
+ .optional()
+ .group()
+ .char(":")
+ .one_or_more()
+ .digit()
+ .end()
+ .char("/")
+ .end()
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .zero_or_more()
+ .group()
+ .any_of_chars("._-")
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .end()
+ .end()
+ .zero_or_more()
+ .group()
+ .char("/")
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .zero_or_more()
+ .group()
+ .any_of_chars("._-")
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .end()
+ .end()
+ .optional()
+ .group()
+ .char(":")
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char("_")
+ .end()
+ .between(0, 127)
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char(".")
+ .char("_")
+ .char("-")
+ .end()
+ .end()
+ .optional()
+ .group()
+ .string("@sha256:")
+ .exactly(64)
+ .any_of()
+ .range("a", "f")
+ .range("0", "9")
+ .end()
+ .end()
+ .end_of_input()
)
-"""Callable :class:`Pattern` for a container image reference (same as ``docker``)."""
+"""Callable :class:`Pattern` for a image image reference."""
diff --git a/edify/library/software/makefile.py b/edify/library/software/makefile.py
index 20e8e06..eecf316 100644
--- a/edify/library/software/makefile.py
+++ b/edify/library/software/makefile.py
@@ -1,8 +1,44 @@
-"""``makefile`` — Makefile-target line shape (``target: [deps]``)."""
+"""``makefile`` — Makefile-target declaration line shape."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-makefile = RegexBackedPattern(r"^\.?[a-zA-Z][a-zA-Z0-9._-]*(?:\s+[a-zA-Z][a-zA-Z0-9._-]*)*\s*:.*$")
+makefile = (
+ Pattern()
+ .start_of_input()
+ .optional()
+ .char(".")
+ .letter()
+ .zero_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char(".")
+ .char("_")
+ .char("-")
+ .end()
+ .zero_or_more()
+ .group()
+ .one_or_more()
+ .whitespace_char()
+ .letter()
+ .zero_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char(".")
+ .char("_")
+ .char("-")
+ .end()
+ .end()
+ .zero_or_more()
+ .whitespace_char()
+ .char(":")
+ .zero_or_more()
+ .any_char()
+ .end_of_input()
+)
"""Callable :class:`Pattern` for a Makefile-target declaration line."""
diff --git a/edify/library/software/package.py b/edify/library/software/package.py
index 002688a..ec69af4 100644
--- a/edify/library/software/package.py
+++ b/edify/library/software/package.py
@@ -1,8 +1,39 @@
-"""``package`` — package identifier shape (``@scope/name`` or ``name``)."""
+"""``package`` — npm/pypi-style package identifier shape."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-package = RegexBackedPattern(r"^(?:@[a-z0-9][a-z0-9-]*/)?[a-z0-9][a-z0-9._-]{0,213}$")
+package = (
+ Pattern()
+ .start_of_input()
+ .optional()
+ .group()
+ .char("@")
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .zero_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .char("-")
+ .end()
+ .char("/")
+ .end()
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .end()
+ .between(0, 213)
+ .any_of()
+ .range("a", "z")
+ .range("0", "9")
+ .char(".")
+ .char("_")
+ .char("-")
+ .end()
+ .end_of_input()
+)
"""Callable :class:`Pattern` for an npm/pypi-style package identifier."""
diff --git a/edify/library/software/ref.py b/edify/library/software/ref.py
index 7a78148..ba9d253 100644
--- a/edify/library/software/ref.py
+++ b/edify/library/software/ref.py
@@ -1,14 +1,69 @@
-"""``ref`` — git ref shape (branch, tag, or SHA)."""
+"""``ref`` — git ref shape (SHA, ``refs/…``, or bare branch/tag name)."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern, any_of
-ref = RegexBackedPattern(
- r"^(?:"
- r"[a-f0-9]{7,40}"
- r"|refs/(?:heads|tags|remotes)/[^\s~^:?*[\\]+"
- r"|[^\s~^:?*[\\/][^\s~^:?*[\\]{0,127}"
- r")$"
+
+def _forbidden() -> Pattern:
+ return (
+ Pattern()
+ .assert_not_ahead()
+ .any_of()
+ .whitespace_char()
+ .any_of_chars("~^:?*[\\")
+ .end()
+ .end()
+ .any_char()
+ )
+
+
+def _forbidden_incl_slash() -> Pattern:
+ return (
+ Pattern()
+ .assert_not_ahead()
+ .any_of()
+ .whitespace_char()
+ .any_of_chars("~^:?*[\\/")
+ .end()
+ .end()
+ .any_char()
+ )
+
+
+_sha = (
+ Pattern()
+ .between(7, 40)
+ .any_of()
+ .range("a", "f")
+ .range("0", "9")
+ .end()
+)
+_refs = (
+ Pattern()
+ .string("refs/")
+ .group()
+ .any_of()
+ .string("heads")
+ .string("tags")
+ .string("remotes")
+ .end()
+ .end()
+ .char("/")
+ .one_or_more()
+ .subexpression(_forbidden())
+)
+_bare = (
+ Pattern()
+ .subexpression(_forbidden_incl_slash())
+ .between(0, 127)
+ .subexpression(_forbidden())
+)
+
+ref = (
+ Pattern()
+ .start_of_input()
+ .subexpression(any_of(_sha, _refs, _bare))
+ .end_of_input()
)
"""Callable :class:`Pattern` for a git ref: SHA, ``refs/heads/…``, or bare branch/tag name."""
diff --git a/edify/library/software/semver.py b/edify/library/software/semver.py
index 560c984..3f9aeb0 100644
--- a/edify/library/software/semver.py
+++ b/edify/library/software/semver.py
@@ -2,12 +2,90 @@
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern, any_of
-semver = RegexBackedPattern(
- r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)"
- r"(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
- r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
- r"(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
+
+def _positive() -> Pattern:
+ return any_of(
+ Pattern().char("0"),
+ Pattern().range("1", "9").zero_or_more().digit(),
+ )
+
+
+def _pre_id() -> Pattern:
+ return any_of(
+ Pattern().char("0"),
+ Pattern().range("1", "9").zero_or_more().digit(),
+ (
+ Pattern()
+ .zero_or_more()
+ .digit()
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .char("-")
+ .end()
+ .zero_or_more()
+ .any_of()
+ .range("0", "9")
+ .range("a", "z")
+ .range("A", "Z")
+ .char("-")
+ .end()
+ ),
+ )
+
+
+semver = (
+ Pattern()
+ .start_of_input()
+ .named_capture("major")
+ .subexpression(_positive())
+ .end()
+ .char(".")
+ .named_capture("minor")
+ .subexpression(_positive())
+ .end()
+ .char(".")
+ .named_capture("patch")
+ .subexpression(_positive())
+ .end()
+ .optional()
+ .group()
+ .char("-")
+ .named_capture("prerelease")
+ .subexpression(_pre_id())
+ .zero_or_more()
+ .group()
+ .char(".")
+ .subexpression(_pre_id())
+ .end()
+ .end()
+ .end()
+ .optional()
+ .group()
+ .char("+")
+ .named_capture("buildmetadata")
+ .one_or_more()
+ .any_of()
+ .range("0", "9")
+ .range("a", "z")
+ .range("A", "Z")
+ .char("-")
+ .end()
+ .zero_or_more()
+ .group()
+ .char(".")
+ .one_or_more()
+ .any_of()
+ .range("0", "9")
+ .range("a", "z")
+ .range("A", "Z")
+ .char("-")
+ .end()
+ .end()
+ .end()
+ .end()
+ .end_of_input()
)
"""Callable :class:`Pattern` for SemVer 2.0.0 versions."""
diff --git a/edify/library/software/version.py b/edify/library/software/version.py
index 59ac6be..0f34438 100644
--- a/edify/library/software/version.py
+++ b/edify/library/software/version.py
@@ -1,8 +1,34 @@
-"""``version`` — permissive version-string shape."""
+"""``version`` — permissive dotted version string shape."""
from __future__ import annotations
-from edify.library._support.regex import RegexBackedPattern
+from edify import Pattern
-version = RegexBackedPattern(r"^v?\d+(?:\.\d+){0,3}(?:[-.+][a-zA-Z0-9.\-]+)?$")
+version = (
+ Pattern()
+ .start_of_input()
+ .optional()
+ .char("v")
+ .one_or_more()
+ .digit()
+ .between(0, 3)
+ .group()
+ .char(".")
+ .one_or_more()
+ .digit()
+ .end()
+ .optional()
+ .group()
+ .any_of_chars("-.+")
+ .one_or_more()
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char(".")
+ .char("-")
+ .end()
+ .end()
+ .end_of_input()
+)
"""Callable :class:`Pattern` for a permissive dotted version string."""