diff --git a/Makefile b/Makefile index deaab0a4..a6b91fea 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,12 @@ UV_RUN=uv run all: help -dev: + +src/ssvc/utils/namespace_patterns.py: src/ssvc/utils/ssvc_namespace_pattern.abnf + echo "# AUTOGENERATED, DO NOT CHANGE manually, see Makefile" >$@ + uv run --group codegen abnf-to-regexp --format python-nested -i $< >>$@ + +dev: src/ssvc/utils/namespace_patterns.py @echo "Set up dev environment..." uv sync --dev @@ -18,32 +23,32 @@ mdlint_fix: @echo "Running markdownlint..." markdownlint --config .markdownlint.yml --fix . -test: +test: src/ssvc/utils/namespace_patterns.py @echo "Running tests locally..." $(UV_RUN) pytest -v -docker_test: +docker_test: src/ssvc/utils/namespace_patterns.py @echo "Building the latest test image..." $(DOCKER_COMPOSE) build test @echo "Running tests in Docker..." $(DOCKER_COMPOSE) run --rm test -docs_local: +docs_local: src/ssvc/utils/namespace_patterns.py @echo "Building and running docs locally..." $(UV_RUN) mkdocs serve -docs: +docs: src/ssvc/utils/namespace_patterns.py @echo "Building and running docs in Docker..." $(DOCKER_COMPOSE) up docs -api: +api: src/ssvc/utils/namespace_patterns.py @echo "Building and running API in Docker..." $(DOCKER_COMPOSE) up api -api_dev: +api_dev: src/ssvc/utils/namespace_patterns.py $(UV_RUN) uvicorn ssvc.api.main:app --reload -up: +up: src/ssvc/utils/namespace_patterns.py @echo "Starting Docker services..." $(DOCKER_COMPOSE) up -d @@ -51,7 +56,7 @@ down: @echo "Stopping Docker services..." $(DOCKER_COMPOSE) down -regenerate_json: +regenerate_json: src/ssvc/utils/namespace_patterns.py @echo "Regenerating JSON files..." rm -rf data/json/decision_points export PYTHONPATH=$(PWD)/src && ./src/ssvc/doctools.py --datadir=./data --overwrite diff --git a/docker/Dockerfile b/docker/Dockerfile index 3fd4c214..bd171b6b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ COPY . /app ENV PYTHONPATH=/app/src # install requirements -RUN uv sync --frozen +RUN uv sync --frozen --no-dev FROM dependencies AS test @@ -19,10 +19,10 @@ ENV PYTHONPATH=/app/src # Install pytest and dev dependencies RUN uv sync --frozen --dev # Run the unit tests -CMD ["uv", "run", "pytest"] +CMD ["uv", "run", "--no-sync", "pytest"] FROM dependencies AS docs -CMD ["uv", "run", "mkdocs", "serve", "--dev-addr", "0.0.0.0:8000"] +CMD ["uv", "run", "--no-sync", "mkdocs", "serve", "--dev-addr", "0.0.0.0:8000"] FROM dependencies AS registry_api -CMD ["uv", "run", "uvicorn", "ssvc.api.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uv", "run", "--no-sync", "uvicorn", "ssvc.api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/pyproject.toml b/pyproject.toml index 4c438fe0..83eb7b8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ dependencies = [ "pydantic>=2.11.7", "semver>=3.0.4", "fastapi[all,standard]>=0.116.1", - "abnf-to-regexp>=1.2.0", ] dynamic = ["version",] @@ -93,3 +92,6 @@ dev = [ "linkchecker>=10.6.0", "pytest>=8.4.1", ] +codegen = [ + "abnf-to-regexp>=1.2.0", +] diff --git a/src/ssvc/utils/namespace_patterns.py b/src/ssvc/utils/namespace_patterns.py new file mode 100644 index 00000000..fd9d9233 --- /dev/null +++ b/src/ssvc/utils/namespace_patterns.py @@ -0,0 +1,29 @@ +# AUTOGENERATED, DO NOT CHANGE manually, see Makefile +alnum = '[a-zA-Z0-9]' +lower = '[a-z]' +alnumlow = f'({lower}|[0-9])' +dash = '-' +alnumlowdash = f'({alnumlow}|{dash})' +label = f'{alnumlow}(({alnumlowdash}){{0,61}}{alnumlow})?' +reverse_dns = f'{label}(\\.{label})+' +dot = '\\.' +specialchar = f'({dot}|{dash})' +fragment_seg = f'({alnumlow})+({specialchar}({alnumlow})+)*' +x_name = f'{reverse_dns}#{fragment_seg}' +x_base = f'x_{x_name}' +ns_core = f'{lower}{alnumlow}(({specialchar})?({alnumlow})+)+' +reg_base = f'{ns_core}(#{fragment_seg})?' +base_ns = f'({x_base}|{reg_base})' +singleton = '[0-9A-WY-Za-wy-z]' +bcp47 = ( + '(([a-zA-Z]{2,3}(-[a-zA-Z]{3}(-[a-zA-Z]{3}){0,2})?|[a-z' + 'A-Z]{4,8})(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-' + f'(({alnum}){{5,8}}|[0-9]({alnum}){{3}}))*(-{singleton}(-' + f'({alnum}){{2,8}})+)*(-[xX](-({alnum}){{2,8}})+)?|[xX](-' + f'({alnum}){{2,8}})+|i-default|i-mingo)' +) +translation = f'\\.({reverse_dns}|{x_name})\\${bcp47}' +ext_seg = f'({bcp47}|\\.{x_name}|{translation})' +lang_ext = f'(/|/{bcp47})' +extensions = f'{lang_ext}((/{ext_seg})+)?' +namespace = f'{base_ns}({extensions})?' diff --git a/src/ssvc/utils/patterns.py b/src/ssvc/utils/patterns.py index 2f24373a..9f4c677c 100644 --- a/src/ssvc/utils/patterns.py +++ b/src/ssvc/utils/patterns.py @@ -3,6 +3,7 @@ Provides python regular expressions and utility functions for SSVC-related patterns. """ + # Copyright (c) 2026 Carnegie Mellon University. # NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE # ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. @@ -21,10 +22,8 @@ # This Software includes and/or makes use of Third-Party Software each # subject to its own license. # DM24-0278 -from io import StringIO -from pathlib import Path -from abnf_to_regexp.main import run as run_abnf_to_regexp, Params, Format +import ssvc.utils.namespace_patterns as namespace_patterns # from https://semver.org/ VERSION_PATTERN = r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" @@ -34,44 +33,14 @@ BCP_47_PATTERN = r"(([A-Za-z]{2,3}(-[A-Za-z]{3}(-[A-Za-z]{3}){0,2})?|[A-Za-z]{4,8})(-[A-Za-z]{4})?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-[A-WY-Za-wy-z0-9](-[A-Za-z0-9]{2,8})+)*(-[Xx](-[A-Za-z0-9]{1,8})+)?|[Xx](-[A-Za-z0-9]{1,8})+|[Ii]-[Dd][Ee][Ff][Aa][Uu][Ll][Tt]|[Ii]-[Mm][Ii][Nn][Gg][Oo])" """A regular expression pattern for BCP-47 language tags.""" - # --- Namespace Regex Components --- - -# --- Length constraint --- +# ---- Length constraint ---- LENGTH_CHECK_PATTERN = r"(?=.{3,1000}$)" +# ---- define base patterns to be compatible with previously existing tests +BASE_PATTERN = namespace_patterns.ns_core +BASE_NS_PATTERN = namespace_patterns.base_ns +EXT_SEGMENT_PATTERN = namespace_patterns.fragment_seg -# use abnf-to-regexp as library to catch the output str of the equivalent of -# abnf-to-regexp --format python-nested -i ssvc_namespace_pattern.abnf -# and execute it as python code - -_abnf_python_code = StringIO() -_err = StringIO() -_input_pth = Path(__file__).parent / "ssvc_namespace_pattern.abnf" -_params = Params( - input_path=_input_pth, - output_path=None, - fmt=Format.PYTHON_NESTED, -) - -if ( - run_abnf_to_regexp(params=_params, stdout=_abnf_python_code, stderr=_err) - > 0 -): - raise RuntimeError( - f"Reading of {_input_pth} failed with {_err.getvalue()}" - ) - -exec(_abnf_python_code.getvalue()) - -# we do not need them anymore -del _abnf_python_code -del _err - -# --- define base patterns to be compatible with previously existing tests -BASE_PATTERN = ns_core -BASE_NS_PATTERN = base_ns -EXT_SEGMENT_PATTERN = fragment_seg - -# --- Combine all parts into the full namespace pattern --- -NS_PATTERN_STR = rf"^{namespace}$" +# ---- Combine all parts into the full namespace pattern ---- +NS_PATTERN_STR = rf"^{namespace_patterns.namespace}$" diff --git a/uv.lock b/uv.lock index 6be03e7b..13ca544c 100644 --- a/uv.lock +++ b/uv.lock @@ -171,7 +171,6 @@ wheels = [ name = "certcc-ssvc" source = { editable = "." } dependencies = [ - { name = "abnf-to-regexp" }, { name = "fastapi", extra = ["all", "standard"] }, { name = "jsonschema" }, { name = "markdown-exec", extra = ["ansi"] }, @@ -194,6 +193,9 @@ dependencies = [ ] [package.dev-dependencies] +codegen = [ + { name = "abnf-to-regexp" }, +] dev = [ { name = "black" }, { name = "linkchecker" }, @@ -202,7 +204,6 @@ dev = [ [package.metadata] requires-dist = [ - { name = "abnf-to-regexp", specifier = ">=1.2.0" }, { name = "fastapi", extras = ["all", "standard"], specifier = ">=0.116.1" }, { name = "jsonschema", specifier = ">=4.25.1" }, { name = "markdown-exec", extras = ["ansi"], specifier = ">=1.11.0" }, @@ -225,6 +226,9 @@ requires-dist = [ ] [package.metadata.requires-dev] +codegen = [ + { name = "abnf-to-regexp", specifier = ">=1.2.0" }, +] dev = [ { name = "black", specifier = ">=25.9.0" }, { name = "linkchecker", specifier = ">=10.6.0" },