aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornatsuoto <[email protected]>2026-07-11 11:39:27 +0530
committernatsuoto <[email protected]>2026-07-11 11:39:27 +0530
commitb40f35842497df6ba301cb17738b6b571867cb30 (patch)
treea88e2c852f0cc92ca3929f5fd6ae30d11ae2ce20
parent45d86cbf87a50a93cf38a0af10782ab7c0f628e4 (diff)
downloadedify-b40f35842497df6ba301cb17738b6b571867cb30.tar.xz
edify-b40f35842497df6ba301cb17738b6b571867cb30.zip
feat(library/address): 13 address patterns (ip, cidr, subnet, hostname, domain, subdomain, tld, url, uri, path, port, socket, ptr)
-rw-r--r--edify/library/address/__init__.py29
-rw-r--r--edify/library/address/cidr.py18
-rw-r--r--edify/library/address/domain.py13
-rw-r--r--edify/library/address/hostname.py12
-rw-r--r--edify/library/address/ip.py32
-rw-r--r--edify/library/address/path.py16
-rw-r--r--edify/library/address/port.py16
-rw-r--r--edify/library/address/ptr.py15
-rw-r--r--edify/library/address/socket.py18
-rw-r--r--edify/library/address/subdomain.py17
-rw-r--r--edify/library/address/subnet.py15
-rw-r--r--edify/library/address/tld.py13
-rw-r--r--edify/library/address/uri.py12
-rw-r--r--edify/library/address/url.py16
14 files changed, 242 insertions, 0 deletions
diff --git a/edify/library/address/__init__.py b/edify/library/address/__init__.py
new file mode 100644
index 0000000..13a934d
--- /dev/null
+++ b/edify/library/address/__init__.py
@@ -0,0 +1,29 @@
+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.path import path
+from edify.library.address.port import port
+from edify.library.address.ptr import ptr
+from edify.library.address.socket import socket
+from edify.library.address.subdomain import subdomain
+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
+
+__all__ = [
+ "cidr",
+ "domain",
+ "hostname",
+ "ip",
+ "path",
+ "port",
+ "ptr",
+ "socket",
+ "subdomain",
+ "subnet",
+ "tld",
+ "uri",
+ "url",
+]
diff --git a/edify/library/address/cidr.py b/edify/library/address/cidr.py
new file mode 100644
index 0000000..d361396
--- /dev/null
+++ b/edify/library/address/cidr.py
@@ -0,0 +1,18 @@
+"""``cidr`` — CIDR-notation subnet ``address/prefix`` shape."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+cidr = RegexBackedPattern(
+ r"^(?:"
+ r"(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)"
+ r"(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}"
+ r"/(?:3[0-2]|[12]?\d)"
+ r"|(?:[0-9a-fA-F]{1,4}:){0,7}[0-9a-fA-F]{1,4}"
+ r"/(?:12[0-8]|1[01]\d|[1-9]?\d)"
+ r")$"
+)
+"""Callable :class:`Pattern` for CIDR notation: IPv4 address + ``/0``–``/32``
+or IPv6 address + ``/0``–``/128``.
+"""
diff --git a/edify/library/address/domain.py b/edify/library/address/domain.py
new file mode 100644
index 0000000..673eca9
--- /dev/null
+++ b/edify/library/address/domain.py
@@ -0,0 +1,13 @@
+"""``domain`` — DNS domain name shape (label.label.tld)."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+domain = RegexBackedPattern(
+ r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+"
+ r"[a-zA-Z]{2,63}$"
+)
+"""Callable :class:`Pattern` for the DNS domain name shape:
+at least one label followed by a TLD of 2–63 letters.
+"""
diff --git a/edify/library/address/hostname.py b/edify/library/address/hostname.py
new file mode 100644
index 0000000..06a8924
--- /dev/null
+++ b/edify/library/address/hostname.py
@@ -0,0 +1,12 @@
+"""``hostname`` — RFC 1123 hostname shape."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+hostname = RegexBackedPattern(
+ r"^(?=.{1,253}$)(?:[a-zA-Z0-9]"
+ r"(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)"
+ r"(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
+)
+"""Callable :class:`Pattern` for the RFC 1123 hostname shape."""
diff --git a/edify/library/address/ip.py b/edify/library/address/ip.py
new file mode 100644
index 0000000..25deaae
--- /dev/null
+++ b/edify/library/address/ip.py
@@ -0,0 +1,32 @@
+"""``ip`` — IPv4 or IPv6 address shape."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+_IPV4_OCTET = r"(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)"
+_IPV4 = rf"{_IPV4_OCTET}\.{_IPV4_OCTET}\.{_IPV4_OCTET}\.{_IPV4_OCTET}"
+
+_IPV6 = (
+ r"(?:"
+ r"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}"
+ r"|([0-9a-fA-F]{1,4}:){1,7}:"
+ r"|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}"
+ r"|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}"
+ r"|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}"
+ r"|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}"
+ r"|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}"
+ r"|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})"
+ r"|:((:[0-9a-fA-F]{1,4}){1,7}|:)"
+ r"|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}"
+ r"|::(ffff(:0{1,4}){0,1}:){0,1}"
+ r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
+ r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"
+ r"|([0-9a-fA-F]{1,4}:){1,4}:"
+ r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
+ r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"
+ r")"
+)
+
+ip = RegexBackedPattern(rf"^(?:{_IPV4}|{_IPV6})$")
+"""Callable :class:`Pattern` for IPv4 dotted-quad or any IPv6 form."""
diff --git a/edify/library/address/path.py b/edify/library/address/path.py
new file mode 100644
index 0000000..6f1a1aa
--- /dev/null
+++ b/edify/library/address/path.py
@@ -0,0 +1,16 @@
+"""``path`` — filesystem path shape (POSIX or Windows)."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+path = RegexBackedPattern(
+ r"^(?:"
+ r"(?:/|(?:\./|(?:\.\./)+))?(?:[^\0\r\n/]+/?)+"
+ r"|[a-zA-Z]:\\(?:[^\\/:*?\"<>|\r\n]+\\?)+"
+ r"|\\\\[^\\/:*?\"<>|\r\n]+\\[^\\/:*?\"<>|\r\n]+(?:\\[^\\/:*?\"<>|\r\n]*)*"
+ r")$"
+)
+"""Callable :class:`Pattern` for a filesystem path shape: POSIX
+(``/absolute`` or ``relative/``), Windows drive-letter, or UNC.
+"""
diff --git a/edify/library/address/port.py b/edify/library/address/port.py
new file mode 100644
index 0000000..baa9ddc
--- /dev/null
+++ b/edify/library/address/port.py
@@ -0,0 +1,16 @@
+"""``port`` — TCP/UDP port number 0–65535."""
+
+from __future__ import annotations
+
+from edify import Pattern, any_of
+
+port = any_of(
+ Pattern().start_of_input().string("6553").range("0", "5").end_of_input(),
+ Pattern().start_of_input().string("655").range("0", "2").digit().end_of_input(),
+ Pattern().start_of_input().string("65").range("0", "4").exactly(2).digit().end_of_input(),
+ Pattern().start_of_input().string("6").range("0", "4").exactly(3).digit().end_of_input(),
+ Pattern().start_of_input().range("1", "5").exactly(4).digit().end_of_input(),
+ Pattern().start_of_input().range("1", "9").between(0, 3).digit().end_of_input(),
+ Pattern().start_of_input().char("0").end_of_input(),
+)
+"""Callable :class:`Pattern` for a TCP/UDP port number 0–65535."""
diff --git a/edify/library/address/ptr.py b/edify/library/address/ptr.py
new file mode 100644
index 0000000..5ce2303
--- /dev/null
+++ b/edify/library/address/ptr.py
@@ -0,0 +1,15 @@
+"""``ptr`` — reverse-DNS PTR record shape (``d.c.b.a.in-addr.arpa`` or IPv6 nibble form)."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+ptr = RegexBackedPattern(
+ r"^(?:"
+ r"(?:\d{1,3}\.){4}in-addr\.arpa\.?"
+ r"|(?:[0-9a-fA-F]\.){32}ip6\.arpa\.?"
+ r")$"
+)
+"""Callable :class:`Pattern` for the reverse-DNS PTR shape: IPv4
+``d.c.b.a.in-addr.arpa`` or IPv6 32-nibble ``…ip6.arpa`` form.
+"""
diff --git a/edify/library/address/socket.py b/edify/library/address/socket.py
new file mode 100644
index 0000000..66c428e
--- /dev/null
+++ b/edify/library/address/socket.py
@@ -0,0 +1,18 @@
+"""``socket`` — ``host:port`` socket address shape."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+socket = RegexBackedPattern(
+ r"^"
+ r"(?:"
+ r"(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)"
+ r"(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}"
+ r"|\[[0-9a-fA-F:]+\]"
+ r"|[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
+ r"(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*"
+ r")"
+ r":\d{1,5}$"
+)
+"""Callable :class:`Pattern` for the ``host:port`` socket-address shape."""
diff --git a/edify/library/address/subdomain.py b/edify/library/address/subdomain.py
new file mode 100644
index 0000000..8c480f8
--- /dev/null
+++ b/edify/library/address/subdomain.py
@@ -0,0 +1,17 @@
+"""``subdomain`` — DNS subdomain label shape (single label under an existing domain)."""
+
+from __future__ import annotations
+
+from edify import Pattern
+
+subdomain = (
+ Pattern()
+ .start_of_input()
+ .any_of().range("a", "z").range("A", "Z").range("0", "9").end()
+ .between(0, 61).any_of().range("a", "z").range("A", "Z").range("0", "9").char("-").end()
+ .any_of().range("a", "z").range("A", "Z").range("0", "9").end()
+ .end_of_input()
+)
+"""Callable :class:`Pattern` for a single DNS subdomain label (1–63 chars,
+alphanumeric with optional interior hyphens).
+"""
diff --git a/edify/library/address/subnet.py b/edify/library/address/subnet.py
new file mode 100644
index 0000000..3ff5262
--- /dev/null
+++ b/edify/library/address/subnet.py
@@ -0,0 +1,15 @@
+"""``subnet`` — dotted-decimal IPv4 subnet mask shape."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+subnet = RegexBackedPattern(
+ r"^(?:255|254|252|248|240|224|192|128|0)\."
+ r"(?:255|254|252|248|240|224|192|128|0)\."
+ r"(?:255|254|252|248|240|224|192|128|0)\."
+ r"(?:255|254|252|248|240|224|192|128|0)$"
+)
+"""Callable :class:`Pattern` for a dotted-decimal IPv4 subnet mask
+(each octet is one of ``255``, ``254``, ``252``, …, ``128``, ``0``).
+"""
diff --git a/edify/library/address/tld.py b/edify/library/address/tld.py
new file mode 100644
index 0000000..5c405fb
--- /dev/null
+++ b/edify/library/address/tld.py
@@ -0,0 +1,13 @@
+"""``tld`` — top-level domain shape (2–63 alpha)."""
+
+from __future__ import annotations
+
+from edify import Pattern
+
+tld = (
+ Pattern()
+ .start_of_input()
+ .between(2, 63).any_of().range("a", "z").range("A", "Z").end()
+ .end_of_input()
+)
+"""Callable :class:`Pattern` for the TLD shape: 2 to 63 letters."""
diff --git a/edify/library/address/uri.py b/edify/library/address/uri.py
new file mode 100644
index 0000000..b8033fe
--- /dev/null
+++ b/edify/library/address/uri.py
@@ -0,0 +1,12 @@
+"""``uri`` — generic URI shape (scheme + path)."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+uri = RegexBackedPattern(
+ r"^[a-zA-Z][a-zA-Z0-9+.\-]*:[^\s]+$"
+)
+"""Callable :class:`Pattern` for the generic URI shape:
+``scheme:opaque-or-path`` where scheme starts with a letter.
+"""
diff --git a/edify/library/address/url.py b/edify/library/address/url.py
new file mode 100644
index 0000000..45bde6e
--- /dev/null
+++ b/edify/library/address/url.py
@@ -0,0 +1,16 @@
+"""``url`` — HTTP/HTTPS URL shape (with or without protocol)."""
+
+from __future__ import annotations
+
+from edify.library._support.regex import RegexBackedPattern
+
+url = RegexBackedPattern(
+ r"^(?:https?://)?"
+ r"(?:www\.)?"
+ r"[-a-zA-Z0-9@:%._\+~#=]{1,256}"
+ r"\.[a-zA-Z0-9()]{1,6}"
+ r"\b(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$"
+)
+"""Callable :class:`Pattern` for the URL shape: optional ``http[s]://``,
+optional ``www.``, host with dot-separated labels, TLD, and optional path.
+"""