diff options
77 files changed, 2365 insertions, 267 deletions
diff --git a/edify/library/address/cidr.py b/edify/library/address/cidr.py index 5894952..a2b9454 100644 --- a/edify/library/address/cidr.py +++ b/edify/library/address/cidr.py @@ -2,16 +2,66 @@ 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")$" +from edify import Pattern, any_of + +_ipv4_octet = any_of( + Pattern().string("25").any_of().range("0", "5").end(), + Pattern().char("2").any_of().range("0", "4").end().digit(), + Pattern().char("1").digit().digit(), + Pattern().any_of().range("1", "9").end().digit(), + Pattern().digit(), +) + +_ipv4_prefix = any_of( + Pattern().char("3").any_of().range("0", "2").end(), + Pattern().optional().any_of_chars("12").digit(), +) + +_hextet = ( + Pattern() + .between(1, 4) + .any_of() + .range("0", "9") + .range("a", "f") + .range("A", "F") + .end() +) + +_ipv6_prefix = any_of( + Pattern().string("12").any_of().range("0", "8").end(), + Pattern().char("1").any_of_chars("01").digit(), + Pattern().optional().any_of().range("1", "9").end().digit(), +) + +_ipv4_cidr = ( + Pattern() + .subexpression(_ipv4_octet) + .exactly(3) + .group() + .char(".") + .subexpression(_ipv4_octet) + .end() + .char("/") + .subexpression(_ipv4_prefix) +) + +_ipv6_cidr = ( + Pattern() + .between(0, 7) + .group() + .subexpression(_hextet) + .char(":") + .end() + .subexpression(_hextet) + .char("/") + .subexpression(_ipv6_prefix) +) + +cidr = ( + Pattern() + .start_of_input() + .subexpression(any_of(_ipv4_cidr, _ipv6_cidr)) + .end_of_input() ) """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 index ef8d310..2112e4c 100644 --- a/edify/library/address/domain.py +++ b/edify/library/address/domain.py @@ -2,11 +2,30 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -domain = RegexBackedPattern( - r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+" - r"[a-zA-Z]{2,63}$" +domain = ( + Pattern() + .start_of_input() + .one_or_more() + .group() + .alphanumeric() + .optional() + .group() + .at_most(61) + .any_of() + .range("a", "z") + .range("A", "Z") + .range("0", "9") + .char("-") + .end() + .alphanumeric() + .end() + .char(".") + .end() + .between(2, 63) + .letter() + .end_of_input() ) """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 index 06a8924..cbd691c 100644 --- a/edify/library/address/hostname.py +++ b/edify/library/address/hostname.py @@ -2,11 +2,41 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import END, Pattern -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])?)*$" +_label_tail = ( + Pattern() + .optional() + .group() + .at_most(61) + .any_of() + .range("a", "z") + .range("A", "Z") + .range("0", "9") + .char("-") + .end() + .alphanumeric() + .end() +) + +hostname = ( + Pattern() + .start_of_input() + .assert_ahead() + .between(1, 253) + .any_char() + .subexpression(END, ignore_start_and_end=False) + .end() + .group() + .alphanumeric() + .subexpression(_label_tail) + .end() + .zero_or_more() + .group() + .char(".") + .alphanumeric() + .subexpression(_label_tail) + .end() + .end_of_input() ) """Callable :class:`Pattern` for the RFC 1123 hostname shape.""" diff --git a/edify/library/address/ip.py b/edify/library/address/ip.py index 25deaae..e51ea54 100644 --- a/edify/library/address/ip.py +++ b/edify/library/address/ip.py @@ -2,31 +2,213 @@ 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")" +from edify import Pattern, any_of +from edify.library._support.atoms import hex_any, octet + + +def _hex_group() -> Pattern: + return Pattern().between(1, 4).subexpression(hex_any) + + +_ipv4 = ( + Pattern() + .subexpression(octet) + .exactly(3) + .group() + .char(".") + .subexpression(octet) + .end() +) + + +def _b1() -> Pattern: + return ( + Pattern() + .exactly(7) + .group() + .subexpression(_hex_group()) + .char(":") + .end() + .subexpression(_hex_group()) + ) + + +def _b2() -> Pattern: + return ( + Pattern() + .between(1, 7) + .group() + .subexpression(_hex_group()) + .char(":") + .end() + .char(":") + ) + + +def _b3() -> Pattern: + return ( + Pattern() + .between(1, 6) + .group() + .subexpression(_hex_group()) + .char(":") + .end() + .char(":") + .subexpression(_hex_group()) + ) + + +def _b_mixed(prefix_count: int, suffix_count: int) -> Pattern: + return ( + Pattern() + .between(1, prefix_count) + .group() + .subexpression(_hex_group()) + .char(":") + .end() + .between(1, suffix_count) + .group() + .char(":") + .subexpression(_hex_group()) + .end() + ) + + +def _b8() -> Pattern: + return ( + Pattern() + .subexpression(_hex_group()) + .char(":") + .group() + .between(1, 6) + .group() + .char(":") + .subexpression(_hex_group()) + .end() + .end() + ) + + +def _b9() -> Pattern: + return ( + Pattern() + .char(":") + .group() + .any_of() + .subexpression( + Pattern() + .between(1, 7) + .group() + .char(":") + .subexpression(_hex_group()) + .end() + ) + .char(":") + .end() + .end() + ) + + +def _b_link_local() -> Pattern: + return ( + Pattern() + .string("fe80:") + .between(0, 4) + .group() + .char(":") + .between(0, 4) + .subexpression(hex_any) + .end() + .char("%") + .one_or_more() + .any_of() + .range("0", "9") + .range("a", "z") + .range("A", "Z") + .end() + ) + + +def _map_octet() -> Pattern: + return any_of( + Pattern().string("25").range("0", "5"), + ( + Pattern() + .optional() + .group() + .any_of() + .subexpression(Pattern().char("2").range("0", "4")) + .subexpression(Pattern().optional().char("1").digit()) + .end() + .end() + .digit() + ), + ) + + +def _mapped_ipv4() -> Pattern: + return ( + Pattern() + .exactly(3) + .group() + .subexpression(_map_octet()) + .char(".") + .end() + .subexpression(_map_octet()) + ) + + +def _b_ipv4_mapped() -> Pattern: + return ( + Pattern() + .string("::") + .optional() + .group() + .string("ffff") + .optional() + .group() + .char(":") + .between(1, 4) + .char("0") + .end() + .char(":") + .end() + .subexpression(_mapped_ipv4()) + ) + + +def _b_hybrid() -> Pattern: + return ( + Pattern() + .between(1, 4) + .group() + .subexpression(_hex_group()) + .char(":") + .end() + .char(":") + .subexpression(_mapped_ipv4()) + ) + + +_ipv6 = any_of( + _b1(), + _b2(), + _b3(), + _b_mixed(5, 2), + _b_mixed(4, 3), + _b_mixed(3, 4), + _b_mixed(2, 5), + _b8(), + _b9(), + _b_link_local(), + _b_ipv4_mapped(), + _b_hybrid(), ) -ip = RegexBackedPattern(rf"^(?:{_IPV4}|{_IPV6})$") +ip = ( + Pattern() + .start_of_input() + .subexpression(any_of(_ipv4, _ipv6)) + .end_of_input() +) """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 index 6f1a1aa..529f225 100644 --- a/edify/library/address/path.py +++ b/edify/library/address/path.py @@ -2,14 +2,64 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -path = RegexBackedPattern( - r"^(?:" - r"(?:/|(?:\./|(?:\.\./)+))?(?:[^\0\r\n/]+/?)+" - r"|[a-zA-Z]:\\(?:[^\\/:*?\"<>|\r\n]+\\?)+" - r"|\\\\[^\\/:*?\"<>|\r\n]+\\[^\\/:*?\"<>|\r\n]+(?:\\[^\\/:*?\"<>|\r\n]*)*" - r")$" +_posix = ( + Pattern() + .optional() + .group() + .any_of() + .char("/") + .group() + .string("./") + .end() + .group() + .one_or_more() + .string("../") + .end() + .end() + .end() + .one_or_more() + .group() + .one_or_more() + .anything_but_chars("\x00\r\n/") + .optional() + .char("/") + .end() +) +_windows = ( + Pattern() + .letter() + .string(":\\") + .one_or_more() + .group() + .one_or_more() + .anything_but_chars("\\/:*?\"<>|\r\n") + .optional() + .char("\\") + .end() +) +_unc = ( + Pattern() + .string("\\\\") + .one_or_more() + .anything_but_chars("\\/:*?\"<>|\r\n") + .char("\\") + .one_or_more() + .anything_but_chars("\\/:*?\"<>|\r\n") + .zero_or_more() + .group() + .char("\\") + .zero_or_more() + .anything_but_chars("\\/:*?\"<>|\r\n") + .end() +) + +path = ( + Pattern() + .start_of_input() + .subexpression(any_of(_posix, _windows, _unc)) + .end_of_input() ) """Callable :class:`Pattern` for a filesystem path shape: POSIX (``/absolute`` or ``relative/``), Windows drive-letter, or UNC. diff --git a/edify/library/address/ptr.py b/edify/library/address/ptr.py index 5ce2303..a8020d6 100644 --- a/edify/library/address/ptr.py +++ b/edify/library/address/ptr.py @@ -2,13 +2,42 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of +from edify.library._support.atoms import hex_any -ptr = RegexBackedPattern( - r"^(?:" - r"(?:\d{1,3}\.){4}in-addr\.arpa\.?" - r"|(?:[0-9a-fA-F]\.){32}ip6\.arpa\.?" - r")$" +_ipv4_ptr = ( + Pattern() + .exactly(4) + .group() + .between(1, 3) + .digit() + .char(".") + .end() + .string("in-addr") + .char(".") + .string("arpa") + .optional() + .char(".") +) +_ipv6_ptr = ( + Pattern() + .exactly(32) + .group() + .subexpression(hex_any) + .char(".") + .end() + .string("ip6") + .char(".") + .string("arpa") + .optional() + .char(".") +) + +ptr = ( + Pattern() + .start_of_input() + .subexpression(any_of(_ipv4_ptr, _ipv6_ptr)) + .end_of_input() ) """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 index 66c428e..020c7dc 100644 --- a/edify/library/address/socket.py +++ b/edify/library/address/socket.py @@ -2,17 +2,70 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of +from edify.library._support.atoms import octet -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}$" +_ipv4 = ( + Pattern() + .subexpression(octet) + .exactly(3) + .group() + .char(".") + .subexpression(octet) + .end() +) +_ipv6_bracket = ( + Pattern() + .char("[") + .one_or_more() + .any_of() + .range("0", "9") + .range("a", "f") + .range("A", "F") + .char(":") + .end() + .char("]") +) +_hostname_label = ( + Pattern() + .any_of() + .range("a", "z") + .range("A", "Z") + .range("0", "9") + .end() + .optional() + .group() + .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() +) +_hostname = ( + Pattern() + .subexpression(_hostname_label) + .zero_or_more() + .group() + .char(".") + .subexpression(_hostname_label) + .end() +) + +socket = ( + Pattern() + .start_of_input() + .subexpression(any_of(_ipv4, _ipv6_bracket, _hostname)) + .char(":") + .between(1, 5) + .digit() + .end_of_input() ) """Callable :class:`Pattern` for the ``host:port`` socket-address shape.""" diff --git a/edify/library/address/subnet.py b/edify/library/address/subnet.py index 3ff5262..cdf50fc 100644 --- a/edify/library/address/subnet.py +++ b/edify/library/address/subnet.py @@ -2,13 +2,34 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -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)$" + +def _mask_octet() -> Pattern: + return any_of( + Pattern().string("255"), + Pattern().string("254"), + Pattern().string("252"), + Pattern().string("248"), + Pattern().string("240"), + Pattern().string("224"), + Pattern().string("192"), + Pattern().string("128"), + Pattern().char("0"), + ) + + +subnet = ( + Pattern() + .start_of_input() + .subexpression(_mask_octet()) + .char(".") + .subexpression(_mask_octet()) + .char(".") + .subexpression(_mask_octet()) + .char(".") + .subexpression(_mask_octet()) + .end_of_input() ) """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/uri.py b/edify/library/address/uri.py index cb417be..9421a98 100644 --- a/edify/library/address/uri.py +++ b/edify/library/address/uri.py @@ -2,9 +2,26 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -uri = RegexBackedPattern(r"^[a-zA-Z][a-zA-Z0-9+.\-]*:[^\s]+$") +uri = ( + Pattern() + .start_of_input() + .letter() + .zero_or_more() + .any_of() + .range("a", "z") + .range("A", "Z") + .range("0", "9") + .char("+") + .char(".") + .char("-") + .end() + .char(":") + .one_or_more() + .non_whitespace_char() + .end_of_input() +) """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 index 45bde6e..9061c34 100644 --- a/edify/library/address/url.py +++ b/edify/library/address/url.py @@ -2,14 +2,70 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -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()@:%_\+.~#?&/=]*)$" +url = ( + Pattern() + .start_of_input() + .optional() + .group() + .string("http") + .optional() + .char("s") + .string("://") + .end() + .optional() + .group() + .string("www.") + .end() + .between(1, 256) + .any_of() + .char("-") + .range("a", "z") + .range("A", "Z") + .range("0", "9") + .char("@") + .char(":") + .char("%") + .char(".") + .char("_") + .char("+") + .char("~") + .char("#") + .char("=") + .end() + .char(".") + .between(1, 6) + .any_of() + .range("a", "z") + .range("A", "Z") + .range("0", "9") + .char("(") + .char(")") + .end() + .word_boundary() + .zero_or_more() + .any_of() + .char("-") + .range("a", "z") + .range("A", "Z") + .range("0", "9") + .char("(") + .char(")") + .char("@") + .char(":") + .char("%") + .char("_") + .char("+") + .char(".") + .char("~") + .char("#") + .char("?") + .char("&") + .char("/") + .char("=") + .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. diff --git a/edify/library/api/atom.py b/edify/library/api/atom.py index e2f1b88..1693b08 100644 --- a/edify/library/api/atom.py +++ b/edify/library/api/atom.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -atom = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +atom = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive atom-related identifier.""" diff --git a/edify/library/api/graphql.py b/edify/library/api/graphql.py index d4e7527..be30834 100644 --- a/edify/library/api/graphql.py +++ b/edify/library/api/graphql.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -graphql = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +graphql = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive graphql-related identifier.""" diff --git a/edify/library/api/hal.py b/edify/library/api/hal.py index d2aef10..ad4456a 100644 --- a/edify/library/api/hal.py +++ b/edify/library/api/hal.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -hal = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +hal = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive hal-related identifier.""" diff --git a/edify/library/api/jsonapi.py b/edify/library/api/jsonapi.py index 367c112..f02a453 100644 --- a/edify/library/api/jsonapi.py +++ b/edify/library/api/jsonapi.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -jsonapi = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +jsonapi = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive jsonapi-related identifier.""" diff --git a/edify/library/api/oauth.py b/edify/library/api/oauth.py index 4dd0c1f..ed0ba57 100644 --- a/edify/library/api/oauth.py +++ b/edify/library/api/oauth.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -oauth = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +oauth = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive oauth-related identifier.""" diff --git a/edify/library/api/openapi.py b/edify/library/api/openapi.py index 0d6cd51..dd898b3 100644 --- a/edify/library/api/openapi.py +++ b/edify/library/api/openapi.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -openapi = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +openapi = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive openapi-related identifier.""" diff --git a/edify/library/api/openid.py b/edify/library/api/openid.py index 20f309e..75c71c2 100644 --- a/edify/library/api/openid.py +++ b/edify/library/api/openid.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -openid = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +openid = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive openid-related identifier.""" diff --git a/edify/library/api/rss.py b/edify/library/api/rss.py index d5dc323..bc402f4 100644 --- a/edify/library/api/rss.py +++ b/edify/library/api/rss.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -rss = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +rss = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive rss-related identifier.""" diff --git a/edify/library/api/saml.py b/edify/library/api/saml.py index 2650e50..730ff42 100644 --- a/edify/library/api/saml.py +++ b/edify/library/api/saml.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -saml = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +saml = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive saml-related identifier.""" diff --git a/edify/library/api/soap.py b/edify/library/api/soap.py index 445f658..c13c24d 100644 --- a/edify/library/api/soap.py +++ b/edify/library/api/soap.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -soap = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +soap = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive soap-related identifier.""" diff --git a/edify/library/api/swagger.py b/edify/library/api/swagger.py index 6bf9d67..b169b72 100644 --- a/edify/library/api/swagger.py +++ b/edify/library/api/swagger.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -swagger = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +swagger = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive swagger-related identifier.""" diff --git a/edify/library/api/webhook.py b/edify/library/api/webhook.py index 80a4a82..b737d77 100644 --- a/edify/library/api/webhook.py +++ b/edify/library/api/webhook.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -webhook = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{3,256}$") +webhook = ( + Pattern() + .start_of_input() + .between(3, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive webhook-related identifier.""" diff --git a/edify/library/auth/mnemonic.py b/edify/library/auth/mnemonic.py index 366fe0c..2929175 100644 --- a/edify/library/auth/mnemonic.py +++ b/edify/library/auth/mnemonic.py @@ -2,9 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -mnemonic = RegexBackedPattern(r"^(?:[a-z]+ ){11,23}[a-z]+$") +mnemonic = ( + Pattern() + .start_of_input() + .between(11, 23) + .group() + .one_or_more() + .lowercase() + .char(" ") + .end() + .one_or_more() + .lowercase() + .end_of_input() +) """Callable :class:`Pattern` for a BIP-39 mnemonic phrase: 12 to 24 lowercase words separated by single spaces. """ diff --git a/edify/library/color/color.py b/edify/library/color/color.py index 27469d5..7503cc3 100644 --- a/edify/library/color/color.py +++ b/edify/library/color/color.py @@ -2,13 +2,131 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -color = RegexBackedPattern( - r"^(?:#(?:[0-9A-Fa-f]{3,4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})" - r"|rgba?\(\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?(?:\s*,\s*[\d.]+)?\s*\)" - r"|hsla?\(\s*\d{1,3}(?:deg)?\s*,\s*\d{1,3}%\s*,\s*\d{1,3}%(?:\s*,\s*[\d.]+)?\s*\)" - r"|[a-zA-Z]{3,20}" - r")$" +color = any_of( + Pattern() + .start_of_input() + .char("#") + .any_of() + .between(3, 4) + .any_of() + .range("0", "9") + .range("A", "F") + .range("a", "f") + .end() + .exactly(6) + .any_of() + .range("0", "9") + .range("A", "F") + .range("a", "f") + .end() + .exactly(8) + .any_of() + .range("0", "9") + .range("A", "F") + .range("a", "f") + .end() + .end() + .end_of_input(), + Pattern() + .start_of_input() + .string("rgb") + .optional() + .char("a") + .char("(") + .zero_or_more() + .whitespace_char() + .between(1, 3) + .digit() + .optional() + .char("%") + .zero_or_more() + .whitespace_char() + .char(",") + .zero_or_more() + .whitespace_char() + .between(1, 3) + .digit() + .optional() + .char("%") + .zero_or_more() + .whitespace_char() + .char(",") + .zero_or_more() + .whitespace_char() + .between(1, 3) + .digit() + .optional() + .char("%") + .optional() + .group() + .zero_or_more() + .whitespace_char() + .char(",") + .zero_or_more() + .whitespace_char() + .one_or_more() + .any_of() + .digit() + .char(".") + .end() + .end() + .zero_or_more() + .whitespace_char() + .char(")") + .end_of_input(), + Pattern() + .start_of_input() + .string("hsl") + .optional() + .char("a") + .char("(") + .zero_or_more() + .whitespace_char() + .between(1, 3) + .digit() + .optional() + .group() + .string("deg") + .end() + .zero_or_more() + .whitespace_char() + .char(",") + .zero_or_more() + .whitespace_char() + .between(1, 3) + .digit() + .char("%") + .zero_or_more() + .whitespace_char() + .char(",") + .zero_or_more() + .whitespace_char() + .between(1, 3) + .digit() + .char("%") + .optional() + .group() + .zero_or_more() + .whitespace_char() + .char(",") + .zero_or_more() + .whitespace_char() + .one_or_more() + .any_of() + .digit() + .char(".") + .end() + .end() + .zero_or_more() + .whitespace_char() + .char(")") + .end_of_input(), + Pattern() + .start_of_input() + .between(3, 20) + .letter() + .end_of_input(), ) """Callable :class:`Pattern` for any common CSS colour shape.""" diff --git a/edify/library/color/filter.py b/edify/library/color/filter.py index 18fb975..7391950 100644 --- a/edify/library/color/filter.py +++ b/edify/library/color/filter.py @@ -2,11 +2,27 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -filter = RegexBackedPattern( - r"^(?:blur|brightness|contrast|grayscale|hue-rotate|invert|opacity" - r"|saturate|sepia|drop-shadow)" - r"\([^)]+\)$" +filter = ( + Pattern() + .start_of_input() + .any_of( + "blur", + "brightness", + "contrast", + "grayscale", + "hue-rotate", + "invert", + "opacity", + "saturate", + "sepia", + "drop-shadow", + ) + .char("(") + .one_or_more() + .anything_but_chars(")") + .char(")") + .end_of_input() ) """Callable :class:`Pattern` for a CSS filter function call.""" diff --git a/edify/library/color/gradient.py b/edify/library/color/gradient.py index 49bcede..c3ca027 100644 --- a/edify/library/color/gradient.py +++ b/edify/library/color/gradient.py @@ -2,7 +2,25 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -gradient = RegexBackedPattern(r"^(?:linear|radial|conic)-gradient\([^()]*(?:\([^()]*\)[^()]*)*\)$") +gradient = ( + Pattern() + .start_of_input() + .any_of("linear", "radial", "conic") + .string("-gradient(") + .zero_or_more() + .anything_but_chars("()") + .zero_or_more() + .group() + .char("(") + .zero_or_more() + .anything_but_chars("()") + .char(")") + .zero_or_more() + .anything_but_chars("()") + .end() + .char(")") + .end_of_input() +) """Callable :class:`Pattern` for a CSS gradient function call.""" diff --git a/edify/library/color/palette.py b/edify/library/color/palette.py index 441d512..f818400 100644 --- a/edify/library/color/palette.py +++ b/edify/library/color/palette.py @@ -2,10 +2,45 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -palette = RegexBackedPattern( - r"^(?:#[0-9A-Fa-f]{3,8}|[a-zA-Z]{3,20})" - r"(?:\s*,\s*(?:#[0-9A-Fa-f]{3,8}|[a-zA-Z]{3,20})){1,15}$" +palette = ( + Pattern() + .start_of_input() + .any_of() + .group() + .char("#") + .between(3, 8) + .any_of() + .range("0", "9") + .range("A", "F") + .range("a", "f") + .end() + .end() + .between(3, 20) + .letter() + .end() + .between(1, 15) + .group() + .zero_or_more() + .whitespace_char() + .char(",") + .zero_or_more() + .whitespace_char() + .any_of() + .group() + .char("#") + .between(3, 8) + .any_of() + .range("0", "9") + .range("A", "F") + .range("a", "f") + .end() + .end() + .between(3, 20) + .letter() + .end() + .end() + .end_of_input() ) """Callable :class:`Pattern` for a comma-separated list of 2-16 colours.""" diff --git a/edify/library/color/swatch.py b/edify/library/color/swatch.py index 0d68bbd..1680deb 100644 --- a/edify/library/color/swatch.py +++ b/edify/library/color/swatch.py @@ -2,7 +2,24 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -swatch = RegexBackedPattern(r"^(?:#[0-9A-Fa-f]{3,8}|[a-zA-Z]{3,20})$") +swatch = ( + Pattern() + .start_of_input() + .any_of() + .group() + .char("#") + .between(3, 8) + .any_of() + .range("0", "9") + .range("A", "F") + .range("a", "f") + .end() + .end() + .between(3, 20) + .letter() + .end() + .end_of_input() +) """Callable :class:`Pattern` for a single hex colour or CSS named colour.""" diff --git a/edify/library/contact/address.py b/edify/library/contact/address.py index 4d2bdaf..bcac596 100644 --- a/edify/library/contact/address.py +++ b/edify/library/contact/address.py @@ -2,9 +2,25 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -address = RegexBackedPattern(r"^\d+\s+[A-Za-z0-9\s.,'\-#/]+$") +address = ( + Pattern() + .start_of_input() + .one_or_more() + .digit() + .one_or_more() + .whitespace_char() + .one_or_more() + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .whitespace_char() + .any_of_chars(".,'-#/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive street-address shape: one or more digits followed by whitespace and address body characters. """ diff --git a/edify/library/contact/email.py b/edify/library/contact/email.py index d6fcd86..dc98cdd 100644 --- a/edify/library/contact/email.py +++ b/edify/library/contact/email.py @@ -2,22 +2,209 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern - -email = RegexBackedPattern( - r"^(?:" - r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*" - r"@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" - r"|(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*" - r"|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]" - r"|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")" - r"@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+" - r"[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" - r"|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}" - r"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:" - r"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]" - r"|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])" - r")$" +from edify import Pattern, any_of + + +def _local_char_class() -> Pattern: + return ( + Pattern() + .any_of() + .range("a", "z") + .range("0", "9") + .char("!") + .char("#") + .char("$") + .char("%") + .char("&") + .char("'") + .char("*") + .char("+") + .char("/") + .char("=") + .char("?") + .char("^") + .char("_") + .char("`") + .char("{") + .char("|") + .char("}") + .char("~") + .char("-") + .end() + ) + + +def _domain_label() -> Pattern: + return ( + Pattern() + .any_of() + .range("a", "z") + .range("0", "9") + .end() + .optional() + .group() + .zero_or_more() + .any_of() + .range("a", "z") + .range("0", "9") + .char("-") + .end() + .any_of() + .range("a", "z") + .range("0", "9") + .end() + .end() + ) + + +_basic_local = ( + Pattern() + .one_or_more() + .subexpression(_local_char_class()) + .zero_or_more() + .group() + .char(".") + .one_or_more() + .subexpression(_local_char_class()) + .end() +) + +_basic_domain = ( + Pattern() + .one_or_more() + .group() + .subexpression(_domain_label()) + .char(".") + .end() + .subexpression(_domain_label()) +) + +_basic = ( + Pattern() + .subexpression(_basic_local) + .char("@") + .subexpression(_basic_domain) +) + + +def _quoted_text() -> Pattern: + return ( + Pattern() + .any_of() + .range("\x01", "\x08") + .char("\x0b") + .char("\x0c") + .range("\x0e", "\x1f") + .char("\x21") + .range("\x23", "\x5b") + .range("\x5d", "\x7f") + .end() + ) + + +def _quoted_escape() -> Pattern: + return ( + Pattern() + .char("\\") + .any_of() + .range("\x01", "\x09") + .char("\x0b") + .char("\x0c") + .range("\x0e", "\x7f") + .end() + ) + + +_quoted_local = ( + Pattern() + .char('"') + .zero_or_more() + .subexpression(any_of(_quoted_text(), _quoted_escape())) + .char('"') +) + + +def _octet() -> Pattern: + return any_of( + Pattern().string("25").range("0", "5"), + Pattern().char("2").range("0", "4").digit(), + Pattern().optional().any_of_chars("01").digit().optional().digit(), + ) + + +def _bracket_text() -> Pattern: + return ( + Pattern() + .any_of() + .range("\x01", "\x08") + .char("\x0b") + .char("\x0c") + .range("\x0e", "\x1f") + .range("\x21", "\x5a") + .range("\x53", "\x7f") + .end() + ) + + +def _bracket_escape() -> Pattern: + return ( + Pattern() + .char("\\") + .any_of() + .range("\x01", "\x09") + .char("\x0b") + .char("\x0c") + .range("\x0e", "\x7f") + .end() + ) + + +_ip_literal_tail = any_of( + _octet(), + ( + Pattern() + .zero_or_more() + .any_of() + .range("a", "z") + .range("0", "9") + .char("-") + .end() + .any_of() + .range("a", "z") + .range("0", "9") + .end() + .char(":") + .one_or_more() + .subexpression(any_of(_bracket_text(), _bracket_escape())) + ), +) + +_ip_literal = ( + Pattern() + .char("[") + .exactly(3) + .group() + .subexpression(_octet()) + .char(".") + .end() + .subexpression(_ip_literal_tail) + .char("]") +) + +_rfc_local = any_of(_basic_local, _quoted_local) +_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. diff --git a/edify/library/contact/fax.py b/edify/library/contact/fax.py index 84a8160..453a6d2 100644 --- a/edify/library/contact/fax.py +++ b/edify/library/contact/fax.py @@ -2,10 +2,51 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -fax = RegexBackedPattern( - r"^\+?\d{1,4}?[-.\s]?\(?\d{1,3}?\)?[-.\s]?" - r"\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}$" +fax = ( + Pattern() + .start_of_input() + .optional() + .char("+") + .between_lazy(1, 4) + .digit() + .optional() + .any_of() + .char("-") + .char(".") + .whitespace_char() + .end() + .optional() + .char("(") + .between_lazy(1, 3) + .digit() + .optional() + .char(")") + .optional() + .any_of() + .char("-") + .char(".") + .whitespace_char() + .end() + .between(1, 4) + .digit() + .optional() + .any_of() + .char("-") + .char(".") + .whitespace_char() + .end() + .between(1, 4) + .digit() + .optional() + .any_of() + .char("-") + .char(".") + .whitespace_char() + .end() + .between(1, 9) + .digit() + .end_of_input() ) """Callable :class:`Pattern` for the permissive international fax-number shape.""" diff --git a/edify/library/contact/phone.py b/edify/library/contact/phone.py index eee5b77..2293f5d 100644 --- a/edify/library/contact/phone.py +++ b/edify/library/contact/phone.py @@ -2,14 +2,58 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -phone = RegexBackedPattern( - r"^(?:" - r"\+?\d{1,4}?[-.\s]?\(?\d{1,3}?\)?[-.\s]?" - r"\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}" - r"|\d{2,4}" - r")$" +_international = ( + Pattern() + .optional() + .char("+") + .between_lazy(1, 4) + .digit() + .optional() + .any_of() + .char("-") + .char(".") + .whitespace_char() + .end() + .optional() + .char("(") + .between_lazy(1, 3) + .digit() + .optional() + .char(")") + .optional() + .any_of() + .char("-") + .char(".") + .whitespace_char() + .end() + .between(1, 4) + .digit() + .optional() + .any_of() + .char("-") + .char(".") + .whitespace_char() + .end() + .between(1, 4) + .digit() + .optional() + .any_of() + .char("-") + .char(".") + .whitespace_char() + .end() + .between(1, 9) + .digit() +) +_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) diff --git a/edify/library/data/avro.py b/edify/library/data/avro.py index 2771d16..d54e3e3 100644 --- a/edify/library/data/avro.py +++ b/edify/library/data/avro.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -avro = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +avro = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for avro data-format identifier or content marker.""" diff --git a/edify/library/data/csv.py b/edify/library/data/csv.py index 5044162..fa08131 100644 --- a/edify/library/data/csv.py +++ b/edify/library/data/csv.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -csv = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +csv = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for csv data-format identifier or content marker.""" diff --git a/edify/library/data/hdf5.py b/edify/library/data/hdf5.py index a3e070f..f38d5d5 100644 --- a/edify/library/data/hdf5.py +++ b/edify/library/data/hdf5.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -hdf5 = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +hdf5 = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for hdf5 data-format identifier or content marker.""" diff --git a/edify/library/data/html.py b/edify/library/data/html.py index ffccd73..2a60d51 100644 --- a/edify/library/data/html.py +++ b/edify/library/data/html.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -html = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +html = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for html data-format identifier or content marker.""" diff --git a/edify/library/data/ini.py b/edify/library/data/ini.py index 42b0ef8..5f53e04 100644 --- a/edify/library/data/ini.py +++ b/edify/library/data/ini.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -ini = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +ini = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for ini data-format identifier or content marker.""" diff --git a/edify/library/data/json.py b/edify/library/data/json.py index 905a5db..bd610ae 100644 --- a/edify/library/data/json.py +++ b/edify/library/data/json.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -json = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +json = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for json data-format identifier or content marker.""" diff --git a/edify/library/data/msgpack.py b/edify/library/data/msgpack.py index 85bed6b..fd7dae6 100644 --- a/edify/library/data/msgpack.py +++ b/edify/library/data/msgpack.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -msgpack = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +msgpack = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for msgpack data-format identifier or content marker.""" diff --git a/edify/library/data/protobuf.py b/edify/library/data/protobuf.py index 1677310..0c39617 100644 --- a/edify/library/data/protobuf.py +++ b/edify/library/data/protobuf.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -protobuf = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +protobuf = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for protobuf data-format identifier or content marker.""" diff --git a/edify/library/data/toml.py b/edify/library/data/toml.py index 32b710f..8aeb0f7 100644 --- a/edify/library/data/toml.py +++ b/edify/library/data/toml.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -toml = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +toml = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for toml data-format identifier or content marker.""" diff --git a/edify/library/data/tsv.py b/edify/library/data/tsv.py index 9a7dedd..d32da08 100644 --- a/edify/library/data/tsv.py +++ b/edify/library/data/tsv.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -tsv = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +tsv = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for tsv data-format identifier or content marker.""" diff --git a/edify/library/data/xml.py b/edify/library/data/xml.py index 37ae850..613c9c6 100644 --- a/edify/library/data/xml.py +++ b/edify/library/data/xml.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -xml = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +xml = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for xml data-format identifier or content marker.""" diff --git a/edify/library/data/yaml.py b/edify/library/data/yaml.py index 4b67953..01cd897 100644 --- a/edify/library/data/yaml.py +++ b/edify/library/data/yaml.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -yaml = RegexBackedPattern(r"^[A-Za-z0-9_.\-/+]{2,256}$") +yaml = ( + Pattern() + .start_of_input() + .between(2, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .char("+") + .end() + .end_of_input() +) """Callable :class:`Pattern` for yaml data-format identifier or content marker.""" diff --git a/edify/library/document/docx.py b/edify/library/document/docx.py index cc503f3..ad559bd 100644 --- a/edify/library/document/docx.py +++ b/edify/library/document/docx.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -docx = RegexBackedPattern(r"^[A-Za-z0-9_.\-/]{1,256}$") +docx = ( + Pattern() + .start_of_input() + .between(1, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a docx document identifier or file name.""" diff --git a/edify/library/document/epub.py b/edify/library/document/epub.py index ea09d30..d3e547d 100644 --- a/edify/library/document/epub.py +++ b/edify/library/document/epub.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -epub = RegexBackedPattern(r"^[A-Za-z0-9_.\-/]{1,256}$") +epub = ( + Pattern() + .start_of_input() + .between(1, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a epub document identifier or file name.""" diff --git a/edify/library/document/mobi.py b/edify/library/document/mobi.py index eb7e967..406923c 100644 --- a/edify/library/document/mobi.py +++ b/edify/library/document/mobi.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -mobi = RegexBackedPattern(r"^[A-Za-z0-9_.\-/]{1,256}$") +mobi = ( + Pattern() + .start_of_input() + .between(1, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a mobi document identifier or file name.""" diff --git a/edify/library/document/odt.py b/edify/library/document/odt.py index 41ec7f0..b4fe810 100644 --- a/edify/library/document/odt.py +++ b/edify/library/document/odt.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -odt = RegexBackedPattern(r"^[A-Za-z0-9_.\-/]{1,256}$") +odt = ( + Pattern() + .start_of_input() + .between(1, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a odt document identifier or file name.""" diff --git a/edify/library/document/pdf.py b/edify/library/document/pdf.py index 3b5f5c3..871ffa7 100644 --- a/edify/library/document/pdf.py +++ b/edify/library/document/pdf.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -pdf = RegexBackedPattern(r"^[A-Za-z0-9_.\-/]{1,256}$") +pdf = ( + Pattern() + .start_of_input() + .between(1, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a pdf document identifier or file name.""" diff --git a/edify/library/document/pptx.py b/edify/library/document/pptx.py index 906e94b..c95be95 100644 --- a/edify/library/document/pptx.py +++ b/edify/library/document/pptx.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -pptx = RegexBackedPattern(r"^[A-Za-z0-9_.\-/]{1,256}$") +pptx = ( + Pattern() + .start_of_input() + .between(1, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a pptx document identifier or file name.""" diff --git a/edify/library/document/readme.py b/edify/library/document/readme.py index 5c52ba7..f0ae663 100644 --- a/edify/library/document/readme.py +++ b/edify/library/document/readme.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -readme = RegexBackedPattern(r"^[A-Za-z0-9_.\-/]{1,256}$") +readme = ( + Pattern() + .start_of_input() + .between(1, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a readme document identifier or file name.""" diff --git a/edify/library/document/svg.py b/edify/library/document/svg.py index 261d3a4..bf28220 100644 --- a/edify/library/document/svg.py +++ b/edify/library/document/svg.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -svg = RegexBackedPattern(r"^[A-Za-z0-9_.\-/]{1,256}$") +svg = ( + Pattern() + .start_of_input() + .between(1, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a svg document identifier or file name.""" diff --git a/edify/library/document/xlsx.py b/edify/library/document/xlsx.py index 417c154..63c3af4 100644 --- a/edify/library/document/xlsx.py +++ b/edify/library/document/xlsx.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -xlsx = RegexBackedPattern(r"^[A-Za-z0-9_.\-/]{1,256}$") +xlsx = ( + Pattern() + .start_of_input() + .between(1, 256) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char(".") + .char("-") + .char("/") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a xlsx document identifier or file name.""" diff --git a/edify/library/financial/card.py b/edify/library/financial/card.py index 53c38ce..ba2ba07 100644 --- a/edify/library/financial/card.py +++ b/edify/library/financial/card.py @@ -2,9 +2,27 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -card = RegexBackedPattern(r"^\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{1,7}$") +card = ( + Pattern() + .start_of_input() + .exactly(4) + .digit() + .optional() + .any_of_chars("- ") + .exactly(4) + .digit() + .optional() + .any_of_chars("- ") + .exactly(4) + .digit() + .optional() + .any_of_chars("- ") + .between(1, 7) + .digit() + .end_of_input() +) """Callable :class:`Pattern` for credit-card number shape: groups of 4 digits with optional dash/space separators, 13-19 digits total. """ diff --git a/edify/library/financial/wallet.py b/edify/library/financial/wallet.py index 5acf7ec..6d6b106 100644 --- a/edify/library/financial/wallet.py +++ b/edify/library/financial/wallet.py @@ -2,18 +2,99 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern - -wallet = RegexBackedPattern( - r"^(?:" - r"[13][a-km-zA-HJ-NP-Z1-9]{25,34}" - r"|bc1[a-z0-9]{25,89}" - r"|0x[a-fA-F0-9]{40}" - r"|[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}" - r"|D[5-9A-HJ-NP-U][1-9A-HJ-NP-Za-km-z]{32}" - r"|X[1-9A-HJ-NP-Za-km-z]{33}" - r")$" +from edify import Pattern, any_of + +_bitcoin_legacy = ( + Pattern() + .any_of_chars("13") + .between(25, 34) + .any_of() + .range("a", "k") + .range("m", "z") + .range("A", "H") + .range("J", "N") + .range("P", "Z") + .range("1", "9") + .end() +) + +_bitcoin_bech32 = ( + Pattern() + .string("bc1") + .between(25, 89) + .any_of() + .range("a", "z") + .range("0", "9") + .end() +) + +_ethereum = ( + Pattern() + .string("0x") + .exactly(40) + .any_of() + .range("a", "f") + .range("A", "F") + .range("0", "9") + .end() +) + +_litecoin = ( + Pattern() + .any_of_chars("LM3") + .between(26, 33) + .any_of() + .range("a", "k") + .range("m", "z") + .range("A", "H") + .range("J", "N") + .range("P", "Z") + .range("1", "9") + .end() +) + +_dogecoin = ( + Pattern() + .char("D") + .any_of() + .range("5", "9") + .range("A", "H") + .range("J", "N") + .range("P", "U") + .end() + .exactly(32) + .any_of() + .range("1", "9") + .range("A", "H") + .range("J", "N") + .range("P", "Z") + .range("a", "k") + .range("m", "z") + .end() +) + +_dash = ( + Pattern() + .char("X") + .exactly(33) + .any_of() + .range("1", "9") + .range("A", "H") + .range("J", "N") + .range("P", "Z") + .range("a", "k") + .range("m", "z") + .end() +) + +wallet = ( + Pattern() + .start_of_input() + .subexpression(any_of(_bitcoin_legacy, _bitcoin_bech32, _ethereum, _litecoin, _dogecoin, _dash)) + .end_of_input() ) """Callable :class:`Pattern` for cryptocurrency-wallet address shapes: Bitcoin (legacy, SegWit, Bech32), Ethereum, Litecoin, Dogecoin, Dash. """ + +del _bitcoin_legacy, _bitcoin_bech32, _ethereum, _litecoin, _dogecoin, _dash diff --git a/edify/library/geo/altitude.py b/edify/library/geo/altitude.py index 38b89de..cdbe2eb 100644 --- a/edify/library/geo/altitude.py +++ b/edify/library/geo/altitude.py @@ -2,7 +2,25 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -altitude = RegexBackedPattern(r"^-?\d+(?:\.\d+)?\s?(?:m|ft|km|mi)?$") +altitude = ( + Pattern() + .start_of_input() + .optional() + .char("-") + .one_or_more() + .digit() + .optional() + .group() + .char(".") + .one_or_more() + .digit() + .end() + .optional() + .whitespace_char() + .optional() + .any_of("m", "ft", "km", "mi") + .end_of_input() +) """Callable :class:`Pattern` for a signed altitude value with optional unit.""" diff --git a/edify/library/geo/coordinate.py b/edify/library/geo/coordinate.py index a6bf066..d69bdd4 100644 --- a/edify/library/geo/coordinate.py +++ b/edify/library/geo/coordinate.py @@ -2,10 +2,81 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -coordinate = RegexBackedPattern( - r"^-?(?:90(?:\.0+)?|[0-8]?\d(?:\.\d+)?)\s*,\s*" - r"-?(?:180(?:\.0+)?|(?:1[0-7]\d|[0-9]?\d)(?:\.\d+)?)$" +_lat_ninety = ( + Pattern() + .string("90") + .optional() + .group() + .char(".") + .one_or_more() + .char("0") + .end() +) + +_lat_below_ninety = ( + Pattern() + .optional() + .range("0", "8") + .digit() + .optional() + .group() + .char(".") + .one_or_more() + .digit() + .end() +) + +_lon_one_eighty = ( + Pattern() + .string("180") + .optional() + .group() + .char(".") + .one_or_more() + .char("0") + .end() +) + +_lon_below_one_eighty_integer = any_of( + Pattern().char("1").range("0", "7").digit(), + Pattern().optional().range("0", "9").digit(), +) + +_lon_below_one_eighty = ( + Pattern() + .subexpression(_lon_below_one_eighty_integer) + .optional() + .group() + .char(".") + .one_or_more() + .digit() + .end() +) + +coordinate = ( + Pattern() + .start_of_input() + .optional() + .char("-") + .subexpression(any_of(_lat_ninety, _lat_below_ninety)) + .zero_or_more() + .whitespace_char() + .char(",") + .zero_or_more() + .whitespace_char() + .optional() + .char("-") + .subexpression(any_of(_lon_one_eighty, _lon_below_one_eighty)) + .end_of_input() ) """Callable :class:`Pattern` for the ``latitude,longitude`` coordinate shape.""" + +del ( + _lat_ninety, + _lat_below_ninety, + _lon_one_eighty, + _lon_below_one_eighty_integer, + _lon_below_one_eighty, +) diff --git a/edify/library/geo/place.py b/edify/library/geo/place.py index 8b187f3..340a2ab 100644 --- a/edify/library/geo/place.py +++ b/edify/library/geo/place.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -place = RegexBackedPattern(r"^[A-Za-z][A-Za-z .,'\-]{1,99}$") +place = ( + Pattern() + .start_of_input() + .letter() + .between(1, 99) + .any_of() + .range("A", "Z") + .range("a", "z") + .char(" ") + .char(".") + .char(",") + .char("'") + .char("-") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive place-name shape.""" diff --git a/edify/library/geo/plus.py b/edify/library/geo/plus.py index fe2176f..ca96a39 100644 --- a/edify/library/geo/plus.py +++ b/edify/library/geo/plus.py @@ -2,7 +2,27 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -plus = RegexBackedPattern(r"^[23456789CFGHJMPQRVWX]{2,8}\+[23456789CFGHJMPQRVWX]{2,3}(?:\s+.+)?$") +_PLUS_CODE_ALPHABET = "23456789CFGHJMPQRVWX" + +plus = ( + Pattern() + .start_of_input() + .between(2, 8) + .any_of_chars(_PLUS_CODE_ALPHABET) + .char("+") + .between(2, 3) + .any_of_chars(_PLUS_CODE_ALPHABET) + .optional() + .group() + .one_or_more() + .whitespace_char() + .one_or_more() + .any_char() + .end() + .end_of_input() +) """Callable :class:`Pattern` for a Google Plus Code (Open Location Code).""" + +del _PLUS_CODE_ALPHABET diff --git a/edify/library/grammar/abnf.py b/edify/library/grammar/abnf.py index a87f3d8..21c35ff 100644 --- a/edify/library/grammar/abnf.py +++ b/edify/library/grammar/abnf.py @@ -2,7 +2,40 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -abnf = RegexBackedPattern(r"^[A-Za-z0-9_\-<>:=|*+?()\[\]{}\s.'\"/;,]{4,65536}$") +abnf = ( + Pattern() + .start_of_input() + .between(4, 65536) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char("-") + .char("<") + .char(">") + .char(":") + .char("=") + .char("|") + .char("*") + .char("+") + .char("?") + .char("(") + .char(")") + .char("[") + .char("]") + .char("{") + .char("}") + .whitespace_char() + .char(".") + .char("'") + .char('"') + .char("/") + .char(";") + .char(",") + .end() + .end_of_input() +) """Callable :class:`Pattern` for abnf grammar-specification content.""" diff --git a/edify/library/grammar/bnf.py b/edify/library/grammar/bnf.py index 40ec7c6..aeb81b6 100644 --- a/edify/library/grammar/bnf.py +++ b/edify/library/grammar/bnf.py @@ -2,7 +2,40 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -bnf = RegexBackedPattern(r"^[A-Za-z0-9_\-<>:=|*+?()\[\]{}\s.'\"/;,]{4,65536}$") +bnf = ( + Pattern() + .start_of_input() + .between(4, 65536) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char("-") + .char("<") + .char(">") + .char(":") + .char("=") + .char("|") + .char("*") + .char("+") + .char("?") + .char("(") + .char(")") + .char("[") + .char("]") + .char("{") + .char("}") + .whitespace_char() + .char(".") + .char("'") + .char('"') + .char("/") + .char(";") + .char(",") + .end() + .end_of_input() +) """Callable :class:`Pattern` for bnf grammar-specification content.""" diff --git a/edify/library/grammar/ebnf.py b/edify/library/grammar/ebnf.py index 2492688..8055306 100644 --- a/edify/library/grammar/ebnf.py +++ b/edify/library/grammar/ebnf.py @@ -2,7 +2,40 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -ebnf = RegexBackedPattern(r"^[A-Za-z0-9_\-<>:=|*+?()\[\]{}\s.'\"/;,]{4,65536}$") +ebnf = ( + Pattern() + .start_of_input() + .between(4, 65536) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char("-") + .char("<") + .char(">") + .char(":") + .char("=") + .char("|") + .char("*") + .char("+") + .char("?") + .char("(") + .char(")") + .char("[") + .char("]") + .char("{") + .char("}") + .whitespace_char() + .char(".") + .char("'") + .char('"') + .char("/") + .char(";") + .char(",") + .end() + .end_of_input() +) """Callable :class:`Pattern` for ebnf grammar-specification content.""" diff --git a/edify/library/grammar/peg.py b/edify/library/grammar/peg.py index 74d1ffc..f06ea00 100644 --- a/edify/library/grammar/peg.py +++ b/edify/library/grammar/peg.py @@ -2,7 +2,40 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -peg = RegexBackedPattern(r"^[A-Za-z0-9_\-<>:=|*+?()\[\]{}\s.'\"/;,]{4,65536}$") +peg = ( + Pattern() + .start_of_input() + .between(4, 65536) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char("-") + .char("<") + .char(">") + .char(":") + .char("=") + .char("|") + .char("*") + .char("+") + .char("?") + .char("(") + .char(")") + .char("[") + .char("]") + .char("{") + .char("}") + .whitespace_char() + .char(".") + .char("'") + .char('"') + .char("/") + .char(";") + .char(",") + .end() + .end_of_input() +) """Callable :class:`Pattern` for peg grammar-specification content.""" diff --git a/edify/library/grammar/pest.py b/edify/library/grammar/pest.py index be083ae..dc1903f 100644 --- a/edify/library/grammar/pest.py +++ b/edify/library/grammar/pest.py @@ -2,7 +2,40 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -pest = RegexBackedPattern(r"^[A-Za-z0-9_\-<>:=|*+?()\[\]{}\s.'\"/;,]{4,65536}$") +pest = ( + Pattern() + .start_of_input() + .between(4, 65536) + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char("-") + .char("<") + .char(">") + .char(":") + .char("=") + .char("|") + .char("*") + .char("+") + .char("?") + .char("(") + .char(")") + .char("[") + .char("]") + .char("{") + .char("}") + .whitespace_char() + .char(".") + .char("'") + .char('"') + .char("/") + .char(";") + .char(",") + .end() + .end_of_input() +) """Callable :class:`Pattern` for pest grammar-specification content.""" diff --git a/edify/library/medical/medical.py b/edify/library/medical/medical.py index ef5407f..e702681 100644 --- a/edify/library/medical/medical.py +++ b/edify/library/medical/medical.py @@ -2,14 +2,28 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern - -medical = RegexBackedPattern( - r"^(?:" - r"\d{6,18}" - r"|[A-TV-Z][0-9][A-Z0-9](?:\.[A-Z0-9]{1,4})?" - r"|\d{10}" - r"|\d{1,7}-\d" - r")$" +from edify import Pattern, any_of + +_snomed = Pattern().between(6, 18).digit() +_icd = ( + Pattern() + .any_of().range("A", "T").range("V", "Z").end() + .digit() + .any_of().range("A", "Z").range("0", "9").end() + .optional() + .group() + .char(".") + .between(1, 4) + .any_of().range("A", "Z").range("0", "9").end() + .end() +) +_npi = Pattern().exactly(10).digit() +_loinc = Pattern().between(1, 7).digit().char("-").digit() + +medical = ( + Pattern() + .start_of_input() + .subexpression(any_of(_snomed, _icd, _npi, _loinc)) + .end_of_input() ) """Callable :class:`Pattern` for medical-coding-system codes.""" diff --git a/edify/library/numeric/fraction.py b/edify/library/numeric/fraction.py index 3a7123b..5cefd27 100644 --- a/edify/library/numeric/fraction.py +++ b/edify/library/numeric/fraction.py @@ -2,9 +2,27 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -fraction = RegexBackedPattern(r"^-?(?:\d+\s+)?\d+/\d+$") +fraction = ( + Pattern() + .start_of_input() + .optional() + .char("-") + .optional() + .group() + .one_or_more() + .digit() + .one_or_more() + .whitespace_char() + .end() + .one_or_more() + .digit() + .char("/") + .one_or_more() + .digit() + .end_of_input() +) """Callable :class:`Pattern` for a fraction shape: ``numerator/denominator`` with optional whole-number prefix. """ diff --git a/edify/library/product/barcode.py b/edify/library/product/barcode.py index 5502943..d107fe5 100644 --- a/edify/library/product/barcode.py +++ b/edify/library/product/barcode.py @@ -2,7 +2,16 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -barcode = RegexBackedPattern(r"^[A-Z0-9]{6,48}$") +barcode = ( + Pattern() + .start_of_input() + .between(6, 48) + .any_of() + .range("A", "Z") + .range("0", "9") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a generic barcode value shape.""" diff --git a/edify/library/product/gtin.py b/edify/library/product/gtin.py index deceb6a..b0d9198 100644 --- a/edify/library/product/gtin.py +++ b/edify/library/product/gtin.py @@ -2,7 +2,19 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -gtin = RegexBackedPattern(r"^\d{8}$|^\d{12}$|^\d{13}$|^\d{14}$") +gtin = ( + Pattern() + .start_of_input() + .subexpression( + any_of( + Pattern().exactly(8).digit(), + Pattern().exactly(12).digit(), + Pattern().exactly(13).digit(), + Pattern().exactly(14).digit(), + ) + ) + .end_of_input() +) """Callable :class:`Pattern` for the GTIN family: 8-, 12-, 13-, or 14-digit barcode number.""" diff --git a/edify/library/product/mpn.py b/edify/library/product/mpn.py index cfe42ac..8afcbe2 100644 --- a/edify/library/product/mpn.py +++ b/edify/library/product/mpn.py @@ -2,7 +2,23 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -mpn = RegexBackedPattern(r"^[A-Z0-9][A-Z0-9\-_.]{1,63}$") +mpn = ( + Pattern() + .start_of_input() + .any_of() + .range("A", "Z") + .range("0", "9") + .end() + .between(1, 63) + .any_of() + .range("A", "Z") + .range("0", "9") + .char("-") + .char("_") + .char(".") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive Manufacturer Part Number.""" diff --git a/edify/library/publishing/arxiv.py b/edify/library/publishing/arxiv.py index 481479a..c65f7cb 100644 --- a/edify/library/publishing/arxiv.py +++ b/edify/library/publishing/arxiv.py @@ -2,9 +2,47 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -arxiv = RegexBackedPattern( - r"^(?:\d{4}\.\d{4,5}(?:v\d+)?|[a-z]{2,10}(?:\.[A-Z]{2})?/\d{7}(?:v\d+)?)$" +_new = ( + Pattern() + .exactly(4) + .digit() + .char(".") + .between(4, 5) + .digit() + .optional() + .group() + .char("v") + .one_or_more() + .digit() + .end() +) +_old = ( + Pattern() + .between(2, 10) + .lowercase() + .optional() + .group() + .char(".") + .exactly(2) + .uppercase() + .end() + .char("/") + .exactly(7) + .digit() + .optional() + .group() + .char("v") + .one_or_more() + .digit() + .end() +) + +arxiv = ( + Pattern() + .start_of_input() + .subexpression(any_of(_new, _old)) + .end_of_input() ) """Callable :class:`Pattern` for an arXiv identifier.""" diff --git a/edify/library/publishing/doi.py b/edify/library/publishing/doi.py index ce7c34e..39689d8 100644 --- a/edify/library/publishing/doi.py +++ b/edify/library/publishing/doi.py @@ -2,7 +2,29 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -doi = RegexBackedPattern(r"^10\.\d{4,9}/[-._;()/:A-Za-z0-9]+$") +doi = ( + Pattern() + .start_of_input() + .string("10.") + .between(4, 9) + .digit() + .char("/") + .one_or_more() + .any_of() + .char("-") + .char(".") + .char("_") + .char(";") + .char("(") + .char(")") + .char("/") + .char(":") + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .end() + .end_of_input() +) """Callable :class:`Pattern` for the DOI shape.""" diff --git a/edify/library/publishing/isbn.py b/edify/library/publishing/isbn.py index 379c9a8..ac9e170 100644 --- a/edify/library/publishing/isbn.py +++ b/edify/library/publishing/isbn.py @@ -2,7 +2,36 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -isbn = RegexBackedPattern(r"^(?:\d[- ]?){9}[\dXx]$|^(?:\d[- ]?){12}\d$") +_isbn10 = ( + Pattern() + .start_of_input() + .exactly(9) + .group() + .digit() + .optional() + .any_of_chars("- ") + .end() + .any_of() + .digit() + .char("X") + .char("x") + .end() + .end_of_input() +) +_isbn13 = ( + Pattern() + .start_of_input() + .exactly(12) + .group() + .digit() + .optional() + .any_of_chars("- ") + .end() + .digit() + .end_of_input() +) + +isbn = any_of(_isbn10, _isbn13) """Callable :class:`Pattern` for ISBN-10 or ISBN-13 shape.""" diff --git a/edify/library/publishing/issn.py b/edify/library/publishing/issn.py index 53216d7..98a2998 100644 --- a/edify/library/publishing/issn.py +++ b/edify/library/publishing/issn.py @@ -2,7 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -issn = RegexBackedPattern(r"^\d{4}-\d{3}[\dXx]$") +issn = ( + Pattern() + .start_of_input() + .exactly(4) + .digit() + .char("-") + .exactly(3) + .digit() + .any_of() + .digit() + .char("X") + .char("x") + .end() + .end_of_input() +) """Callable :class:`Pattern` for the ISSN shape.""" diff --git a/edify/library/publishing/pmc.py b/edify/library/publishing/pmc.py index dc97b0b..f70e8a8 100644 --- a/edify/library/publishing/pmc.py +++ b/edify/library/publishing/pmc.py @@ -2,7 +2,14 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -pmc = RegexBackedPattern(r"^PMC\d{1,9}$") +pmc = ( + Pattern() + .start_of_input() + .string("PMC") + .between(1, 9) + .digit() + .end_of_input() +) """Callable :class:`Pattern` for a PubMed Central identifier.""" diff --git a/edify/library/publishing/pmid.py b/edify/library/publishing/pmid.py index 968ad9a..85134ef 100644 --- a/edify/library/publishing/pmid.py +++ b/edify/library/publishing/pmid.py @@ -2,7 +2,13 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -pmid = RegexBackedPattern(r"^\d{1,8}$") +pmid = ( + Pattern() + .start_of_input() + .between(1, 8) + .digit() + .end_of_input() +) """Callable :class:`Pattern` for a PubMed identifier (1-8 digits).""" diff --git a/edify/library/transport/aircraft.py b/edify/library/transport/aircraft.py index 86ff2cd..a49f949 100644 --- a/edify/library/transport/aircraft.py +++ b/edify/library/transport/aircraft.py @@ -2,7 +2,20 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -aircraft = RegexBackedPattern(r"^[A-Z]{1,2}-?[A-Z0-9]{1,5}$") +aircraft = ( + Pattern() + .start_of_input() + .between(1, 2) + .uppercase() + .optional() + .char("-") + .between(1, 5) + .any_of() + .range("A", "Z") + .range("0", "9") + .end() + .end_of_input() +) """Callable :class:`Pattern` for an aircraft-registration mark.""" diff --git a/edify/library/transport/vehicle.py b/edify/library/transport/vehicle.py index 7fc3c0c..1bc17b3 100644 --- a/edify/library/transport/vehicle.py +++ b/edify/library/transport/vehicle.py @@ -2,7 +2,22 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -vehicle = RegexBackedPattern(r"^[A-Z0-9][A-Z0-9\- ]{3,17}$") +vehicle = ( + Pattern() + .start_of_input() + .any_of() + .range("A", "Z") + .range("0", "9") + .end() + .between(3, 17) + .any_of() + .range("A", "Z") + .range("0", "9") + .char("-") + .char(" ") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a permissive transport-vehicle identifier.""" |
