Skip to content

Commit aea2bbd

Browse files
committed
Add GitHub Pages deployment for VitePress
1 parent 9e6c108 commit aea2bbd

7 files changed

Lines changed: 187 additions & 0 deletions

File tree

‎docs/.vitepress/config.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { defineConfig } from 'vitepress'
2+
3+
export default defineConfig({
4+
title: 'Code Notes',
5+
description: 'Python 代码笔记与示例',
6+
lang: 'zh-CN',
7+
themeConfig: {
8+
nav: [
9+
{ text: '首页', link: '/' },
10+
{ text: 'Python 基础', link: '/python-basics/default_args' }
11+
],
12+
sidebar: {
13+
'/python-basics/': [
14+
{
15+
text: 'Python 基础',
16+
items: [
17+
{ text: '默认参数示例', link: '/python-basics/default_args' }
18+
]
19+
}
20+
],
21+
'/': [
22+
{
23+
text: '指南',
24+
items: [{ text: '简介', link: '/' }]
25+
}
26+
]
27+
}
28+
}
29+
})
30+

‎docs/index.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# 我的代码笔记
2+
3+
欢迎来到我的代码笔记站点。这是一个用 Markdown 写笔记、从 Python 源码自动插入示例代码的仓库。
4+
5+
可以从左侧导航进入具体的 Python 笔记页面。
6+

‎docs/python-basics/default_args.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Python 函数默认参数示例
2+
3+
下面展示 `default_args.py` 中的 `append_item` 函数,代码来自真实的 `.py` 文件,而不是写在 Markdown 里。
4+
5+
::: py-symbol
6+
path: python-demos/basics/default_args.py
7+
symbol: append_item
8+
type: function
9+
:::
10+
11+
你只需要维护 `python-demos/basics/default_args.py` 中的代码,这里的函数会在构建时自动同步。
12+

‎package.json‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "code-notes-templete",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"docs:prebuild": "python scripts/expand_py_symbols.py",
7+
"docs:dev": "vitepress dev docs_generated",
8+
"docs:build": "npm run docs:prebuild && vitepress build docs_generated",
9+
"docs:preview": "vitepress preview docs_generated"
10+
},
11+
"devDependencies": {
12+
"vitepress": "^1.6.4"
13+
}
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
def append_item(item, lst=None):
2+
"""Append an item to a list, creating a new list by default.
3+
4+
This示例用于说明为什么不推荐在函数定义里使用可变对象作为默认参数。
5+
"""
6+
if lst is None:
7+
lst = []
8+
lst.append(item)
9+
return lst
10+
11+
12+
if __name__ == "__main__":
13+
print(append_item(1)) # [1]
14+
print(append_item(2)) # [2] - 不会和上一次共用列表
15+

‎scripts/expand_py_symbols.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from __future__ import annotations
2+
3+
import re
4+
from pathlib import Path
5+
6+
from extract_snippet import extract_symbol_source, SymbolType
7+
8+
9+
ROOT = Path(__file__).resolve().parents[1]
10+
DOCS_DIR = ROOT / "docs"
11+
OUTPUT_DIR = ROOT / "docs_generated"
12+
13+
14+
BLOCK_RE = re.compile(
15+
r"::: py-symbol\s+"
16+
r"path:\s*(?P<path>.+?)\s+"
17+
r"symbol:\s*(?P<symbol>.+?)\s+"
18+
r"type:\s*(?P<type>function|class)\s*"
19+
r":::",
20+
re.DOTALL,
21+
)
22+
23+
24+
def replace_block(md_text: str) -> str:
25+
def _repl(match: re.Match) -> str:
26+
path_str = match.group("path").strip()
27+
symbol = match.group("symbol").strip()
28+
symbol_type_str = match.group("type").strip()
29+
symbol_type: SymbolType = symbol_type_str # type: ignore[assignment]
30+
31+
py_path = ROOT / path_str
32+
code = extract_symbol_source(py_path, symbol, symbol_type)
33+
34+
return f"```python\n{code}\n```"
35+
36+
return BLOCK_RE.sub(_repl, md_text)
37+
38+
39+
def clean_output_dir() -> None:
40+
if not OUTPUT_DIR.exists():
41+
return
42+
for p in sorted(OUTPUT_DIR.rglob("*"), reverse=True):
43+
if p.is_file():
44+
p.unlink()
45+
elif p.is_dir():
46+
p.rmdir()
47+
48+
49+
def main() -> None:
50+
clean_output_dir()
51+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
52+
53+
for md_path in DOCS_DIR.rglob("*.md"):
54+
rel = md_path.relative_to(DOCS_DIR)
55+
out_path = OUTPUT_DIR / rel
56+
out_path.parent.mkdir(parents=True, exist_ok=True)
57+
58+
text = md_path.read_text(encoding="utf-8")
59+
new_text = replace_block(text)
60+
out_path.write_text(new_text, encoding="utf-8")
61+
print(f"processed {rel}")
62+
63+
print(f"All markdown files expanded into {OUTPUT_DIR}")
64+
65+
66+
if __name__ == "__main__":
67+
main()
68+

‎scripts/extract_snippet.py‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from __future__ import annotations
2+
3+
import ast
4+
from pathlib import Path
5+
from typing import Literal
6+
7+
8+
SymbolType = Literal["function", "class"]
9+
10+
11+
def extract_symbol_source(py_path: Path, symbol: str, symbol_type: SymbolType) -> str:
12+
"""Extract the source code of a top-level function or class by name."""
13+
source = py_path.read_text(encoding="utf-8")
14+
tree = ast.parse(source)
15+
16+
lines = source.splitlines()
17+
18+
target_node: ast.AST | None = None
19+
for node in tree.body:
20+
if symbol_type == "function" and isinstance(node, ast.FunctionDef) and node.name == symbol:
21+
target_node = node
22+
break
23+
if symbol_type == "class" and isinstance(node, ast.ClassDef) and node.name == symbol:
24+
target_node = node
25+
break
26+
27+
if target_node is None:
28+
raise ValueError(f"Symbol {symbol!r} ({symbol_type}) not found in {py_path}")
29+
30+
# Python 3.8+ provides end_lineno on AST nodes
31+
if not hasattr(target_node, "end_lineno"):
32+
raise RuntimeError("Python version is too old: AST nodes lack end_lineno")
33+
34+
start = target_node.lineno - 1 # convert to 0-based index
35+
end = target_node.end_lineno # inclusive in terms of 1-based line numbers
36+
37+
snippet_lines = lines[start:end]
38+
return "\n".join(snippet_lines)
39+
40+
41+
__all__ = ["extract_symbol_source", "SymbolType"]
42+

0 commit comments

Comments
 (0)