diff options
| author | natsuoto <[email protected]> | 2026-07-11 16:50:55 +0530 |
|---|---|---|
| committer | natsuoto <[email protected]> | 2026-07-11 16:50:55 +0530 |
| commit | df4322c63b2937f803d1f35705f73db200b6dec7 (patch) | |
| tree | 4b150a08fd658c6a2d99d9e34ef449ca845106e7 | |
| parent | 1d751146b2435fc5baf8cd903d2f88d2ac9ebd83 (diff) | |
| download | edify-df4322c63b2937f803d1f35705f73db200b6dec7.tar.xz edify-df4322c63b2937f803d1f35705f73db200b6dec7.zip | |
refactor(library): rewrite temporal/, text/ validators to fluent Pattern() chain
| -rw-r--r-- | edify/library/temporal/cron.py | 48 | ||||
| -rw-r--r-- | edify/library/temporal/date.py | 25 | ||||
| -rw-r--r-- | edify/library/temporal/datetime.py | 47 | ||||
| -rw-r--r-- | edify/library/temporal/duration.py | 43 | ||||
| -rw-r--r-- | edify/library/temporal/epoch.py | 12 | ||||
| -rw-r--r-- | edify/library/temporal/interval.py | 71 | ||||
| -rw-r--r-- | edify/library/temporal/offset.py | 23 | ||||
| -rw-r--r-- | edify/library/temporal/time.py | 36 | ||||
| -rw-r--r-- | edify/library/temporal/timestamp.py | 12 | ||||
| -rw-r--r-- | edify/library/temporal/timezone.py | 42 | ||||
| -rw-r--r-- | edify/library/text/alpha.py | 10 | ||||
| -rw-r--r-- | edify/library/text/alphanumeric.py | 10 | ||||
| -rw-r--r-- | edify/library/text/ascii.py | 14 | ||||
| -rw-r--r-- | edify/library/text/base.py | 71 | ||||
| -rw-r--r-- | edify/library/text/emoji.py | 15 | ||||
| -rw-r--r-- | edify/library/text/numeric.py | 10 | ||||
| -rw-r--r-- | edify/library/text/printable.py | 28 | ||||
| -rw-r--r-- | edify/library/text/script.py | 42 | ||||
| -rw-r--r-- | edify/library/text/slug.py | 22 | ||||
| -rw-r--r-- | edify/library/text/unicode.py | 26 | ||||
| -rw-r--r-- | edify/library/text/word.py | 14 |
21 files changed, 514 insertions, 107 deletions
diff --git a/edify/library/temporal/cron.py b/edify/library/temporal/cron.py index fc50172..ada3d89 100644 --- a/edify/library/temporal/cron.py +++ b/edify/library/temporal/cron.py @@ -2,11 +2,51 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -cron = RegexBackedPattern( - r"^(?:@(?:annually|yearly|monthly|weekly|daily|hourly|reboot)" - r"|(?:[*?\d/,\-]+\s+){4,5}[*?\d/,\-]+)$" +_alias = ( + Pattern() + .char("@") + .group() + .any_of() + .string("annually") + .string("yearly") + .string("monthly") + .string("weekly") + .string("daily") + .string("hourly") + .string("reboot") + .end() + .end() +) +_field = ( + Pattern() + .one_or_more() + .any_of() + .char("*") + .char("?") + .range("0", "9") + .char("/") + .char(",") + .char("-") + .end() +) +_expr = ( + Pattern() + .between(4, 5) + .group() + .subexpression(_field) + .one_or_more() + .whitespace_char() + .end() + .subexpression(_field) +) + +cron = ( + Pattern() + .start_of_input() + .subexpression(any_of(_alias, _expr)) + .end_of_input() ) """Callable :class:`Pattern` for cron-expression shapes: shortcut aliases (``@daily`` etc.) or 5-/6-field whitespace-separated expressions. diff --git a/edify/library/temporal/date.py b/edify/library/temporal/date.py index 07e64de..f92eaf9 100644 --- a/edify/library/temporal/date.py +++ b/edify/library/temporal/date.py @@ -2,18 +2,21 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -date = RegexBackedPattern( - r"^(?:" - r"\d{1,2}/\d{1,2}/\d{4}" - r"|\d{4}-\d{2}-\d{2}" - r"|\d{2}-\d{2}-\d{4}" - r"|\d{4}/\d{2}/\d{2}" - r"|\d{1,2}\.\d{1,2}\.\d{4}" - r"|\d{4}\.\d{2}\.\d{2}" - r"|\d{8}" - r")$" +_d1 = Pattern().between(1, 2).digit().char("/").between(1, 2).digit().char("/").exactly(4).digit() +_d2 = Pattern().exactly(4).digit().char("-").exactly(2).digit().char("-").exactly(2).digit() +_d3 = Pattern().exactly(2).digit().char("-").exactly(2).digit().char("-").exactly(4).digit() +_d4 = Pattern().exactly(4).digit().char("/").exactly(2).digit().char("/").exactly(2).digit() +_d5 = Pattern().between(1, 2).digit().char(".").between(1, 2).digit().char(".").exactly(4).digit() +_d6 = Pattern().exactly(4).digit().char(".").exactly(2).digit().char(".").exactly(2).digit() +_d7 = Pattern().exactly(8).digit() + +date = ( + Pattern() + .start_of_input() + .subexpression(any_of(_d1, _d2, _d3, _d4, _d5, _d6, _d7)) + .end_of_input() ) """Callable :class:`Pattern` for calendar-date shapes: ``M/D/YYYY``, ``YYYY-MM-DD``, ``DD-MM-YYYY``, ``YYYY/MM/DD``, diff --git a/edify/library/temporal/datetime.py b/edify/library/temporal/datetime.py index a1aa6ec..1e659d3 100644 --- a/edify/library/temporal/datetime.py +++ b/edify/library/temporal/datetime.py @@ -2,14 +2,45 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern - -datetime = RegexBackedPattern( - r"^(?:" - r"\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?" - r"(?:[Zz]|[+-]\d{2}:?\d{2})?" - r"|\d{4}\d{2}\d{2}[Tt]\d{2}\d{2}\d{2}(?:[Zz]|[+-]\d{4})?" - r")$" +from edify import Pattern, any_of + + +def _iso_extended() -> Pattern: + return ( + Pattern() + .exactly(4).digit().char("-").exactly(2).digit().char("-").exactly(2).digit() + .any_of_chars("Tt ") + .exactly(2).digit().char(":").exactly(2).digit() + .optional().group().char(":").exactly(2).digit() + .optional().group().char(".").one_or_more().digit().end() + .end() + .optional().group().any_of() + .any_of_chars("Zz") + .subexpression( + Pattern().any_of_chars("+-").exactly(2).digit().optional().char(":").exactly(2).digit() + ) + .end().end() + ) + + +def _iso_basic() -> Pattern: + return ( + Pattern() + .exactly(4).digit().exactly(2).digit().exactly(2).digit() + .any_of_chars("Tt") + .exactly(2).digit().exactly(2).digit().exactly(2).digit() + .optional().group().any_of() + .any_of_chars("Zz") + .subexpression(Pattern().any_of_chars("+-").exactly(4).digit()) + .end().end() + ) + + +datetime = ( + Pattern() + .start_of_input() + .subexpression(any_of(_iso_extended(), _iso_basic())) + .end_of_input() ) """Callable :class:`Pattern` for combined date-time shapes: ISO 8601 / RFC 3339 forms with ``T`` or space separator, optional fractional seconds, diff --git a/edify/library/temporal/duration.py b/edify/library/temporal/duration.py index 640812b..40129fe 100644 --- a/edify/library/temporal/duration.py +++ b/edify/library/temporal/duration.py @@ -2,13 +2,44 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -duration = RegexBackedPattern( - r"^P(?!$)(?:\d+(?:\.\d+)?Y)?(?:\d+(?:\.\d+)?M)?" - r"(?:\d+(?:\.\d+)?W)?(?:\d+(?:\.\d+)?D)?" - r"(?:T(?=\d)(?:\d+(?:\.\d+)?H)?(?:\d+(?:\.\d+)?M)?" - r"(?:\d+(?:\.\d+)?S)?)?$" + +def _num_with_letter(letter: str) -> Pattern: + return ( + Pattern() + .optional() + .group() + .one_or_more().digit() + .optional().group().char(".").one_or_more().digit().end() + .char(letter) + .end() + ) + + +def _duration_body() -> Pattern: + return ( + Pattern() + .char("P") + .assert_ahead().any_char().end() + .subexpression(_num_with_letter("Y")) + .subexpression(_num_with_letter("M")) + .subexpression(_num_with_letter("W")) + .subexpression(_num_with_letter("D")) + .optional().group() + .char("T").assert_ahead().digit().end() + .subexpression(_num_with_letter("H")) + .subexpression(_num_with_letter("M")) + .subexpression(_num_with_letter("S")) + .end() + ) + + +duration = ( + Pattern() + .start_of_input() + .subexpression(_duration_body()) + .end_of_input() ) """Callable :class:`Pattern` for the ISO 8601 duration shape: ``PnYnMnDTnHnMnS`` with optional fractional components. diff --git a/edify/library/temporal/epoch.py b/edify/library/temporal/epoch.py index 859dc01..fec49bb 100644 --- a/edify/library/temporal/epoch.py +++ b/edify/library/temporal/epoch.py @@ -2,9 +2,17 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -epoch = RegexBackedPattern(r"^-?\d{1,10}$") +epoch = ( + Pattern() + .start_of_input() + .optional() + .char("-") + .between(1, 10) + .digit() + .end_of_input() +) """Callable :class:`Pattern` for a Unix epoch-seconds value: optional sign followed by 1-10 digits (fits in a 32-bit signed integer). """ diff --git a/edify/library/temporal/interval.py b/edify/library/temporal/interval.py index fddf317..e1397b6 100644 --- a/edify/library/temporal/interval.py +++ b/edify/library/temporal/interval.py @@ -2,19 +2,64 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern - -interval = RegexBackedPattern( - r"^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?" - r"(?:[Zz]|[+-]\d{2}:?\d{2})?" - r"/" - r"(?:\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?" - r"(?:[Zz]|[+-]\d{2}:?\d{2})?" - r"|P(?!$)(?:\d+(?:\.\d+)?Y)?(?:\d+(?:\.\d+)?M)?" - r"(?:\d+(?:\.\d+)?W)?(?:\d+(?:\.\d+)?D)?" - r"(?:T(?=\d)(?:\d+(?:\.\d+)?H)?(?:\d+(?:\.\d+)?M)?" - r"(?:\d+(?:\.\d+)?S)?)?" - r")$" +from edify import Pattern, any_of + + +def _iso_extended() -> Pattern: + return ( + Pattern() + .exactly(4).digit().char("-").exactly(2).digit().char("-").exactly(2).digit() + .any_of_chars("Tt ") + .exactly(2).digit().char(":").exactly(2).digit() + .optional().group().char(":").exactly(2).digit() + .optional().group().char(".").one_or_more().digit().end() + .end() + .optional().group().any_of() + .any_of_chars("Zz") + .subexpression( + Pattern().any_of_chars("+-").exactly(2).digit().optional().char(":").exactly(2).digit() + ) + .end().end() + ) + + +def _num_with_letter(letter: str) -> Pattern: + return ( + Pattern() + .optional() + .group() + .one_or_more().digit() + .optional().group().char(".").one_or_more().digit().end() + .char(letter) + .end() + ) + + +def _duration_body() -> Pattern: + return ( + Pattern() + .char("P") + .assert_ahead().any_char().end() + .subexpression(_num_with_letter("Y")) + .subexpression(_num_with_letter("M")) + .subexpression(_num_with_letter("W")) + .subexpression(_num_with_letter("D")) + .optional().group() + .char("T").assert_ahead().digit().end() + .subexpression(_num_with_letter("H")) + .subexpression(_num_with_letter("M")) + .subexpression(_num_with_letter("S")) + .end() + ) + + +interval = ( + Pattern() + .start_of_input() + .subexpression(_iso_extended()) + .char("/") + .subexpression(any_of(_iso_extended(), _duration_body())) + .end_of_input() ) """Callable :class:`Pattern` for the ISO 8601 time-interval shape: ``start-datetime/end-datetime`` or ``start-datetime/duration``. diff --git a/edify/library/temporal/offset.py b/edify/library/temporal/offset.py index adcf642..b72bf40 100644 --- a/edify/library/temporal/offset.py +++ b/edify/library/temporal/offset.py @@ -2,9 +2,28 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -offset = RegexBackedPattern(r"^(?:Z|[+-](?:0\d|1[0-4]):?[0-5]\d)$") +_hh = any_of( + Pattern().char("0").digit(), + Pattern().char("1").range("0", "4"), +) + +offset = ( + Pattern() + .start_of_input() + .any_of() + .char("Z") + .subexpression( + Pattern() + .any_of_chars("+-") + .subexpression(_hh) + .optional().char(":") + .range("0", "5").digit() + ) + .end() + .end_of_input() +) """Callable :class:`Pattern` for the UTC-offset shape: ``Z`` for UTC or ``±HH:MM`` / ``±HHMM`` with range ``-14:00`` to ``+14:00``. """ diff --git a/edify/library/temporal/time.py b/edify/library/temporal/time.py index 3389f4b..5b2b2f5 100644 --- a/edify/library/temporal/time.py +++ b/edify/library/temporal/time.py @@ -2,13 +2,37 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -time = RegexBackedPattern( - r"^(?:" - r"(?:2[0-3]|[01]?\d):[0-5]\d(?::[0-5]\d(?:\.\d{1,6})?)?" - r"|(?:1[0-2]|0?[1-9]):[0-5]\d(?::[0-5]\d)?\s?[AaPp][Mm]" - r")$" +_h24 = ( + Pattern() + .any_of() + .subexpression(Pattern().char("2").range("0", "3")) + .subexpression(Pattern().optional().any_of_chars("01").digit()) + .end() + .char(":").range("0", "5").digit() + .optional().group().char(":").range("0", "5").digit() + .optional().group().char(".").between(1, 6).digit().end() + .end() +) + +_h12 = ( + Pattern() + .any_of() + .subexpression(Pattern().char("1").range("0", "2")) + .subexpression(Pattern().optional().char("0").range("1", "9")) + .end() + .char(":").range("0", "5").digit() + .optional().group().char(":").range("0", "5").digit().end() + .optional().whitespace_char() + .any_of_chars("AaPp").any_of_chars("Mm") +) + +time = ( + Pattern() + .start_of_input() + .subexpression(any_of(_h24, _h12)) + .end_of_input() ) """Callable :class:`Pattern` for clock-time shapes: 24-hour ``HH:MM[:SS[.ffffff]]`` or 12-hour ``H:MM[:SS] AM/PM``. diff --git a/edify/library/temporal/timestamp.py b/edify/library/temporal/timestamp.py index 8be8d7a..2cf4ed2 100644 --- a/edify/library/temporal/timestamp.py +++ b/edify/library/temporal/timestamp.py @@ -2,9 +2,17 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -timestamp = RegexBackedPattern(r"^-?\d{10,13}$") +timestamp = ( + Pattern() + .start_of_input() + .optional() + .char("-") + .between(10, 13) + .digit() + .end_of_input() +) """Callable :class:`Pattern` for a Unix epoch timestamp in seconds or milliseconds: optional sign followed by 10-13 digits. """ diff --git a/edify/library/temporal/timezone.py b/edify/library/temporal/timezone.py index b582136..2bf5b54 100644 --- a/edify/library/temporal/timezone.py +++ b/edify/library/temporal/timezone.py @@ -2,14 +2,42 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -timezone = RegexBackedPattern( - r"^(?:" - r"[A-Z][a-zA-Z_+\-]+(?:/[A-Z][a-zA-Z_+\-]+)+" - r"|UTC|GMT|UT|Z" - r"|[A-Z]{2,5}" - r")$" +_iana_seg = ( + Pattern() + .uppercase() + .one_or_more() + .any_of() + .range("a", "z") + .range("A", "Z") + .char("_") + .char("+") + .char("-") + .end() +) +_iana = ( + Pattern() + .subexpression(_iana_seg) + .one_or_more() + .group() + .char("/") + .subexpression(_iana_seg) + .end() +) +_short = any_of( + Pattern().string("UTC"), + Pattern().string("GMT"), + Pattern().string("UT"), + Pattern().string("Z"), +) +_abbrev = Pattern().between(2, 5).uppercase() + +timezone = ( + Pattern() + .start_of_input() + .subexpression(any_of(_iana, _short, _abbrev)) + .end_of_input() ) """Callable :class:`Pattern` for the timezone shape: IANA region/city (``America/Los_Angeles``), or short abbreviation diff --git a/edify/library/text/alpha.py b/edify/library/text/alpha.py index ea5f78f..538aff8 100644 --- a/edify/library/text/alpha.py +++ b/edify/library/text/alpha.py @@ -2,7 +2,13 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -alpha = RegexBackedPattern(r"^[A-Za-z]+$") +alpha = ( + Pattern() + .start_of_input() + .one_or_more() + .letter() + .end_of_input() +) """Callable :class:`Pattern` for a letters-only string.""" diff --git a/edify/library/text/alphanumeric.py b/edify/library/text/alphanumeric.py index 7bc6fad..6d3cadc 100644 --- a/edify/library/text/alphanumeric.py +++ b/edify/library/text/alphanumeric.py @@ -2,7 +2,13 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -alphanumeric = RegexBackedPattern(r"^[A-Za-z0-9]+$") +alphanumeric = ( + Pattern() + .start_of_input() + .one_or_more() + .alphanumeric() + .end_of_input() +) """Callable :class:`Pattern` for a letters-and-digits-only string.""" diff --git a/edify/library/text/ascii.py b/edify/library/text/ascii.py index 700963d..4412196 100644 --- a/edify/library/text/ascii.py +++ b/edify/library/text/ascii.py @@ -1,10 +1,16 @@ -"""``ascii`` — printable-ASCII-only string shape.""" +"""``ascii`` — printable-ASCII string shape.""" from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -ascii = RegexBackedPattern(r"^[\x20-\x7E]+$") +ascii = ( + Pattern() + .start_of_input() + .one_or_more() + .range("\x20", "\x7E") + .end_of_input() +) """Callable :class:`Pattern` for a printable-ASCII-only string -(characters ``0x20``-``0x7E``). +(``0x20``-``0x7E`` — space through tilde). """ diff --git a/edify/library/text/base.py b/edify/library/text/base.py index 526d94e..959d7a0 100644 --- a/edify/library/text/base.py +++ b/edify/library/text/base.py @@ -2,16 +2,69 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -base = RegexBackedPattern( - r"^(?:" - r"[0-9A-Fa-f]+" - r"|[A-Z2-7]+=*" - r"|[1-9A-HJ-NP-Za-km-z]+" - r"|[A-Za-z0-9+/]+=*" - r"|[A-Za-z0-9_-]+" - r")$" +_base16 = ( + Pattern() + .one_or_more() + .any_of() + .range("0", "9") + .range("A", "F") + .range("a", "f") + .end() +) +_base32 = ( + Pattern() + .one_or_more() + .any_of() + .range("A", "Z") + .range("2", "7") + .end() + .zero_or_more() + .char("=") +) +_base58 = ( + Pattern() + .one_or_more() + .any_of() + .range("1", "9") + .range("A", "H") + .range("J", "N") + .range("P", "Z") + .range("a", "k") + .range("m", "z") + .end() +) +_base64 = ( + Pattern() + .one_or_more() + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("+") + .char("/") + .end() + .zero_or_more() + .char("=") +) +_base64url = ( + Pattern() + .one_or_more() + .any_of() + .range("A", "Z") + .range("a", "z") + .range("0", "9") + .char("_") + .char("-") + .end() +) + +base = ( + Pattern() + .start_of_input() + .subexpression(any_of(_base16, _base32, _base58, _base64, _base64url)) + .end_of_input() ) """Callable :class:`Pattern` that accepts any of base16, base32, base58, base64, or base64url encoded strings. diff --git a/edify/library/text/emoji.py b/edify/library/text/emoji.py index 7f55330..ec1caa8 100644 --- a/edify/library/text/emoji.py +++ b/edify/library/text/emoji.py @@ -1,8 +1,17 @@ -"""``emoji`` — one-or-more emoji-character shape.""" +"""``emoji`` — Unicode-emoji run shape.""" from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -emoji = RegexBackedPattern(r"^[\U0001F300-\U0001FAFF☀-➿]+$") +emoji = ( + Pattern() + .start_of_input() + .one_or_more() + .any_of() + .range("\U0001F300", "\U0001FAFF") + .range("☀", "➿") + .end() + .end_of_input() +) """Callable :class:`Pattern` for a run of one or more emoji characters.""" diff --git a/edify/library/text/numeric.py b/edify/library/text/numeric.py index 1bc5485..67b6bc5 100644 --- a/edify/library/text/numeric.py +++ b/edify/library/text/numeric.py @@ -2,7 +2,13 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -numeric = RegexBackedPattern(r"^\d+$") +numeric = ( + Pattern() + .start_of_input() + .one_or_more() + .digit() + .end_of_input() +) """Callable :class:`Pattern` for a digits-only string.""" diff --git a/edify/library/text/printable.py b/edify/library/text/printable.py index f729e91..96e2382 100644 --- a/edify/library/text/printable.py +++ b/edify/library/text/printable.py @@ -1,10 +1,30 @@ -"""``printable`` — printable-character string shape (excludes control codes).""" +"""``printable`` — printable-character string shape.""" from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -printable = RegexBackedPattern(r"^[^\x00-\x1F\x7F]+$") + +def _not_ctrl() -> Pattern: + return ( + Pattern() + .assert_not_ahead() + .any_of() + .range("\x00", "\x1F") + .char("\x7F") + .end() + .end() + .any_char() + ) + + +printable = ( + Pattern() + .start_of_input() + .one_or_more() + .subexpression(_not_ctrl()) + .end_of_input() +) """Callable :class:`Pattern` for a printable-character string -(excludes ASCII control codes). +(anything except ASCII control codes ``0x00``-``0x1F`` and ``0x7F``). """ diff --git a/edify/library/text/script.py b/edify/library/text/script.py index 0a5e1fd..d5e053d 100644 --- a/edify/library/text/script.py +++ b/edify/library/text/script.py @@ -2,18 +2,38 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern, any_of -script = RegexBackedPattern( - r"^(?:" - r"[A-Za-zÀ-ɏ]+" - r"|[Ѐ-ӿ]+" - r"|[Ͱ-Ͽ]+" - r"|[一-鿿-ゟ゠-ヿ가-]+" # noqa: RUF001 - r"|[-ۿ]+" - r"|[-]+" - r"|[ऀ-ॿ]+" - r")$" +_latin = ( + Pattern() + .one_or_more() + .any_of() + .range("A", "Z") + .range("a", "z") + .range("À", "ɏ") + .end() +) +_cyrillic = Pattern().one_or_more().range("Ѐ", "ӿ") +_greek = Pattern().one_or_more().range("Ͱ", "Ͽ") +_cjk = ( + Pattern() + .one_or_more() + .any_of() + .range("一", "鿿") + .range("", "ゟ") + .range("゠", "ヿ") # noqa: RUF001 + .range("가", "") + .end() +) +_arabic = Pattern().one_or_more().range("", "ۿ") +_hebrew = Pattern().one_or_more().range("", "") +_devanagari = Pattern().one_or_more().range("ऀ", "ॿ") + +script = ( + Pattern() + .start_of_input() + .subexpression(any_of(_latin, _cyrillic, _greek, _cjk, _arabic, _hebrew, _devanagari)) + .end_of_input() ) """Callable :class:`Pattern` for text in a single common Unicode script: Latin (with extensions), Cyrillic, Greek, CJK (Chinese/Japanese/Korean), diff --git a/edify/library/text/slug.py b/edify/library/text/slug.py index 7d45a76..0537a3b 100644 --- a/edify/library/text/slug.py +++ b/edify/library/text/slug.py @@ -2,9 +2,27 @@ from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -slug = RegexBackedPattern(r"^[a-z0-9]+(?:-[a-z0-9]+)*$") +slug = ( + Pattern() + .start_of_input() + .one_or_more() + .any_of() + .range("a", "z") + .range("0", "9") + .end() + .zero_or_more() + .group() + .char("-") + .one_or_more() + .any_of() + .range("a", "z") + .range("0", "9") + .end() + .end() + .end_of_input() +) """Callable :class:`Pattern` for a URL-safe slug: lowercase alphanumerics separated by single hyphens. """ diff --git a/edify/library/text/unicode.py b/edify/library/text/unicode.py index cc1eacd..a6d95ae 100644 --- a/edify/library/text/unicode.py +++ b/edify/library/text/unicode.py @@ -1,8 +1,28 @@ -"""``unicode`` — any Unicode-letter-or-digit string shape.""" +"""``unicode`` — Unicode string free of control codes.""" from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -unicode = RegexBackedPattern(r"^[^\x00-\x1F\x7F]+$") + +def _not_ctrl() -> Pattern: + return ( + Pattern() + .assert_not_ahead() + .any_of() + .range("\x00", "\x1F") + .char("\x7F") + .end() + .end() + .any_char() + ) + + +unicode = ( + Pattern() + .start_of_input() + .one_or_more() + .subexpression(_not_ctrl()) + .end_of_input() +) """Callable :class:`Pattern` for any Unicode string containing no control codes.""" diff --git a/edify/library/text/word.py b/edify/library/text/word.py index 44ee068..7fbaff6 100644 --- a/edify/library/text/word.py +++ b/edify/library/text/word.py @@ -1,10 +1,16 @@ -"""``word`` — Python word-character string shape (``\\w+``).""" +"""``word`` — word-character string shape.""" from __future__ import annotations -from edify.library._support.regex import RegexBackedPattern +from edify import Pattern -word = RegexBackedPattern(r"^\w+$") +word = ( + Pattern() + .start_of_input() + .one_or_more() + .word() + .end_of_input() +) """Callable :class:`Pattern` for a word-character string: -letters, digits, and underscores. +letters, digits, and underscore. """ |
