aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornatsuoto <[email protected]>2026-07-11 11:19:20 +0530
committernatsuoto <[email protected]>2026-07-11 11:19:20 +0530
commitace5f7c2a92f3fb8ba80ff06d7a543182919095f (patch)
treef2dd0c96e3238b82430a6cb7b795d0c093744168
parent3a3fe09ddad3ad9649f0990de33ffeaeca46413c (diff)
downloadedify-ace5f7c2a92f3fb8ba80ff06d7a543182919095f.tar.xz
edify-ace5f7c2a92f3fb8ba80ff06d7a543182919095f.zip
feat(library): infrastructure and first three identifier migrations (uuid, guid, mac)
-rw-r--r--edify/library/_support/atoms.py62
-rw-r--r--edify/library/_support/coerce.py20
-rw-r--r--edify/library/identifier/__init__.py0
-rw-r--r--edify/library/identifier/guid.py34
-rw-r--r--edify/library/identifier/mac.py29
-rw-r--r--edify/library/identifier/uuid.py36
6 files changed, 181 insertions, 0 deletions
diff --git a/edify/library/_support/atoms.py b/edify/library/_support/atoms.py
new file mode 100644
index 0000000..8eab9e3
--- /dev/null
+++ b/edify/library/_support/atoms.py
@@ -0,0 +1,62 @@
+"""Private composable atoms shared by the library validators.
+
+Every atom is a :class:`Pattern` fragment that renders exactly what the
+comment beside it says; nothing here is exported publicly, but every entry
+is imported freely by the exported validators to keep their fluent chains
+readable.
+"""
+
+from __future__ import annotations
+
+from edify import RegexBuilder, any_of, char, range_of
+from edify.library._support.coerce import as_pattern
+
+
+def _hex_lower_atom() -> object:
+ return as_pattern(RegexBuilder().any_of().range("0", "9").range("a", "f").end())
+
+
+def _hex_upper_atom() -> object:
+ return as_pattern(RegexBuilder().any_of().range("0", "9").range("A", "F").end())
+
+
+def _hex_any_atom() -> object:
+ chain = (
+ RegexBuilder().any_of().range("0", "9").range("a", "f").range("A", "F").end()
+ )
+ return as_pattern(chain)
+
+
+def _digit_atom() -> object:
+ return as_pattern(RegexBuilder().digit())
+
+
+def _octet_atom() -> object:
+ branch_250_255 = RegexBuilder().string("25").range("0", "5")
+ branch_200_249 = RegexBuilder().char("2").range("0", "4").digit()
+ branch_100_199 = RegexBuilder().char("1").digit().digit()
+ branch_10_99 = RegexBuilder().range("1", "9").digit()
+ branch_0_9 = RegexBuilder().digit()
+ return any_of(
+ as_pattern(branch_250_255),
+ as_pattern(branch_200_249),
+ as_pattern(branch_100_199),
+ as_pattern(branch_10_99),
+ as_pattern(branch_0_9),
+ )
+
+
+hex_lower = _hex_lower_atom()
+"""A single lowercase hex nibble: ``[0-9a-f]``."""
+
+hex_upper = _hex_upper_atom()
+"""A single uppercase hex nibble: ``[0-9A-F]``."""
+
+hex_any = _hex_any_atom()
+"""A single mixed-case hex nibble: ``[0-9a-fA-F]``."""
+
+digit_atom = _digit_atom()
+"""A single decimal digit: ``\\d``."""
+
+octet = _octet_atom()
+"""An IPv4 octet in ``0``-``255``.""" \ No newline at end of file
diff --git a/edify/library/_support/coerce.py b/edify/library/_support/coerce.py
new file mode 100644
index 0000000..b77ec29
--- /dev/null
+++ b/edify/library/_support/coerce.py
@@ -0,0 +1,20 @@
+"""Helper to freeze a :class:`RegexBuilder` chain into an exported :class:`Pattern`.
+
+The library validators build their regex via ``RegexBuilder()`` chains at
+import time and expose the result at module level as a :class:`Pattern`
+instance so ``isinstance(uuid, Pattern) is True`` and ``uuid(value)`` works.
+This helper copies the immutable builder state into a fresh :class:`Pattern`
+instance without recompiling or reparsing the emitted regex.
+"""
+
+from __future__ import annotations
+
+from edify.builder.core import BuilderCore
+from edify.pattern.composition import Pattern
+
+
+def as_pattern(builder: BuilderCore) -> Pattern:
+ """Return a fresh :class:`Pattern` carrying ``builder``'s immutable state."""
+ pattern_instance = Pattern()
+ pattern_instance._state = builder._state
+ return pattern_instance \ No newline at end of file
diff --git a/edify/library/identifier/__init__.py b/edify/library/identifier/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/edify/library/identifier/__init__.py
diff --git a/edify/library/identifier/guid.py b/edify/library/identifier/guid.py
new file mode 100644
index 0000000..b17d2f4
--- /dev/null
+++ b/edify/library/identifier/guid.py
@@ -0,0 +1,34 @@
+"""``guid`` — Microsoft-flavour GUID 8-4-4-4-12 hex shape (callable :class:`Pattern`)."""
+
+from __future__ import annotations
+
+from edify import RegexBuilder
+from edify.library._support.coerce import as_pattern
+
+
+def _build() -> object:
+ chain = (
+ RegexBuilder()
+ .start_of_input()
+ .optional().char("{")
+ .exactly(8).any_of().range("0", "9").range("a", "f").range("A", "F").end()
+ .char("-")
+ .exactly(4).any_of().range("0", "9").range("a", "f").range("A", "F").end()
+ .char("-")
+ .exactly(4).any_of().range("0", "9").range("a", "f").range("A", "F").end()
+ .char("-")
+ .exactly(4).any_of().range("0", "9").range("a", "f").range("A", "F").end()
+ .char("-")
+ .exactly(12).any_of().range("0", "9").range("a", "f").range("A", "F").end()
+ .optional().char("}")
+ .end_of_input()
+ )
+ return as_pattern(chain)
+
+
+guid = _build()
+"""Callable :class:`Pattern` that validates the Microsoft-flavour GUID shape:
+the 8-4-4-4-12 hex form (either case) optionally wrapped in braces.
+"""
+
+del _build
diff --git a/edify/library/identifier/mac.py b/edify/library/identifier/mac.py
new file mode 100644
index 0000000..0a1e270
--- /dev/null
+++ b/edify/library/identifier/mac.py
@@ -0,0 +1,29 @@
+"""``mac`` — IEEE 802 MAC-address 6-octet hex shape (callable :class:`Pattern`)."""
+
+from __future__ import annotations
+
+from edify import RegexBuilder
+from edify.library._support.coerce import as_pattern
+
+
+def _build() -> object:
+ chain = (
+ RegexBuilder()
+ .start_of_input()
+ .exactly(5)
+ .group()
+ .exactly(2).any_of().range("0", "9").range("a", "f").range("A", "F").end()
+ .any_of_chars(":-")
+ .end()
+ .exactly(2).any_of().range("0", "9").range("a", "f").range("A", "F").end()
+ .end_of_input()
+ )
+ return as_pattern(chain)
+
+
+mac = _build()
+"""Callable :class:`Pattern` that validates the IEEE 802 MAC-address form:
+six ``:``- or ``-``-separated hex octets (either case).
+"""
+
+del _build
diff --git a/edify/library/identifier/uuid.py b/edify/library/identifier/uuid.py
new file mode 100644
index 0000000..09774d9
--- /dev/null
+++ b/edify/library/identifier/uuid.py
@@ -0,0 +1,36 @@
+"""``uuid`` — canonical UUID 8-4-4-4-12 hex shape (callable :class:`Pattern`)."""
+
+from __future__ import annotations
+
+from edify import RegexBuilder
+from edify.library._support.atoms import hex_lower
+from edify.library._support.coerce import as_pattern
+
+
+def _build() -> object:
+ chain = (
+ RegexBuilder()
+ .start_of_input()
+ .exactly(8).any_of().range("0", "9").range("a", "f").end()
+ .char("-")
+ .exactly(4).any_of().range("0", "9").range("a", "f").end()
+ .char("-")
+ .range("0", "5")
+ .exactly(3).any_of().range("0", "9").range("a", "f").end()
+ .char("-")
+ .any_of_chars("089ab")
+ .exactly(3).any_of().range("0", "9").range("a", "f").end()
+ .char("-")
+ .exactly(12).any_of().range("0", "9").range("a", "f").end()
+ .end_of_input()
+ )
+ return as_pattern(chain)
+
+
+uuid = _build()
+"""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``.
+"""
+
+del _build, hex_lower