mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
a9f58428bf
A skill is a directory with a SKILL.md; roots are ordered by priority (an explicit path, then our own user directory, then the directories other harnesses install into, with the project-scope one behind its own switch). The first copy of a name wins and the ones it shadows stay listed for reporting instead of vanishing. The frontmatter reader covers the subset those files actually use — scalars, booleans, ints, inline and block lists, | and > blocks, folded plain scalars — so no YAML dependency is needed; a nested mapping is skipped and a structurally broken block degrades to "no metadata" with a note, keeping a third-party skill listable rather than dropping it. Metadata is cached against the directory list and its SKILL.md timestamps, so a listing is cheap yet notices an edit.
115 lines
3.4 KiB
Python
115 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from plyngent.skills.frontmatter import (
|
|
FrontmatterError,
|
|
parse_frontmatter,
|
|
parse_yaml_subset,
|
|
split_frontmatter,
|
|
)
|
|
|
|
|
|
def test_parse_frontmatter_basic() -> None:
|
|
text = (
|
|
"---\n"
|
|
"name: pdf-processing\n"
|
|
'description: "Extract text and tables from PDFs"\n'
|
|
"version: 2\n"
|
|
"license: MIT\n"
|
|
"---\n"
|
|
"\n"
|
|
"# PDF processing\n"
|
|
"\n"
|
|
"Use pdftotext first.\n"
|
|
)
|
|
metadata, body = parse_frontmatter(text)
|
|
assert metadata == {
|
|
"name": "pdf-processing",
|
|
"description": "Extract text and tables from PDFs",
|
|
"version": 2,
|
|
"license": "MIT",
|
|
}
|
|
assert body.startswith("# PDF processing")
|
|
assert "pdftotext" in body
|
|
|
|
|
|
def test_parse_frontmatter_without_block_keeps_the_whole_text() -> None:
|
|
text = "# Just a skill\n\nNo frontmatter here.\n"
|
|
metadata, body = parse_frontmatter(text)
|
|
assert metadata == {}
|
|
assert body == text
|
|
|
|
|
|
def test_parse_frontmatter_unterminated_block_is_not_frontmatter() -> None:
|
|
text = "---\nname: x\n\nbody without a closing fence\n"
|
|
metadata, body = parse_frontmatter(text)
|
|
assert metadata == {}
|
|
assert body == text
|
|
|
|
|
|
def test_frontmatter_closing_dots_are_accepted() -> None:
|
|
block, body = split_frontmatter("---\nname: x\n...\nbody\n")
|
|
assert block == "name: x"
|
|
assert body == "body\n"
|
|
|
|
|
|
def test_yaml_subset_types_lists_and_booleans() -> None:
|
|
block = "\n".join(
|
|
[
|
|
"name: acme",
|
|
"read_only: true",
|
|
"confirm: no",
|
|
"retries: 3",
|
|
"allowed-tools: [read_file, grep_files]",
|
|
"tags:",
|
|
" - alpha",
|
|
" - beta",
|
|
]
|
|
)
|
|
assert parse_yaml_subset(block) == {
|
|
"name": "acme",
|
|
"read_only": True,
|
|
"confirm": False,
|
|
"retries": 3,
|
|
"allowed-tools": ["read_file", "grep_files"],
|
|
"tags": ["alpha", "beta"],
|
|
}
|
|
|
|
|
|
def test_yaml_subset_plain_scalar_folds_continuation_lines() -> None:
|
|
block = "description: A long description\n that continues on the next line\n"
|
|
assert parse_yaml_subset(block) == {
|
|
"description": "A long description that continues on the next line",
|
|
}
|
|
|
|
|
|
def test_yaml_subset_block_scalars() -> None:
|
|
literal = "description: |\n first\n second\n"
|
|
folded = "description: >\n first\n second\n"
|
|
assert parse_yaml_subset(literal) == {"description": "first\nsecond"}
|
|
assert parse_yaml_subset(folded) == {"description": "first second"}
|
|
|
|
|
|
def test_yaml_subset_nested_mapping_is_skipped() -> None:
|
|
"""Unsupported nesting must not lose the flat fields around it."""
|
|
block = "name: acme\nmetadata:\n author: someone\n homepage: https://example.com\nlicense: MIT\n"
|
|
assert parse_yaml_subset(block) == {"name": "acme", "license": "MIT"}
|
|
|
|
|
|
def test_yaml_subset_comments_and_blank_lines() -> None:
|
|
block = "# a comment\n\nname: acme\n"
|
|
assert parse_yaml_subset(block) == {"name": "acme"}
|
|
|
|
|
|
def test_yaml_subset_rejects_broken_lines() -> None:
|
|
with pytest.raises(FrontmatterError):
|
|
_ = parse_yaml_subset("name: acme\nnot a pair\n")
|
|
with pytest.raises(FrontmatterError):
|
|
_ = parse_yaml_subset("- orphan item\n")
|
|
|
|
|
|
def test_yaml_subset_rejects_indented_top_level() -> None:
|
|
with pytest.raises(FrontmatterError):
|
|
_ = parse_yaml_subset(" name: acme\n")
|