From 04a7957598052ce582a30fc0171c21e8829d15fc Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 12:17:29 +0200 Subject: [PATCH 01/14] docs Signed-off-by: Jan Kowalleck --- .readthedocs.yaml | 21 ++ docs/.gitignore | 1 + docs/conf.py | 45 +++++ docs/examples.rst | 4 + docs/index.rst | 13 ++ docs/install.rst | 4 + docs/requirements.txt | 6 + src/rfc3987_syntax2/__init__.py | 55 +++++ src/rfc3987_syntax2/syntax_helpers.py | 279 ++++++++++++++++---------- 9 files changed, 321 insertions(+), 107 deletions(-) create mode 100644 .readthedocs.yaml create mode 100644 docs/.gitignore create mode 100644 docs/conf.py create mode 100644 docs/examples.rst create mode 100644 docs/index.rst create mode 100644 docs/install.rst create mode 100644 docs/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..ba1bb1a --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,21 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version, and other tools you might need +build: + os: ubuntu-lts-latest + tools: + python: "3.14" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/requirements.txt + +formats: all diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..a485625 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +/_build diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..eae48f7 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,45 @@ +project = "TODO" +copyright = "TODO" +author = "TODO" + +release = "TODO" + +extensions = [ + "sphinx.ext.autodoc", + # "sphinx.ext.viewcode", + "autoapi.extension", + "sphinx_rtd_theme", + "m2r2" +] + +source_suffix = ['.rst', '.md'] + +autoapi_type = 'python' +autoapi_dirs = ['../src/rfc3987_syntax2'] +# see https://sphinx-autoapi.readthedocs.io/en/latest/reference/config.html#confval-autoapi_options +autoapi_options = [ + 'show-module-summary', + 'members', + 'undoc-members', + 'inherited-members', + 'show-inheritance', + 'imported-members' +] + + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +# see https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-exclude_patterns +exclude_patterns = [ + '_dev', # internal docs + '_build', # build target + '.*', '**/.*', # dotfiles and folders + 'Thumbs.db', '**/Thumbs.db', +] + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..07b1838 --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,4 @@ +Examples +======== + +TODO diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..3741da0 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,13 @@ +Docs +==== + +TODO + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + install + examples + diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..5eafa69 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,4 @@ +Install +======= + +TODO diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..812fc83 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,6 @@ + + +m2r2>=0.3.4 +sphinx>=9,<10 +sphinx-autoapi>=3,<4 +sphinx-rtd-theme>=3,<4 diff --git a/src/rfc3987_syntax2/__init__.py b/src/rfc3987_syntax2/__init__.py index 7300822..2381cd7 100644 --- a/src/rfc3987_syntax2/__init__.py +++ b/src/rfc3987_syntax2/__init__.py @@ -3,3 +3,58 @@ # SPDX-License-Identifier: MIT from .syntax_helpers import * + + +__all__ = [ + "RFC3987_SYNTAX_PARSER_TYPE", + "RFC3987_SYNTAX_GRAMMAR_PATH", + "RFC3987_SYNTAX_TERMS", + "grammar", + "syntax_parser", + "parse", + "is_valid_syntax", + "make_syntax_validator", + "RFC3987_SYNTAX_TERM_VALIDATORS", + "is_valid_syntax_iri", + "is_valid_syntax_iri_reference", + "is_valid_syntax_absolute_iri", + "is_valid_syntax_scheme", + "is_valid_syntax_ihier_part", + "is_valid_syntax_irelative_ref", + "is_valid_syntax_irelative_part", + "is_valid_syntax_iauthority", + "is_valid_syntax_iuserinfo", + "is_valid_syntax_ihost", + "is_valid_syntax_ireg_name", + "is_valid_syntax_ipath", + "is_valid_syntax_ipath_abempty", + "is_valid_syntax_ipath_absolute", + "is_valid_syntax_ipath_noscheme", + "is_valid_syntax_ipath_rootless", + "is_valid_syntax_ipath_empty", + "is_valid_syntax_isegment", + "is_valid_syntax_isegment_nz", + "is_valid_syntax_isegment_nz_nc", + "is_valid_syntax_ipchar", + "is_valid_syntax_iquery", + "is_valid_syntax_ifragment", + "is_valid_syntax_iunreserved", + "is_valid_syntax_ucschar", + "is_valid_syntax_iprivate", + "is_valid_syntax_sub_delims", + "is_valid_syntax_ip_literal", + "is_valid_syntax_ipvfuture", + "is_valid_syntax_ipv6address", + "is_valid_syntax_h16", + "is_valid_syntax_ls32", + "is_valid_syntax_ipv4address", + "is_valid_syntax_dec_octet", + "is_valid_syntax_digit", + "is_valid_syntax_non_zero", + "is_valid_syntax_unreserved", + "is_valid_syntax_alpha", + "is_valid_syntax_hexdig", + "is_valid_syntax_port", + "is_valid_syntax_pct_encoded", +] + diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index fd1ebda..deac7c9 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -11,59 +11,6 @@ from .utils import load_grammar -__all__ = [ - "RFC3987_SYNTAX_PARSER_TYPE", - "RFC3987_SYNTAX_GRAMMAR_PATH", - "RFC3987_SYNTAX_TERMS", - "grammar", - "syntax_parser", - "parse", - "is_valid_syntax", - "make_syntax_validator", - "RFC3987_SYNTAX_TERM_VALIDATORS", - "is_valid_syntax_iri", - "is_valid_syntax_iri_reference", - "is_valid_syntax_absolute_iri", - "is_valid_syntax_scheme", - "is_valid_syntax_ihier_part", - "is_valid_syntax_irelative_ref", - "is_valid_syntax_irelative_part", - "is_valid_syntax_iauthority", - "is_valid_syntax_iuserinfo", - "is_valid_syntax_ihost", - "is_valid_syntax_ireg_name", - "is_valid_syntax_ipath", - "is_valid_syntax_ipath_abempty", - "is_valid_syntax_ipath_absolute", - "is_valid_syntax_ipath_noscheme", - "is_valid_syntax_ipath_rootless", - "is_valid_syntax_ipath_empty", - "is_valid_syntax_isegment", - "is_valid_syntax_isegment_nz", - "is_valid_syntax_isegment_nz_nc", - "is_valid_syntax_ipchar", - "is_valid_syntax_iquery", - "is_valid_syntax_ifragment", - "is_valid_syntax_iunreserved", - "is_valid_syntax_ucschar", - "is_valid_syntax_iprivate", - "is_valid_syntax_sub_delims", - "is_valid_syntax_ip_literal", - "is_valid_syntax_ipvfuture", - "is_valid_syntax_ipv6address", - "is_valid_syntax_h16", - "is_valid_syntax_ls32", - "is_valid_syntax_ipv4address", - "is_valid_syntax_dec_octet", - "is_valid_syntax_digit", - "is_valid_syntax_non_zero", - "is_valid_syntax_unreserved", - "is_valid_syntax_alpha", - "is_valid_syntax_hexdig", - "is_valid_syntax_port", - "is_valid_syntax_pct_encoded", -] - RFC3987_SYNTAX_PARSER_TYPE: str = "earley" RFC3987_SYNTAX_GRAMMAR_PATH: Path = Path(__file__).parent / "syntax_rfc3987.lark" @@ -110,15 +57,53 @@ "port", "pct_encoded", ] +"""All supported RFC 3987 grammar rule names exposed by this module. + +These term names can be used to select validators from +:data:`RFC3987_SYNTAX_TERM_VALIDATORS` and correspond to rules +defined in the RFC 3987 Lark grammar. +""" + +T_SYNTAX_PARSER_TERM = Literal["iri", "iri_reference", "absolute_iri"] +SYNTAX_PARSER_STARTS: list[T_SYNTAX_PARSER_TERM] = ["iri", "iri_reference", "absolute_iri"] + +def parse(term: T_SYNTAX_PARSER_TERM, value: str) -> ParseTree: + """Parse text as one of the top-level RFC 3987 syntax terms. + + Args: + term: Start rule used for parsing. Must be one of + ``"iri"``, ``"iri_reference"``, or ``"absolute_iri"``. + value: Input text to parse. -_T_SYNTAX_PARSER_TERM = Literal["iri", "iri_reference", "absolute_iri"] -_SYNTAX_PARSER_STARTS: list[_T_SYNTAX_PARSER_TERM] = ["iri", "iri_reference", "absolute_iri"] + Returns: + The Lark parse tree for ``value`` under the selected start rule. + + Raises: + lark.exceptions.LarkError: If parser initialization fails or parsing + cannot be completed. + """ -def parse(term: _T_SYNTAX_PARSER_TERM, value: str) -> 'ParseTree': return _get_syntax_parser().parse(value, start=term) -def is_valid_syntax(term: _T_SYNTAX_PARSER_TERM, value: str) -> bool: +def is_valid_syntax(term: T_SYNTAX_PARSER_TERM, value: str) -> bool: + """Check whether text is valid for a top-level RFC 3987 syntax term. + + This is a boolean convenience wrapper around :func:`parse`. + + Args: + term: Start rule used for validation. Must be one of + ``"iri"``, ``"iri_reference"``, or ``"absolute_iri"``. + value: Input text to validate. + + Returns: + ``True`` if parsing succeeds; otherwise ``False``. + + Warns: + RuntimeWarning: Emitted when a non-``UnexpectedInput`` + ``lark.exceptions.LarkError`` occurs. + """ + try: parse(term=term, value=value) except exceptions.UnexpectedInput: @@ -133,9 +118,33 @@ def is_valid_syntax(term: _T_SYNTAX_PARSER_TERM, value: str) -> bool: return False return True -_T_SYNTAX_VALIDATOR = Callable[[str], bool] +T_SYNTAX_VALIDATOR = Callable[[str], bool] +"""Callable validator for one RFC 3987 grammar rule. + +Args: + text: Input text to validate. + +Returns: + ``True`` if ``text`` matches the target rule, otherwise ``False``. +""" + + +def make_syntax_validator(rule_name: str) -> T_SYNTAX_VALIDATOR: + """Create a validator function for a specific RFC 3987 grammar rule. + + The returned callable lazily initializes and caches a dedicated + :class:`lark.Lark` parser configured with ``start=rule_name`` and + ``parser=RFC3987_SYNTAX_PARSER_TYPE``. + + Args: + rule_name: Grammar rule name to validate against. Must be a value from + :data:`RFC3987_SYNTAX_TERMS`. + + Returns: + A callable ``(text: str) -> bool`` that returns ``True`` when parsing + succeeds and ``False`` if a ``lark.exceptions.LarkError`` is raised. + """ -def make_syntax_validator(rule_name: str) -> _T_SYNTAX_VALIDATOR: parser: Optional[Lark] = None parser_lock = Lock() @@ -161,6 +170,7 @@ def syntax_validator(text: str) -> bool: def _get_grammar() -> str: """this is private API""" + global _grammar with _grammar_lock: if _grammar is None: @@ -173,6 +183,7 @@ def _get_grammar() -> str: def _get_syntax_parser() -> Lark: """this is private API""" + global _syntax_parser if _syntax_parser is not None: return _syntax_parser @@ -180,95 +191,136 @@ def _get_syntax_parser() -> Lark: with _syntax_parser_lock: if _syntax_parser is None: _syntax_parser = Lark(grammar, - start=_SYNTAX_PARSER_STARTS, + start=SYNTAX_PARSER_STARTS, parser=RFC3987_SYNTAX_PARSER_TYPE) return _syntax_parser -is_valid_syntax_iri = make_syntax_validator("iri") +is_valid_syntax_iri: T_SYNTAX_VALIDATOR = make_syntax_validator("iri") +"""Validate that input text conforms to the RFC 3987 ``IRI`` rule.""" -is_valid_syntax_iri_reference = make_syntax_validator("iri_reference") +is_valid_syntax_iri_reference: T_SYNTAX_VALIDATOR = make_syntax_validator("iri_reference") +"""Validate that input text conforms to the RFC 3987 ``IRI-reference`` rule.""" -is_valid_syntax_absolute_iri = make_syntax_validator("absolute_iri") +is_valid_syntax_absolute_iri: T_SYNTAX_VALIDATOR = make_syntax_validator("absolute_iri") +"""Validate that input text conforms to the RFC 3987 ``absolute-IRI`` rule.""" -is_valid_syntax_scheme = make_syntax_validator("scheme") +is_valid_syntax_scheme: T_SYNTAX_VALIDATOR = make_syntax_validator("scheme") +"""Validate that input text conforms to the RFC 3987 ``scheme`` rule.""" -is_valid_syntax_ihier_part = make_syntax_validator("ihier_part") +is_valid_syntax_ihier_part: T_SYNTAX_VALIDATOR = make_syntax_validator("ihier_part") +"""Validate that input text conforms to the RFC 3987 ``ihier-part`` rule.""" -is_valid_syntax_irelative_ref = make_syntax_validator("irelative_ref") +is_valid_syntax_irelative_ref: T_SYNTAX_VALIDATOR = make_syntax_validator("irelative_ref") +"""Validate that input text conforms to the RFC 3987 ``irelative-ref`` rule.""" -is_valid_syntax_irelative_part = make_syntax_validator("irelative_part") +is_valid_syntax_irelative_part: T_SYNTAX_VALIDATOR = make_syntax_validator("irelative_part") +"""Validate that input text conforms to the RFC 3987 ``irelative-part`` rule.""" -is_valid_syntax_iauthority = make_syntax_validator("iauthority") +is_valid_syntax_iauthority: T_SYNTAX_VALIDATOR = make_syntax_validator("iauthority") +"""Validate that input text conforms to the RFC 3987 ``iauthority`` rule.""" -is_valid_syntax_iuserinfo = make_syntax_validator("iuserinfo") +is_valid_syntax_iuserinfo: T_SYNTAX_VALIDATOR = make_syntax_validator("iuserinfo") +"""Validate that input text conforms to the RFC 3987 ``iuserinfo`` rule.""" -is_valid_syntax_ihost = make_syntax_validator("ihost") +is_valid_syntax_ihost: T_SYNTAX_VALIDATOR = make_syntax_validator("ihost") +"""Validate that input text conforms to the RFC 3987 ``ihost`` rule.""" -is_valid_syntax_ireg_name = make_syntax_validator("ireg_name") +is_valid_syntax_ireg_name: T_SYNTAX_VALIDATOR = make_syntax_validator("ireg_name") +"""Validate that input text conforms to the RFC 3987 ``ireg-name`` rule.""" -is_valid_syntax_ipath = make_syntax_validator("ipath") +is_valid_syntax_ipath: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath") +"""Validate that input text conforms to the RFC 3987 ``ipath`` rule.""" -is_valid_syntax_ipath_abempty = make_syntax_validator("ipath_abempty") +is_valid_syntax_ipath_abempty: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_abempty") +"""Validate that input text conforms to the RFC 3987 ``ipath-abempty`` rule.""" -is_valid_syntax_ipath_absolute = make_syntax_validator("ipath_absolute") +is_valid_syntax_ipath_absolute: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_absolute") +"""Validate that input text conforms to the RFC 3987 ``ipath-absolute`` rule.""" -is_valid_syntax_ipath_noscheme = make_syntax_validator("ipath_noscheme") +is_valid_syntax_ipath_noscheme: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_noscheme") +"""Validate that input text conforms to the RFC 3987 ``ipath-noscheme`` rule.""" -is_valid_syntax_ipath_rootless = make_syntax_validator("ipath_rootless") +is_valid_syntax_ipath_rootless: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_rootless") +"""Validate that input text conforms to the RFC 3987 ``ipath-rootless`` rule.""" -is_valid_syntax_ipath_empty = make_syntax_validator("ipath_empty") +is_valid_syntax_ipath_empty: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_empty") +"""Validate that input text conforms to the RFC 3987 ``ipath-empty`` rule.""" -is_valid_syntax_isegment = make_syntax_validator("isegment") +is_valid_syntax_isegment: T_SYNTAX_VALIDATOR = make_syntax_validator("isegment") +"""Validate that input text conforms to the RFC 3987 ``isegment`` rule.""" -is_valid_syntax_isegment_nz = make_syntax_validator("isegment_nz") +is_valid_syntax_isegment_nz: T_SYNTAX_VALIDATOR = make_syntax_validator("isegment_nz") +"""Validate that input text conforms to the RFC 3987 ``isegment-nz`` rule.""" -is_valid_syntax_isegment_nz_nc = make_syntax_validator("isegment_nz_nc") +is_valid_syntax_isegment_nz_nc: T_SYNTAX_VALIDATOR = make_syntax_validator("isegment_nz_nc") +"""Validate that input text conforms to the RFC 3987 ``isegment-nz-nc`` rule.""" -is_valid_syntax_ipchar = make_syntax_validator("ipchar") +is_valid_syntax_ipchar: T_SYNTAX_VALIDATOR = make_syntax_validator("ipchar") +"""Validate that input text conforms to the RFC 3987 ``ipchar`` rule.""" -is_valid_syntax_iquery = make_syntax_validator("iquery") +is_valid_syntax_iquery: T_SYNTAX_VALIDATOR = make_syntax_validator("iquery") +"""Validate that input text conforms to the RFC 3987 ``iquery`` rule.""" -is_valid_syntax_ifragment = make_syntax_validator("ifragment") +is_valid_syntax_ifragment: T_SYNTAX_VALIDATOR = make_syntax_validator("ifragment") +"""Validate that input text conforms to the RFC 3987 ``ifragment`` rule.""" -is_valid_syntax_iunreserved = make_syntax_validator("iunreserved") +is_valid_syntax_iunreserved: T_SYNTAX_VALIDATOR = make_syntax_validator("iunreserved") +"""Validate that input text conforms to the RFC 3987 ``iunreserved`` rule.""" -is_valid_syntax_ucschar = make_syntax_validator("ucschar") +is_valid_syntax_ucschar: T_SYNTAX_VALIDATOR = make_syntax_validator("ucschar") +"""Validate that input text conforms to the RFC 3987 ``ucschar`` rule.""" -is_valid_syntax_iprivate = make_syntax_validator("iprivate") +is_valid_syntax_iprivate: T_SYNTAX_VALIDATOR = make_syntax_validator("iprivate") +"""Validate that input text conforms to the RFC 3987 ``iprivate`` rule.""" -is_valid_syntax_sub_delims = make_syntax_validator("sub_delims") +is_valid_syntax_sub_delims: T_SYNTAX_VALIDATOR = make_syntax_validator("sub_delims") +"""Validate that input text conforms to the RFC 3987 ``sub-delims`` rule.""" -is_valid_syntax_ip_literal = make_syntax_validator("ip_literal") +is_valid_syntax_ip_literal: T_SYNTAX_VALIDATOR = make_syntax_validator("ip_literal") +"""Validate that input text conforms to the RFC 3987 ``IP-literal`` rule.""" -is_valid_syntax_ipvfuture = make_syntax_validator("ipvfuture") +is_valid_syntax_ipvfuture: T_SYNTAX_VALIDATOR = make_syntax_validator("ipvfuture") +"""Validate that input text conforms to the RFC 3987 ``IPvFuture`` rule.""" -is_valid_syntax_ipv6address = make_syntax_validator("ipv6address") +is_valid_syntax_ipv6address: T_SYNTAX_VALIDATOR = make_syntax_validator("ipv6address") +"""Validate that input text conforms to the RFC 3987 ``IPv6address`` rule.""" -is_valid_syntax_h16 = make_syntax_validator("h16") +is_valid_syntax_h16: T_SYNTAX_VALIDATOR = make_syntax_validator("h16") +"""Validate that input text conforms to the RFC 3987 ``h16`` rule.""" -is_valid_syntax_ls32 = make_syntax_validator("ls32") +is_valid_syntax_ls32: T_SYNTAX_VALIDATOR = make_syntax_validator("ls32") +"""Validate that input text conforms to the RFC 3987 ``ls32`` rule.""" -is_valid_syntax_ipv4address = make_syntax_validator("ipv4address") +is_valid_syntax_ipv4address: T_SYNTAX_VALIDATOR = make_syntax_validator("ipv4address") +"""Validate that input text conforms to the RFC 3987 ``IPv4address`` rule.""" -is_valid_syntax_dec_octet = make_syntax_validator("dec_octet") +is_valid_syntax_dec_octet: T_SYNTAX_VALIDATOR = make_syntax_validator("dec_octet") +"""Validate that input text conforms to the RFC 3987 ``dec-octet`` rule.""" -is_valid_syntax_digit = make_syntax_validator("digit") +is_valid_syntax_digit: T_SYNTAX_VALIDATOR = make_syntax_validator("digit") +"""Validate that input text conforms to the RFC 3987 ``DIGIT`` rule.""" -is_valid_syntax_non_zero = make_syntax_validator("non_zero") +is_valid_syntax_non_zero: T_SYNTAX_VALIDATOR = make_syntax_validator("non_zero") +"""deprecated - not a known entry point according to RFC 3987""" -is_valid_syntax_unreserved = make_syntax_validator("unreserved") +is_valid_syntax_unreserved: T_SYNTAX_VALIDATOR = make_syntax_validator("unreserved") +"""Validate that input text conforms to the RFC 3987 ``unreserved`` rule.""" -is_valid_syntax_alpha = make_syntax_validator("alpha") +is_valid_syntax_alpha: T_SYNTAX_VALIDATOR = make_syntax_validator("alpha") +"""Validate that input text conforms to the RFC 3987 ``ALPHA`` rule.""" -is_valid_syntax_hexdig = make_syntax_validator("hexdig") +is_valid_syntax_hexdig: T_SYNTAX_VALIDATOR = make_syntax_validator("hexdig") +"""Validate that input text conforms to the RFC 3987 ``HEXDIG`` rule.""" -is_valid_syntax_port = make_syntax_validator("port") +is_valid_syntax_port: T_SYNTAX_VALIDATOR = make_syntax_validator("port") +"""Validate that input text conforms to the RFC 3987 ``port`` rule.""" -is_valid_syntax_pct_encoded = make_syntax_validator("pct_encoded") +is_valid_syntax_pct_encoded: T_SYNTAX_VALIDATOR = make_syntax_validator("pct_encoded") +"""Validate that input text conforms to the RFC 3987 ``pct-encoded`` rule.""" -RFC3987_SYNTAX_TERM_VALIDATORS: dict[str, _T_SYNTAX_VALIDATOR] = { # frozendict - "iri": is_valid_syntax_iri, +RFC3987_SYNTAX_TERM_VALIDATORS: dict[str, T_SYNTAX_VALIDATOR] = { # frozendict + "iri": make_syntax_validator("iri"), "iri_reference": is_valid_syntax_iri_reference, "absolute_iri": is_valid_syntax_absolute_iri, "scheme": is_valid_syntax_scheme, @@ -310,26 +362,39 @@ def _get_syntax_parser() -> Lark: "port": is_valid_syntax_port, "pct_encoded": is_valid_syntax_pct_encoded, } +"""Mapping from syntax term to validator. +Allowed keys are the RFC3987 term literals (see :data:`RFC3987_SYNTAX_TERMS`). +""" # region lazy loaded attrs if TYPE_CHECKING: # types for lazy-loaded symbols grammar: str + """Lark grammar text for RFC 3987. + + This is the grammar source loaded from :data:`RFC3987_SYNTAX_GRAMMAR_PATH` + via :func:`_get_grammar`. + """ + syntax_parser: Lark + """Lazily initialized parser for RFC 3987 syntax. + + Built from :data:`grammar` using :class:`lark.Lark` with + ``parser=RFC3987_SYNTAX_PARSER_TYPE`` and + ``start=_SYNTAX_PARSER_STARTS``. + """ def __getattr__(name: str) -> Any: - if name not in __all__: - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") if name == 'grammar': return _get_grammar() if name == 'syntax_parser': return _get_syntax_parser() - raise AttributeError(f"module {__name__!r} failed to implement attribute {name!r}") + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") def __dir__() -> list[str]: - return sorted(set(globals().keys()) | set(__all__)) + return sorted(set(globals().keys()) | {'grammar', 'syntax_parser'}) # endregion lazy loaded attrs From e2b9f6468ea2b630c17849b1bf7bbeb5cdad0aca Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 12:18:16 +0200 Subject: [PATCH 02/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/syntax_helpers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index deac7c9..4800bfb 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -316,8 +316,13 @@ def _get_syntax_parser() -> Lark: is_valid_syntax_port: T_SYNTAX_VALIDATOR = make_syntax_validator("port") """Validate that input text conforms to the RFC 3987 ``port`` rule.""" -is_valid_syntax_pct_encoded: T_SYNTAX_VALIDATOR = make_syntax_validator("pct_encoded") -"""Validate that input text conforms to the RFC 3987 ``pct-encoded`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_pct_encoded(text: str) -> bool: + """typed """ +else: + is_valid_syntax_pct_encoded: T_SYNTAX_VALIDATOR = make_syntax_validator("pct_encoded") + """Validate that input text conforms to the RFC 3987 ``pct-encoded`` rule.""" + RFC3987_SYNTAX_TERM_VALIDATORS: dict[str, T_SYNTAX_VALIDATOR] = { # frozendict "iri": make_syntax_validator("iri"), From 68f6d3d9c8f8e78e832f945dc3ed54682bfd0eb6 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 12:42:03 +0200 Subject: [PATCH 03/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/syntax_helpers.py | 331 +++++++++++++++++++------- 1 file changed, 248 insertions(+), 83 deletions(-) diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index 4800bfb..c129c49 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -195,133 +195,298 @@ def _get_syntax_parser() -> Lark: parser=RFC3987_SYNTAX_PARSER_TYPE) return _syntax_parser +if TYPE_CHECKING: + def is_valid_syntax_iri(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``IRI`` rule.""" + ... +else: + is_valid_syntax_iri = make_syntax_validator("iri") -is_valid_syntax_iri: T_SYNTAX_VALIDATOR = make_syntax_validator("iri") -"""Validate that input text conforms to the RFC 3987 ``IRI`` rule.""" -is_valid_syntax_iri_reference: T_SYNTAX_VALIDATOR = make_syntax_validator("iri_reference") -"""Validate that input text conforms to the RFC 3987 ``IRI-reference`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_iri_reference(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``IRI-reference`` rule.""" + ... +else: + is_valid_syntax_iri_reference = make_syntax_validator("iri_reference") -is_valid_syntax_absolute_iri: T_SYNTAX_VALIDATOR = make_syntax_validator("absolute_iri") -"""Validate that input text conforms to the RFC 3987 ``absolute-IRI`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_absolute_iri(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``absolute-IRI`` rule.""" + ... +else: + is_valid_syntax_absolute_iri = make_syntax_validator("absolute_iri") -is_valid_syntax_scheme: T_SYNTAX_VALIDATOR = make_syntax_validator("scheme") -"""Validate that input text conforms to the RFC 3987 ``scheme`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_scheme(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``scheme`` rule.""" + ... +else: + is_valid_syntax_scheme = make_syntax_validator("scheme") -is_valid_syntax_ihier_part: T_SYNTAX_VALIDATOR = make_syntax_validator("ihier_part") -"""Validate that input text conforms to the RFC 3987 ``ihier-part`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ihier_part(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ihier-part`` rule.""" + ... +else: + is_valid_syntax_ihier_part = make_syntax_validator("ihier_part") -is_valid_syntax_irelative_ref: T_SYNTAX_VALIDATOR = make_syntax_validator("irelative_ref") -"""Validate that input text conforms to the RFC 3987 ``irelative-ref`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_irelative_ref(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``irelative-ref`` rule.""" + ... +else: + is_valid_syntax_irelative_ref = make_syntax_validator("irelative_ref") -is_valid_syntax_irelative_part: T_SYNTAX_VALIDATOR = make_syntax_validator("irelative_part") -"""Validate that input text conforms to the RFC 3987 ``irelative-part`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_irelative_part(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``irelative-part`` rule.""" + ... +else: + is_valid_syntax_irelative_part = make_syntax_validator("irelative_part") -is_valid_syntax_iauthority: T_SYNTAX_VALIDATOR = make_syntax_validator("iauthority") -"""Validate that input text conforms to the RFC 3987 ``iauthority`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_iauthority(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``iauthority`` rule.""" + ... +else: + is_valid_syntax_iauthority = make_syntax_validator("iauthority") -is_valid_syntax_iuserinfo: T_SYNTAX_VALIDATOR = make_syntax_validator("iuserinfo") -"""Validate that input text conforms to the RFC 3987 ``iuserinfo`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_iuserinfo(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``iuserinfo`` rule.""" + ... +else: + is_valid_syntax_iuserinfo = make_syntax_validator("iuserinfo") -is_valid_syntax_ihost: T_SYNTAX_VALIDATOR = make_syntax_validator("ihost") -"""Validate that input text conforms to the RFC 3987 ``ihost`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ihost(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ihost`` rule.""" + ... +else: + is_valid_syntax_ihost = make_syntax_validator("ihost") -is_valid_syntax_ireg_name: T_SYNTAX_VALIDATOR = make_syntax_validator("ireg_name") -"""Validate that input text conforms to the RFC 3987 ``ireg-name`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ireg_name(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ireg-name`` rule.""" + ... +else: + is_valid_syntax_ireg_name = make_syntax_validator("ireg_name") -is_valid_syntax_ipath: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath") -"""Validate that input text conforms to the RFC 3987 ``ipath`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipath(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ipath`` rule.""" + ... +else: + is_valid_syntax_ipath = make_syntax_validator("ipath") -is_valid_syntax_ipath_abempty: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_abempty") -"""Validate that input text conforms to the RFC 3987 ``ipath-abempty`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipath_abempty(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ipath-abempty`` rule.""" + ... +else: + is_valid_syntax_ipath_abempty = make_syntax_validator("ipath_abempty") -is_valid_syntax_ipath_absolute: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_absolute") -"""Validate that input text conforms to the RFC 3987 ``ipath-absolute`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipath_absolute(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ipath-absolute`` rule.""" + ... +else: + is_valid_syntax_ipath_absolute = make_syntax_validator("ipath_absolute") -is_valid_syntax_ipath_noscheme: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_noscheme") -"""Validate that input text conforms to the RFC 3987 ``ipath-noscheme`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipath_noscheme(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ipath-noscheme`` rule.""" + ... +else: + is_valid_syntax_ipath_noscheme = make_syntax_validator("ipath_noscheme") -is_valid_syntax_ipath_rootless: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_rootless") -"""Validate that input text conforms to the RFC 3987 ``ipath-rootless`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipath_rootless(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ipath-rootless`` rule.""" + ... +else: + is_valid_syntax_ipath_rootless = make_syntax_validator("ipath_rootless") -is_valid_syntax_ipath_empty: T_SYNTAX_VALIDATOR = make_syntax_validator("ipath_empty") -"""Validate that input text conforms to the RFC 3987 ``ipath-empty`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipath_empty(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ipath-empty`` rule.""" + ... +else: + is_valid_syntax_ipath_empty = make_syntax_validator("ipath_empty") -is_valid_syntax_isegment: T_SYNTAX_VALIDATOR = make_syntax_validator("isegment") -"""Validate that input text conforms to the RFC 3987 ``isegment`` rule.""" -is_valid_syntax_isegment_nz: T_SYNTAX_VALIDATOR = make_syntax_validator("isegment_nz") -"""Validate that input text conforms to the RFC 3987 ``isegment-nz`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_isegment(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``isegment`` rule.""" + ... +else: + is_valid_syntax_isegment = make_syntax_validator("isegment") -is_valid_syntax_isegment_nz_nc: T_SYNTAX_VALIDATOR = make_syntax_validator("isegment_nz_nc") -"""Validate that input text conforms to the RFC 3987 ``isegment-nz-nc`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_isegment_nz(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``isegment-nz`` rule.""" + ... +else: + is_valid_syntax_isegment_nz = make_syntax_validator("isegment_nz") -is_valid_syntax_ipchar: T_SYNTAX_VALIDATOR = make_syntax_validator("ipchar") -"""Validate that input text conforms to the RFC 3987 ``ipchar`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_isegment_nz_nc(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``isegment-nz-nc`` rule.""" + ... +else: + is_valid_syntax_isegment_nz_nc = make_syntax_validator("isegment_nz_nc") -is_valid_syntax_iquery: T_SYNTAX_VALIDATOR = make_syntax_validator("iquery") -"""Validate that input text conforms to the RFC 3987 ``iquery`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipchar(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ipchar`` rule.""" + ... +else: + is_valid_syntax_ipchar = make_syntax_validator("ipchar") -is_valid_syntax_ifragment: T_SYNTAX_VALIDATOR = make_syntax_validator("ifragment") -"""Validate that input text conforms to the RFC 3987 ``ifragment`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_iquery(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``iquery`` rule.""" + ... +else: + is_valid_syntax_iquery = make_syntax_validator("iquery") + +if TYPE_CHECKING: + def is_valid_syntax_ifragment(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ifragment`` rule.""" + ... +else: + is_valid_syntax_ifragment = make_syntax_validator("ifragment") -is_valid_syntax_iunreserved: T_SYNTAX_VALIDATOR = make_syntax_validator("iunreserved") -"""Validate that input text conforms to the RFC 3987 ``iunreserved`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_iunreserved(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``iunreserved`` rule.""" + ... +else: + is_valid_syntax_iunreserved = make_syntax_validator("iunreserved") -is_valid_syntax_ucschar: T_SYNTAX_VALIDATOR = make_syntax_validator("ucschar") -"""Validate that input text conforms to the RFC 3987 ``ucschar`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ucschar(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ucschar`` rule.""" + ... +else: + is_valid_syntax_ucschar = make_syntax_validator("ucschar") -is_valid_syntax_iprivate: T_SYNTAX_VALIDATOR = make_syntax_validator("iprivate") -"""Validate that input text conforms to the RFC 3987 ``iprivate`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_iprivate(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``iprivate`` rule.""" + ... +else: + is_valid_syntax_iprivate = make_syntax_validator("iprivate") -is_valid_syntax_sub_delims: T_SYNTAX_VALIDATOR = make_syntax_validator("sub_delims") -"""Validate that input text conforms to the RFC 3987 ``sub-delims`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_sub_delims(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``sub-delims`` rule.""" + ... +else: + is_valid_syntax_sub_delims = make_syntax_validator("sub_delims") -is_valid_syntax_ip_literal: T_SYNTAX_VALIDATOR = make_syntax_validator("ip_literal") -"""Validate that input text conforms to the RFC 3987 ``IP-literal`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ip_literal(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``IP-literal`` rule.""" + ... +else: + is_valid_syntax_ip_literal = make_syntax_validator("ip_literal") -is_valid_syntax_ipvfuture: T_SYNTAX_VALIDATOR = make_syntax_validator("ipvfuture") -"""Validate that input text conforms to the RFC 3987 ``IPvFuture`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipvfuture(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``IPvFuture`` rule.""" + ... +else: + is_valid_syntax_ipvfuture = make_syntax_validator("ipvfuture") -is_valid_syntax_ipv6address: T_SYNTAX_VALIDATOR = make_syntax_validator("ipv6address") -"""Validate that input text conforms to the RFC 3987 ``IPv6address`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipv6address(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``IPv6address`` rule.""" + ... +else: + is_valid_syntax_ipv6address = make_syntax_validator("ipv6address") -is_valid_syntax_h16: T_SYNTAX_VALIDATOR = make_syntax_validator("h16") -"""Validate that input text conforms to the RFC 3987 ``h16`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_h16(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``h16`` rule.""" + ... +else: + is_valid_syntax_h16 = make_syntax_validator("h16") -is_valid_syntax_ls32: T_SYNTAX_VALIDATOR = make_syntax_validator("ls32") -"""Validate that input text conforms to the RFC 3987 ``ls32`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ls32(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ls32`` rule.""" + ... +else: + is_valid_syntax_ls32 = make_syntax_validator("ls32") -is_valid_syntax_ipv4address: T_SYNTAX_VALIDATOR = make_syntax_validator("ipv4address") -"""Validate that input text conforms to the RFC 3987 ``IPv4address`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_ipv4address(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``IPv4address`` rule.""" + ... +else: + is_valid_syntax_ipv4address = make_syntax_validator("ipv4address") -is_valid_syntax_dec_octet: T_SYNTAX_VALIDATOR = make_syntax_validator("dec_octet") -"""Validate that input text conforms to the RFC 3987 ``dec-octet`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_dec_octet(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``dec-octet`` rule.""" + ... +else: + is_valid_syntax_dec_octet = make_syntax_validator("dec_octet") -is_valid_syntax_digit: T_SYNTAX_VALIDATOR = make_syntax_validator("digit") -"""Validate that input text conforms to the RFC 3987 ``DIGIT`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_digit(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``DIGIT`` rule.""" + ... +else: + is_valid_syntax_digit = make_syntax_validator("digit") -is_valid_syntax_non_zero: T_SYNTAX_VALIDATOR = make_syntax_validator("non_zero") -"""deprecated - not a known entry point according to RFC 3987""" +if TYPE_CHECKING: + def is_valid_syntax_non_zero(text: str) -> bool: + """ + .. warning:: + Deprecated: this is not a stable/public entry point and may be removed + in a future release. + """ + ... +else: + is_valid_syntax_non_zero = make_syntax_validator("non_zero") -is_valid_syntax_unreserved: T_SYNTAX_VALIDATOR = make_syntax_validator("unreserved") -"""Validate that input text conforms to the RFC 3987 ``unreserved`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_unreserved(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``unreserved`` rule.""" + ... +else: + is_valid_syntax_unreserved = make_syntax_validator("unreserved") -is_valid_syntax_alpha: T_SYNTAX_VALIDATOR = make_syntax_validator("alpha") -"""Validate that input text conforms to the RFC 3987 ``ALPHA`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_alpha(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``ALPHA`` rule.""" + ... +else: + is_valid_syntax_alpha = make_syntax_validator("alpha") -is_valid_syntax_hexdig: T_SYNTAX_VALIDATOR = make_syntax_validator("hexdig") -"""Validate that input text conforms to the RFC 3987 ``HEXDIG`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_hexdig(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``HEXDIG`` rule.""" + ... +else: + is_valid_syntax_hexdig = make_syntax_validator("hexdig") -is_valid_syntax_port: T_SYNTAX_VALIDATOR = make_syntax_validator("port") -"""Validate that input text conforms to the RFC 3987 ``port`` rule.""" +if TYPE_CHECKING: + def is_valid_syntax_port(text: str) -> bool: + """Validate that input text conforms to the RFC 3987 ``port`` rule.""" + ... +else: + is_valid_syntax_port = make_syntax_validator("port") if TYPE_CHECKING: def is_valid_syntax_pct_encoded(text: str) -> bool: - """typed """ + """Validate that input text conforms to the RFC 3987 ``pct-encoded`` rule.""" + ... else: - is_valid_syntax_pct_encoded: T_SYNTAX_VALIDATOR = make_syntax_validator("pct_encoded") - """Validate that input text conforms to the RFC 3987 ``pct-encoded`` rule.""" + is_valid_syntax_pct_encoded = make_syntax_validator("pct_encoded") RFC3987_SYNTAX_TERM_VALIDATORS: dict[str, T_SYNTAX_VALIDATOR] = { # frozendict From dc0dd3fa0e9975000dfcedf5f7f2f04b0670e290 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 12:49:25 +0200 Subject: [PATCH 04/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/syntax_helpers.py | 60 ++++++++++----------------- 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index c129c49..6ccd9f8 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -70,17 +70,12 @@ def parse(term: T_SYNTAX_PARSER_TERM, value: str) -> ParseTree: """Parse text as one of the top-level RFC 3987 syntax terms. - Args: - term: Start rule used for parsing. Must be one of - ``"iri"``, ``"iri_reference"``, or ``"absolute_iri"``. - value: Input text to parse. - - Returns: - The Lark parse tree for ``value`` under the selected start rule. - - Raises: - lark.exceptions.LarkError: If parser initialization fails or parsing - cannot be completed. + :param term: Start rule used for parsing. Must be one of ``"iri"``, + ``"iri_reference"``, or ``"absolute_iri"``. + :param value: Input text to parse. + :return: The Lark parse tree for ``value`` under the selected start rule. + :raises lark.exceptions.LarkError: If parser initialization fails or parsing + cannot be completed. """ return _get_syntax_parser().parse(value, start=term) @@ -91,17 +86,12 @@ def is_valid_syntax(term: T_SYNTAX_PARSER_TERM, value: str) -> bool: This is a boolean convenience wrapper around :func:`parse`. - Args: - term: Start rule used for validation. Must be one of - ``"iri"``, ``"iri_reference"``, or ``"absolute_iri"``. - value: Input text to validate. - - Returns: - ``True`` if parsing succeeds; otherwise ``False``. - - Warns: - RuntimeWarning: Emitted when a non-``UnexpectedInput`` - ``lark.exceptions.LarkError`` occurs. + :param term: Start rule used for validation. Must be one of ``"iri"``, + ``"iri_reference"``, or ``"absolute_iri"``. + :param value: Input text to validate. + :return: ``True`` if parsing succeeds; otherwise ``False``. + :raises RuntimeWarning: Emitted when a non-``UnexpectedInput`` + ``lark.exceptions.LarkError`` occurs. """ try: @@ -121,11 +111,8 @@ def is_valid_syntax(term: T_SYNTAX_PARSER_TERM, value: str) -> bool: T_SYNTAX_VALIDATOR = Callable[[str], bool] """Callable validator for one RFC 3987 grammar rule. -Args: - text: Input text to validate. - -Returns: - ``True`` if ``text`` matches the target rule, otherwise ``False``. +:param text: Input text to validate. +:return: ``True`` if ``text`` matches the target rule, otherwise ``False``. """ @@ -136,12 +123,9 @@ def make_syntax_validator(rule_name: str) -> T_SYNTAX_VALIDATOR: :class:`lark.Lark` parser configured with ``start=rule_name`` and ``parser=RFC3987_SYNTAX_PARSER_TYPE``. - Args: - rule_name: Grammar rule name to validate against. Must be a value from - :data:`RFC3987_SYNTAX_TERMS`. - - Returns: - A callable ``(text: str) -> bool`` that returns ``True`` when parsing + :param rule_name: Grammar rule name to validate against. Must be a value from + :data:`RFC3987_SYNTAX_TERMS`. + :return: A callable ``(text: str) -> bool`` that returns ``True`` when parsing succeeds and ``False`` if a ``lark.exceptions.LarkError`` is raised. """ @@ -444,10 +428,9 @@ def is_valid_syntax_digit(text: str) -> bool: if TYPE_CHECKING: def is_valid_syntax_non_zero(text: str) -> bool: - """ - .. warning:: - Deprecated: this is not a stable/public entry point and may be removed - in a future release. + """.. warning:: + Deprecated: this is not a stable/public entry point and may be removed + in a future release. """ ... else: @@ -543,8 +526,7 @@ def is_valid_syntax_pct_encoded(text: str) -> bool: grammar: str """Lark grammar text for RFC 3987. - This is the grammar source loaded from :data:`RFC3987_SYNTAX_GRAMMAR_PATH` - via :func:`_get_grammar`. + This is the grammar source loaded from :data:`RFC3987_SYNTAX_GRAMMAR_PATH`. """ syntax_parser: Lark From f54264b824e149a4e40b44ae6d4cd6ed17c7afce Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 13:08:55 +0200 Subject: [PATCH 05/14] docs Signed-off-by: Jan Kowalleck --- docs/conf.py | 19 ++++++++++++++----- docs/examples.rst | 4 ---- docs/index.rst | 11 ++--------- docs/install.rst | 4 ---- docs/requirements.txt | 2 +- src/rfc3987_syntax2/syntax_helpers.py | 6 +++++- src/rfc3987_syntax2/syntax_rfc3987.lark | 4 ++++ 7 files changed, 26 insertions(+), 24 deletions(-) delete mode 100644 docs/examples.rst delete mode 100644 docs/install.rst diff --git a/docs/conf.py b/docs/conf.py index eae48f7..0850117 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,15 +1,24 @@ -project = "TODO" -copyright = "TODO" -author = "TODO" +import tomllib +from pathlib import Path +from datetime import datetime + + +_ROOT = Path(__file__).resolve().parents[1] +_pyproject = tomllib.loads((_ROOT / "pyproject.toml").read_text(encoding="utf-8")) + +project = _pyproject["project"]["name"] +author = ", ".join(a["name"] for a in _pyproject["project"].get("authors", [])) or "Unknown" +copyright = f"{datetime.now().year}, {author}" + +release = _pyproject["project"].get("version", "-dev") -release = "TODO" extensions = [ "sphinx.ext.autodoc", # "sphinx.ext.viewcode", "autoapi.extension", "sphinx_rtd_theme", - "m2r2" + "myst_parser", ] source_suffix = ['.rst', '.md'] diff --git a/docs/examples.rst b/docs/examples.rst deleted file mode 100644 index 07b1838..0000000 --- a/docs/examples.rst +++ /dev/null @@ -1,4 +0,0 @@ -Examples -======== - -TODO diff --git a/docs/index.rst b/docs/index.rst index 3741da0..ec9a21b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,13 +1,6 @@ -Docs -==== - -TODO - +.. include:: ../README.md + :parser: myst_parser.sphinx_ .. toctree:: :maxdepth: 2 :caption: Contents: - - install - examples - diff --git a/docs/install.rst b/docs/install.rst deleted file mode 100644 index 5eafa69..0000000 --- a/docs/install.rst +++ /dev/null @@ -1,4 +0,0 @@ -Install -======= - -TODO diff --git a/docs/requirements.txt b/docs/requirements.txt index 812fc83..2985c12 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ -m2r2>=0.3.4 +myst-parser>=5.1.0,<6 sphinx>=9,<10 sphinx-autoapi>=3,<4 sphinx-rtd-theme>=3,<4 diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index 6ccd9f8..d003d53 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -50,6 +50,7 @@ "ipv4address", "dec_octet", "digit", + # non_zero - a local helper token used in `dec_octet` "non_zero", "unreserved", "alpha", @@ -428,7 +429,10 @@ def is_valid_syntax_digit(text: str) -> bool: if TYPE_CHECKING: def is_valid_syntax_non_zero(text: str) -> bool: - """.. warning:: + """ + Local helper token used in ``dec_octet``. + + .. warning:: Deprecated: this is not a stable/public entry point and may be removed in a future release. """ diff --git a/src/rfc3987_syntax2/syntax_rfc3987.lark b/src/rfc3987_syntax2/syntax_rfc3987.lark index 1c30753..e3fab8d 100644 --- a/src/rfc3987_syntax2/syntax_rfc3987.lark +++ b/src/rfc3987_syntax2/syntax_rfc3987.lark @@ -2,6 +2,9 @@ // Copyright (c) 2026 Jan Kowalleck - modifications and maintenance // SPDX-License-Identifier: MIT +// Lark grammar based on RFC 3987's ABNF: +// https://www.rfc-editor.org/info/rfc3987/#section-2.2 + iri: scheme ":" ihier_part ("?" iquery)? ("#" ifragment)? iri_reference: iri | irelative_ref @@ -98,6 +101,7 @@ dec_octet: digit digit: T_DIGIT T_DIGIT: /[0-9]/ +// Local helper token used in `dec_octet` non_zero: T_NON_ZERO T_NON_ZERO: /[1-9]/ From e0d3d5d850c6c9e5e5eb75394ac558959cc85af5 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 13:11:22 +0200 Subject: [PATCH 06/14] docs Signed-off-by: Jan Kowalleck --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c0099..909510b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ All notable changes to this project will be documented in this file. * New dict `RFC3987_SYNTAX_TERM_VALIDATORS` mapping `RFC3987_SYNTAX_TERMS` to validator function (via [#49]) * Performance * Public symbols use lazy loading (via [#43]) +* Docs: + * Added docstrings to public API (via [#58]) * Tests * Add grammar regression tests (via [#48]) @@ -36,6 +38,7 @@ All notable changes to this project will be documented in this file. [#50]: https://github.com/jkowalleck/rfc3987-syntax2/pull/50 [#51]: https://github.com/jkowalleck/rfc3987-syntax2/pull/51 [#52]: https://github.com/jkowalleck/rfc3987-syntax2/pull/52 +[#58]: https://github.com/jkowalleck/rfc3987-syntax2/pull/58 ## v1.2.0 - 2026-07-16 From 2b01cf327bd102dc8044017576b5263a229f8393 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 13:41:12 +0200 Subject: [PATCH 07/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/syntax_helpers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index d003d53..1c59db4 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -66,7 +66,12 @@ """ T_SYNTAX_PARSER_TERM = Literal["iri", "iri_reference", "absolute_iri"] -SYNTAX_PARSER_STARTS: list[T_SYNTAX_PARSER_TERM] = ["iri", "iri_reference", "absolute_iri"] +"""Top-level RFC 3987 parser start terms accepted by :func:`parse` and :func:`check`. + +Allowed values are ``"iri"``, ``"iri_reference"``, and ``"absolute_iri"``. +""" +_SYNTAX_PARSER_STARTS: list[T_SYNTAX_PARSER_TERM] = ["iri", "iri_reference", "absolute_iri"] +""""Top-level RFC 3987 parser start terms accepted by :func:`parse` and :func:`check`.""" def parse(term: T_SYNTAX_PARSER_TERM, value: str) -> ParseTree: """Parse text as one of the top-level RFC 3987 syntax terms. @@ -538,7 +543,7 @@ def is_valid_syntax_pct_encoded(text: str) -> bool: Built from :data:`grammar` using :class:`lark.Lark` with ``parser=RFC3987_SYNTAX_PARSER_TYPE`` and - ``start=_SYNTAX_PARSER_STARTS``. + ``start=SYNTAX_PARSER_STARTS``. """ From 089eca426d86cd1a349680defddd886c9e88f3eb Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 13:42:39 +0200 Subject: [PATCH 08/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/syntax_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index 1c59db4..157c949 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -482,7 +482,7 @@ def is_valid_syntax_pct_encoded(text: str) -> bool: RFC3987_SYNTAX_TERM_VALIDATORS: dict[str, T_SYNTAX_VALIDATOR] = { # frozendict - "iri": make_syntax_validator("iri"), + "iri": is_valid_syntax_iri, "iri_reference": is_valid_syntax_iri_reference, "absolute_iri": is_valid_syntax_absolute_iri, "scheme": is_valid_syntax_scheme, From 135a8ad9a2d245dca3148b2f7306336b465849e8 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 13:43:52 +0200 Subject: [PATCH 09/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/syntax_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index 157c949..1078867 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -70,7 +70,7 @@ Allowed values are ``"iri"``, ``"iri_reference"``, and ``"absolute_iri"``. """ -_SYNTAX_PARSER_STARTS: list[T_SYNTAX_PARSER_TERM] = ["iri", "iri_reference", "absolute_iri"] +SYNTAX_PARSER_STARTS: list[T_SYNTAX_PARSER_TERM] = ["iri", "iri_reference", "absolute_iri"] """"Top-level RFC 3987 parser start terms accepted by :func:`parse` and :func:`check`.""" def parse(term: T_SYNTAX_PARSER_TERM, value: str) -> ParseTree: From db45f76b6d813b694ae54ae973358132d1654d58 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 13:48:18 +0200 Subject: [PATCH 10/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/syntax_helpers.py | 54 ++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index 1078867..469495d 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -11,6 +11,58 @@ from .utils import load_grammar +__all__ = [ + "RFC3987_SYNTAX_PARSER_TYPE", + "RFC3987_SYNTAX_GRAMMAR_PATH", + "RFC3987_SYNTAX_TERMS", + "grammar", + "syntax_parser", + "parse", + "is_valid_syntax", + "make_syntax_validator", + "RFC3987_SYNTAX_TERM_VALIDATORS", + "is_valid_syntax_iri", + "is_valid_syntax_iri_reference", + "is_valid_syntax_absolute_iri", + "is_valid_syntax_scheme", + "is_valid_syntax_ihier_part", + "is_valid_syntax_irelative_ref", + "is_valid_syntax_irelative_part", + "is_valid_syntax_iauthority", + "is_valid_syntax_iuserinfo", + "is_valid_syntax_ihost", + "is_valid_syntax_ireg_name", + "is_valid_syntax_ipath", + "is_valid_syntax_ipath_abempty", + "is_valid_syntax_ipath_absolute", + "is_valid_syntax_ipath_noscheme", + "is_valid_syntax_ipath_rootless", + "is_valid_syntax_ipath_empty", + "is_valid_syntax_isegment", + "is_valid_syntax_isegment_nz", + "is_valid_syntax_isegment_nz_nc", + "is_valid_syntax_ipchar", + "is_valid_syntax_iquery", + "is_valid_syntax_ifragment", + "is_valid_syntax_iunreserved", + "is_valid_syntax_ucschar", + "is_valid_syntax_iprivate", + "is_valid_syntax_sub_delims", + "is_valid_syntax_ip_literal", + "is_valid_syntax_ipvfuture", + "is_valid_syntax_ipv6address", + "is_valid_syntax_h16", + "is_valid_syntax_ls32", + "is_valid_syntax_ipv4address", + "is_valid_syntax_dec_octet", + "is_valid_syntax_digit", + "is_valid_syntax_non_zero", + "is_valid_syntax_unreserved", + "is_valid_syntax_alpha", + "is_valid_syntax_hexdig", + "is_valid_syntax_port", + "is_valid_syntax_pct_encoded", +] RFC3987_SYNTAX_PARSER_TYPE: str = "earley" RFC3987_SYNTAX_GRAMMAR_PATH: Path = Path(__file__).parent / "syntax_rfc3987.lark" @@ -556,6 +608,6 @@ def __getattr__(name: str) -> Any: def __dir__() -> list[str]: - return sorted(set(globals().keys()) | {'grammar', 'syntax_parser'}) + return sorted(set(globals().keys()) | set(__all__)) # endregion lazy loaded attrs From 8e85cd85f225739c675bbc043f77ab296e745e92 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 13:50:17 +0200 Subject: [PATCH 11/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/syntax_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index 469495d..dae3ca1 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -590,6 +590,7 @@ def is_valid_syntax_pct_encoded(text: str) -> bool: This is the grammar source loaded from :data:`RFC3987_SYNTAX_GRAMMAR_PATH`. """ +if TYPE_CHECKING: # types for lazy-loaded symbols syntax_parser: Lark """Lazily initialized parser for RFC 3987 syntax. From cb433e7157d134570e83b1aaae5b8a4de7c896b2 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 17:42:04 +0200 Subject: [PATCH 12/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/__init__.py | 5 +++++ src/rfc3987_syntax2/syntax_helpers.py | 21 +++++++-------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/rfc3987_syntax2/__init__.py b/src/rfc3987_syntax2/__init__.py index b05620a..cb9cb7e 100644 --- a/src/rfc3987_syntax2/__init__.py +++ b/src/rfc3987_syntax2/__init__.py @@ -2,6 +2,10 @@ # Copyright (c) 2026 Jan Kowalleck - modifications and maintenance # SPDX-License-Identifier: MIT +""" +Re-export of :mod:`.syntax_helpers` +""" + from typing import Any, TYPE_CHECKING from . import syntax_helpers as _syntax_helpers @@ -12,6 +16,7 @@ if TYPE_CHECKING: from .syntax_helpers import * +# cannot simply re-export - need to respect lazy-loading def __getattr__(name: str) -> Any: if name in _EXPORTS_SH: diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index 87fd004..425a0bc 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -17,8 +17,10 @@ "RFC3987_SYNTAX_GRAMMAR_PATH", "RFC3987_SYNTAX_TERMS", "grammar", - "syntax_parser", + "T_SYNTAX_PARSER_TERM", "parse", + "syntax_parser", + "T_SYNTAX_VALIDATOR", "is_valid_syntax", "make_syntax_validator", "RFC3987_SYNTAX_TERM_VALIDATORS", @@ -123,8 +125,7 @@ Allowed values are ``"iri"``, ``"iri_reference"``, and ``"absolute_iri"``. """ -SYNTAX_PARSER_STARTS: list[T_SYNTAX_PARSER_TERM] = ["iri", "iri_reference", "absolute_iri"] -""""Top-level RFC 3987 parser start terms accepted by :func:`parse` and :func:`check`.""" +_SYNTAX_PARSER_STARTS: list[T_SYNTAX_PARSER_TERM] = ["iri", "iri_reference", "absolute_iri"] def parse(term: T_SYNTAX_PARSER_TERM, value: str) -> ParseTree: """Parse text as one of the top-level RFC 3987 syntax terms. @@ -234,7 +235,7 @@ def _get_syntax_parser() -> Lark: with _syntax_parser_lock: if _syntax_parser is None: _syntax_parser = Lark(grammar, - start=SYNTAX_PARSER_STARTS, + start=_SYNTAX_PARSER_STARTS, parser=RFC3987_SYNTAX_PARSER_TYPE) return _syntax_parser @@ -586,19 +587,11 @@ def is_valid_syntax_pct_encoded(text: str) -> bool: if TYPE_CHECKING: # types for lazy-loaded symbols grammar: str - """Lark grammar text for RFC 3987. - - This is the grammar source loaded from :data:`RFC3987_SYNTAX_GRAMMAR_PATH`. - """ + """Lark grammar text for RFC 3987.""" if TYPE_CHECKING: # types for lazy-loaded symbols syntax_parser: Lark - """Lazily initialized parser for RFC 3987 syntax. - - Built from :data:`grammar` using :class:`lark.Lark` with - ``parser=RFC3987_SYNTAX_PARSER_TYPE`` and - ``start=SYNTAX_PARSER_STARTS``. - """ + """Lazily initialized parser for RFC 3987 syntax.""" def __getattr__(name: str) -> Any: From 281215867706bbc2346bd5e894fc99e0b48bbb92 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 17:45:34 +0200 Subject: [PATCH 13/14] docs Signed-off-by: Jan Kowalleck --- src/rfc3987_syntax2/syntax_helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rfc3987_syntax2/syntax_helpers.py b/src/rfc3987_syntax2/syntax_helpers.py index 425a0bc..3952bf3 100644 --- a/src/rfc3987_syntax2/syntax_helpers.py +++ b/src/rfc3987_syntax2/syntax_helpers.py @@ -146,12 +146,12 @@ def is_valid_syntax(term: T_SYNTAX_PARSER_TERM, value: str) -> bool: This is a boolean convenience wrapper around :func:`parse`. + .. warning:: Emits a ``RuntimeWarning`` (via :func:`warnings.warn`) when a non-``UnexpectedInput`` ``lark.exceptions.LarkError`` occurs. + :param term: Start rule used for validation. Must be one of ``"iri"``, ``"iri_reference"``, or ``"absolute_iri"``. :param value: Input text to validate. :return: ``True`` if parsing succeeds; otherwise ``False``. - :raises RuntimeWarning: Emitted when a non-``UnexpectedInput`` - ``lark.exceptions.LarkError`` occurs. """ try: From 3169c6ad597e39dcac4f50cbf695022404943af6 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 21 Jul 2026 17:59:30 +0200 Subject: [PATCH 14/14] docs Signed-off-by: Jan Kowalleck --- CHANGELOG.md | 2 +- docs/index.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6953358..665f021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ All notable changes to this project will be documented in this file. * Performance * Public symbols use lazy loading (via [#43], [#59]) * Docs: - * Added docstrings to public API (via [#58]) + * Added docstrings to public API (via [#58]) * Tests * Add grammar regression tests (via [#48]) diff --git a/docs/index.rst b/docs/index.rst index ec9a21b..15332f0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,3 +4,6 @@ .. toctree:: :maxdepth: 2 :caption: Contents: + :titlesonly: + + autoapi/index