Source code for semantic_release.commit_parser.conventional.options_monorepo
from __future__ import annotations
from pathlib import Path
from re import compile as regexp, error as RegExpError # noqa: N812
from typing import TYPE_CHECKING, Any, Iterable, Tuple
from pydantic import Field, field_validator
from pydantic.dataclasses import dataclass
# typing_extensions is for Python 3.8, 3.9, 3.10 compatibility
from typing_extensions import Annotated
from semantic_release.commit_parser.conventional.options import (
ConventionalCommitParserOptions,
)
if TYPE_CHECKING: # pragma: no cover
pass
[docs]
@dataclass
class ConventionalCommitMonorepoParserOptions(ConventionalCommitParserOptions):
# TODO: add example into the docstring
"""Options dataclass for ConventionalCommitMonorepoParser."""
path_filters: Annotated[Tuple[str, ...], Field(validate_default=True)] = (".",)
"""
A set of relative paths to filter commits by. Only commits with file changes that
match these file paths or its subdirectories will be considered valid commits.
Syntax is similar to .gitignore with file path globs and inverse file match globs
via `!` prefix. Paths should be relative to the current working directory.
"""
scope_prefix: str = ""
"""
A prefix that will be striped from the scope when parsing commit messages.
If set, it will cause unscoped commits to be ignored. Use this in tandem with
the `path_filters` option to filter commits by directory and scope. This will
be fed into a regular expression so you must escape any special characters that
are meaningful in regular expressions (e.g. `.`, `*`, `?`, `+`, etc.) if you want
to match them literally.
"""
[docs]
@field_validator("path_filters", mode="before")
@classmethod
def convert_strs_to_paths(cls, value: Any) -> tuple[str, ...]:
if isinstance(value, str):
return (value,)
if isinstance(value, Path):
return (str(value),)
if isinstance(value, Iterable):
results: list[str] = []
for val in value:
if isinstance(val, (str, Path)):
results.append(str(Path(val)))
continue
msg = f"Invalid type: {type(val)}, expected str or Path."
raise TypeError(msg)
return tuple(results)
msg = f"Invalid type: {type(value)}, expected str, Path, or Iterable."
raise TypeError(msg)
[docs]
@field_validator("path_filters", mode="after")
@classmethod
def resolve_path(cls, dir_path_strs: tuple[str, ...]) -> tuple[str, ...]:
return tuple(
(
f"!{Path(str_path[1:]).expanduser().absolute().resolve()}"
# maintains the negation prefix if it exists
if str_path.startswith("!")
# otherwise, resolve the path normally
else str(Path(str_path).expanduser().absolute().resolve())
)
for str_path in dir_path_strs
)
[docs]
@field_validator("scope_prefix", mode="after")
@classmethod
def validate_scope_prefix(cls, scope_prefix: str) -> str:
if not scope_prefix:
return ""
# Allow the special case of a plain wildcard although it's not a valid regex
if scope_prefix == "*":
return ".*"
try:
regexp(scope_prefix)
except RegExpError as err:
raise ValueError(f"Invalid regex {scope_prefix!r}") from err
return scope_prefix