@chengzhao-dev/dsh-quarto-book 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/docs/plugin-flow.md +2 -2
- package/docs/versioning.md +8 -8
- package/package.json +1 -1
- package/skills/designing-theme/assets/theme/css/base.css +368 -368
- package/skills/designing-theme/assets/theme/includes/footer.html +63 -63
- package/skills/designing-theme/assets/theme/palettes/github/meta.md +38 -38
- package/skills/designing-theme/knowledge/reading-surface.md +68 -68
- package/skills/designing-theme/scripts/measuring-pages.mjs +309 -309
- package/skills/writing-quarto/SKILL.md +43 -43
- package/skills/writing-quarto/scripts/checking-callouts.py +102 -102
- package/skills/writing-quarto/scripts/checking-dom.py +577 -577
- package/skills/writing-quarto/scripts/diagrams/rendering-coords.py +250 -250
- package/skills/writing-quarto/scripts/testing-check-ascii-names.py +71 -71
- package/skills/writing-quarto/scripts/testing-content.py +149 -149
- package/skills/writing-quarto/scripts/verifying-content.py +378 -378
- package/templates/quarto-book-project/writing/chapter-map.md +22 -22
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Regression tests for verifying-content.py.
|
|
3
|
-
|
|
4
|
-
Each case writes a temporary book (an `_quarto.yml`, a root `index.qmd` and
|
|
5
|
-
one chapter), runs the verifier as a subprocess and asserts the exit code and
|
|
6
|
-
a diagnostic substring. Project rules are covered with and without
|
|
7
|
-
`.agents/skills/quarto-book-project/`.
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
python testing-content.py
|
|
11
|
-
Exit code: 0 when every case passes, 1 otherwise.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import subprocess
|
|
17
|
-
import sys
|
|
18
|
-
import tempfile
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
|
|
21
|
-
HERE = Path(__file__).resolve().parent
|
|
22
|
-
VERIFIER = HERE / "verifying-content.py"
|
|
23
|
-
|
|
24
|
-
QUARTO_YML = """project:
|
|
25
|
-
type: book
|
|
26
|
-
book:
|
|
27
|
-
title: 示例书
|
|
28
|
-
chapters:
|
|
29
|
-
- index.qmd
|
|
30
|
-
- content/part/01-intro.qmd
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
INDEX = """---
|
|
34
|
-
title: 示例书
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 前言
|
|
38
|
-
|
|
39
|
-
这是示例书的入口页。读者从这里进入正文。
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
VALID_CHAPTER = """---
|
|
43
|
-
title: 入门
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## 目标
|
|
47
|
-
|
|
48
|
-
这一章说明一个可以重复验证的目标。读者读完之后能够判断结果是否正确。
|
|
49
|
-
|
|
50
|
-
### 步骤
|
|
51
|
-
|
|
52
|
-
先准备输入,再运行检查,最后对照输出。
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def run_case(tmp: Path) -> subprocess.CompletedProcess[str]:
|
|
57
|
-
return subprocess.run(
|
|
58
|
-
[sys.executable, str(VERIFIER), "--root", str(tmp)],
|
|
59
|
-
text=True, capture_output=True, encoding="utf-8", errors="replace",
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def make_book(root: Path, chapter: str = VALID_CHAPTER) -> None:
|
|
64
|
-
(root / "content" / "part").mkdir(parents=True, exist_ok=True)
|
|
65
|
-
(root / "_quarto.yml").write_text(QUARTO_YML, encoding="utf-8")
|
|
66
|
-
(root / "index.qmd").write_text(INDEX, encoding="utf-8")
|
|
67
|
-
(root / "content" / "part" / "01-intro.qmd").write_text(chapter, encoding="utf-8")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def main() -> int:
|
|
71
|
-
try:
|
|
72
|
-
sys.stdout.reconfigure(encoding="utf-8")
|
|
73
|
-
except Exception:
|
|
74
|
-
pass
|
|
75
|
-
|
|
76
|
-
failures: list[str] = []
|
|
77
|
-
|
|
78
|
-
def expect(name: str, chapter: str, code: int, needle: str, project: bool = False) -> None:
|
|
79
|
-
with tempfile.TemporaryDirectory() as raw:
|
|
80
|
-
root = Path(raw)
|
|
81
|
-
make_book(root, chapter)
|
|
82
|
-
if project:
|
|
83
|
-
skill_dir = root / ".agents" / "skills" / "quarto-book-project"
|
|
84
|
-
skill_dir.mkdir(parents=True)
|
|
85
|
-
(skill_dir / "SKILL.md").write_text(
|
|
86
|
-
"---\nname: quarto-book-project\ndescription: 项目约定\n---\n",
|
|
87
|
-
encoding="utf-8",
|
|
88
|
-
)
|
|
89
|
-
(skill_dir / "checks.json").write_text(
|
|
90
|
-
'{"forbidden_terms": ["棋盘格子"]}', encoding="utf-8"
|
|
91
|
-
)
|
|
92
|
-
result = run_case(root)
|
|
93
|
-
output = result.stdout + result.stderr
|
|
94
|
-
if result.returncode != code:
|
|
95
|
-
failures.append(f"{name}: 退出码 {result.returncode},期望 {code}\n{output[:600]}")
|
|
96
|
-
elif needle and needle not in output:
|
|
97
|
-
failures.append(f"{name}: 输出缺少「{needle}」\n{output[:600]}")
|
|
98
|
-
|
|
99
|
-
expect("合法样书", VALID_CHAPTER, 0, "OK")
|
|
100
|
-
expect("正文 H1", VALID_CHAPTER.replace("## 目标", "# 目标"), 1, "正文出现 H1")
|
|
101
|
-
expect("缺 title", VALID_CHAPTER.replace("title: 入门\n", ""), 1, "缺少 YAML frontmatter")
|
|
102
|
-
expect(
|
|
103
|
-
"段落超长",
|
|
104
|
-
"---\ntitle: 入门\n---\n\n## 目标\n\n" + "这是一个很长的段落。" * 60 + "\n",
|
|
105
|
-
1, "超过",
|
|
106
|
-
)
|
|
107
|
-
expect(
|
|
108
|
-
"plain mermaid",
|
|
109
|
-
"---\ntitle: 入门\n---\n\n## 目标\n\n```mermaid\ngraph LR\n```\n",
|
|
110
|
-
1, "plain mermaid",
|
|
111
|
-
)
|
|
112
|
-
expect(
|
|
113
|
-
"未声明围栏",
|
|
114
|
-
"---\ntitle: 入门\n---\n\n## 目标\n\n```\nprint(1)\n```\n",
|
|
115
|
-
1, "未声明语言",
|
|
116
|
-
)
|
|
117
|
-
expect(
|
|
118
|
-
"项目禁用词生效",
|
|
119
|
-
"---\ntitle: 入门\n---\n\n## 目标\n\n棋盘格子按行列编号,程序读取当前状态。\n",
|
|
120
|
-
1, "项目禁用词",
|
|
121
|
-
project=True,
|
|
122
|
-
)
|
|
123
|
-
expect(
|
|
124
|
-
"无项目技能时跳过项目词",
|
|
125
|
-
"---\ntitle: 入门\n---\n\n## 目标\n\n棋盘格子按行列编号,程序读取当前状态。\n",
|
|
126
|
-
0, "OK",
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
with tempfile.TemporaryDirectory() as raw:
|
|
130
|
-
root = Path(raw)
|
|
131
|
-
make_book(root)
|
|
132
|
-
(root / "content" / "part" / "01-intro.qmd").unlink()
|
|
133
|
-
result = run_case(root)
|
|
134
|
-
output = result.stdout + result.stderr
|
|
135
|
-
if result.returncode != 1 or "章节不存在" not in output:
|
|
136
|
-
failures.append(f"缺章: 未报告目录中的缺失章节\n{output[:600]}")
|
|
137
|
-
|
|
138
|
-
if failures:
|
|
139
|
-
print(f"发现 {len(failures)} 例失败:")
|
|
140
|
-
for line in failures:
|
|
141
|
-
print(f" FAIL {line}")
|
|
142
|
-
return 1
|
|
143
|
-
|
|
144
|
-
print("PASS:9 个用例全部通过")
|
|
145
|
-
return 0
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if __name__ == "__main__":
|
|
149
|
-
sys.exit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Regression tests for verifying-content.py.
|
|
3
|
+
|
|
4
|
+
Each case writes a temporary book (an `_quarto.yml`, a root `index.qmd` and
|
|
5
|
+
one chapter), runs the verifier as a subprocess and asserts the exit code and
|
|
6
|
+
a diagnostic substring. Project rules are covered with and without
|
|
7
|
+
`.agents/skills/quarto-book-project/`.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python testing-content.py
|
|
11
|
+
Exit code: 0 when every case passes, 1 otherwise.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
import tempfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
HERE = Path(__file__).resolve().parent
|
|
22
|
+
VERIFIER = HERE / "verifying-content.py"
|
|
23
|
+
|
|
24
|
+
QUARTO_YML = """project:
|
|
25
|
+
type: book
|
|
26
|
+
book:
|
|
27
|
+
title: 示例书
|
|
28
|
+
chapters:
|
|
29
|
+
- index.qmd
|
|
30
|
+
- content/part/01-intro.qmd
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
INDEX = """---
|
|
34
|
+
title: 示例书
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 前言
|
|
38
|
+
|
|
39
|
+
这是示例书的入口页。读者从这里进入正文。
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
VALID_CHAPTER = """---
|
|
43
|
+
title: 入门
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 目标
|
|
47
|
+
|
|
48
|
+
这一章说明一个可以重复验证的目标。读者读完之后能够判断结果是否正确。
|
|
49
|
+
|
|
50
|
+
### 步骤
|
|
51
|
+
|
|
52
|
+
先准备输入,再运行检查,最后对照输出。
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def run_case(tmp: Path) -> subprocess.CompletedProcess[str]:
|
|
57
|
+
return subprocess.run(
|
|
58
|
+
[sys.executable, str(VERIFIER), "--root", str(tmp)],
|
|
59
|
+
text=True, capture_output=True, encoding="utf-8", errors="replace",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def make_book(root: Path, chapter: str = VALID_CHAPTER) -> None:
|
|
64
|
+
(root / "content" / "part").mkdir(parents=True, exist_ok=True)
|
|
65
|
+
(root / "_quarto.yml").write_text(QUARTO_YML, encoding="utf-8")
|
|
66
|
+
(root / "index.qmd").write_text(INDEX, encoding="utf-8")
|
|
67
|
+
(root / "content" / "part" / "01-intro.qmd").write_text(chapter, encoding="utf-8")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main() -> int:
|
|
71
|
+
try:
|
|
72
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
failures: list[str] = []
|
|
77
|
+
|
|
78
|
+
def expect(name: str, chapter: str, code: int, needle: str, project: bool = False) -> None:
|
|
79
|
+
with tempfile.TemporaryDirectory() as raw:
|
|
80
|
+
root = Path(raw)
|
|
81
|
+
make_book(root, chapter)
|
|
82
|
+
if project:
|
|
83
|
+
skill_dir = root / ".agents" / "skills" / "quarto-book-project"
|
|
84
|
+
skill_dir.mkdir(parents=True)
|
|
85
|
+
(skill_dir / "SKILL.md").write_text(
|
|
86
|
+
"---\nname: quarto-book-project\ndescription: 项目约定\n---\n",
|
|
87
|
+
encoding="utf-8",
|
|
88
|
+
)
|
|
89
|
+
(skill_dir / "checks.json").write_text(
|
|
90
|
+
'{"forbidden_terms": ["棋盘格子"]}', encoding="utf-8"
|
|
91
|
+
)
|
|
92
|
+
result = run_case(root)
|
|
93
|
+
output = result.stdout + result.stderr
|
|
94
|
+
if result.returncode != code:
|
|
95
|
+
failures.append(f"{name}: 退出码 {result.returncode},期望 {code}\n{output[:600]}")
|
|
96
|
+
elif needle and needle not in output:
|
|
97
|
+
failures.append(f"{name}: 输出缺少「{needle}」\n{output[:600]}")
|
|
98
|
+
|
|
99
|
+
expect("合法样书", VALID_CHAPTER, 0, "OK")
|
|
100
|
+
expect("正文 H1", VALID_CHAPTER.replace("## 目标", "# 目标"), 1, "正文出现 H1")
|
|
101
|
+
expect("缺 title", VALID_CHAPTER.replace("title: 入门\n", ""), 1, "缺少 YAML frontmatter")
|
|
102
|
+
expect(
|
|
103
|
+
"段落超长",
|
|
104
|
+
"---\ntitle: 入门\n---\n\n## 目标\n\n" + "这是一个很长的段落。" * 60 + "\n",
|
|
105
|
+
1, "超过",
|
|
106
|
+
)
|
|
107
|
+
expect(
|
|
108
|
+
"plain mermaid",
|
|
109
|
+
"---\ntitle: 入门\n---\n\n## 目标\n\n```mermaid\ngraph LR\n```\n",
|
|
110
|
+
1, "plain mermaid",
|
|
111
|
+
)
|
|
112
|
+
expect(
|
|
113
|
+
"未声明围栏",
|
|
114
|
+
"---\ntitle: 入门\n---\n\n## 目标\n\n```\nprint(1)\n```\n",
|
|
115
|
+
1, "未声明语言",
|
|
116
|
+
)
|
|
117
|
+
expect(
|
|
118
|
+
"项目禁用词生效",
|
|
119
|
+
"---\ntitle: 入门\n---\n\n## 目标\n\n棋盘格子按行列编号,程序读取当前状态。\n",
|
|
120
|
+
1, "项目禁用词",
|
|
121
|
+
project=True,
|
|
122
|
+
)
|
|
123
|
+
expect(
|
|
124
|
+
"无项目技能时跳过项目词",
|
|
125
|
+
"---\ntitle: 入门\n---\n\n## 目标\n\n棋盘格子按行列编号,程序读取当前状态。\n",
|
|
126
|
+
0, "OK",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
with tempfile.TemporaryDirectory() as raw:
|
|
130
|
+
root = Path(raw)
|
|
131
|
+
make_book(root)
|
|
132
|
+
(root / "content" / "part" / "01-intro.qmd").unlink()
|
|
133
|
+
result = run_case(root)
|
|
134
|
+
output = result.stdout + result.stderr
|
|
135
|
+
if result.returncode != 1 or "章节不存在" not in output:
|
|
136
|
+
failures.append(f"缺章: 未报告目录中的缺失章节\n{output[:600]}")
|
|
137
|
+
|
|
138
|
+
if failures:
|
|
139
|
+
print(f"发现 {len(failures)} 例失败:")
|
|
140
|
+
for line in failures:
|
|
141
|
+
print(f" FAIL {line}")
|
|
142
|
+
return 1
|
|
143
|
+
|
|
144
|
+
print("PASS:9 个用例全部通过")
|
|
145
|
+
return 0
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
sys.exit(main())
|