import re
from dataclasses import dataclass
from importlib.metadata import version as _pkg_version
from typing import Literal
_BANNER_WIDTH = 76
MARKER_SENTINEL = "codegen:generated"
_MARKER_SCAN_LINES = 20
_MARKER_RE = re.compile(
rf"{re.escape(MARKER_SENTINEL)}\s+target=(?P<target>\S+)\s+"
r"version=(?P<version>\S+)"
)
_MARKER_VERSION_RE = re.compile(
rf"({re.escape(MARKER_SENTINEL)}\s+target=\S+\s+version=)\S+"
)
_HASH = CommentStyle(line="#", fill="#")
_SLASH = CommentStyle(line="//", fill="/")
_CSS = CommentStyle(block=("/*", " * ", " */"), fill="*")
_HTML = CommentStyle(block=("<!--", " ", "-->"), fill="=")
def _python_comment_styles() -> dict[str, CommentStyle]:
return {
"py": _HASH,
"pyi": _HASH,
"toml": _HASH,
"ini": _HASH,
"cfg": _HASH,
"env": _HASH,
".env.example": _HASH,
"gitignore": _HASH,
"dockerignore": _HASH,
"Dockerfile": _HASH,
"justfile": _HASH,
"just": _HASH,
}
def _typescript_comment_styles() -> dict[str, CommentStyle]:
return {
"ts": _SLASH,
"tsx": _SLASH,
"js": _SLASH,
"jsx": _SLASH,
"mjs": _SLASH,
"cjs": _SLASH,
"jsonnet": _SLASH,
"libsonnet": _SLASH,
"css": _CSS,
"scss": _CSS,
"html": _HTML,
"htm": _HTML,
"gitignore": _HASH,
"prettierignore": _HASH,
"justfile": _HASH,
}
def _rego_comment_styles() -> dict[str, CommentStyle]:
return {"rego": _HASH, "yaml": _HASH, "yml": _HASH}
_COMMENT_STYLES: dict[str, CommentStyle] = {
**_python_comment_styles(),
**_typescript_comment_styles(),
**_rego_comment_styles(),
}
def comment_style_for(path: str) -> CommentStyle | None:
name = path.rsplit("/", 1)[-1]
if name in _COMMENT_STYLES:
return _COMMENT_STYLES[name]
if "." in name:
ext = name.rsplit(".", 1)[-1]
return _COMMENT_STYLES.get(ext)
return None
def render_banner(
*,
target_name: str,
path: str,
if_exists: Literal["overwrite", "skip"],
version: str | None = None,
) -> str:
style = comment_style_for(path)
if style is None:
return ""
cmd = f"`codegen generate --target {target_name}`"
if if_exists == "skip":
lines = [
f"AUTOGENERATED by {cmd}.",
'One-shot scaffold (if_exists="skip"): your edits are preserved',
"on re-run. Pass --force-paths to regenerate it deliberately.",
]
else:
lines = [
f"AUTOGENERATED by {cmd}.",
(
"─── DO NOT EDIT BY HAND "
"──────────────────────────────────────────────────"
),
"Overwritten on every run; edit the config or source, not here.",
]
resolved = _pkg_version("codegen") if version is None else version
lines.append(f"{MARKER_SENTINEL} target={target_name} version={resolved}")
return style.render(lines)
[docs]
@dataclass(frozen=True)
class ParsedMarker:
target: str
version: str
def parse_marker(content: str) -> ParsedMarker | None:
for line in content.splitlines()[:_MARKER_SCAN_LINES]:
match = _MARKER_RE.search(line)
if match is not None:
return ParsedMarker(
target=match["target"], version=match["version"]
)
return None
def normalize_marker_version(content: str) -> str:
return _MARKER_VERSION_RE.sub(r"\g<1>0", content)