feat: release Dever 0.1.4 with enforceable project guidance
This commit is contained in:
1 parent
201bdee372
commit
89629c2197
86 files changed
+2798
-338
No files matched your search
@@ -0,0 +1,125 @@
|
||||
"""Focused static checks for the self-contained, versioned language skill."""
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SKILL = ROOT / "skills"
|
||||
|
||||
|
||||
def check_tutorial_sources(skill):
|
||||
"""Keep tutorial excerpts tied to the complete projects checked by Rust."""
|
||||
projects = [
|
||||
path.resolve()
|
||||
for path in [skill / "assets/template", *(skill / "assets/examples").iterdir()]
|
||||
]
|
||||
for document in sorted((skill / "references").glob("*.md")):
|
||||
content = document.read_text()
|
||||
blocks = list(re.finditer(
|
||||
r"^```dever[ \t]*\n(.*?)^```[ \t]*$", content, re.MULTILINE | re.DOTALL
|
||||
))
|
||||
if len(blocks) != len(re.findall(r"^```dever\b", content, re.MULTILINE)):
|
||||
raise ValueError(f"{document.name}: unclosed or unsupported dever fence")
|
||||
for block in blocks:
|
||||
binding = re.search(
|
||||
r"源码:\[[^\]\n]+\]\(([^)\n]+)\)\n\n\Z", content[:block.start()]
|
||||
)
|
||||
if binding is None:
|
||||
raise ValueError(f"{document.name}: dever block needs a source link")
|
||||
source = (document.parent / binding.group(1)).resolve()
|
||||
owners = [root for root in projects if source.is_relative_to(root)]
|
||||
if len(owners) != 1 or source.suffix != ".dever":
|
||||
raise ValueError(f"{document.name}: source must belong to a checked skill project")
|
||||
relative = source.relative_to(owners[0])
|
||||
if relative.parts[0] not in ("module", "test") or not source.is_file():
|
||||
raise ValueError(f"{document.name}: source must be a module or test file")
|
||||
excerpt = block.group(1)
|
||||
if not excerpt.strip() or ("\n" + source.read_text()).count("\n" + excerpt) != 1:
|
||||
raise ValueError(f"{document.name}: excerpt differs from its source or is ambiguous")
|
||||
|
||||
|
||||
class SkillExamples(unittest.TestCase):
|
||||
def test_relocated_skill_links_and_release_file_limits(self):
|
||||
with tempfile.TemporaryDirectory(prefix="dever-skill-") as directory:
|
||||
installed = Path(directory) / "dever-language"
|
||||
shutil.copytree(SKILL, installed)
|
||||
files = [path for path in installed.rglob("*") if path.is_file()]
|
||||
self.assertLessEqual(len(files), 256)
|
||||
self.assertLessEqual(sum(path.stat().st_size for path in files), 8 * 1024 * 1024)
|
||||
self.assertTrue((installed / "SKILL.md").is_file())
|
||||
for path in files:
|
||||
self.assertFalse(path.is_symlink())
|
||||
self.assertLessEqual(path.stat().st_size, 1024 * 1024)
|
||||
if path.suffix != ".md":
|
||||
continue
|
||||
for target in re.findall(r"\]\(([^)]+)\)", path.read_text()):
|
||||
if "://" in target or target.startswith("#"):
|
||||
continue
|
||||
resolved = (path.parent / target.split("#", 1)[0]).resolve()
|
||||
self.assertTrue(resolved.is_relative_to(installed), (path, target))
|
||||
self.assertTrue(resolved.exists(), (path, target))
|
||||
|
||||
def test_tutorial_code_matches_relocated_checked_sources(self):
|
||||
with tempfile.TemporaryDirectory(prefix="dever-tutorial-") as directory:
|
||||
installed = Path(directory) / "dever-language"
|
||||
shutil.copytree(SKILL, installed)
|
||||
check_tutorial_sources(installed)
|
||||
|
||||
def test_tutorial_source_drift_and_unbound_code_are_rejected(self):
|
||||
with tempfile.TemporaryDirectory(prefix="dever-tutorial-drift-") as directory:
|
||||
installed = Path(directory) / "dever-language"
|
||||
shutil.copytree(SKILL, installed)
|
||||
document = installed / "references/language.md"
|
||||
original = document.read_text()
|
||||
source = installed / "assets/template/module/hello/greeting/app.dever"
|
||||
original_source = source.read_text()
|
||||
mutations = {
|
||||
"unbound": original.replace("源码:", "完整示例:", 1),
|
||||
"changed_code": original.replace('"Hello, "', '"Welcome, "', 1),
|
||||
"unclosed": original + "\n```dever\nunclosed() () {}\n",
|
||||
"outside_project": original.replace(
|
||||
"../assets/template/module/hello/greeting/app.dever", "../SKILL.md", 1
|
||||
),
|
||||
}
|
||||
for name, changed in mutations.items():
|
||||
with self.subTest(name=name):
|
||||
document.write_text(changed)
|
||||
with self.assertRaises(ValueError):
|
||||
check_tutorial_sources(installed)
|
||||
document.write_text(original)
|
||||
source.write_text(original_source.replace('"Hello, "', '"Welcome, "'))
|
||||
with self.assertRaisesRegex(ValueError, "excerpt differs"):
|
||||
check_tutorial_sources(installed)
|
||||
source.write_text(original_source + "\n" + original_source)
|
||||
with self.assertRaisesRegex(ValueError, "ambiguous"):
|
||||
check_tutorial_sources(installed)
|
||||
|
||||
def test_signature_index_matches_public_library_declarations(self):
|
||||
index = (SKILL / "references/signatures.md").read_text()
|
||||
blocks = re.findall(r"```signatures\n(.*?)```", index, re.DOTALL)
|
||||
self.assertTrue(blocks, "the shipped index must contain concrete signatures")
|
||||
seen = set()
|
||||
for signature in "\n".join(blocks).splitlines():
|
||||
if not signature:
|
||||
continue
|
||||
qualified = signature.split("(", 1)[0]
|
||||
package, function = qualified.rsplit(".", 1)
|
||||
library = ROOT / "library" / (package.replace(".", "/") + ".dever")
|
||||
self.assertNotIn(qualified, seen)
|
||||
seen.add(qualified)
|
||||
declaration = re.search(
|
||||
rf"^public ({re.escape(function)}\s*\([^{{]*?)\s*\{{",
|
||||
library.read_text(),
|
||||
re.MULTILINE,
|
||||
)
|
||||
self.assertIsNotNone(declaration, qualified)
|
||||
expected = package + "." + declaration.group(1)
|
||||
self.assertEqual(re.sub(r"\s", "", signature), re.sub(r"\s", "", expected), qualified)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user