aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authornatsuoto <[email protected]>2026-06-26 19:23:09 +0530
committernatsuoto <[email protected]>2026-06-26 19:23:09 +0530
commit39f16caea1a92c9fb3e7d9c55cdcc20a058174ee (patch)
treef51cbe04af8a387d7d6e2e96eb2a746cca318669 /tests
parent21ec7908c94839fff63b5275871cf84e92787a08 (diff)
downloadedify-39f16caea1a92c9fb3e7d9c55cdcc20a058174ee.tar.xz
edify-39f16caea1a92c9fb3e7d9c55cdcc20a058174ee.zip
chore!: package reorg, toolchain migration to uv+ruff+pytest+hatchling, Python 3.11 floor
Drops the `src/` layout. The package lives at `edify/` at repo root, split into single-purpose subpackages: `builder/` (mixins composing RegexBuilder), `elements/` (sealed dataclass AST), `compile/` (per-family render functions plus dispatch), `validate/`, `errors/` (typed exception hierarchy), and `library/` (per-validator files in family folders). Pattern, serialize, and result subpackages ship as skeletons. Replaces tox / virtualenv / flake8+isort+black / setup.py+setup.cfg / pytest.ini / .coveragerc / .bumpversion.cfg / MANIFEST.in / ci/ / tests.local.sh with a unified uv + ruff + pytest + hatchling + hatch-vcs stack driven entirely from pyproject.toml. CI workflow rewritten to `uv sync --frozen` + `uv run pytest` per matrix entry; required-status- check names preserved for `check`, `docs`, `py3X (ubuntu)`, and `pypy311 (ubuntu)`. `py39` / `py310` / `pypy310` dropped (3.11 floor). Pre-commit replaced with ruff hooks (lint + format), all hook repos SHA-pinned to commit SHAs. Tests reorganised under `tests/builder/` and `tests/library/<family>/` with the `*.test.py` naming shape. 173 tests pass, 100% line coverage. Behaviour changes versus 0.3 (each will get a documented upgrade-guide entry once #80 lands): - `to_regex_string()` returns the bare pattern (no `/.../` wrap) - `to_regex()` on `named_back_reference` compiles successfully via `(?P=name)` instead of raising - builder errors raise typed `EdifyError` subclasses instead of bare `Exception` (catch via `except EdifyError`) - internal helpers (`apply_quantifier`, `frame_creating_element`, `evaluate`, `state`, etc.) removed outright per the 0.3 → 1.0 stub-free policy Closes #68 Closes #69 Closes #253 Co-Authored-By: Claude Opus 4.7 <[email protected]>
Diffstat (limited to 'tests')
-rw-r--r--tests/builder/alternation.test.py13
-rw-r--r--tests/builder/builder.test.py554
-rw-r--r--tests/builder/conflict.test.py20
-rw-r--r--tests/builder/frame.test.py17
-rw-r--r--tests/builder/merge.test.py123
-rw-r--r--tests/builder/passthrough.test.py40
-rw-r--r--tests/builder/validation.test.py157
-rw-r--r--tests/compile/invariants.test.py30
-rw-r--r--tests/errors/errors.test.py153
-rw-r--r--tests/library/date/basic.test.py29
-rw-r--r--tests/library/date/iso.test.py28
-rw-r--r--tests/library/email/basic.test.py32
-rw-r--r--tests/library/email/strict.test.py32
-rw-r--r--tests/library/guid.test.py (renamed from tests/test_guid.py)4
-rw-r--r--tests/library/ip/v4.test.py16
-rw-r--r--tests/library/ip/v6.test.py17
-rw-r--r--tests/library/mac.test.py (renamed from tests/test_mac.py)0
-rw-r--r--tests/library/password.test.py (renamed from tests/test_password.py)7
-rw-r--r--tests/library/phone.test.py (renamed from tests/test_phone.py)3
-rw-r--r--tests/library/ssn.test.py (renamed from tests/ssn_test.py)0
-rw-r--r--tests/library/url.test.py55
-rw-r--r--tests/library/uuid.test.py (renamed from tests/test_uuid.py)0
-rw-r--r--tests/library/zip.test.py43
-rw-r--r--tests/package.test.py16
-rw-r--r--tests/test_builder.py518
-rw-r--r--tests/test_date.py57
-rw-r--r--tests/test_email.py89
-rw-r--r--tests/test_ip.py35
-rw-r--r--tests/test_url.py63
-rw-r--r--tests/test_zip.py34
30 files changed, 1384 insertions, 801 deletions
diff --git a/tests/builder/alternation.test.py b/tests/builder/alternation.test.py
new file mode 100644
index 0000000..8d7fe82
--- /dev/null
+++ b/tests/builder/alternation.test.py
@@ -0,0 +1,13 @@
+"""Tests for alternation rendering branches not covered by the main builder tests."""
+
+from edify import RegexBuilder
+
+
+def test_any_of_with_only_non_fusable_members():
+ expr = RegexBuilder().any_of().string("hello").digit().end()
+ assert expr.to_regex_string() == "(?:hello|\\d)"
+
+
+def test_any_of_with_any_of_chars_member():
+ expr = RegexBuilder().any_of().any_of_chars("xyz").end()
+ assert expr.to_regex_string() == "[xyz]"
diff --git a/tests/builder/builder.test.py b/tests/builder/builder.test.py
new file mode 100644
index 0000000..062e1ae
--- /dev/null
+++ b/tests/builder/builder.test.py
@@ -0,0 +1,554 @@
+import re
+
+import pytest
+
+from edify import RegexBuilder
+from edify.errors.anchors import StartInputAlreadyDefinedError
+from edify.errors.captures import InvalidTotalCaptureGroupsIndexError
+from edify.errors.input import MustBeInstanceError, MustBeSingleCharacterError
+from edify.errors.naming import (
+ CannotCreateDuplicateNamedGroupError,
+ NamedGroupDoesNotExistError,
+ NameNotValidError,
+)
+from edify.errors.structure import CannotEndWhileBuildingRootExpressionError
+
+simple_se = RegexBuilder().string("hello").any_char().string("world")
+flags_se = RegexBuilder().multi_line().ignore_case().string("hello").any_char().string("world")
+start_end_se = (
+ RegexBuilder().start_of_input().string("hello").any_char().string("world").end_of_input()
+)
+nc_se = (
+ RegexBuilder()
+ .named_capture("module")
+ .exactly(2)
+ .any_char()
+ .end()
+ .named_back_reference("module")
+)
+indexed_back_reference_se = RegexBuilder().capture().exactly(2).any_char().end().back_reference(1)
+nested_se = RegexBuilder().exactly(2).any_char()
+first_layer_se = (
+ RegexBuilder()
+ .string("outer begin")
+ .named_capture("inner_subexpression")
+ .optional()
+ .subexpression(nested_se)
+ .end()
+ .string("outer end")
+)
+
+
+def regex_equality(regex, rb_expression):
+ regex_str = str(regex)
+ rb_expression_str = rb_expression.to_regex_string()
+ assert regex_str == str(rb_expression_str)
+
+
+def regex_compilation(regex, rb_expression, f=0):
+ rb_expression_c = rb_expression.to_regex()
+ assert re.compile(regex, flags=f) == rb_expression_c
+
+
+def test_empty_regex():
+ expr = RegexBuilder()
+ regex_equality("(?:)", expr)
+ regex_compilation("(?:)", expr)
+
+
+def test_flag_a():
+ expr = RegexBuilder().ascii_only()
+ regex_equality("(?:)", expr)
+ regex_compilation("(?:)", expr, re.A)
+
+
+def test_flag_d():
+ expr = RegexBuilder().debug()
+ regex_equality("(?:)", expr)
+ regex_compilation("(?:)", expr, re.DEBUG)
+
+
+def test_flag_i():
+ expr = RegexBuilder().ignore_case()
+ regex_equality("(?:)", expr)
+ regex_compilation("(?:)", expr, re.I)
+
+
+def test_flag_m():
+ expr = RegexBuilder().multi_line()
+ regex_equality("(?:)", expr)
+ regex_compilation("(?:)", expr, re.M)
+
+
+def test_flag_s():
+ expr = RegexBuilder().dot_all()
+ regex_equality("(?:)", expr)
+ regex_compilation("(?:)", expr, re.S)
+
+
+def test_flag_x():
+ expr = RegexBuilder().verbose()
+ regex_equality("(?:)", expr)
+ regex_compilation("(?:)", expr, re.X)
+
+
+def test_any_char():
+ expr = RegexBuilder().any_char()
+ regex_equality(".", expr)
+ regex_compilation(".", expr)
+
+
+def test_whitespace_char():
+ expr = RegexBuilder().whitespace_char()
+ regex_equality("\\s", expr)
+ regex_compilation("\\s", expr)
+
+
+def test_non_whitespace_char():
+ expr = RegexBuilder().non_whitespace_char()
+ regex_equality("\\S", expr)
+ regex_compilation("\\S", expr)
+
+
+def test_digit():
+ expr = RegexBuilder().digit()
+ regex_equality("\\d", expr)
+ regex_compilation("\\d", expr)
+
+
+def test_non_digit():
+ expr = RegexBuilder().non_digit()
+ regex_equality("\\D", expr)
+ regex_compilation("\\D", expr)
+
+
+def test_word():
+ expr = RegexBuilder().word()
+ regex_equality("\\w", expr)
+ regex_compilation("\\w", expr)
+
+
+def test_non_word():
+ expr = RegexBuilder().non_word()
+ regex_equality("\\W", expr)
+ regex_compilation("\\W", expr)
+
+
+def test_word_boundary():
+ expr = RegexBuilder().word_boundary()
+ regex_equality("\\b", expr)
+ regex_compilation("\\b", expr)
+
+
+def test_non_word_boundary():
+ expr = RegexBuilder().non_word_boundary()
+ regex_equality("\\B", expr)
+ regex_compilation("\\B", expr)
+
+
+def test_new_line():
+ expr = RegexBuilder().new_line()
+ regex_equality("\\n", expr)
+ regex_compilation("\\n", expr)
+
+
+def test_carriage_return():
+ expr = RegexBuilder().carriage_return()
+ regex_equality("\\r", expr)
+ regex_compilation("\\r", expr)
+
+
+def test_tab():
+ expr = RegexBuilder().tab()
+ regex_equality("\\t", expr)
+ regex_compilation("\\t", expr)
+
+
+def test_null_byte():
+ expr = RegexBuilder().null_byte()
+ regex_equality("\\0", expr)
+ regex_compilation("\\0", expr)
+
+
+def test_any_of_basic():
+ expr = RegexBuilder().any_of().string("hello").digit().word().char(".").char("#").end()
+ regex_equality("(?:hello|\\d|\\w|[\\.\\#])", expr)
+ regex_compilation("(?:hello|\\d|\\w|[\\.\\#])", expr)
+
+
+def test_any_of_range_fusion():
+ expr = (
+ RegexBuilder()
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char(".")
+ .char("#")
+ .end()
+ )
+ regex_equality("[a-zA-Z0-9\\.\\#]", expr)
+ regex_compilation("[a-zA-Z0-9\\.\\#]", expr)
+
+
+def test_any_of_range_fusion_with_other_choices():
+ expr = (
+ RegexBuilder()
+ .any_of()
+ .range("a", "z")
+ .range("A", "Z")
+ .range("0", "9")
+ .char(".")
+ .char("#")
+ .string("hello")
+ .end()
+ )
+ regex_equality("(?:hello|[a-zA-Z0-9\\.\\#])", expr)
+ regex_compilation("(?:hello|[a-zA-Z0-9\\.\\#])", expr)
+
+
+def test_capture():
+ expr = RegexBuilder().capture().string("hello ").word().char("!").end()
+ regex_equality("(hello \\w!)", expr)
+ regex_compilation("(hello \\w!)", expr)
+
+
+def test_named_capture():
+ expr = RegexBuilder().named_capture("this_is_the_name").string("hello ").word().char("!").end()
+ regex_equality("(?P<this_is_the_name>hello \\w!)", expr)
+ regex_compilation("(?P<this_is_the_name>hello \\w!)", expr)
+
+
+def test_bad_name_error():
+ with pytest.raises(NameNotValidError):
+ (RegexBuilder().named_capture("hello world").string("hello ").word().char("!").end())
+
+
+def test_same_name_error():
+ with pytest.raises(CannotCreateDuplicateNamedGroupError):
+ (
+ RegexBuilder()
+ .named_capture("hello")
+ .string("hello ")
+ .word()
+ .char("!")
+ .end()
+ .named_capture("hello")
+ .string("hello ")
+ .word()
+ .char("!")
+ .end()
+ )
+
+
+def test_named_back_reference():
+ expr = (
+ RegexBuilder()
+ .named_capture("this_is_the_name")
+ .string("hello ")
+ .word()
+ .char("!")
+ .end()
+ .named_back_reference("this_is_the_name")
+ )
+ regex_equality("(?P<this_is_the_name>hello \\w!)(?P=this_is_the_name)", expr)
+ regex_compilation("(?P<this_is_the_name>hello \\w!)(?P=this_is_the_name)", expr)
+
+
+def test_named_back_reference_no_cg_exists():
+ with pytest.raises(NamedGroupDoesNotExistError):
+ RegexBuilder().named_back_reference("not_here")
+
+
+def test_back_reference():
+ expr = RegexBuilder().capture().string("hello ").word().char("!").end().back_reference(1)
+ regex_equality("(hello \\w!)\\1", expr)
+ regex_compilation("(hello \\w!)\\1", expr)
+
+
+def test_back_reference_no_cg_exists():
+ with pytest.raises(InvalidTotalCaptureGroupsIndexError):
+ RegexBuilder().back_reference(1)
+
+
+def test_group():
+ expr = RegexBuilder().group().string("hello ").word().char("!").end()
+ regex_equality("(?:hello \\w!)", expr)
+ regex_compilation("(?:hello \\w!)", expr)
+
+
+def test_error_when_called_with_no_stack():
+ with pytest.raises(CannotEndWhileBuildingRootExpressionError):
+ RegexBuilder().end()
+
+
+def test_assert_ahead():
+ expr = RegexBuilder().assert_ahead().range("a", "f").end().range("a", "z")
+ regex_equality("(?=[a-f])[a-z]", expr)
+ regex_compilation("(?=[a-f])[a-z]", expr)
+
+
+def test_assert_behind():
+ expr = RegexBuilder().assert_behind().string("hello ").end().range("a", "z")
+ regex_equality("(?<=hello )[a-z]", expr)
+ regex_compilation("(?<=hello )[a-z]", expr)
+
+
+def test_assert_not_ahead():
+ expr = RegexBuilder().assert_not_ahead().range("a", "f").end().range("0", "9")
+ regex_equality("(?![a-f])[0-9]", expr)
+ regex_compilation("(?![a-f])[0-9]", expr)
+
+
+def test_assert_not_behind():
+ expr = RegexBuilder().assert_not_behind().string("hello ").end().range("a", "z")
+ regex_equality("(?<!hello )[a-z]", expr)
+ regex_compilation("(?<!hello )[a-z]", expr)
+
+
+def test_optional():
+ expr = RegexBuilder().optional().word()
+ regex_equality("\\w?", expr)
+ regex_compilation("\\w?", expr)
+
+
+def test_zero_or_more():
+ expr = RegexBuilder().zero_or_more().word()
+ regex_equality("\\w*", expr)
+ regex_compilation("\\w*", expr)
+
+
+def test_zero_or_more_lazy():
+ expr = RegexBuilder().zero_or_more_lazy().word()
+ regex_equality("\\w*?", expr)
+ regex_compilation("\\w*?", expr)
+
+
+def test_one_or_more():
+ expr = RegexBuilder().one_or_more().word()
+ regex_equality("\\w+", expr)
+ regex_compilation("\\w+", expr)
+
+
+def test_one_or_more_lazy():
+ expr = RegexBuilder().one_or_more_lazy().word()
+ regex_equality("\\w+?", expr)
+ regex_compilation("\\w+?", expr)
+
+
+def test_exactly():
+ expr = RegexBuilder().exactly(3).word()
+ regex_equality("\\w{3}", expr)
+ regex_compilation("\\w{3}", expr)
+
+
+def test_at_least():
+ expr = RegexBuilder().at_least(3).word()
+ regex_equality("\\w{3,}", expr)
+ regex_compilation("\\w{3,}", expr)
+
+
+def test_between():
+ expr = RegexBuilder().between(3, 5).word()
+ regex_equality("\\w{3,5}", expr)
+ regex_compilation("\\w{3,5}", expr)
+
+
+def test_between_lazy():
+ expr = RegexBuilder().between_lazy(3, 5).word()
+ regex_equality("\\w{3,5}?", expr)
+ regex_compilation("\\w{3,5}?", expr)
+
+
+def test_start_of_input():
+ expr = RegexBuilder().start_of_input()
+ regex_equality("^", expr)
+ regex_compilation("^", expr)
+
+
+def test_end_of_input():
+ expr = RegexBuilder().end_of_input()
+ regex_equality("$", expr)
+ regex_compilation("$", expr)
+
+
+def test_any_of_chars():
+ expr = RegexBuilder().any_of_chars("aeiou.-")
+ regex_equality("[aeiou\\.\\-]", expr)
+ regex_compilation("[aeiou\\.\\-]", expr)
+
+
+def test_anything_but_chars():
+ expr = RegexBuilder().anything_but_chars("aeiou.-")
+ regex_equality("[^aeiou\\.\\-]", expr)
+ regex_compilation("[^aeiou\\.\\-]", expr)
+
+
+def test_anything_but_string():
+ expr = RegexBuilder().anything_but_string("aeiou.")
+ regex_equality("(?:[^a][^e][^i][^o][^u][^\\][^.])", expr)
+ regex_compilation("(?:[^a][^e][^i][^o][^u][^\\][^.])", expr)
+
+
+def test_anything_but_range():
+ expr = RegexBuilder().anything_but_range("a", "z")
+ regex_equality("[^a-z]", expr)
+ regex_compilation("[^a-z]", expr)
+ expr = RegexBuilder().anything_but_range("0", "9")
+ regex_equality("[^0-9]", expr)
+ regex_compilation("[^0-9]", expr)
+
+
+def test_string():
+ expr = RegexBuilder().string("hello")
+ regex_equality("hello", expr)
+ regex_compilation("hello", expr)
+
+
+def test_string_escapes_special_chars_with_strings_of_len_1():
+ expr = RegexBuilder().string("^").string("hello")
+ regex_equality("\\^hello", expr)
+ regex_compilation("\\^hello", expr)
+
+
+def test_char():
+ expr = RegexBuilder().char("a")
+ regex_equality("a", expr)
+ regex_compilation("a", expr)
+
+
+def test_char_more_than_one_error():
+ with pytest.raises(MustBeSingleCharacterError):
+ RegexBuilder().char("hello")
+
+
+def test_range():
+ expr = RegexBuilder().range("a", "z")
+ regex_equality("[a-z]", expr)
+ regex_compilation("[a-z]", expr)
+
+
+def test_must_be_instance_error():
+ with pytest.raises(MustBeInstanceError):
+ RegexBuilder().subexpression("nope")
+
+
+def test_simple_se():
+ expr = (
+ RegexBuilder()
+ .start_of_input()
+ .at_least(3)
+ .digit()
+ .subexpression(simple_se)
+ .range("0", "9")
+ .end_of_input()
+ )
+ regex_equality("^\\d{3,}hello.world[0-9]$", expr)
+ regex_compilation("^\\d{3,}hello.world[0-9]$", expr)
+
+
+def test_simple_quantified_se():
+ expr = (
+ RegexBuilder()
+ .start_of_input()
+ .at_least(3)
+ .digit()
+ .one_or_more()
+ .subexpression(simple_se)
+ .range("0", "9")
+ .end_of_input()
+ )
+ regex_equality("^\\d{3,}(?:hello.world)+[0-9]$", expr)
+ regex_compilation("^\\d{3,}(?:hello.world)+[0-9]$", expr)
+
+
+def test_flags_se():
+ expr = (
+ RegexBuilder()
+ .dot_all()
+ .start_of_input()
+ .at_least(3)
+ .digit()
+ .subexpression(flags_se, ignore_flags=False)
+ .range("0", "9")
+ .end_of_input()
+ )
+ regex_equality("^\\d{3,}hello.world[0-9]$", expr)
+ regex_compilation("^\\d{3,}hello.world[0-9]$", expr, f=re.M | re.I | re.S)
+
+
+def test_flags_se_ignore_flags():
+ expr = (
+ RegexBuilder()
+ .dot_all()
+ .start_of_input()
+ .at_least(3)
+ .digit()
+ .subexpression(flags_se)
+ .range("0", "9")
+ .end_of_input()
+ )
+ regex_equality("^\\d{3,}hello.world[0-9]$", expr)
+ regex_compilation("^\\d{3,}hello.world[0-9]$", expr, f=re.S)
+
+
+def test_ignore_start_and_end():
+ expr = RegexBuilder().at_least(3).digit().subexpression(start_end_se).range("0", "9")
+ regex_equality("\\d{3,}hello.world[0-9]", expr)
+ regex_compilation("\\d{3,}hello.world[0-9]", expr)
+
+
+def test_start_defined_in_me_and_se():
+ with pytest.raises(StartInputAlreadyDefinedError):
+ (
+ RegexBuilder()
+ .start_of_input()
+ .at_least(3)
+ .digit()
+ .subexpression(start_end_se, ignore_start_and_end=False)
+ .range("0", "9")
+ )
+
+
+def test_no_namespacing():
+ expr = RegexBuilder().at_least(3).digit().subexpression(nc_se).range("0", "9")
+ regex_equality("\\d{3,}(?P<module>.{2})(?P=module)[0-9]", expr)
+ regex_compilation("\\d{3,}(?P<module>.{2})(?P=module)[0-9]", expr)
+
+
+def test_namespacing():
+ expr = RegexBuilder().at_least(3).digit().subexpression(nc_se, namespace="yolo").range("0", "9")
+ regex_equality("\\d{3,}(?P<yolomodule>.{2})(?P=yolomodule)[0-9]", expr)
+ regex_compilation("\\d{3,}(?P<yolomodule>.{2})(?P=yolomodule)[0-9]", expr)
+
+
+def test_indexed_back_referencing():
+ expr = (
+ RegexBuilder()
+ .capture()
+ .at_least(3)
+ .digit()
+ .end()
+ .subexpression(indexed_back_reference_se)
+ .back_reference(1)
+ .range("0", "9")
+ )
+ regex_equality("(\\d{3,})(.{2})\\2\\1[0-9]", expr)
+ regex_compilation("(\\d{3,})(.{2})\\2\\1[0-9]", expr)
+
+
+def test_deeply_nested_se():
+ expr = (
+ RegexBuilder()
+ .capture()
+ .at_least(3)
+ .digit()
+ .end()
+ .subexpression(first_layer_se)
+ .back_reference(1)
+ .range("0", "9")
+ )
+ regex_equality("(\\d{3,})outer begin(?P<inner_subexpression>(?:.{2})?)outer end\\1[0-9]", expr)
+ regex_compilation(
+ "(\\d{3,})outer begin(?P<inner_subexpression>(?:.{2})?)outer end\\1[0-9]", expr
+ )
diff --git a/tests/builder/conflict.test.py b/tests/builder/conflict.test.py
new file mode 100644
index 0000000..b683c06
--- /dev/null
+++ b/tests/builder/conflict.test.py
@@ -0,0 +1,20 @@
+"""Tests for anchor conflicts when subexpressions merge with ``ignore_start_and_end=False``."""
+
+import pytest
+
+from edify import RegexBuilder
+from edify.errors.anchors import EndInputAlreadyDefinedError, StartInputAlreadyDefinedError
+
+
+def test_parent_with_start_merging_sub_with_start_raises():
+ sub = RegexBuilder().start_of_input().digit()
+ parent = RegexBuilder().start_of_input().digit()
+ with pytest.raises(StartInputAlreadyDefinedError):
+ parent.subexpression(sub, ignore_start_and_end=False)
+
+
+def test_parent_with_end_merging_sub_with_end_raises():
+ sub = RegexBuilder().digit().end_of_input()
+ parent = RegexBuilder().digit().end_of_input()
+ with pytest.raises(EndInputAlreadyDefinedError):
+ parent.subexpression(sub, ignore_start_and_end=False)
diff --git a/tests/builder/frame.test.py b/tests/builder/frame.test.py
new file mode 100644
index 0000000..ffd2242
--- /dev/null
+++ b/tests/builder/frame.test.py
@@ -0,0 +1,17 @@
+"""Test for the ``UnexpectedFrameTypeError`` raise when an AST is built incorrectly."""
+
+import pytest
+
+from edify import RegexBuilder
+from edify.builder.types.frame import StackFrame
+from edify.elements.types.leaves import DigitElement
+from edify.errors.internal import UnexpectedFrameTypeError
+
+
+def test_end_on_unrecognised_frame_type_raises():
+ builder = RegexBuilder().capture()
+ stray_frame = StackFrame(type_node=DigitElement())
+ new_state = builder._state.with_frame_pushed(stray_frame)
+ builder_with_stray = builder._with_state(new_state)
+ with pytest.raises(UnexpectedFrameTypeError, match="DigitElement"):
+ builder_with_stray.end()
diff --git a/tests/builder/merge.test.py b/tests/builder/merge.test.py
new file mode 100644
index 0000000..cc4d513
--- /dev/null
+++ b/tests/builder/merge.test.py
@@ -0,0 +1,123 @@
+"""Tests that exercise every element kind through the subexpression merge path.
+
+Each subexpression is built with one particular element kind and merged
+into a parent; the resulting regex string is asserted byte-for-byte so the
+per-type branches in :mod:`edify.builder.merge` all get exercised.
+"""
+
+from edify import RegexBuilder
+
+
+def _merge(sub: RegexBuilder) -> str:
+ return RegexBuilder().subexpression(sub).to_regex_string()
+
+
+def test_subexpression_with_capture():
+ sub = RegexBuilder().capture().digit().end()
+ assert _merge(sub) == "(\\d)"
+
+
+def test_subexpression_with_named_capture():
+ sub = RegexBuilder().named_capture("token").digit().end()
+ assert _merge(sub) == "(?P<token>\\d)"
+
+
+def test_subexpression_with_back_reference():
+ sub = RegexBuilder().capture().digit().end().back_reference(1)
+ assert _merge(sub) == "(\\d)\\1"
+
+
+def test_subexpression_with_named_back_reference():
+ sub = RegexBuilder().named_capture("token").digit().end().named_back_reference("token")
+ assert _merge(sub) == "(?P<token>\\d)(?P=token)"
+
+
+def test_subexpression_with_group():
+ sub = RegexBuilder().group().digit().end()
+ assert _merge(sub) == "(?:\\d)"
+
+
+def test_subexpression_with_any_of():
+ sub = RegexBuilder().any_of().char("a").char("b").end()
+ assert _merge(sub) == "[ab]"
+
+
+def test_subexpression_with_nested_subexpression():
+ inner = RegexBuilder().digit()
+ middle = RegexBuilder().subexpression(inner)
+ assert _merge(middle) == "\\d"
+
+
+def test_subexpression_with_assert_ahead():
+ sub = RegexBuilder().assert_ahead().digit().end()
+ assert _merge(sub) == "(?=\\d)"
+
+
+def test_subexpression_with_assert_not_ahead():
+ sub = RegexBuilder().assert_not_ahead().digit().end()
+ assert _merge(sub) == "(?!\\d)"
+
+
+def test_subexpression_with_assert_behind():
+ sub = RegexBuilder().assert_behind().digit().end()
+ assert _merge(sub) == "(?<=\\d)"
+
+
+def test_subexpression_with_assert_not_behind():
+ sub = RegexBuilder().assert_not_behind().digit().end()
+ assert _merge(sub) == "(?<!\\d)"
+
+
+def test_subexpression_with_optional():
+ sub = RegexBuilder().optional().digit()
+ assert _merge(sub) == "\\d?"
+
+
+def test_subexpression_with_zero_or_more():
+ sub = RegexBuilder().zero_or_more().digit()
+ assert _merge(sub) == "\\d*"
+
+
+def test_subexpression_with_zero_or_more_lazy():
+ sub = RegexBuilder().zero_or_more_lazy().digit()
+ assert _merge(sub) == "\\d*?"
+
+
+def test_subexpression_with_one_or_more():
+ sub = RegexBuilder().one_or_more().digit()
+ assert _merge(sub) == "\\d+"
+
+
+def test_subexpression_with_one_or_more_lazy():
+ sub = RegexBuilder().one_or_more_lazy().digit()
+ assert _merge(sub) == "\\d+?"
+
+
+def test_subexpression_with_exactly():
+ sub = RegexBuilder().exactly(3).digit()
+ assert _merge(sub) == "\\d{3}"
+
+
+def test_subexpression_with_at_least():
+ sub = RegexBuilder().at_least(2).digit()
+ assert _merge(sub) == "\\d{2,}"
+
+
+def test_subexpression_with_between():
+ sub = RegexBuilder().between(1, 4).digit()
+ assert _merge(sub) == "\\d{1,4}"
+
+
+def test_subexpression_with_between_lazy():
+ sub = RegexBuilder().between_lazy(1, 4).digit()
+ assert _merge(sub) == "\\d{1,4}?"
+
+
+def test_subexpression_with_start_of_input_collapses_to_noop():
+ sub = RegexBuilder().start_of_input().digit()
+ assert _merge(sub) == "\\d"
+
+
+def test_subexpression_with_end_of_input_collapses_to_noop():
+ sub = RegexBuilder().digit().end_of_input()
+ assert _merge(sub) == "\\d"
diff --git a/tests/builder/passthrough.test.py b/tests/builder/passthrough.test.py
new file mode 100644
index 0000000..de45146
--- /dev/null
+++ b/tests/builder/passthrough.test.py
@@ -0,0 +1,40 @@
+"""Tests for the pass-through branches when subexpression anchors merge without conflict."""
+
+from edify import RegexBuilder
+
+
+def test_start_of_input_merges_into_parent_without_existing_start():
+ sub = RegexBuilder().start_of_input().digit()
+ parent = RegexBuilder().digit()
+ pattern = parent.subexpression(sub, ignore_start_and_end=False).to_regex_string()
+ assert "^" in pattern
+
+
+def test_end_of_input_merges_into_parent_without_existing_end():
+ sub = RegexBuilder().digit().end_of_input()
+ parent = RegexBuilder().digit()
+ pattern = parent.subexpression(sub, ignore_start_and_end=False).to_regex_string()
+ assert "$" in pattern
+
+
+def test_subexpression_called_with_unfinished_expression_raises():
+ import pytest
+
+ from edify.errors.structure import CannotCallSubexpressionError
+
+ unfinished_sub = RegexBuilder().capture().digit()
+ parent = RegexBuilder()
+ with pytest.raises(CannotCallSubexpressionError):
+ parent.subexpression(unfinished_sub)
+
+
+def test_to_regex_with_invalid_pattern_raises_failed_to_compile():
+ import pytest
+
+ from edify.errors.internal import FailedToCompileRegexError
+
+ expr = RegexBuilder().capture().digit().end().back_reference(1)
+ state_with_extra = expr._state.with_capture_groups_added(98)
+ bogus = expr._with_state(state_with_extra).back_reference(99)
+ with pytest.raises(FailedToCompileRegexError):
+ bogus.to_regex()
diff --git a/tests/builder/validation.test.py b/tests/builder/validation.test.py
new file mode 100644
index 0000000..e4898ad
--- /dev/null
+++ b/tests/builder/validation.test.py
@@ -0,0 +1,157 @@
+"""Tests for the validation raise paths across every builder mixin.
+
+Each test triggers exactly one of the per-method input-validation branches
+that the happy-path builder tests never hit.
+"""
+
+import pytest
+
+from edify import RegexBuilder
+from edify.errors.anchors import (
+ CannotDefineStartAfterEndError,
+ EndInputAlreadyDefinedError,
+ StartInputAlreadyDefinedError,
+)
+from edify.errors.input import (
+ MustBeAStringError,
+ MustBeIntegerGreaterThanZeroError,
+ MustBeLessThanError,
+ MustBeOneCharacterError,
+ MustBePositiveIntegerError,
+ MustBeSingleCharacterError,
+ MustHaveASmallerValueError,
+)
+from edify.errors.naming import NamedGroupDoesNotExistError
+
+
+def test_start_of_input_twice_raises():
+ with pytest.raises(StartInputAlreadyDefinedError):
+ RegexBuilder().start_of_input().start_of_input()
+
+
+def test_start_of_input_after_end_raises():
+ with pytest.raises(CannotDefineStartAfterEndError):
+ RegexBuilder().end_of_input().start_of_input()
+
+
+def test_end_of_input_twice_raises():
+ with pytest.raises(EndInputAlreadyDefinedError):
+ RegexBuilder().end_of_input().end_of_input()
+
+
+def test_named_capture_non_string_raises():
+ with pytest.raises(MustBeAStringError):
+ RegexBuilder().named_capture(42)
+
+
+def test_named_capture_empty_string_raises():
+ with pytest.raises(MustBeOneCharacterError):
+ RegexBuilder().named_capture("")
+
+
+def test_string_non_string_raises():
+ with pytest.raises(MustBeAStringError):
+ RegexBuilder().string(42)
+
+
+def test_string_empty_raises():
+ with pytest.raises(MustBeOneCharacterError):
+ RegexBuilder().string("")
+
+
+def test_char_non_string_raises():
+ with pytest.raises(MustBeAStringError):
+ RegexBuilder().char(42)
+
+
+def test_range_first_codepoint_not_less_than_second_raises():
+ with pytest.raises(MustHaveASmallerValueError):
+ RegexBuilder().range("z", "a")
+
+
+def test_anything_but_string_non_string_raises():
+ with pytest.raises(MustBeAStringError):
+ RegexBuilder().anything_but_string(42)
+
+
+def test_anything_but_string_empty_raises():
+ with pytest.raises(MustBeOneCharacterError):
+ RegexBuilder().anything_but_string("")
+
+
+def test_anything_but_chars_non_string_raises():
+ with pytest.raises(MustBeAStringError):
+ RegexBuilder().anything_but_chars(42)
+
+
+def test_anything_but_chars_empty_raises():
+ with pytest.raises(MustBeOneCharacterError):
+ RegexBuilder().anything_but_chars("")
+
+
+def test_anything_but_range_multi_char_raises():
+ with pytest.raises(MustBeSingleCharacterError):
+ RegexBuilder().anything_but_range("abc", "z")
+
+
+def test_anything_but_range_ascending_raises():
+ with pytest.raises(MustHaveASmallerValueError):
+ RegexBuilder().anything_but_range("z", "a")
+
+
+def test_exactly_non_positive_raises():
+ with pytest.raises(MustBePositiveIntegerError):
+ RegexBuilder().exactly(0).digit()
+
+
+def test_at_least_non_positive_raises():
+ with pytest.raises(MustBePositiveIntegerError):
+ RegexBuilder().at_least(-1).digit()
+
+
+def test_between_negative_lower_raises():
+ with pytest.raises(MustBeIntegerGreaterThanZeroError):
+ RegexBuilder().between(-1, 5).digit()
+
+
+def test_between_lower_not_less_than_upper_raises():
+ with pytest.raises(MustBeLessThanError):
+ RegexBuilder().between(5, 5).digit()
+
+
+def test_between_lazy_negative_lower_raises():
+ with pytest.raises(MustBeIntegerGreaterThanZeroError):
+ RegexBuilder().between_lazy(-1, 5).digit()
+
+
+def test_between_lazy_lower_not_less_than_upper_raises():
+ with pytest.raises(MustBeLessThanError):
+ RegexBuilder().between_lazy(5, 5).digit()
+
+
+def test_named_back_reference_undeclared_raises():
+ with pytest.raises(NamedGroupDoesNotExistError):
+ RegexBuilder().named_back_reference("missing")
+
+
+def test_to_regex_string_with_open_frame_raises():
+ from edify.errors.structure import CannotCallSubexpressionError
+
+ unfinished = RegexBuilder().capture().digit()
+ with pytest.raises(CannotCallSubexpressionError):
+ unfinished.to_regex_string()
+
+
+def test_to_regex_with_open_frame_raises():
+ from edify.errors.structure import CannotCallSubexpressionError
+
+ unfinished = RegexBuilder().capture().digit()
+ with pytest.raises(CannotCallSubexpressionError):
+ unfinished.to_regex()
+
+
+def test_subexpression_non_builder_raises():
+ from edify.errors.input import MustBeInstanceError
+
+ with pytest.raises(MustBeInstanceError):
+ RegexBuilder().subexpression("not a builder")
diff --git a/tests/compile/invariants.test.py b/tests/compile/invariants.test.py
new file mode 100644
index 0000000..2260427
--- /dev/null
+++ b/tests/compile/invariants.test.py
@@ -0,0 +1,30 @@
+"""Tests for the compile-path invariant raises that public-builder use never triggers.
+
+These exercise defensive branches that fire only when an AST is constructed
+outside the builder (so the type system can't catch it). They guarantee
+the typed exception is raised instead of a silent miscompilation.
+"""
+
+import pytest
+
+from edify.compile.dispatch import render_element
+from edify.compile.fuse import _fragment_for
+from edify.elements.types.base import BaseElement
+from edify.elements.types.leaves import DigitElement
+from edify.errors.internal import NonFusableElementError, UnknownElementTypeError
+
+
+class _StrayElement(BaseElement):
+ """A BaseElement subclass not in the ``Element`` union — used only here."""
+
+
+def test_render_element_unknown_type_raises():
+ stray = _StrayElement()
+ with pytest.raises(UnknownElementTypeError, match="_StrayElement"):
+ render_element(stray)
+
+
+def test_fragment_for_non_fusable_raises():
+ non_fusable = DigitElement()
+ with pytest.raises(NonFusableElementError, match="DigitElement"):
+ _fragment_for(non_fusable)
diff --git a/tests/errors/errors.test.py b/tests/errors/errors.test.py
new file mode 100644
index 0000000..12cfcd5
--- /dev/null
+++ b/tests/errors/errors.test.py
@@ -0,0 +1,153 @@
+from edify.errors.anchors import (
+ CannotDefineStartAfterEndError,
+ EndInputAlreadyDefinedError,
+ StartInputAlreadyDefinedError,
+)
+from edify.errors.captures import InvalidTotalCaptureGroupsIndexError
+from edify.errors.input import (
+ MustBeAStringError,
+ MustBeInstanceError,
+ MustBeIntegerGreaterThanZeroError,
+ MustBeLessThanError,
+ MustBeOneCharacterError,
+ MustBePositiveIntegerError,
+ MustBeSingleCharacterError,
+ MustHaveASmallerValueError,
+)
+from edify.errors.internal import (
+ FailedToCompileRegexError,
+ NonFusableElementError,
+ UnexpectedFrameTypeError,
+ UnknownElementTypeError,
+)
+from edify.errors.naming import (
+ CannotCreateDuplicateNamedGroupError,
+ NamedGroupDoesNotExistError,
+ NameNotValidError,
+)
+from edify.errors.structure import (
+ CannotCallSubexpressionError,
+ CannotEndWhileBuildingRootExpressionError,
+)
+
+
+def test_start_input_already_defined_outside_subexpression():
+ error = StartInputAlreadyDefinedError()
+ assert "already has a start" in str(error)
+ assert "ignore_start_and_end" not in str(error)
+
+
+def test_start_input_already_defined_in_subexpression():
+ error = StartInputAlreadyDefinedError(in_subexpression=True)
+ assert "ignore_start_and_end" in str(error)
+
+
+def test_end_input_already_defined_outside_subexpression():
+ error = EndInputAlreadyDefinedError()
+ assert "already has an end" in str(error)
+ assert "ignore_start_and_end" not in str(error)
+
+
+def test_end_input_already_defined_in_subexpression():
+ error = EndInputAlreadyDefinedError(in_subexpression=True)
+ assert "ignore_start_and_end" in str(error)
+
+
+def test_cannot_define_start_after_end():
+ error = CannotDefineStartAfterEndError()
+ assert "start of input after defining an end" in str(error)
+
+
+def test_invalid_total_capture_groups_index():
+ error = InvalidTotalCaptureGroupsIndexError(5, 3)
+ assert "Invalid index #5" in str(error)
+ assert "only 3 capture groups" in str(error)
+
+
+def test_must_be_a_string():
+ error = MustBeAStringError("Name", "int")
+ assert "Name must be a string" in str(error)
+ assert "int" in str(error)
+
+
+def test_must_be_one_character():
+ error = MustBeOneCharacterError("Value")
+ assert "Value must be one character long" in str(error)
+
+
+def test_must_be_single_character():
+ error = MustBeSingleCharacterError("Value", "str")
+ assert "Value must be a single character" in str(error)
+ assert "str" in str(error)
+
+
+def test_must_be_positive_integer():
+ error = MustBePositiveIntegerError("count")
+ assert "count must be a positive integer" in str(error)
+
+
+def test_must_be_integer_greater_than_zero():
+ error = MustBeIntegerGreaterThanZeroError("x")
+ assert "x must be an integer greater than zero" in str(error)
+
+
+def test_must_be_instance():
+ error = MustBeInstanceError("Expression", "str", "RegexBuilder")
+ assert "Expression must be an instance of RegexBuilder" in str(error)
+ assert "str" in str(error)
+
+
+def test_must_have_a_smaller_value():
+ error = MustHaveASmallerValueError("z", "a")
+ assert "z must have a smaller character value than a" in str(error)
+
+
+def test_must_be_less_than():
+ error = MustBeLessThanError("X", "Y")
+ assert "X must be less than Y" in str(error)
+
+
+def test_name_not_valid():
+ error = NameNotValidError("bad name")
+ assert "Name bad name is not valid" in str(error)
+
+
+def test_cannot_create_duplicate_named_group():
+ error = CannotCreateDuplicateNamedGroupError("dup")
+ assert 'Can not create duplicate named group "dup"' in str(error)
+
+
+def test_named_group_does_not_exist():
+ error = NamedGroupDoesNotExistError("missing")
+ assert 'Named group "missing" does not exist' in str(error)
+
+
+def test_cannot_end_while_building_root_expression():
+ error = CannotEndWhileBuildingRootExpressionError()
+ assert "Can not end while building the root expression" in str(error)
+
+
+def test_cannot_call_subexpression():
+ error = CannotCallSubexpressionError("capture")
+ assert "Can not call subexpression" in str(error)
+ assert "capture" in str(error)
+
+
+def test_unknown_element_type():
+ error = UnknownElementTypeError("WeirdElement")
+ assert "WeirdElement" in str(error)
+
+
+def test_non_fusable_element():
+ error = NonFusableElementError("DigitElement")
+ assert "Cannot fuse element of type DigitElement" in str(error)
+
+
+def test_unexpected_frame_type():
+ error = UnexpectedFrameTypeError("DigitElement")
+ assert "Stack frame anchored at unexpected element type DigitElement" in str(error)
+
+
+def test_failed_to_compile_regex():
+ error = FailedToCompileRegexError("missing )")
+ assert "Cannot compile regex: missing )" in str(error)
diff --git a/tests/library/date/basic.test.py b/tests/library/date/basic.test.py
new file mode 100644
index 0000000..9b3f45a
--- /dev/null
+++ b/tests/library/date/basic.test.py
@@ -0,0 +1,29 @@
+from edify.library import date
+
+_TEST_CASES = {
+ "1/1/2020": True,
+ "01/01/2020": True,
+ "1/01/2020": True,
+ "01/1/2020": True,
+ "1/1/20": False,
+ "01/01/20": False,
+ "1/1/202": False,
+ "01/01/202": False,
+ "12/12/2022": True,
+ "12/12/2": False,
+ "2021-11-04T22:32:47.142354-10:00": False,
+ "2021-11-04T22:32:47.142354Z": False,
+ "2021-11-04T22:32:47.142354": False,
+ "2021-11-04T22:32:47": False,
+ "2021-11-04T22:32": False,
+ "2021-11-04T22": False,
+ "2021-11-04": False,
+ "2021-11": False,
+ "2021": False,
+ "1-1-2020": False,
+}
+
+
+def test_date():
+ for candidate, expectation in _TEST_CASES.items():
+ assert date(candidate) is expectation
diff --git a/tests/library/date/iso.test.py b/tests/library/date/iso.test.py
new file mode 100644
index 0000000..de29a4c
--- /dev/null
+++ b/tests/library/date/iso.test.py
@@ -0,0 +1,28 @@
+from edify.library import iso_date
+
+_TEST_CASES = {
+ "1/1/2020": False,
+ "01/01/2020": False,
+ "1/01/2020": False,
+ "01/1/2020": False,
+ "1/1/20": False,
+ "01/01/20": False,
+ "1/1/202": False,
+ "01/01/202": False,
+ "12/12/2022": False,
+ "12/12/2": False,
+ "2021-11-04T22:32:47.142354-10:00": True,
+ "2021-11-04T22:32:47.142354Z": True,
+ "2021-11-04T22:32:47.142354": True,
+ "2021-11-04T22:32:47": True,
+ "2021-11-04T22:32": False,
+ "2021-11-04T22": False,
+ "2021-11-04": False,
+ "2021-11": False,
+ "2021": False,
+}
+
+
+def test_iso_date():
+ for candidate, expectation in _TEST_CASES.items():
+ assert iso_date(candidate) is expectation
diff --git a/tests/library/email/basic.test.py b/tests/library/email/basic.test.py
new file mode 100644
index 0000000..e77e3b8
--- /dev/null
+++ b/tests/library/email/basic.test.py
@@ -0,0 +1,32 @@
+from edify.library import email
+
+_TEST_CASES = [
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected].", False),
+ ("plainaddress", False),
+ ("#@%^%#$@#$@#.com", False),
+ ("@example.com", False),
+ ("Joe Smith <[email protected]>", False),
+ ("email.example.com", False),
+ ("email@[email protected]", False),
+ ("[email protected]", False),
+ ("[email protected]", False),
+ ("[email protected]", False),
+ ("あいうえお@example.com", False),
+ ("[email protected]", False),
+ ("[email protected]", False),
+]
+
+
+def test_email():
+ for candidate, expectation in _TEST_CASES:
+ assert email(candidate) is expectation
diff --git a/tests/library/email/strict.test.py b/tests/library/email/strict.test.py
new file mode 100644
index 0000000..7cefbce
--- /dev/null
+++ b/tests/library/email/strict.test.py
@@ -0,0 +1,32 @@
+from edify.library import email_rfc_5322
+
+_TEST_CASES = [
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected]", True),
+ ("[email protected].", True),
+ ("plainaddress", False),
+ ("#@%^%#$@#$@#.com", False),
+ ("@example.com", False),
+ ("Joe Smith <[email protected]>", False),
+ ("email.example.com", False),
+ ("email@[email protected]", False),
+ ("[email protected]", False),
+ ("[email protected]", False),
+ ("[email protected]", False),
+ ("あいうえお@example.com", False),
+ ("[email protected]", False),
+ ("[email protected]", False),
+]
+
+
+def test_email_rfc_5322():
+ for candidate, expectation in _TEST_CASES:
+ assert email_rfc_5322(candidate) is expectation
diff --git a/tests/test_guid.py b/tests/library/guid.test.py
index b4b1a64..67fcad1 100644
--- a/tests/test_guid.py
+++ b/tests/library/guid.test.py
@@ -4,8 +4,8 @@ from edify.library import guid
def test_valid_guids():
guids = {
"6ba7b810-9dad-11d1-80b4-00c04fd430c8": True,
- '{51d52cf1-83c9-4f02-b117-703ecb728b74}': True,
- '{51d52cf1-83c9-4f02-b117-703ecb728-b74}': False,
+ "{51d52cf1-83c9-4f02-b117-703ecb728b74}": True,
+ "{51d52cf1-83c9-4f02-b117-703ecb728-b74}": False,
}
for guid_string, expectation in guids.items():
assert guid(guid_string) == expectation
diff --git a/tests/library/ip/v4.test.py b/tests/library/ip/v4.test.py
new file mode 100644
index 0000000..32a1975
--- /dev/null
+++ b/tests/library/ip/v4.test.py
@@ -0,0 +1,16 @@
+from edify.library import ipv4
+
+_TEST_CASES = {
+ "192.168.0.1": True,
+ "244.232.123.233": True,
+ "363.232.123.233": False,
+ "234.234234.234.234": False,
+ "12.12.12.12.12": False,
+ "0.0.0.0": True,
+ "987.987.987.987": False,
+}
+
+
+def test_ipv4():
+ for candidate, expectation in _TEST_CASES.items():
+ assert ipv4(candidate) is expectation
diff --git a/tests/library/ip/v6.test.py b/tests/library/ip/v6.test.py
new file mode 100644
index 0000000..a68f892
--- /dev/null
+++ b/tests/library/ip/v6.test.py
@@ -0,0 +1,17 @@
+from edify.library import ipv6
+
+_TEST_CASES = {
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334": True,
+ "2001:db8:85a3:0:0:8a2e:370:7334": True,
+ "2001:db8:85a3::8a2e:370:7334": True,
+ "2001:db8:85a3:0:0:8A2E:370:7334": True,
+ "2001:db8:85a3:0:0:8a2e:370:7334:": False,
+ "2001:db8:85a3:0:0:8a2e:370:7334:7334": False,
+ "2001:db8:85a3:0:0:8a2e:370:7334:7334:7334": False,
+ "2001:db8:85a3:0:0:8a2e:370:7334:7334:7334:7334": False,
+}
+
+
+def test_ipv6():
+ for candidate, expectation in _TEST_CASES.items():
+ assert ipv6(candidate) is expectation
diff --git a/tests/test_mac.py b/tests/library/mac.test.py
index 2f24b44..2f24b44 100644
--- a/tests/test_mac.py
+++ b/tests/library/mac.test.py
diff --git a/tests/test_password.py b/tests/library/password.test.py
index 538874b..ec3b627 100644
--- a/tests/test_password.py
+++ b/tests/library/password.test.py
@@ -7,4 +7,9 @@ def test_password():
assert password("Password123!", max_length=8) is False
assert password("Password123!", min_upper=2) is False
assert password("password", min_upper=0, min_digit=0, min_special=0) is True
- assert password("pass@#1", min_special=1, special_chars="!", min_digit=0, min_upper=0, min_length=4) is False
+ assert (
+ password(
+ "pass@#1", min_special=1, special_chars="!", min_digit=0, min_upper=0, min_length=4
+ )
+ is False
+ )
diff --git a/tests/test_phone.py b/tests/library/phone.test.py
index cbff29c..6fe16b1 100644
--- a/tests/test_phone.py
+++ b/tests/library/phone.test.py
@@ -7,7 +7,6 @@ def test():
"123 456 7890": True,
"123-456-7890": True,
"123.456.7890": True,
- "123 456 7890": True,
"+1 (123) 456-7890": True,
"+1 (123) 456 7890": True,
"+1-(123)-456-7890": True,
@@ -20,7 +19,7 @@ def test():
"+1 (1) 456-7890": True,
"9012": True,
"911": True,
- "+1 (615) 243-": False
+ "+1 (615) 243-": False,
}
for phone, expectation in phones.items():
assert phone_number(phone) == expectation
diff --git a/tests/ssn_test.py b/tests/library/ssn.test.py
index ce2a57b..ce2a57b 100644
--- a/tests/ssn_test.py
+++ b/tests/library/ssn.test.py
diff --git a/tests/library/url.test.py b/tests/library/url.test.py
new file mode 100644
index 0000000..293bd34
--- /dev/null
+++ b/tests/library/url.test.py
@@ -0,0 +1,55 @@
+import pytest
+
+from edify.library import url
+
+_URLS = [
+ "example.com",
+ "www.example.com",
+ "www.example.com/path/to/file",
+ "http://www.example.com",
+ "http://example.com",
+ "http://www.example.com/path/to/page",
+ "https://example.com",
+ "https://www.example.com/",
+ "https://www.example.com/path/to/page",
+ "//example.com",
+]
+
+
+def test_all_protocols():
+ match_list = ["proto", "no_proto"]
+ expected = [True] * 9 + [False]
+ for uri, expectation in zip(_URLS, expected, strict=True):
+ assert url(uri, match=match_list) is expectation
+
+
+def test_proto_only():
+ match_list = ["proto"]
+ expected = [False] * 3 + [True] * 6 + [False]
+ for uri, expectation in zip(_URLS, expected, strict=True):
+ assert url(uri, match=match_list) is expectation
+
+
+def test_no_proto_only():
+ match_list = ["no_proto"]
+ expected = [True] * 3 + [False] * 7
+ for uri, expectation in zip(_URLS, expected, strict=True):
+ assert url(uri, match=match_list) is expectation
+
+
+def test_invalid_protocol():
+ for uri in _URLS:
+ with pytest.raises(ValueError, match="Invalid protocol"):
+ url(uri, match=["invalid"])
+
+
+def test_invalid_match_type():
+ for uri in _URLS:
+ with pytest.raises(TypeError, match="must be a list"):
+ url(uri, match="invalid")
+
+
+def test_empty_match_list():
+ for uri in _URLS:
+ with pytest.raises(ValueError, match="must not be empty"):
+ url(uri, match=[])
diff --git a/tests/test_uuid.py b/tests/library/uuid.test.py
index df4e840..df4e840 100644
--- a/tests/test_uuid.py
+++ b/tests/library/uuid.test.py
diff --git a/tests/library/zip.test.py b/tests/library/zip.test.py
new file mode 100644
index 0000000..77a3e0e
--- /dev/null
+++ b/tests/library/zip.test.py
@@ -0,0 +1,43 @@
+import pytest
+
+from edify.library import zip
+
+_DEFAULT_US_ZIPS = {
+ "12345": True,
+ "12345-1234": True,
+ "12345-123456": False,
+ "1234": False,
+}
+
+_INDIA_ZIPS = {
+ "123456": True,
+ "000000": False,
+ "012345": False,
+ "12345": False,
+ "1234567": False,
+}
+
+
+def test_valid_zips():
+ for candidate, expectation in _DEFAULT_US_ZIPS.items():
+ assert zip(candidate) is expectation
+
+
+def test_invalid_locale():
+ with pytest.raises(ValueError, match="locale must be one of"):
+ zip("12345", locale="INVALID")
+
+
+def test_invalid_locale_type():
+ with pytest.raises(TypeError, match="locale must be a string"):
+ zip("12345", 5)
+
+
+def test_empty_locale():
+ with pytest.raises(ValueError, match="locale cannot be empty"):
+ zip("12345", "")
+
+
+def test_locale_india():
+ for candidate, expectation in _INDIA_ZIPS.items():
+ assert zip(candidate, locale="IN") is expectation
diff --git a/tests/package.test.py b/tests/package.test.py
new file mode 100644
index 0000000..6e05bda
--- /dev/null
+++ b/tests/package.test.py
@@ -0,0 +1,16 @@
+"""Tests for the top-level :mod:`edify` package surface."""
+
+import importlib
+import importlib.metadata
+import sys
+from importlib.metadata import PackageNotFoundError
+
+
+def test_version_falls_back_when_package_metadata_missing(monkeypatch):
+ def raise_not_found(distribution_name: str) -> str:
+ raise PackageNotFoundError(distribution_name)
+
+ monkeypatch.setattr(importlib.metadata, "version", raise_not_found)
+ sys.modules.pop("edify", None)
+ reloaded_module = importlib.import_module("edify")
+ assert reloaded_module.__version__ == "0.0.0"
diff --git a/tests/test_builder.py b/tests/test_builder.py
deleted file mode 100644
index 76a04b5..0000000
--- a/tests/test_builder.py
+++ /dev/null
@@ -1,518 +0,0 @@
-import re
-
-from edify import RegexBuilder
-
-simple_se = RegexBuilder().string('hello').any_char().string('world')
-flags_se = RegexBuilder().multi_line().ignore_case().string('hello').any_char().string('world')
-start_end_se = RegexBuilder().start_of_input().string('hello').any_char().string('world').end_of_input()
-nc_se = RegexBuilder().named_capture('module').exactly(2).any_char().end().named_back_reference('module')
-indexed_back_reference_se = RegexBuilder().capture().exactly(2).any_char().end().back_reference(1)
-nested_se = RegexBuilder().exactly(2).any_char()
-first_layer_se = (
- RegexBuilder().string('outer begin').named_capture('inner_subexpression').optional().subexpression(nested_se).end().string('outer end')
-)
-
-
-def regex_equality(regex, rb_expression):
- regex_str = str(regex)
- rb_expression_str = rb_expression.to_regex_string()
- assert regex_str == str(rb_expression_str)
-
-
-def regex_compilation(regex, rb_expression, f=0):
- rb_expression_c = rb_expression.to_regex()
- assert re.compile(regex, flags=f) == rb_expression_c
-
-
-def test_empty_regex():
- expr = RegexBuilder()
- regex_equality('/(?:)/', expr)
- regex_compilation('(?:)', expr)
-
-
-def test_flag_a():
- expr = RegexBuilder().ascii_only()
- regex_equality('/(?:)/A', expr)
- regex_compilation('(?:)', expr, re.A)
-
-
-def test_flag_d():
- expr = RegexBuilder().debug()
- regex_equality('/(?:)/D', expr)
- regex_compilation('(?:)', expr, re.DEBUG)
-
-
-def test_flag_i():
- expr = RegexBuilder().ignore_case()
- regex_equality('/(?:)/I', expr)
- regex_compilation('(?:)', expr, re.I)
-
-
-def test_flag_m():
- expr = RegexBuilder().multi_line()
- regex_equality('/(?:)/M', expr)
- regex_compilation('(?:)', expr, re.M)
-
-
-def test_flag_s():
- expr = RegexBuilder().dot_all()
- regex_equality('/(?:)/S', expr)
- regex_compilation('(?:)', expr, re.S)
-
-
-def test_flag_x():
- expr = RegexBuilder().verbose()
- regex_equality('/(?:)/X', expr)
- regex_compilation('(?:)', expr, re.X)
-
-
-def test_any_char():
- expr = RegexBuilder().any_char()
- regex_equality('/./', expr)
- regex_compilation('.', expr)
-
-
-def test_whitespace_char():
- expr = RegexBuilder().whitespace_char()
- regex_equality('/\\s/', expr)
- regex_compilation('\\s', expr)
-
-
-def test_non_whitespace_char():
- expr = RegexBuilder().non_whitespace_char()
- regex_equality('/\\S/', expr)
- regex_compilation('\\S', expr)
-
-
-def test_digit():
- expr = RegexBuilder().digit()
- regex_equality('/\\d/', expr)
- regex_compilation('\\d', expr)
-
-
-def test_non_digit():
- expr = RegexBuilder().non_digit()
- regex_equality('/\\D/', expr)
- regex_compilation('\\D', expr)
-
-
-def test_word():
- expr = RegexBuilder().word()
- regex_equality('/\\w/', expr)
- regex_compilation('\\w', expr)
-
-
-def test_non_word():
- expr = RegexBuilder().non_word()
- regex_equality('/\\W/', expr)
- regex_compilation('\\W', expr)
-
-
-def test_word_boundary():
- expr = RegexBuilder().word_boundary()
- regex_equality('/\\b/', expr)
- regex_compilation('\\b', expr)
-
-
-def test_non_word_boundary():
- expr = RegexBuilder().non_word_boundary()
- regex_equality('/\\B/', expr)
- regex_compilation('\\B', expr)
-
-
-def test_new_line():
- expr = RegexBuilder().new_line()
- regex_equality('/\\n/', expr)
- regex_compilation('\\n', expr)
-
-
-def test_carriage_return():
- expr = RegexBuilder().carriage_return()
- regex_equality('/\\r/', expr)
- regex_compilation('\\r', expr)
-
-
-def test_tab():
- expr = RegexBuilder().tab()
- regex_equality('/\\t/', expr)
- regex_compilation('\\t', expr)
-
-
-def test_null_byte():
- expr = RegexBuilder().null_byte()
- regex_equality('/\\0/', expr)
- regex_compilation('\\0', expr)
-
-
-def test_any_of_basic():
- expr = RegexBuilder().any_of().string('hello').digit().word().char('.').char('#').end()
- regex_equality('/(?:hello|\\d|\\w|[\\.\\#])/', expr)
- regex_compilation('(?:hello|\\d|\\w|[\\.\\#])', expr)
-
-
-def test_any_of_range_fusion():
- expr = RegexBuilder().any_of().range('a', 'z').range('A', 'Z').range('0', '9').char('.').char('#').end()
- regex_equality('/[a-zA-Z0-9\\.\\#]/', expr)
- regex_compilation('[a-zA-Z0-9\\.\\#]', expr)
-
-
-def test_any_of_range_fusion_with_other_choices():
- expr = RegexBuilder().any_of().range('a', 'z').range('A', 'Z').range('0', '9').char('.').char('#').string('hello').end()
- regex_equality('/(?:hello|[a-zA-Z0-9\\.\\#])/', expr)
- regex_compilation('(?:hello|[a-zA-Z0-9\\.\\#])', expr)
-
-
-def test_capture():
- expr = RegexBuilder().capture().string('hello ').word().char('!').end()
- regex_equality('/(hello \\w!)/', expr)
- regex_compilation('(hello \\w!)', expr)
-
-
-def test_named_capture():
- expr = RegexBuilder().named_capture('this_is_the_name').string('hello ').word().char('!').end()
- regex_equality('/(?P<this_is_the_name>hello \\w!)/', expr)
- regex_compilation('(?P<this_is_the_name>hello \\w!)', expr)
-
-
-def test_bad_name_error():
- try:
- (RegexBuilder().named_capture('hello world').string('hello ').word().char('!').end())
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_same_name_error():
- try:
- (
- RegexBuilder()
- .namedCapture('hello')
- .string('hello ')
- .word()
- .char('!')
- .end()
- .namedCapture('hello')
- .string('hello ')
- .word()
- .char('!')
- .end()
- )
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_named_back_reference():
- expr = RegexBuilder().named_capture('this_is_the_name').string('hello ').word().char('!').end().named_back_reference('this_is_the_name')
- regex_equality('/(?P<this_is_the_name>hello \\w!)\\k<this_is_the_name>/', expr)
- # Python does not support named back references, so we raise an error
- try:
- expr.to_regex()
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_named_back_reference_no_cg_exists():
- try:
- RegexBuilder().named_back_reference('not_here')
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_back_reference():
- expr = RegexBuilder().capture().string('hello ').word().char('!').end().back_reference(1)
- regex_equality('/(hello \\w!)\\1/', expr)
- regex_compilation('(hello \\w!)\\1', expr)
-
-
-def test_back_reference_no_cg_exists():
- try:
- RegexBuilder().back_reference(1)
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_group():
- expr = RegexBuilder().group().string('hello ').word().char('!').end()
- regex_equality('/(?:hello \\w!)/', expr)
- regex_compilation('(?:hello \\w!)', expr)
-
-
-def test_error_when_called_with_no_stack():
- try:
- RegexBuilder().end()
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_assert_ahead():
- expr = RegexBuilder().assert_ahead().range('a', 'f').end().range('a', 'z')
- regex_equality('/(?=[a-f])[a-z]/', expr)
- regex_compilation('(?=[a-f])[a-z]', expr)
-
-
-def test_assert_behind():
- expr = RegexBuilder().assert_behind().string('hello ').end().range('a', 'z')
- regex_equality('/(?<=hello )[a-z]/', expr)
- regex_compilation('(?<=hello )[a-z]', expr)
-
-
-def test_assert_not_ahead():
- expr = RegexBuilder().assert_not_ahead().range('a', 'f').end().range('0', '9')
- regex_equality('/(?![a-f])[0-9]/', expr)
- regex_compilation('(?![a-f])[0-9]', expr)
-
-
-def test_assert_not_behind():
- expr = RegexBuilder().assert_not_behind().string('hello ').end().range('a', 'z')
- regex_equality('/(?<!hello )[a-z]/', expr)
- regex_compilation('(?<!hello )[a-z]', expr)
-
-
-def test_optional():
- expr = RegexBuilder().optional().word()
- regex_equality('/\\w?/', expr)
- regex_compilation('\\w?', expr)
-
-
-def test_zero_or_more():
- expr = RegexBuilder().zero_or_more().word()
- regex_equality('/\\w*/', expr)
- regex_compilation('\\w*', expr)
-
-
-def test_zero_or_more_lazy():
- expr = RegexBuilder().zero_or_more_lazy().word()
- regex_equality('/\\w*?/', expr)
- regex_compilation('\\w*?', expr)
-
-
-def test_one_or_more():
- expr = RegexBuilder().one_or_more().word()
- regex_equality('/\\w+/', expr)
- regex_compilation('\\w+', expr)
-
-
-def test_one_or_more_lazy():
- expr = RegexBuilder().one_or_more_lazy().word()
- regex_equality('/\\w+?/', expr)
- regex_compilation('\\w+?', expr)
-
-
-def test_exactly():
- expr = RegexBuilder().exactly(3).word()
- regex_equality('/\\w{3}/', expr)
- regex_compilation('\\w{3}', expr)
-
-
-def test_at_least():
- expr = RegexBuilder().at_least(3).word()
- regex_equality('/\\w{3,}/', expr)
- regex_compilation('\\w{3,}', expr)
-
-
-def test_between():
- expr = RegexBuilder().between(3, 5).word()
- regex_equality('/\\w{3,5}/', expr)
- regex_compilation('\\w{3,5}', expr)
-
-
-def test_between_lazy():
- expr = RegexBuilder().between_lazy(3, 5).word()
- regex_equality('/\\w{3,5}?/', expr)
- regex_compilation('\\w{3,5}?', expr)
-
-
-def test_start_of_input():
- expr = RegexBuilder().start_of_input()
- regex_equality('/^/', expr)
- regex_compilation('^', expr)
-
-
-def test_end_of_input():
- expr = RegexBuilder().end_of_input()
- regex_equality('/$/', expr)
- regex_compilation('$', expr)
-
-
-def test_any_of_chars():
- expr = RegexBuilder().any_of_chars('aeiou.-')
- regex_equality('/[aeiou\\.\\-]/', expr)
- regex_compilation('[aeiou\\.\\-]', expr)
-
-
-def test_anything_but_chars():
- expr = RegexBuilder().anything_but_chars('aeiou.-')
- regex_equality('/[^aeiou\\.\\-]/', expr)
- regex_compilation('[^aeiou\\.\\-]', expr)
-
-
-def test_anything_but_string():
- expr = RegexBuilder().anything_but_string('aeiou.')
- regex_equality('/(?:[^a][^e][^i][^o][^u][^\\][^.])/', expr)
- regex_compilation('(?:[^a][^e][^i][^o][^u][^\\][^.])', expr)
-
-
-def test_anything_but_range():
- expr = RegexBuilder().anything_but_range('a', 'z')
- regex_equality('/[^a-z]/', expr)
- regex_compilation('[^a-z]', expr)
- expr = RegexBuilder().anything_but_range('0', '9')
- regex_equality('/[^0-9]/', expr)
- regex_compilation('[^0-9]', expr)
-
-
-def test_string():
- expr = RegexBuilder().string('hello')
- regex_equality('/hello/', expr)
- regex_compilation('hello', expr)
-
-
-def test_string_escapes_special_chars_with_strings_of_len_1():
- expr = RegexBuilder().string('^').string('hello')
- regex_equality('/\\^hello/', expr)
- regex_compilation('\\^hello', expr)
-
-
-def test_char():
- expr = RegexBuilder().char('a')
- regex_equality('/a/', expr)
- regex_compilation('a', expr)
-
-
-def test_char_more_than_one_error():
- try:
- RegexBuilder().char('hello')
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_range():
- expr = RegexBuilder().range('a', 'z')
- regex_equality('/[a-z]/', expr)
- regex_compilation('[a-z]', expr)
-
-
-def test_must_be_instance_error():
- try:
- RegexBuilder().subexpression('nope')
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_simple_se():
- expr = RegexBuilder().start_of_input().at_least(3).digit().subexpression(simple_se).range('0', '9').end_of_input()
- regex_equality('/^\\d{3,}hello.world[0-9]$/', expr)
- regex_compilation('^\\d{3,}hello.world[0-9]$', expr)
-
-
-def test_simple_quantified_se():
- expr = RegexBuilder().start_of_input().at_least(3).digit().one_or_more().subexpression(simple_se).range('0', '9').end_of_input()
- regex_equality('/^\\d{3,}(?:hello.world)+[0-9]$/', expr)
- regex_compilation('^\\d{3,}(?:hello.world)+[0-9]$', expr)
-
-
-def test_flags_se():
- expr = (
- RegexBuilder()
- .dot_all()
- .start_of_input()
- .at_least(3)
- .digit()
- .subexpression(flags_se, {'ignore_flags': False})
- .range('0', '9')
- .end_of_input()
- )
- regex_equality('/^\\d{3,}hello.world[0-9]$/IMS', expr)
- regex_compilation('^\\d{3,}hello.world[0-9]$', expr, f=re.M | re.I | re.S)
-
-
-def test_flags_se_ignore_flags():
- expr = RegexBuilder().dot_all().start_of_input().at_least(3).digit().subexpression(flags_se).range('0', '9').end_of_input()
- regex_equality('/^\\d{3,}hello.world[0-9]$/S', expr)
- regex_compilation('^\\d{3,}hello.world[0-9]$', expr, f=re.S)
-
-
-def test_ignore_start_and_end():
- expr = RegexBuilder().at_least(3).digit().subexpression(start_end_se).range('0', '9')
- regex_equality('/\\d{3,}hello.world[0-9]/', expr)
- regex_compilation('\\d{3,}hello.world[0-9]', expr)
-
-
-def test_dont_ignore_start_and_end():
- try:
- (RegexBuilder().at_least(3).digit().subexpression(start_end_se, {'ignore_start_and_end': False}).range('0', '9'))
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_dont_ignore_start_and_end2():
- try:
- se = RegexBuilder().start_of_input().string('hello').any_char().string('world')
- (RegexBuilder().at_least(3).digit().subexpression(se, {'ignore_start_and_end': False}).range('0', '9'))
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_dont_ignore_start_and_end3():
- try:
- se = RegexBuilder().string('hello').any_char().string('world').end_of_input()
- (RegexBuilder().at_least(3).digit().subexpression(se, {'ignore_start_and_end': False}).range('0', '9'))
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_start_defined_in_me_and_se():
- try:
- (RegexBuilder().start_of_input().at_least(3).digit().subexpression(start_end_se, {'ignore_start_and_end': False}).range('0', '9'))
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_end_defined_in_me_and_se():
- try:
- (RegexBuilder().at_least(3).digit().subexpression(start_end_se, {'ignore_start_and_end': False}).range('0', '9').end_of_input())
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_no_namespacing():
- expr = RegexBuilder().at_least(3).digit().subexpression(nc_se).range('0', '9')
- regex_equality('/\\d{3,}(?P<module>.{2})\\k<module>[0-9]/', expr)
- try:
- expr.to_regex()
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_namespacing():
- expr = RegexBuilder().at_least(3).digit().subexpression(nc_se, {'namespace': 'yolo'}).range('0', '9')
- regex_equality('/\\d{3,}(?P<yolomodule>.{2})\\k<yolomodule>[0-9]/', expr)
- try:
- expr.to_regex()
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_group_name_collision_error():
- try:
- (RegexBuilder().namedCapture('module').at_least(3).digit().end().subexpression(nc_se).range('0', '9'))
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_group_name_collision_error_after_namespacing():
- try:
- (RegexBuilder().namedCapture('module').at_least(3).digit().end().subexpression(nc_se, {'namespace': 'yolo'}).range('0', '9'))
- except Exception as e:
- assert isinstance(e, Exception)
-
-
-def test_indexed_back_referencing():
- expr = RegexBuilder().capture().at_least(3).digit().end().subexpression(indexed_back_reference_se).back_reference(1).range('0', '9')
- regex_equality('/(\\d{3,})(.{2})\\2\\1[0-9]/', expr)
- regex_compilation('(\\d{3,})(.{2})\\2\\1[0-9]', expr)
-
-
-def test_deeply_nested_se():
- expr = RegexBuilder().capture().at_least(3).digit().end().subexpression(first_layer_se).back_reference(1).range('0', '9')
- regex_equality('/(\\d{3,})outer begin(?P<inner_subexpression>(?:.{2})?)outer end\\1[0-9]/', expr)
- regex_compilation('(\\d{3,})outer begin(?P<inner_subexpression>(?:.{2})?)outer end\\1[0-9]', expr)
diff --git a/tests/test_date.py b/tests/test_date.py
deleted file mode 100644
index 555dfb0..0000000
--- a/tests/test_date.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from edify.library import date
-from edify.library import iso_date
-
-
-def test_date():
- dates = {
- "1/1/2020": True,
- "01/01/2020": True,
- "1/01/2020": True,
- "01/1/2020": True,
- "1/1/20": False,
- "01/01/20": False,
- "1/1/202": False,
- "01/01/202": False,
- "12/12/2022": True,
- "12/12/2": False,
- "2021-11-04T22:32:47.142354-10:00": False,
- "2021-11-04T22:32:47.142354Z": False,
- "2021-11-04T22:32:47.142354": False,
- "2021-11-04T22:32:47": False,
- "2021-11-04T22:32": False,
- "2021-11-04T22": False,
- "2021-11-04": False,
- "2021-11": False,
- "2021": False,
- "1-1-2020": False
- }
-
- for date_string, expectation in dates.items():
- assert date(date_string) == expectation
-
-
-def test_iso_date():
- dates = {
- "1/1/2020": False,
- "01/01/2020": False,
- "1/01/2020": False,
- "01/1/2020": False,
- "1/1/20": False,
- "01/01/20": False,
- "1/1/202": False,
- "01/01/202": False,
- "12/12/2022": False,
- "12/12/2": False,
- "2021-11-04T22:32:47.142354-10:00": True,
- "2021-11-04T22:32:47.142354Z": True,
- "2021-11-04T22:32:47.142354": True,
- "2021-11-04T22:32:47": True,
- "2021-11-04T22:32": False,
- "2021-11-04T22": False,
- "2021-11-04": False,
- "2021-11": False,
- "2021": False,
- }
-
- for date_string, expectation in dates.items():
- assert iso_date(date_string) == expectation
diff --git a/tests/test_email.py b/tests/test_email.py
deleted file mode 100644
index cc28472..0000000
--- a/tests/test_email.py
+++ /dev/null
@@ -1,89 +0,0 @@
-from edify.library import email
-from edify.library import email_rfc_5322
-
-emails = [
- "plainaddress",
- "#@%^%#$@#$@#.com",
- "@example.com",
- "Joe Smith <[email protected]>",
- "email.example.com",
- "あいうえお@example.com",
-]
-
-
-def test_email():
-
- expectations = [
- True,
- True,
- True,
- True,
- True,
- True,
- True,
- True,
- True,
- True,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False
- ]
- for i in range(len(emails)):
- assert email(emails[i]) == expectations[i]
-
-
-def test_email_rfc_5322():
- expectations = [
- True,
- True,
- True,
- True,
- True,
- True,
- True,
- True,
- True,
- True,
- True,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False,
- False
- ]
- for i in range(len(emails)):
- assert email_rfc_5322(emails[i]) == expectations[i]
diff --git a/tests/test_ip.py b/tests/test_ip.py
deleted file mode 100644
index dc081a5..0000000
--- a/tests/test_ip.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from edify.library import ipv4
-from edify.library import ipv6
-
-# Generate ipv4 dictionary
-ipv4_dict = {
- "192.168.0.1": True,
- "244.232.123.233": True,
- "363.232.123.233": False,
- "234.234234.234.234": False,
- "12.12.12.12.12": False,
- "0.0.0.0": True,
- "987.987.987.987": False,
-}
-
-# Generate ipv6 dictionary
-ipv6_dict = {
- "2001:0db8:85a3:0000:0000:8a2e:0370:7334": True,
- "2001:db8:85a3:0:0:8a2e:370:7334": True,
- "2001:db8:85a3::8a2e:370:7334": True,
- "2001:db8:85a3:0:0:8A2E:370:7334": True,
- "2001:db8:85a3:0:0:8a2e:370:7334:": False,
- "2001:db8:85a3:0:0:8a2e:370:7334:7334": False,
- "2001:db8:85a3:0:0:8a2e:370:7334:7334:7334": False,
- "2001:db8:85a3:0:0:8a2e:370:7334:7334:7334:7334": False,
-}
-
-
-def test_ipv4():
- for ip, expectation in ipv4_dict.items():
- assert ipv4(ip) == expectation
-
-
-def test_ipv6():
- for ip, expectation in ipv6_dict.items():
- assert ipv6(ip) == expectation
diff --git a/tests/test_url.py b/tests/test_url.py
deleted file mode 100644
index 74f8ed1..0000000
--- a/tests/test_url.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from edify.library import url
-
-urls = [
- "example.com",
- "www.example.com",
- "www.example.com/path/to/file",
- "http://www.example.com",
- "http://example.com",
- "http://www.example.com/path/to/page",
- "https://example.com",
- "https://www.example.com/",
- "https://www.example.com/path/to/page",
- "//example.com",
-]
-
-
-def test_all_protocols():
- match_list = ["proto", "no_proto"]
- expected = [True] * 9 + [False]
- for uri, expectation in zip(urls, expected):
- assert url(uri, match=match_list) == expectation
-
-
-def test_proto_only():
- match_list = ["proto"]
- expected = [False] * 3 + [True] * 6 + [False]
- for uri, expectation in zip(urls, expected):
- print(uri, expectation)
- assert url(uri, match=match_list) == expectation
-
-
-def test_no_proto_only():
- match_list = ["no_proto"]
- expected = [True] * 3 + [False] * 7
- for uri, expectation in zip(urls, expected):
- assert url(uri, match=match_list) == expectation
-
-
-def test_invalid_protocol():
- match_list = ["invalid"]
- for uri in urls:
- try:
- url(uri, match=match_list)
- except ValueError:
- assert True
-
-
-def test_invalid_match_type():
- match_list = "invalid"
- for uri in urls:
- try:
- url(uri, match=match_list)
- except TypeError:
- assert True
-
-
-def test_empty_match_list():
- match_list = []
- for uri in urls:
- try:
- url(uri, match=match_list)
- except ValueError:
- assert True
diff --git a/tests/test_zip.py b/tests/test_zip.py
deleted file mode 100644
index 999d55d..0000000
--- a/tests/test_zip.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from edify.library import zip
-
-
-def test_valid_zips():
- zips = {"12345": True, "12345-1234": True, "12345-123456": False, "1234": False}
- for zip_string, expectation in zips.items():
- assert zip(zip_string) == expectation
-
-
-def test_invalid_locale():
- try:
- zip("12345", locale="INVALID")
- except ValueError:
- assert True
-
-
-def test_invalid_locale_type():
- try:
- zip("12345", 5)
- except TypeError:
- assert True
-
-
-def test_empty_locale():
- try:
- zip("12345", "")
- except ValueError:
- assert True
-
-
-def test_locale_IN():
- zips = {"123456": True, "000000": False, "012345": False, "12345": False, "1234567": False}
- for zip_string, expectation in zips.items():
- assert zip(zip_string, locale="IN") == expectation