refactor: consolidate SKILL.md version regex into lib/skill_meta.py
The same `^version:\s*"([^"]+)"\s*$` regex (or a slight variant) was
duplicated across three files: render.py inline, test_plugin_contract.py
local helper, test_version_consistency.py local helper. A future change
to the SKILL.md frontmatter version format would have needed to update
three places without any compile-time pressure to keep them in sync.
New skills/last30days/scripts/lib/skill_meta.py provides:
- `_VERSION_RE` private compiled pattern (accepts double-quoted,
single-quoted, or unquoted YAML version scalars per the widening
landed in 997708a)
- `read_skill_version(skill_md_path: Path) -> str | None` helper that
catches OSError + UnicodeDecodeError and returns None on miss
Callers updated:
- render.py::_skill_version now calls skill_meta.read_skill_version
inside the SKILL.md fallback loop, returning `read_skill_version(...) or "?"`.
Semantically equivalent to the old break-after-first-SKILL.md logic.
- test_plugin_contract.py and test_version_consistency.py import the
helper instead of defining the regex inline. Both files use the
established sys.path.insert pattern.
Added tests/test_skill_meta.py with 6 direct unit tests covering the
helper's full contract: missing file, undecodable bytes, no-version-line,
and all three quoting styles (double, single, unquoted). Previously the
helper was only exercised transitively through render._skill_version().
Added test_skill_md_uses_double_quoted_version to
test_version_consistency.py — the old per-test regex incidentally
asserted "this repo's SKILL.md uses double-quotes" by being strict;
the shared helper accepts all three styles, so the assertion is now
explicit instead of implicit.
Code-reviewed by ce-code-review (8 reviewers); safe_auto fixes applied
inline (rename to _VERSION_RE, group or-chain instead of generator,
docstring tightened, dropped unnecessary `from __future__ import
annotations`, tightened signature to Path-only).
Conftest.py refactor for the sys.path.insert duplication across ~20 test
files filed as issue #411 — out of scope for this PR (touches many
files, separate concern).
Test results: 23 passed in the affected test set (16 prior + 6 new
test_skill_meta tests + 1 new double-quote assertion). Full suite shows
same 13 pre-existing failures as main; zero new failures.
This commit is contained in:
@@ -4,18 +4,11 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
from collections import Counter
|
||||
from datetime import date
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from . import dates, schema
|
||||
|
||||
|
||||
_VERSION_RE = re.compile(
|
||||
r'''^version:\s*(?:"([^"]+)"|'([^']+)'|(\S+))\s*$''',
|
||||
re.MULTILINE,
|
||||
)
|
||||
from . import dates, schema, skill_meta
|
||||
|
||||
|
||||
def _skill_version() -> str:
|
||||
@@ -25,11 +18,12 @@ def _skill_version() -> str:
|
||||
Hermes, etc.) do not always carry `.claude-plugin/plugin.json` — that file ships with
|
||||
plugin-cache installs but not with per-harness skill installs. SKILL.md frontmatter is
|
||||
the fallback that keeps the badge from emitting v? on those installs. Returns "?" only
|
||||
if both sources are missing.
|
||||
if no usable version string is found from either source (missing files, corrupt JSON,
|
||||
or SKILL.md without a version line).
|
||||
|
||||
A corrupt manifest at one ancestor does not shadow a valid manifest at a deeper one
|
||||
(continue, not break). YAML frontmatter accepts double-quoted, single-quoted, or
|
||||
unquoted version scalars.
|
||||
(continue, not break). SKILL.md parsing accepts double-quoted, single-quoted, or
|
||||
unquoted YAML version scalars (delegated to skill_meta.read_skill_version).
|
||||
"""
|
||||
here = pathlib.Path(__file__).resolve()
|
||||
for parent in here.parents:
|
||||
@@ -43,16 +37,11 @@ def _skill_version() -> str:
|
||||
return version
|
||||
|
||||
# No usable manifest found at any ancestor — fall back to SKILL.md frontmatter.
|
||||
# First SKILL.md found in the walk is THIS skill's; never traverse past it.
|
||||
for parent in here.parents:
|
||||
skill_md = parent / "SKILL.md"
|
||||
if skill_md.is_file():
|
||||
try:
|
||||
match = _VERSION_RE.search(skill_md.read_text())
|
||||
except (OSError, UnicodeDecodeError):
|
||||
break
|
||||
if match:
|
||||
return next(g for g in match.groups() if g is not None)
|
||||
break
|
||||
return skill_meta.read_skill_version(skill_md) or "?"
|
||||
return "?"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"""SKILL.md metadata helpers — single source of truth for parsing skill frontmatter.
|
||||
|
||||
Centralizes the version regex that previously lived in render.py and was
|
||||
duplicated in tests/test_plugin_contract.py and tests/test_version_consistency.py.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
# Matches `version: "x.y.z"`, `version: 'x.y.z'`, or `version: x.y.z` in YAML
|
||||
# frontmatter. Multiline so the pattern can be applied to a full SKILL.md text.
|
||||
# Three alternation groups — exactly one captures per successful match.
|
||||
_VERSION_RE = re.compile(
|
||||
r'''^version:\s*(?:"([^"]+)"|'([^']+)'|(\S+))\s*$''',
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
|
||||
def read_skill_version(skill_md_path: Path) -> str | None:
|
||||
"""Return the version string from a SKILL.md's frontmatter, or None.
|
||||
|
||||
Returns None if the file can't be read (missing, permission, decode error)
|
||||
or if no `version:` line is found. Accepts double-quoted, single-quoted,
|
||||
or unquoted YAML version scalars.
|
||||
"""
|
||||
try:
|
||||
text = skill_md_path.read_text()
|
||||
except (OSError, UnicodeDecodeError):
|
||||
return None
|
||||
match = _VERSION_RE.search(text)
|
||||
if not match:
|
||||
return None
|
||||
return match.group(1) or match.group(2) or match.group(3)
|
||||
Reference in New Issue
Block a user