Skip to content

Commit 5fb98eb

Browse files
feat: fix 404 urls (#26)
1 parent c1eeda6 commit 5fb98eb

7 files changed

Lines changed: 112 additions & 104 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,13 @@ flowchart TD
133133
| `pnpm dev` | 启动 VitePress 开发服务器,支持热更新 |
134134
| `pnpm build` | 生产构建,按分卷并行构建并合并搜索索引 |
135135
| `pnpm build:single` | 使用 VitePress 单体构建 |
136+
| `pnpm check:links` | 检查 Markdown 与组件内部链接有效性 |
136137
| `pnpm preview` | 预览生产构建结果 |
137138
| `pnpm hooks:install` / `scripts/setup_precommit.sh` | 安装提交前 Git hook |
138139
| `pnpm coverage` | 查看英文翻译覆盖率 |
139140
| `pnpm coverage:update` | 更新 `README.md` 中的英文翻译覆盖率徽章 |
140141
| `python3 scripts/validate_frontmatter.py` | 验证文章 frontmatter |
141-
| `python3 scripts/check_links.py` | 检查内部链接有效性 |
142+
| `python3 scripts/check_links.py` | 检查 Markdown 与组件内部链接有效性 |
142143
| `python3 scripts/check_quality.py documents/` | 内容质量检查 |
143144
| `python3 scripts/build_examples.py --host` | 编译主机侧 CMake 示例 |
144145
| `python3 scripts/build_examples.py --stm32` | 编译 STM32 示例工程 |
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
---
2-
title: Arrays and Strings
3-
description: The evolution from C-style arrays to std::array and std::string
2+
title: Function
3+
description: Function definition, parameter passing, overloading, and constexpr functions
44
---
5-
# Arrays and Strings
5+
# Functions
66

7-
Data needs to live somewhere, and arrays and strings are the most fundamental containers. In this chapter, we first review the underlying mechanics of C-style arrays—understanding exactly why they are so "bare"—and then jump straight to `std::array` to experience how sweet zero-overhead abstraction can be. The string section follows a similar path, transitioning from C-style strings to `std::string`. You will find that handling text in modern C++ is leagues ahead of C.
7+
Functions are the fundamental units of code organization, and they represent our first step from "writing scripts" to "writing engineering code." In this chapter, we start with function definition and invocation, focusing on the different parameter passing mechanisms—by value, by reference, and by pointer. Understanding the differences between them is a prerequisite for writing efficient code. We then look at how function overloading and default parameters work together, and finally, we explore `inline` and `constexpr` functions, two keywords that appear frequently in embedded and performance-sensitive scenarios.
88

99
## Chapter Contents
1010

1111
<ChapterNav variant="sub">
12-
<ChapterLink href="01-c-arrays">C-style Arrays</ChapterLink>
13-
<ChapterLink href="02-std-array">std::array</ChapterLink>
14-
<ChapterLink href="03-std-string">std::string</ChapterLink>
12+
<ChapterLink href="01-function-basics">Function Basics</ChapterLink>
13+
<ChapterLink href="02-pass-by-value-ref">Parameter Passing Mechanisms</ChapterLink>
14+
<ChapterLink href="03-overloading-default">Overloading and Default Parameters</ChapterLink>
15+
<ChapterLink href="04-inline-constexpr">inline and constexpr Functions</ChapterLink>
1516
</ChapterNav>
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
---
2-
title: Function
3-
description: Function definition, parameter passing, overloading, and constexpr functions
2+
title: Arrays and Strings
3+
description: The evolution from C-style arrays to std::array and std::string
44
---
5-
# Functions
5+
# Arrays and Strings
66

7-
Functions are the fundamental units of code organization, and they represent our first step from "writing scripts" to "writing engineering code." In this chapter, we start with function definition and invocation, focusing on the different parameter passing mechanisms—by value, by reference, and by pointer. Understanding the differences between them is a prerequisite for writing efficient code. We then look at how function overloading and default parameters work together, and finally, we explore `inline` and `constexpr` functions, two keywords that appear frequently in embedded and performance-sensitive scenarios.
7+
Data needs to live somewhere, and arrays and strings are the most fundamental containers. In this chapter, we first review the underlying mechanics of C-style arrays—understanding exactly why they are so "bare"—and then jump straight to `std::array` to experience how sweet zero-overhead abstraction can be. The string section follows a similar path, transitioning from C-style strings to `std::string`. You will find that handling text in modern C++ is leagues ahead of C.
88

99
## Chapter Contents
1010

1111
<ChapterNav variant="sub">
12-
<ChapterLink href="01-function-basics">Function Basics</ChapterLink>
13-
<ChapterLink href="02-pass-by-value-ref">Parameter Passing Mechanisms</ChapterLink>
14-
<ChapterLink href="03-overloading-default">Overloading and Default Parameters</ChapterLink>
15-
<ChapterLink href="04-inline-constexpr">inline and constexpr Functions</ChapterLink>
12+
<ChapterLink href="01-c-arrays">C-style Arrays</ChapterLink>
13+
<ChapterLink href="02-std-array">std::array</ChapterLink>
14+
<ChapterLink href="03-std-string">std::string</ChapterLink>
1615
</ChapterNav>
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
---
2-
title: "内存模型基础"
2+
title: "STL 初见"
33
---
44

5-
# 内存模型基础
5+
# STL 初见
66

77
## 本章内容
88

99
<ChapterNav variant="sub">
10-
<ChapterLink href="01-memory-layout">内存布局</ChapterLink>
11-
<ChapterLink href="02-new-delete">动态内存管理</ChapterLink>
12-
<ChapterLink href="03-alignment-padding">内存对齐与填充</ChapterLink>
10+
<ChapterLink href="01-vector">std::vector 快速上手</ChapterLink>
11+
<ChapterLink href="02-map-set">关联容器快速上手</ChapterLink>
12+
<ChapterLink href="03-algorithms-intro">算法库初见</ChapterLink>
13+
<ChapterLink href="04-stl-patterns">STL 常用模式</ChapterLink>
1314
</ChapterNav>
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
---
2-
title: "STL 初见"
2+
title: "内存模型基础"
33
---
44

5-
# STL 初见
5+
# 内存模型基础
66

77
## 本章内容
88

99
<ChapterNav variant="sub">
10-
<ChapterLink href="01-vector">std::vector 快速上手</ChapterLink>
11-
<ChapterLink href="02-map-set">关联容器快速上手</ChapterLink>
12-
<ChapterLink href="03-algorithms-intro">算法库初见</ChapterLink>
13-
<ChapterLink href="04-stl-patterns">STL 常用模式</ChapterLink>
10+
<ChapterLink href="01-memory-layout">内存布局</ChapterLink>
11+
<ChapterLink href="02-new-delete">动态内存管理</ChapterLink>
12+
<ChapterLink href="03-alignment-padding">内存对齐与填充</ChapterLink>
1413
</ChapterNav>

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vitepress dev site",
8-
"build": "tsx scripts/build.ts",
9-
"build:single": "vitepress build site",
8+
"check:links": "python3 scripts/check_links.py",
9+
"build": "pnpm check:links && tsx scripts/build.ts",
10+
"build:single": "pnpm check:links && vitepress build site",
1011
"preview": "vitepress preview site",
1112
"hooks:install": "scripts/setup_precommit.sh",
1213
"coverage": "python3 scripts/coverage.py",

scripts/check_links.py

Lines changed: 81 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ class LinkChecker:
2525
# Image extensions to check against filesystem
2626
IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.bmp', '.webp', '.ico'}
2727

28-
# Files to skip from checking
29-
SKIP_FILES = {'index.md', 'tags.md'}
28+
# Files to skip from checking
29+
SKIP_FILES = {'tags.md'}
3030

3131
def __init__(self, tutorial_dir: Path, fix: bool = False):
3232
self.tutorial_dir = tutorial_dir
@@ -56,8 +56,13 @@ def extract_links(self, content: str, filepath: Path) -> List[Tuple[int, str, st
5656
Returns: List of (line_number, link_text, link_url)
5757
"""
5858
links = []
59-
# Match [text](url) and [text](<url>)
60-
pattern = r'\[([^\]]+)\]\(([^)]+)\)|\[([^\]]+)\]\(<([^>]+)>\)'
59+
# Match [text](url) and [text](<url>)
60+
markdown_pattern = r'\[([^\]]+)\]\(([^)]+)\)|\[([^\]]+)\]\(<([^>]+)>\)'
61+
# Match Vue components with literal href attributes, such as
62+
# <ChapterLink href="01-vector">Title</ChapterLink>.
63+
component_href_pattern = (
64+
r'<([A-Z][\w.]*)\b[^>]*\bhref=(["\'])([^"\']+)\2'
65+
)
6166

6267
in_code_block = False
6368
for line_num, line in enumerate(content.split('\n'), 1):
@@ -72,34 +77,52 @@ def extract_links(self, content: str, filepath: Path) -> List[Tuple[int, str, st
7277
# Strip inline code spans to avoid matching C++ syntax like `[&](args)`
7378
cleaned = re.sub(r'`[^`]+`', '', line)
7479

75-
for match in re.finditer(pattern, cleaned):
76-
groups = match.groups()
77-
if groups[1]: # Regular link
78-
link_text, link_url = groups[0], groups[1]
79-
else: # Angle bracket link
80-
link_text, link_url = groups[2], groups[3]
81-
82-
links.append((line_num, link_text, link_url))
83-
84-
return links
85-
86-
def normalize_path(self, link_url: str, source_file: Path) -> str:
87-
"""Normalize a relative link path."""
88-
# Remove fragments/anchors
89-
link_url = link_url.split('#')[0]
90-
if not link_url:
91-
return ''
92-
93-
# Get source directory
94-
source_dir = source_file.parent
95-
96-
# Resolve relative path
97-
try:
98-
resolved = (source_dir / link_url).resolve()
99-
# Convert back to relative path from tutorial_dir
100-
return str(resolved.relative_to(self.tutorial_dir))
101-
except (ValueError, RuntimeError):
102-
return link_url
80+
for match in re.finditer(markdown_pattern, cleaned):
81+
groups = match.groups()
82+
if groups[1]: # Regular link
83+
link_text, link_url = groups[0], groups[1]
84+
else: # Angle bracket link
85+
link_text, link_url = groups[2], groups[3]
86+
87+
links.append((line_num, link_text, link_url))
88+
89+
for match in re.finditer(component_href_pattern, cleaned):
90+
component_name = match.group(1)
91+
link_url = match.group(3)
92+
links.append((line_num, component_name, link_url))
93+
94+
return links
95+
96+
def candidate_paths(self, link_url: str, source_file: Path) -> List[str]:
97+
"""Return possible markdown targets for a link path."""
98+
# Remove fragments/anchors
99+
link_url = link_url.split('#')[0]
100+
if not link_url:
101+
return []
102+
103+
# VitePress treats a trailing slash as an index page.
104+
link_url = link_url.rstrip('/')
105+
106+
if not link_url:
107+
link_url = 'index'
108+
109+
if link_url.startswith('/'):
110+
target = self.tutorial_dir / link_url.lstrip('/')
111+
else:
112+
target = source_file.parent / link_url
113+
114+
try:
115+
resolved = target.resolve()
116+
rel = resolved.relative_to(self.tutorial_dir)
117+
except (ValueError, RuntimeError):
118+
return [link_url]
119+
120+
candidates = [rel]
121+
if rel.suffix != '.md':
122+
candidates.append(rel.with_suffix('.md'))
123+
candidates.append(rel / 'index.md')
124+
125+
return [str(candidate) for candidate in candidates]
103126

104127
def check_file(self, filepath: Path):
105128
"""Check links in a single file."""
@@ -121,49 +144,32 @@ def check_file(self, filepath: Path):
121144
continue
122145

123146
# Check image links against filesystem
124-
link_ext = Path(link_url.split('#')[0]).suffix.lower()
125-
if link_ext in self.IMAGE_EXTENSIONS:
126-
normalized = self.normalize_path(link_url, filepath)
127-
if normalized:
128-
resolved = self.tutorial_dir / normalized
129-
if not resolved.exists():
130-
self.errors.append(
131-
f"{rel_path}:{line_num} - Broken image: [{link_text}]({link_url})"
132-
)
133-
continue
134-
135-
# Normalize the link path
136-
normalized = self.normalize_path(link_url, filepath)
137-
138-
if not normalized:
139-
continue
140-
141-
# Check if file exists
142-
if normalized not in self.all_files:
143-
# Try with .md extension if missing
144-
if not normalized.endswith('.md'):
145-
normalized_with_md = normalized + '.md'
146-
if normalized_with_md not in self.all_files:
147-
self.errors.append(
148-
f"{rel_path}:{line_num} - Broken link: [{link_text}]({link_url})"
149-
)
150-
else:
151-
# Track for potential fix
152-
self.warnings.append(
153-
f"{rel_path}:{line_num} - Missing .md extension: [{link_text}]({link_url})"
154-
)
155-
if self.fix:
156-
self.suggest_fix(filepath, line_num, link_url, link_url + '.md')
157-
else:
158-
self.errors.append(
159-
f"{rel_path}:{line_num} - Broken link: [{link_text}]({link_url})"
160-
)
161-
else:
162-
# Track valid link for reverse index
163-
key = str(normalized)
164-
if key not in self.link_map:
165-
self.link_map[key] = []
166-
self.link_map[key].append((filepath, link_text))
147+
link_ext = Path(link_url.split('#')[0]).suffix.lower()
148+
if link_ext in self.IMAGE_EXTENSIONS:
149+
candidates = self.candidate_paths(link_url, filepath)
150+
if candidates and not any((self.tutorial_dir / candidate).exists() for candidate in candidates):
151+
self.errors.append(
152+
f"{rel_path}:{line_num} - Broken image: [{link_text}]({link_url})"
153+
)
154+
continue
155+
156+
candidates = self.candidate_paths(link_url, filepath)
157+
158+
if not candidates:
159+
continue
160+
161+
# Check if file exists
162+
existing = next((candidate for candidate in candidates if candidate in self.all_files), None)
163+
if existing is None:
164+
self.errors.append(
165+
f"{rel_path}:{line_num} - Broken link: [{link_text}]({link_url})"
166+
)
167+
else:
168+
# Track valid link for reverse index
169+
key = str(existing)
170+
if key not in self.link_map:
171+
self.link_map[key] = []
172+
self.link_map[key].append((filepath, link_text))
167173

168174
def suggest_fix(self, filepath: Path, line_num: int, old_link: str, new_link: str):
169175
"""Suggest a fix for a link."""

0 commit comments

Comments
 (0)