Skip to content

Commit ea44d3e

Browse files
authored
refactor: render each guideline as a standalone page (rustfoundation#385)
1 parent b84ed59 commit ea44d3e

Some content is hidden

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

53 files changed

+304
-221
lines changed

builder/build_cli.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,15 +284,18 @@ def main(root):
284284
)
285285
args = parser.parse_args()
286286

287+
debug = args.debug or args.verbose
288+
builder = "linkcheck" if args.check_links else "xml" if args.xml else "html"
289+
287290
if args.update_spec_lock_file:
288291
update_spec_lockfile(SPEC_CHECKSUM_URL, root / "src" / SPEC_LOCKFILE)
289292

290293
build_docs(
291294
root,
292-
"xml" if args.xml else "html",
295+
builder,
293296
args.clear,
294297
args.serve,
295-
args.debug,
298+
debug,
296299
args.offline,
297300
not args.ignore_spec_lock_diff,
298301
args.validate_urls,

exts/coding_guidelines/write_guidelines_ids.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ def write_guidelines_ids(app):
5252
# List to track guidelines with missing elements
5353
incomplete_guidelines = []
5454

55+
def resolve_document_key(docname: str) -> str:
56+
parts = docname.split("/")
57+
if len(parts) >= 3 and parts[0] == "coding-guidelines":
58+
if parts[2].startswith("gui_"):
59+
return "/".join([parts[0], parts[1], "index"])
60+
return docname
61+
5562
# Process all guidelines
5663
for need_id, need in all_needs.items():
5764
if need["type"] == "guideline":
@@ -138,8 +145,9 @@ def write_guidelines_ids(app):
138145
}
139146
)
140147

141-
# Add this guideline to the document
142-
documents_data[docname]["guidelines"].append(guideline_data)
148+
# Add this guideline to the document (group by chapter index when applicable)
149+
document_key = resolve_document_key(docname)
150+
documents_data[document_key]["guidelines"].append(guideline_data)
143151

144152
# Prepare the final structure for JSON
145153
documents = []

scripts/common/guideline_pages.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SPDX-License-Identifier: MIT OR Apache-2.0
2+
# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
3+
4+
import re
5+
6+
GUIDELINE_FILE_HEADER = """\
7+
.. SPDX-License-Identifier: MIT OR Apache-2.0
8+
SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
9+
10+
.. default-domain:: coding-guidelines
11+
12+
"""
13+
14+
GUIDELINE_TITLE_PATTERN = re.compile(
15+
r"^\s*\.\.\s+guideline::\s*(.*?)\s*$",
16+
re.MULTILINE,
17+
)
18+
19+
20+
def extract_guideline_title(content: str) -> str:
21+
"""
22+
Extract the guideline title from the guideline directive.
23+
24+
Args:
25+
content: RST content containing a guideline directive
26+
27+
Returns:
28+
Guideline title or empty string if not found
29+
"""
30+
match = GUIDELINE_TITLE_PATTERN.search(content)
31+
return match.group(1).strip() if match else ""
32+
33+
34+
def build_guideline_page_content(title: str, guideline_body: str) -> str:
35+
"""
36+
Build the full guideline page content.
37+
38+
Args:
39+
title: Guideline title to use for the page heading
40+
guideline_body: Guideline directive content (without the file header)
41+
42+
Returns:
43+
Full guideline page content as a string
44+
"""
45+
body = guideline_body.strip()
46+
underline = "=" * len(title)
47+
return "\n".join(
48+
[
49+
GUIDELINE_FILE_HEADER.rstrip(),
50+
"",
51+
title,
52+
underline,
53+
"",
54+
body,
55+
"",
56+
]
57+
)

scripts/common/guideline_templates.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import re
77
import string
88
from textwrap import dedent, indent
9+
from typing import Optional
910

1011
# Configuration
1112
CHARS = string.ascii_letters + string.digits
@@ -252,7 +253,7 @@ def guideline_rst_template(
252253
rationale: str,
253254
non_compliant_examples: list, # List of (prose, code) tuples
254255
compliant_examples: list, # List of (prose, code) tuples
255-
bibliography_entries: list = None, # List of (key, author, title, url) tuples
256+
bibliography_entries: Optional[list] = None, # List of (key, author, title, url) tuples
256257
) -> str:
257258
"""
258259
Generate a .rst guideline entry from field values.
@@ -310,7 +311,7 @@ def norm(value: str) -> str:
310311

311312
# Generate bibliography block if entries provided
312313
bibliography_block = ""
313-
if bibliography_entries:
314+
if bibliography_entries is not None and bibliography_entries:
314315
bibliography_id = generate_id("bib")
315316
bibliography_block = generate_bibliography_block(
316317
bibliography_id,

scripts/extract_rust_examples.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
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).
14+
Supports monolithic chapter files and per-guideline files (*.rst).
1515
1616
Configuration is loaded from src/rust_examples_config.toml for default values.
1717
@@ -163,10 +163,10 @@ class RustExamplesConfig:
163163
"""Configuration loaded from rust_examples_config.toml"""
164164

165165
def __init__(self):
166-
self.edition = None
167-
self.channel = None
168-
self.version = None
169-
self.version_mismatch_threshold = None
166+
self.edition: str = ""
167+
self.channel: str = ""
168+
self.version: str = ""
169+
self.version_mismatch_threshold: int = 0
170170
# Miri settings
171171
self.miri_require_for_unsafe = True
172172
self.miri_timeout = 60
@@ -232,7 +232,7 @@ def load(cls, config_path: Path) -> "RustExamplesConfig":
232232
return config
233233

234234
@classmethod
235-
def find_and_load(cls, search_paths: List[Path] = None) -> "RustExamplesConfig":
235+
def find_and_load(cls, search_paths: Optional[List[Path]] = None) -> "RustExamplesConfig":
236236
"""
237237
Find and load configuration from standard locations.
238238
@@ -444,7 +444,7 @@ def extract_rust_examples_from_file(
444444
- Legacy code-block:: rust directives (for backwards compatibility during migration)
445445
446446
Args:
447-
file_path: Path to the RST file (supports .rst and .rst.inc)
447+
file_path: Path to the RST file
448448
config: Configuration with default values
449449
450450
Returns:
@@ -509,8 +509,8 @@ def extract_rust_examples_from_file(
509509
# Note: min_version should only be set if explicitly specified in the example
510510
# config.version is the reference toolchain version, not a requirement for all examples
511511
min_version = options.get('version') or options.get('min-version') or None
512-
channel = options.get('channel', config.channel)
513-
edition = options.get('edition', config.edition)
512+
channel = options.get('channel') or config.channel
513+
edition = options.get('edition') or config.edition
514514

515515
# Find parent context
516516
parent_type, parent_id, guideline_id = find_parent_context(content, start)
@@ -586,19 +586,16 @@ def find_rst_files(src_dir: Path) -> List[Path]:
586586
"""
587587
Find all RST files in the source directory.
588588
589-
Searches for both:
590-
- *.rst files (chapter index files, monolithic chapter files)
591-
- *.rst.inc files (per-guideline include files)
589+
Searches for:
590+
- *.rst files (chapter index files, monolithic chapter files, per-guideline files)
592591
593592
Args:
594593
src_dir: Directory to search
595594
596595
Returns:
597596
List of Path objects for all RST files found
598597
"""
599-
rst_files = list(src_dir.glob("**/*.rst"))
600-
rst_inc_files = list(src_dir.glob("**/*.rst.inc"))
601-
return rst_files + rst_inc_files
598+
return list(src_dir.glob("**/*.rst"))
602599

603600

604601
def extract_all_examples(

scripts/fls_audit.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ def scan_guideline_references(
413413
) -> dict[str, list[dict[str, str]]]:
414414
fls_to_guidelines: dict[str, list[dict[str, str]]] = {}
415415
file_paths = set(src_dir.rglob("*.rst"))
416-
file_paths.update(src_dir.rglob("*.rst.inc"))
417416
for path in sorted(file_paths):
418417
collect_guidelines_from_file(path, repo_root, fls_to_guidelines)
419418
return fls_to_guidelines
@@ -535,7 +534,6 @@ def build_guideline_text_index(
535534
) -> dict[str, dict[str, Any]]:
536535
index: dict[str, dict[str, Any]] = {}
537536
file_paths = set((repo_root / "src").rglob("*.rst"))
538-
file_paths.update((repo_root / "src").rglob("*.rst.inc"))
539537

540538
current_id: str | None = None
541539
current_title = ""

scripts/generate-rst-comment.py

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
from dataclasses import dataclass, field
2424
from typing import List, Set, Tuple
2525

26+
from scripts.common.guideline_pages import (
27+
build_guideline_page_content,
28+
extract_guideline_title,
29+
)
2630
from scripts.common.guideline_templates import parse_bibliography_entries
2731
from scripts.guideline_utils import (
2832
chapter_to_filename,
@@ -34,13 +38,6 @@
3438
normalize_md,
3539
)
3640

37-
# SPDX header to prepend to guideline files
38-
GUIDELINE_FILE_HEADER = """\
39-
.. SPDX-License-Identifier: MIT OR Apache-2.0
40-
SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
41-
42-
"""
43-
4441

4542
@dataclass
4643
class CodeTestResult:
@@ -138,9 +135,9 @@ def wrap_in_main(code: str) -> str:
138135
has_mod = re.search(r'\bmod\b', code)
139136
has_type = re.search(r'\btype\b', code)
140137

141-
# If it looks like top-level items, don't wrap
138+
# If it looks like top-level items, add a stub main
142139
if has_functions or has_impl or has_struct or has_enum or has_trait or has_mod or has_type:
143-
return code
140+
return code + "\n\nfn main() {}"
144141

145142
# Check for use/const/static statements
146143
has_use = re.search(r'\buse\b', code)
@@ -575,8 +572,8 @@ def generate_comment(
575572
chapter_slug = chapter_to_filename(chapter)
576573
guideline_id = extract_guideline_id(rst_content)
577574

578-
# Prepend the SPDX header to the RST content for display
579-
full_rst_content = GUIDELINE_FILE_HEADER + rst_content.strip()
575+
page_title = extract_guideline_title(rst_content) or f"Guideline {guideline_id}"
576+
full_rst_content = build_guideline_page_content(page_title, rst_content)
580577

581578
# Format test results
582579
test_results_section = format_test_results(test_results)
@@ -587,34 +584,36 @@ def generate_comment(
587584
# Determine target path based on whether we have a guideline ID
588585
if guideline_id:
589586
target_dir = f"src/coding-guidelines/{chapter_slug}/"
590-
target_file = f"{target_dir}{guideline_id}.rst.inc"
587+
target_file = f"{target_dir}{guideline_id}.rst"
591588
chapter_index_file = f"{target_dir}index.rst"
592589
file_instructions = f"""
593590
### 📁 Target Location
594591
595-
Create a new file: `{target_file}`
592+
Create a new file:
593+
594+
- `{target_file}`
596595
597-
> **Note:** The `.rst.inc` extension prevents Sphinx from auto-discovering the file.
598-
> It will be included via the chapter's `index.rst`.
596+
Create the guideline page like this:
599597
600-
We add it to this path, to allow the newly added guideline to appear in the correct chapter.
598+
```rst
599+
{full_rst_content}
600+
```
601601
602602
### 🗂️ Update Chapter Index
603603
604-
Update `{chapter_index_file}` to include `{guideline_id}.rst.inc`, like so:
604+
Ensure `{chapter_index_file}` lists guideline pages with a toctree:
605+
606+
```rst
607+
.. toctree::
608+
:maxdepth: 1
609+
:titlesonly:
610+
:glob:
605611
612+
gui_*
606613
```
607-
Chapter Name Here <- chapter heading inside of `{chapter_index_file}`
608-
=================
609-
610-
.. include:: gui_7y0GAMmtMhch.rst.inc -| existing guidelines
611-
.. include:: gui_ADHABsmK9FXz.rst.inc |
612-
... |
613-
... |
614-
.. include:: gui_RHvQj8BHlz9b.rst.inc |
615-
.. include:: gui_dCquvqE1csI3.rst.inc -|
616-
.. include:: {guideline_id}.rst.inc <- your new guideline to add
617-
```"""
614+
615+
With `:glob:` you do not need to add the new guideline manually.
616+
"""
618617
else:
619618
return "No guideline ID generated, failing!"
620619

@@ -632,16 +631,21 @@ def generate_comment(
632631
git pull origin main
633632
git checkout -b guideline/your-descriptive-branch-name
634633
```
635-
3. **Create the guideline file**:
634+
3. **Create the guideline directory**:
636635
```bash
637636
mkdir -p src/coding-guidelines/{chapter_slug}
638637
```
639-
4. **Copy the RST content** below into a new file named `{guideline_id}.rst.inc`
640-
5. **Update the chapter index** - Add an include directive to `src/coding-guidelines/{chapter_slug}/index.rst`:
638+
4. **Copy the RST content** below into a new file named `{guideline_id}.rst`
639+
5. **Ensure the chapter index** `src/coding-guidelines/{chapter_slug}/index.rst` contains the toctree block:
641640
```rst
642-
.. include:: {guideline_id}.rst.inc
641+
.. toctree::
642+
:maxdepth: 1
643+
:titlesonly:
644+
:glob:
645+
646+
gui_*
643647
```
644-
Keep the includes in alphabetical order by guideline ID.
648+
With `:glob:` you do not need to update the index per guideline.
645649
6. **Build locally** to verify the guideline renders correctly:
646650
```bash
647651
./make.py

0 commit comments

Comments
 (0)