Skip to content

Commit a0eb787

Browse files
authored
Split up into one .rst.inc file per guideline to reduce conflicts (#279)
* feat: able to use split up guidelines files * feat: updated extract_rust_examples.py to handle individual files * feat: ensure we can handle both .rst and .rst.inc files * fix: make check-rust-examples.yml work with split guidelines * fix: update migrate_rust_examples.py to handle separate guidelines * fix: update instructions of rst guideline comment * fix: attempt to fix ruff issues
1 parent a56f211 commit a0eb787

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2829
-1802
lines changed

.github/workflows/check-rust-examples.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,8 @@ jobs:
424424
LEGACY_COUNT=0
425425
LEGACY_FILES=""
426426
427-
for file in $(find src/coding-guidelines -name "*.rst" 2>/dev/null); do
427+
# Check both .rst and .rst.inc files (per-guideline structure uses .rst.inc)
428+
for file in $(find src/coding-guidelines \( -name "*.rst" -o -name "*.rst.inc" \) 2>/dev/null); do
428429
COUNT=$(grep -c "^\s*\.\. code-block:: rust" "$file" 2>/dev/null || echo 0)
429430
if [ "$COUNT" -gt 0 ]; then
430431
LEGACY_FILES="$LEGACY_FILES\n- $file: $COUNT occurrences"

scripts/extract_rust_examples.py

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
3. Generates a test crate with all examples as doc tests
1212
4. Runs the tests and reports results
1313
14+
Supports both monolithic chapter files (*.rst) and per-guideline files (*.rst.inc).
15+
1416
Usage:
1517
# Extract examples and generate test crate
1618
uv run python scripts/extract_rust_examples.py --extract
@@ -220,7 +222,7 @@ def extract_rust_examples_from_file(file_path: Path) -> List[RustExample]:
220222
- Legacy code-block:: rust directives (for backwards compatibility during migration)
221223
222224
Args:
223-
file_path: Path to the RST file
225+
file_path: Path to the RST file (supports .rst and .rst.inc)
224226
225227
Returns:
226228
List of RustExample objects
@@ -326,6 +328,25 @@ def extract_rust_examples_from_file(file_path: Path) -> List[RustExample]:
326328
return examples
327329

328330

331+
def find_rst_files(src_dir: Path) -> List[Path]:
332+
"""
333+
Find all RST files in the source directory.
334+
335+
Searches for both:
336+
- *.rst files (chapter index files, monolithic chapter files)
337+
- *.rst.inc files (per-guideline include files)
338+
339+
Args:
340+
src_dir: Directory to search
341+
342+
Returns:
343+
List of Path objects for all RST files found
344+
"""
345+
rst_files = list(src_dir.glob("**/*.rst"))
346+
rst_inc_files = list(src_dir.glob("**/*.rst.inc"))
347+
return rst_files + rst_inc_files
348+
349+
329350
def extract_all_examples(src_dirs: List[Path], quiet: bool = False) -> List[RustExample]:
330351
"""
331352
Extract all Rust examples from all RST files in the given directories.
@@ -340,17 +361,47 @@ def extract_all_examples(src_dirs: List[Path], quiet: bool = False) -> List[Rust
340361
examples = []
341362

342363
for src_dir in src_dirs:
343-
rst_files = list(src_dir.glob("**/*.rst"))
364+
all_files = find_rst_files(src_dir)
344365

345366
if not quiet:
346-
print(f"🔍 Scanning {len(rst_files)} RST files in {src_dir}", file=sys.stderr)
347-
348-
for file_path in rst_files:
349-
file_examples = extract_rust_examples_from_file(file_path)
350-
if file_examples:
351-
if not quiet:
352-
print(f" 📄 {file_path.name}: {len(file_examples)} examples", file=sys.stderr)
353-
examples.extend(file_examples)
367+
print(f"🔍 Scanning {len(all_files)} RST files in {src_dir}", file=sys.stderr)
368+
369+
# Group files by parent directory (chapter)
370+
files_by_chapter: Dict[str, List[Path]] = {}
371+
for file_path in all_files:
372+
# Get chapter name (parent directory relative to src_dir)
373+
try:
374+
rel_path = file_path.relative_to(src_dir)
375+
if len(rel_path.parts) > 1:
376+
chapter = rel_path.parts[0]
377+
else:
378+
chapter = "(root)"
379+
except ValueError:
380+
chapter = "(other)"
381+
382+
if chapter not in files_by_chapter:
383+
files_by_chapter[chapter] = []
384+
files_by_chapter[chapter].append(file_path)
385+
386+
# Process files grouped by chapter
387+
for chapter in sorted(files_by_chapter.keys()):
388+
chapter_files = files_by_chapter[chapter]
389+
chapter_has_examples = False
390+
file_results: List[Tuple[Path, List[RustExample]]] = []
391+
392+
# Extract examples from each file
393+
for file_path in sorted(chapter_files):
394+
file_examples = extract_rust_examples_from_file(file_path)
395+
if file_examples:
396+
file_results.append((file_path, file_examples))
397+
examples.extend(file_examples)
398+
chapter_has_examples = True
399+
400+
# Print chapter heading and files with examples
401+
if chapter_has_examples and not quiet:
402+
print(f"\n {chapter}/", file=sys.stderr)
403+
for file_path, file_examples in file_results:
404+
print(f" {file_path.name}: {len(file_examples)} examples", file=sys.stderr)
354405

355406
if not quiet:
356407
print(f"\n📊 Total: {len(examples)} examples found", file=sys.stderr)
@@ -683,14 +734,15 @@ def main():
683734
filter_min_version=args.filter_min_version,
684735
filter_default=args.filter_default,
685736
)
686-
if args.verbose:
687-
print(f"🔍 Filtered {original_count} -> {len(examples)} examples")
737+
filtered_count = original_count - len(examples)
738+
if filtered_count > 0:
739+
print(f"\n🔍 Filtered: {len(examples)} examples to test ({filtered_count} excluded)")
688740
if args.filter_channel:
689741
print(f" Filter: channel={args.filter_channel}")
690742
if args.filter_min_version:
691-
print(f" Filter: min_version={args.filter_min_version}")
743+
print(f" Filter: min_version>={args.filter_min_version}")
692744
if args.filter_default:
693-
print(" Filter: default (no special requirements)")
745+
print(f" Filter: default toolchain only (excluded {filtered_count} requiring nightly/beta or specific version)")
694746

695747
if args.extract or args.test:
696748
# Generate test crate

scripts/generate-rst-comment.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import json
1515
import os
16+
import re
1617
import sys
1718

1819
# Add the scripts directory to Python path so we can import guideline_utils
@@ -28,6 +29,20 @@
2829
)
2930

3031

32+
def extract_guideline_id(rst_content: str) -> str:
33+
"""
34+
Extract the guideline ID from RST content.
35+
36+
Args:
37+
rst_content: The generated RST content
38+
39+
Returns:
40+
The guideline ID (e.g., "gui_abc123XYZ") or empty string if not found
41+
"""
42+
match = re.search(r':id:\s*(gui_[a-zA-Z0-9]+)', rst_content)
43+
return match.group(1) if match else ""
44+
45+
3146
def generate_comment(rst_content: str, chapter: str) -> str:
3247
"""
3348
Generate a formatted GitHub comment with instructions and RST content.
@@ -40,17 +55,39 @@ def generate_comment(rst_content: str, chapter: str) -> str:
4055
Formatted Markdown comment string
4156
"""
4257
chapter_slug = chapter_to_filename(chapter)
43-
target_file = f"src/coding-guidelines/{chapter_slug}.rst"
58+
guideline_id = extract_guideline_id(rst_content)
59+
60+
# Determine target path based on whether we have a guideline ID
61+
if guideline_id:
62+
target_dir = f"src/coding-guidelines/{chapter_slug}/"
63+
target_file = f"{target_dir}{guideline_id}.rst.inc"
64+
file_instructions = f"""
65+
### 📁 Target Location
66+
67+
Create a new file: `{target_file}`
68+
69+
> **Note:** The `.rst.inc` extension prevents Sphinx from auto-discovering the file.
70+
> It will be included via the chapter's `index.rst`."""
71+
else:
72+
# Fallback for legacy structure (shouldn't happen with new template)
73+
target_file = f"src/coding-guidelines/{chapter_slug}.rst"
74+
file_instructions = f"""
75+
### 📁 Target File
76+
77+
Add this guideline to: `{target_file}`"""
4478

4579
comment = f"""## 📋 RST Preview for Coding Guideline
4680
4781
This is an automatically generated preview of your coding guideline in reStructuredText format.
82+
{file_instructions}
4883
49-
### 📁 Target File
84+
### 📝 How to Use This
5085
51-
Add this guideline to: `{target_file}`
86+
**Option A: Automatic (Recommended)**
5287
53-
### 📝 How to Use This
88+
Once this issue is approved, a maintainer will add the `sign-off: create pr from issue` label, which automatically creates a PR with the guideline file.
89+
90+
**Option B: Manual**
5491
5592
1. **Fork the repository** (if you haven't already) and clone it locally
5693
2. **Create a new branch** from `main`:
@@ -59,19 +96,27 @@ def generate_comment(rst_content: str, chapter: str) -> str:
5996
git pull origin main
6097
git checkout -b guideline/your-descriptive-branch-name
6198
```
62-
3. **Open the target file** `{target_file}` in your editor
63-
4. **Copy the RST content** below and paste it at the end of the file (before any final directives if present)
64-
5. **Build locally** to verify the guideline renders correctly:
99+
3. **Create the guideline file**:
100+
```bash
101+
mkdir -p src/coding-guidelines/{chapter_slug}
102+
```
103+
4. **Copy the RST content** below into a new file named `{guideline_id}.rst.inc`
104+
5. **Update the chapter index** - Add an include directive to `src/coding-guidelines/{chapter_slug}/index.rst`:
105+
```rst
106+
.. include:: {guideline_id}.rst.inc
107+
```
108+
Keep the includes in alphabetical order by guideline ID.
109+
6. **Build locally** to verify the guideline renders correctly:
65110
```bash
66111
./make.py
67112
```
68-
6. **Commit and push** your changes:
113+
7. **Commit and push** your changes:
69114
```bash
70-
git add {target_file}
115+
git add src/coding-guidelines/{chapter_slug}/
71116
git commit -m "Add guideline: <your guideline title>"
72117
git push origin guideline/your-descriptive-branch-name
73118
```
74-
7. **Open a Pull Request** against `main`
119+
8. **Open a Pull Request** against `main`
75120
76121
<details>
77122
<summary><strong>📄 Click to expand RST content</strong></summary>

0 commit comments

Comments
 (0)