From 72676ecc7980346c8d66b522c0da82433f261dc6 Mon Sep 17 00:00:00 2001 From: x0rw Date: Tue, 20 May 2025 16:08:59 +0100 Subject: [PATCH 1/7] Clean --- exts/coding_guidelines/__init__.py | 1 + exts/coding_guidelines/rust_examples_test.py | 41 ++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 exts/coding_guidelines/rust_examples_test.py diff --git a/exts/coding_guidelines/__init__.py b/exts/coding_guidelines/__init__.py index f5d74d1..2266cdd 100644 --- a/exts/coding_guidelines/__init__.py +++ b/exts/coding_guidelines/__init__.py @@ -6,6 +6,7 @@ from . import std_role from . import fls_linking from . import guidelines_checks +from . import rust_examples_test from .common import logger, get_tqdm, bar_format, logging from sphinx.domains import Domain diff --git a/exts/coding_guidelines/rust_examples_test.py b/exts/coding_guidelines/rust_examples_test.py new file mode 100644 index 0000000..a2fa228 --- /dev/null +++ b/exts/coding_guidelines/rust_examples_test.py @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: MIT OR Apache-2.0 +# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors + +import re +from sphinx.errors import SphinxError +from sphinx_needs.data import SphinxNeedsData +import logging + +logger = logging.getLogger('sphinx') + +class ExecuteRustExamples(SphinxError): + category = "Integrity Check Error" + +def execute_tests(app, env): + """ + Aggregate and test rust examples + """ + logger.debug("Testing examples") + data = SphinxNeedsData(env) + needs = data.get_needs_view() + + required_fields = app.config.required_guideline_fields # Access the configured values + + for key, value in needs.items(): + print("======++++++++") + + # print(key, " -- ", value) + if key.startswith("non_compl_ex") or key.startswith("compl_ex"): + + text = value.get("content", "") + match = re.search( + r"\.\. code-block:: rust\s*\n\n((?: {2,}.*\n?)+)", text, re.DOTALL + ) + if match: + code_block = match.group(1) + code_lines = [line[2:] if line.startswith(" ") else line for line in code_block.splitlines()] + rust_code = "\n".join(code_lines) + print("Extracted Rust code:\n") + print(rust_code) + else: + print("No Rust code block found.") From 25bcf8bdddf3e62ba0510633119d8212f9f9c1ab Mon Sep 17 00:00:00 2001 From: x0rw Date: Tue, 20 May 2025 21:24:42 +0100 Subject: [PATCH 2/7] Fix - minor --- exts/coding_guidelines/rust_examples_test.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/exts/coding_guidelines/rust_examples_test.py b/exts/coding_guidelines/rust_examples_test.py index a2fa228..aabaf00 100644 --- a/exts/coding_guidelines/rust_examples_test.py +++ b/exts/coding_guidelines/rust_examples_test.py @@ -9,9 +9,9 @@ logger = logging.getLogger('sphinx') class ExecuteRustExamples(SphinxError): - category = "Integrity Check Error" + category = "ExecuteRustExamples Error" -def execute_tests(app, env): +def extract_execute_tests(app, env): """ Aggregate and test rust examples """ @@ -19,15 +19,12 @@ def execute_tests(app, env): data = SphinxNeedsData(env) needs = data.get_needs_view() - required_fields = app.config.required_guideline_fields # Access the configured values - for key, value in needs.items(): - print("======++++++++") - + print("+++++++++++++") # print(key, " -- ", value) if key.startswith("non_compl_ex") or key.startswith("compl_ex"): - text = value.get("content", "") + # todo: seperate this and test it well match = re.search( r"\.\. code-block:: rust\s*\n\n((?: {2,}.*\n?)+)", text, re.DOTALL ) From b5db0c1a8a40d32bafa9ddfca6fbe5976f209a82 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 25 May 2025 22:41:42 +0100 Subject: [PATCH 3/7] extract code-blocks, run code-blocks, parse rustc output --- exts/coding_guidelines/__init__.py | 6 + exts/coding_guidelines/rust_examples_test.py | 38 --- exts/rust-code-runner/__init__.py | 20 ++ exts/rust-code-runner/generated.rs | 232 ++++++++++++++++++ .../rust_examples_aggregate.py | 91 +++++++ exts/rust-code-runner/rustc.py | 68 +++++ exts/rust-code-runner/tests/tests.py | 0 src/coding-guidelines/macros.rst | 25 +- src/conf.py | 1 + 9 files changed, 438 insertions(+), 43 deletions(-) delete mode 100644 exts/coding_guidelines/rust_examples_test.py create mode 100644 exts/rust-code-runner/__init__.py create mode 100644 exts/rust-code-runner/generated.rs create mode 100644 exts/rust-code-runner/rust_examples_aggregate.py create mode 100644 exts/rust-code-runner/rustc.py create mode 100644 exts/rust-code-runner/tests/tests.py diff --git a/exts/coding_guidelines/__init__.py b/exts/coding_guidelines/__init__.py index 2266cdd..63da7d8 100644 --- a/exts/coding_guidelines/__init__.py +++ b/exts/coding_guidelines/__init__.py @@ -11,6 +11,12 @@ from .common import logger, get_tqdm, bar_format, logging from sphinx.domains import Domain +import logging + +# Get the Sphinx logger +logger = logging.getLogger('sphinx') +logger.setLevel(logging.ERROR) + class CodingGuidelinesDomain(Domain): name = "coding-guidelines" label = "Rust Standard Library" diff --git a/exts/coding_guidelines/rust_examples_test.py b/exts/coding_guidelines/rust_examples_test.py deleted file mode 100644 index aabaf00..0000000 --- a/exts/coding_guidelines/rust_examples_test.py +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: MIT OR Apache-2.0 -# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors - -import re -from sphinx.errors import SphinxError -from sphinx_needs.data import SphinxNeedsData -import logging - -logger = logging.getLogger('sphinx') - -class ExecuteRustExamples(SphinxError): - category = "ExecuteRustExamples Error" - -def extract_execute_tests(app, env): - """ - Aggregate and test rust examples - """ - logger.debug("Testing examples") - data = SphinxNeedsData(env) - needs = data.get_needs_view() - - for key, value in needs.items(): - print("+++++++++++++") - # print(key, " -- ", value) - if key.startswith("non_compl_ex") or key.startswith("compl_ex"): - text = value.get("content", "") - # todo: seperate this and test it well - match = re.search( - r"\.\. code-block:: rust\s*\n\n((?: {2,}.*\n?)+)", text, re.DOTALL - ) - if match: - code_block = match.group(1) - code_lines = [line[2:] if line.startswith(" ") else line for line in code_block.splitlines()] - rust_code = "\n".join(code_lines) - print("Extracted Rust code:\n") - print(rust_code) - else: - print("No Rust code block found.") diff --git a/exts/rust-code-runner/__init__.py b/exts/rust-code-runner/__init__.py new file mode 100644 index 0000000..409801e --- /dev/null +++ b/exts/rust-code-runner/__init__.py @@ -0,0 +1,20 @@ + +from . import rust_examples_aggregate +from . import rustc +import os +def setup(app): + + app.output_rust_file = "exts/rust-code-runner/generated.rs" + if os.path.isfile(app.output_rust_file): + with open(app.output_rust_file, 'w'): + pass + + # we hook into 'source-read' because data is mutable at this point and easier to parse + # and it also makes this extension indepandant from `needs` + # + app.connect('source-read', rust_examples_aggregate.preprocess_rst_for_rust_code) + app.connect('build-finished', rustc.check_rust_test_errors) + return { + 'version': '0.1', + 'parallel_read_safe': False, + } diff --git a/exts/rust-code-runner/generated.rs b/exts/rust-code-runner/generated.rs new file mode 100644 index 0000000..bfb9d2f --- /dev/null +++ b/exts/rust-code-runner/generated.rs @@ -0,0 +1,232 @@ +// ==== Code Block 1 ==== +#[test] +fn test_block_coding_guidelines_expressions_1() { + #[repr(C)] + struct Base { + position: (u32, u32) + } +} + +// ==== Code Block 2 ==== +#[test] +fn test_block_coding_guidelines_expressions_2() { + #[repr(C)] + struct Base { + position: (u32, u32) + } +} + +// ==== Code Block 1 ==== +#[test] +fn test_block_coding_guidelines_macros_1() { + fn example_function() { + // Non-compliant implementation + } +} + +// ==== Code Block 2 ==== +#[test] +fn test_block_coding_guidelines_macros_2() { + fn example_function() { + // Compliant implementation + } +} + +// ==== Code Block 3 ==== +#[test] +fn test_block_coding_guidelines_macros_3() { + // TODO +} + +// ==== Code Block 4 ==== +#[test] +fn test_block_coding_guidelines_macros_4() { + // TODO +} + +// ==== Code Block 5 ==== +#[test] +fn test_block_coding_guidelines_macros_5() { + // HIDDEN START + use std::vec::*; + // HIDDEN END + macro_rules! increment_and_double { + ($x:expr) => { + { + $x += 1; // mutation is implicit + $x * 2 + } + }; + } + let mut num = 5; + let vv = Vec![]; + let result = increment_and_double!(num); + println!("Result: {}, Num: {}", result, num); + // Result: 12, Num: 6 +} + +// ==== Code Block 6 ==== +#[test] +fn test_block_coding_guidelines_macros_6() { + fn increment_and_double(x: &mut i32) -> i32 { + *x += 1; // mutation is explicit + *x * 2 + } + let mut num = 5; + let result = increment_and_double(&mut num); + println!("Result: {}, Num: {}", result, num); + // Result: 12, Num: 6 +} + +// ==== Code Block 7 ==== +#[test] +fn test_block_coding_guidelines_macros_7() { + fn example_function() { + // Non-compliant implementation + } +} + +// ==== Code Block 8 ==== +#[test] +fn test_block_coding_guidelines_macros_8() { + fn example_function() { + // Compliant implementation + } +} + +// ==== Code Block 9 ==== +#[test] +fn test_block_coding_guidelines_macros_9() { + fn example_function() { + // Non-compliant implementation + } +} + +// ==== Code Block 10 ==== +#[test] +fn test_block_coding_guidelines_macros_10() { + fn example_function() { + // Compliant implementation + } +} + +// ==== Code Block 11 ==== +#[test] +fn test_block_coding_guidelines_macros_11() { + fn example_function() { + // Non-compliant implementation + } +} + +// ==== Code Block 12 ==== +#[test] +fn test_block_coding_guidelines_macros_12() { + fn example_function() { + // Compliant implementation + } +} + +// ==== Code Block 13 ==== +#[test] +fn test_block_coding_guidelines_macros_13() { + fn example_function() { + // Non-compliant implementation + } +} + +// ==== Code Block 14 ==== +#[test] +fn test_block_coding_guidelines_macros_14() { + fn example_function() { + // Compliant implementation + } +} + +// ==== Code Block 15 ==== +#[test] +fn test_block_coding_guidelines_macros_15() { + #[tokio::main] // non-compliant + async fn maind() { + //dd + } +} + +// ==== Code Block 16 ==== +#[test] +fn test_block_coding_guidelines_macros_16() { + fn example_function() { + // Compliant implementation + } +} + +// ==== Code Block 17 ==== +#[test] +fn test_block_coding_guidelines_macros_17() { + fn example_function() { + // Non-compliant implementation + } +} + +// ==== Code Block 18 ==== +#[test] +fn test_block_coding_guidelines_macros_18() { + // HIDDEN START + use std::fs; + use std::io::{self, Read}; + // HIDDEN END +} + +// ==== Code Block 1 ==== +#[test] +fn test_block_coding_guidelines_types_and_traits_1() { + fn calculate_next_position(current: u32, velocity: u32) -> u32 { + // Potential for silent overflow in release builds + current + velocity + } +} + +// ==== Code Block 2 ==== +#[test] +fn test_block_coding_guidelines_types_and_traits_2() { + fn calculate_next_position(current: u32, velocity: u32) -> u32 { + // Explicitly handle potential overflow with checked addition + current.checked_add(velocity).expect("Position calculation overflowed") + } +} + +// ==== Code Block 1 ==== +#[test] +fn test_block_process_style_guideline_1() { + fn calculate_next_position(current: u32, velocity: u32) -> u32 { + // Potential for silent overflow in release builds + current + velocity + } +} + +// ==== Code Block 2 ==== +#[test] +fn test_block_process_style_guideline_2() { + fn calculate_next_position(current: u32, velocity: u32) -> u32 { + // Explicitly handle potential overflow with checked addition + current.checked_add(velocity).expect("Position calculation overflowed") + } +} + +// ==== Code Block 3 ==== +#[test] +fn test_block_process_style_guideline_3() { + fn calculate_next_position(current: u32, velocity: u32) -> u32 { + // Potential for silent overflow in release builds + current + velocity + } +} + +// ==== Code Block 4 ==== +#[test] +fn test_block_process_style_guideline_4() { + fn calculate_next_position(current: u32, velocity: u32) -> u32 { + // Explicitly handle potential overflow with checked addition + current.checked_add(velocity).expect("Position calculation overflowed") + } +} + diff --git a/exts/rust-code-runner/rust_examples_aggregate.py b/exts/rust-code-runner/rust_examples_aggregate.py new file mode 100644 index 0000000..8c821c1 --- /dev/null +++ b/exts/rust-code-runner/rust_examples_aggregate.py @@ -0,0 +1,91 @@ +from sphinx.errors import SphinxError +import logging +import re + +logger = logging.getLogger('sphinx') +# reducing errors temporarly for dev +logger.setLevel(logging.ERROR) + +class ExecuteRustExamples(SphinxError): + category = "ExecuteRustExamples Error" + + +def extract_code_blocks(text): + pattern = re.compile( + r"\.\. code-block:: rust\s*\n(?:(?:\s*\n)+)?((?: {2,}.*(?:\n|$))+)", + re.MULTILINE + ) + + matches = pattern.findall(text) + blocks = [] + for i, block in enumerate(matches): + lines = block.splitlines() + non_empty_lines = [line for line in lines if line.strip()] + processed_block = "\n".join(non_empty_lines) + blocks.append(processed_block) + + # print(f"====== code block {i + 1} ========") + # print(processed_block) + # print("====== end code block ========") + + return blocks + +def strip_hidden(code_block): + lines = code_block.splitlines() + result = [] + hidden = [] + is_hidden = False + + for line in lines: + stripped_for_marker_check = line[2:] if line.startswith(" ") else line + if "// HIDDEN START" in stripped_for_marker_check: + is_hidden = True + continue + if "// HIDDEN END" in stripped_for_marker_check: + is_hidden = False + continue + if not is_hidden: + result.append(line) + else: + hidden.append(line) + return "\n".join(result), "\n".join(hidden) + +def remove_hidden_blocks_from_document(source_text): + code_block_re = re.compile( + r"(\.\. code-block:: rust\s*\n\n)((?: {2}.*\n)+)", + re.DOTALL + ) + # callback for replacing + def replacer(match): + prefix = match.group(1) + code_content = match.group(2) + cleaned_code, hidden_code = strip_hidden(code_content) + print("============") + print(hidden_code) + print("============") + return prefix + cleaned_code + + modified_text = code_block_re.sub(replacer, source_text) + return modified_text + + +def preprocess_rst_for_rust_code(app, docname, source): + + original_content = source[0] + code_blocks = extract_code_blocks(original_content) + modified_content = remove_hidden_blocks_from_document(original_content) + source[0] = modified_content + + print(f"Original content length: {len(original_content)}") + print(f"Extracted {len(code_blocks)} code blocks") + + output_path = "exts/rust-code-runner/generated.rs" + safe_docname = docname.replace("/", "_").replace("-", "_") + with open(output_path, "a", encoding="utf-8") as f: + for i, block in enumerate(code_blocks, start=1): + f.write(f"// ==== Code Block {i} ====\n") + f.write("#[test]\n") + f.write(f"fn test_block_{safe_docname}_{i}() {{\n") + for line in block.splitlines(): + f.write(f" {line}\n") + f.write("}\n\n") diff --git a/exts/rust-code-runner/rustc.py b/exts/rust-code-runner/rustc.py new file mode 100644 index 0000000..f0e85be --- /dev/null +++ b/exts/rust-code-runner/rustc.py @@ -0,0 +1,68 @@ +import json + +def print_code_snippet(file_path, line_num, context=3): + try: + stripped_lines = [] + with open(file_path, "r") as f: + lines = f.readlines() + start = max(line_num - context - 1, 0) + end = min(line_num + context, len(lines)) + for i in range(start, end): + prefix = ">" if i == line_num - 1 else " " + stripped_lines.append(f"{prefix} {i+1:4}: {lines[i].rstrip()}") + return "\n".join(stripped_lines) + except Exception as e: + print(f"Could not read file {file_path}: {e}") + +def parse_rustc_json(stderr: str, file): + for line in stderr.splitlines(): + line = line.strip() + if not line: + continue + try: + obj = json.loads(line) + except json.JSONDecodeError: + continue + if obj.get("$message_type") != "diagnostic": + continue + + level = obj.get("level") + msg = obj.get("message") + spans = obj.get("spans", []) + + line_num = None + for span in spans: + if span.get("is_primary"): + line_num = span.get("line_start") + break + + if line_num is not None: + print(f"{level}: line {line_num}: {msg}") + diag_f = print_code_snippet(file, line_num) + if level == "error": + print("=========================") + print(f" {diag_f}") + print("=========================\n\n") + else: + print(f"{level}: {msg}") + +import subprocess +def check_rust_test_errors(app, exception): + rs_path = app.output_rust_file + result = subprocess.run( + ["rustc", "--test", "--edition=2021", "--error-format=json", rs_path], + capture_output=True, + text=True + ) + if result.returncode != 0: + print("--- rustc Errors/Warnings ---") + parse_rustc_json(result.stderr, app.output_rust_file) + print("--- rustc Output ---") + print(result.stdout) + + else: + print("--- rustc Output ---") + print(result.stdout) + if result.stderr: + print("\n\n--- rustc Warnings---") + print(result.stderr) diff --git a/exts/rust-code-runner/tests/tests.py b/exts/rust-code-runner/tests/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/src/coding-guidelines/macros.rst b/src/coding-guidelines/macros.rst index 03751a0..7d3d2fd 100644 --- a/src/coding-guidelines/macros.rst +++ b/src/coding-guidelines/macros.rst @@ -137,6 +137,9 @@ Macros .. code-block:: rust + // HIDDEN START + use std::vec::*; + // HIDDEN END macro_rules! increment_and_double { ($x:expr) => { { @@ -145,7 +148,9 @@ Macros } }; } + let mut num = 5; + let vv = Vec![]; let result = increment_and_double!(num); println!("Result: {}, Num: {}", result, num); // Result: 12, Num: 6 @@ -370,8 +375,8 @@ Macros .. code-block:: rust #[tokio::main] // non-compliant - async fn main() { - + async fn maind() { + //dd } .. compliant_example:: @@ -402,7 +407,7 @@ Macros :id: rat_WJubG7KuUDLW :status: draft - Explanation of why this guideline is important. + Test rust code blocks test .. non_compliant_example:: :id: non_compl_ex_AyFnP0lJLHxi @@ -424,8 +429,18 @@ Macros .. code-block:: rust - fn example_function() { - // Compliant implementation + // HIDDEN START + use std::fs; + use std::io::{self, Read}; + // HIDDEN END + + pub fn read_file_to_string(file_path: &str) -> io::Result { + fs::read_to_string(file_path) + + let mut file = fs::File::open(file_path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + Ok(contents) } .. guideline:: Names in a macro definition shall use a fully qualified path diff --git a/src/conf.py b/src/conf.py index fda72ec..5e25694 100644 --- a/src/conf.py +++ b/src/conf.py @@ -24,6 +24,7 @@ 'sphinx.ext.autosectionlabel', 'sphinx_needs', 'coding_guidelines', + 'rust-code-runner', ] # Basic needs configuration From 7caee520e01b307b63752742dd616ad949b2e924 Mon Sep 17 00:00:00 2001 From: x0rw Date: Sun, 25 May 2025 23:06:15 +0100 Subject: [PATCH 4/7] chores: missing import --- exts/coding_guidelines/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/exts/coding_guidelines/__init__.py b/exts/coding_guidelines/__init__.py index 63da7d8..6d6cefc 100644 --- a/exts/coding_guidelines/__init__.py +++ b/exts/coding_guidelines/__init__.py @@ -6,7 +6,6 @@ from . import std_role from . import fls_linking from . import guidelines_checks -from . import rust_examples_test from .common import logger, get_tqdm, bar_format, logging from sphinx.domains import Domain From ae114386f9fb8e9acb837115d9f1f36a9ebbb043 Mon Sep 17 00:00:00 2001 From: x0rw Date: Wed, 25 Jun 2025 21:26:43 +0100 Subject: [PATCH 5/7] add comments --- exts/rust-code-runner/__init__.py | 3 +- exts/rust-code-runner/generated.rs | 40 ++++++++++++++++++++++++ exts/rust-code-runner/rustc.py | 50 ++++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/exts/rust-code-runner/__init__.py b/exts/rust-code-runner/__init__.py index 409801e..7b54f47 100644 --- a/exts/rust-code-runner/__init__.py +++ b/exts/rust-code-runner/__init__.py @@ -3,14 +3,13 @@ from . import rustc import os def setup(app): - app.output_rust_file = "exts/rust-code-runner/generated.rs" if os.path.isfile(app.output_rust_file): with open(app.output_rust_file, 'w'): pass # we hook into 'source-read' because data is mutable at this point and easier to parse - # and it also makes this extension indepandant from `needs` + # and it also makes this extension indepandant from `needs`. # app.connect('source-read', rust_examples_aggregate.preprocess_rst_for_rust_code) app.connect('build-finished', rustc.check_rust_test_errors) diff --git a/exts/rust-code-runner/generated.rs b/exts/rust-code-runner/generated.rs index bfb9d2f..c7a74cc 100644 --- a/exts/rust-code-runner/generated.rs +++ b/exts/rust-code-runner/generated.rs @@ -1,3 +1,43 @@ +// ==== Code Block 1 ==== +#[test] +fn test_block_coding_guidelines_concurrency_1() { + test ga +} + +// ==== Code Block 2 ==== +#[test] +fn test_block_coding_guidelines_concurrency_2() { + test ga +} + +// ==== Code Block 3 ==== +#[test] +fn test_block_coding_guidelines_concurrency_3() { + .. compliant_example:: + :id: compl_ex_yp7aQuEi3Sag + :status: draft +} + +// ==== Code Block 4 ==== +#[test] +fn test_block_coding_guidelines_concurrency_4() { + test ga +} + +// ==== Code Block 5 ==== +#[test] +fn test_block_coding_guidelines_concurrency_5() { + .. compliant_example:: + :id: compl_ex_gqeLAg0YBu9P + :status: draft +} + +// ==== Code Block 6 ==== +#[test] +fn test_block_coding_guidelines_concurrency_6() { + test ga +} + // ==== Code Block 1 ==== #[test] fn test_block_coding_guidelines_expressions_1() { diff --git a/exts/rust-code-runner/rustc.py b/exts/rust-code-runner/rustc.py index f0e85be..0f55b56 100644 --- a/exts/rust-code-runner/rustc.py +++ b/exts/rust-code-runner/rustc.py @@ -1,6 +1,22 @@ import json +import subprocess def print_code_snippet(file_path, line_num, context=3): + """ + Prints a code snippet from a file with context around a specific line. + + This function is typically used to display source code around an error line + for better debugging and error reporting. + + Args: + file_path (str): Path to the source file. + line_num (int): The line number where the error occurred (1-based index). + context (int, optional): The number of lines to display before and after + the error line. Defaults to 3. + + Returns: + None + """ try: stripped_lines = [] with open(file_path, "r") as f: @@ -14,7 +30,21 @@ def print_code_snippet(file_path, line_num, context=3): except Exception as e: print(f"Could not read file {file_path}: {e}") + def parse_rustc_json(stderr: str, file): + """ + Parses the JSON diagnostics output from `rustc`. + + This function takes the standard error output (in JSON format) from the Rust compiler (`rustc`) + and processes it, possibly filtering or reporting diagnostics relevant to the specified file. + + Args: + stderr (str): The JSON-formatted stderr output from `rustc`. + file: The file object or path that the diagnostics should relate to. + + Returns: + Any + """ for line in stderr.splitlines(): line = line.strip() if not line: @@ -46,14 +76,30 @@ def parse_rustc_json(stderr: str, file): else: print(f"{level}: {msg}") -import subprocess + def check_rust_test_errors(app, exception): + """ + Sphinx 'build-finished' event handler that compiles the generated Rust file in test mode. + + This function is connected to the Sphinx build lifecycle and is executed after the build finishes. + It invokes `rustc` in test mode on the generated Rust file and reports any compilation or test-related + errors. + + Args: + app: The Sphinx application object. Must have an `output_rust_file` attribute containing + the path to the generated Rust source file. + exception: Exception raised during the build process, or None if the build completed successfully. + + """ rs_path = app.output_rust_file + # Run the Rust compiler in test mode with JSON error output format. + # capturing stdout and stderr as text. result = subprocess.run( - ["rustc", "--test", "--edition=2021", "--error-format=json", rs_path], + ["rustc", "--test", "--edition=2024", "--error-format=json", rs_path], capture_output=True, text=True ) + if result.returncode != 0: print("--- rustc Errors/Warnings ---") parse_rustc_json(result.stderr, app.output_rust_file) From 65a67332b4aefcbee61def4626ec98a2a489ac62 Mon Sep 17 00:00:00 2001 From: x0rw Date: Wed, 25 Jun 2025 23:47:11 +0100 Subject: [PATCH 6/7] add feature flag --test-rust-blocks --- builder/build_cli.py | 13 ++- exts/coding_guidelines/__init__.py | 21 +++-- exts/rust-code-runner/__init__.py | 9 +- exts/rust-code-runner/generated.rs | 91 ++++++++----------- .../rust_examples_aggregate.py | 13 ++- exts/rust-code-runner/rustc.py | 64 ++++++------- src/coding-guidelines/macros.rst | 25 +---- 7 files changed, 116 insertions(+), 120 deletions(-) diff --git a/builder/build_cli.py b/builder/build_cli.py index 35c7917..a0aa205 100644 --- a/builder/build_cli.py +++ b/builder/build_cli.py @@ -29,6 +29,7 @@ def build_docs( debug: bool, offline: bool, spec_lock_consistency_check: bool, + test_rust_blocks: bool, ) -> Path: """ Builds the Sphinx documentation with the specified options. @@ -72,6 +73,8 @@ def build_docs( conf_opt_values.append("offline=1") if debug: conf_opt_values.append("debug=1") + if test_rust_blocks: + conf_opt_values.append("test_rust_blocks=1") # Only add the --define argument if there are options to define if conf_opt_values: @@ -151,6 +154,14 @@ def main(root): help="build in offline mode", action="store_true", ) + + parser.add_argument( + "--test-rust-blocks", + help="Test extracted rust code blocks using rustc", + default=False, + action="store_true" + ) + group = parser.add_mutually_exclusive_group() parser.add_argument( "--ignore-spec-lock-diff", @@ -192,6 +203,6 @@ def main(root): update_spec_lockfile(SPEC_CHECKSUM_URL, root / "src" / SPEC_LOCKFILE) rendered = build_docs( - root, "xml" if args.xml else "html", args.clear, args.serve, args.debug, args.offline, not args.ignore_spec_lock_diff, + root, "xml" if args.xml else "html", args.clear, args.serve, args.debug, args.offline, not args.ignore_spec_lock_diff, args.test_rust_blocks ) diff --git a/exts/coding_guidelines/__init__.py b/exts/coding_guidelines/__init__.py index 6d6cefc..fa9dbcf 100644 --- a/exts/coding_guidelines/__init__.py +++ b/exts/coding_guidelines/__init__.py @@ -48,6 +48,13 @@ def on_build_finished(app, exception): def setup(app): app.add_domain(CodingGuidelinesDomain) + + app.add_config_value( + name='test_rust_blocks', + default=False, + rebuild='env' + ) + app.add_config_value( name = "offline", default=False, @@ -79,12 +86,14 @@ def setup(app): logger.setLevel(logging.INFO) common.disable_tqdm = True - app.connect('env-check-consistency', guidelines_checks.validate_required_fields) - app.connect('env-check-consistency', fls_checks.check_fls) - app.connect('build-finished', write_guidelines_ids.build_finished) - app.connect('build-finished', fls_linking.build_finished) - app.connect('build-finished', on_build_finished) - + # Ignore builds while testing code blocks + if not app.config.test_rust_blocks: + app.connect('env-check-consistency', guidelines_checks.validate_required_fields) + app.connect('env-check-consistency', fls_checks.check_fls) + app.connect('build-finished', write_guidelines_ids.build_finished) + app.connect('build-finished', fls_linking.build_finished) + app.connect('build-finished', on_build_finished) + return { 'version': '0.1', 'parallel_read_safe': True, diff --git a/exts/rust-code-runner/__init__.py b/exts/rust-code-runner/__init__.py index 7b54f47..7ed4d29 100644 --- a/exts/rust-code-runner/__init__.py +++ b/exts/rust-code-runner/__init__.py @@ -1,9 +1,10 @@ - from . import rust_examples_aggregate from . import rustc import os + def setup(app): - app.output_rust_file = "exts/rust-code-runner/generated.rs" + + app.output_rust_file = "build/rust-code-blocks/generated.rs" if os.path.isfile(app.output_rust_file): with open(app.output_rust_file, 'w'): pass @@ -12,7 +13,9 @@ def setup(app): # and it also makes this extension indepandant from `needs`. # app.connect('source-read', rust_examples_aggregate.preprocess_rst_for_rust_code) - app.connect('build-finished', rustc.check_rust_test_errors) + + if app.config.test_rust_blocks: + app.connect('build-finished', rustc.check_rust_test_errors) return { 'version': '0.1', 'parallel_read_safe': False, diff --git a/exts/rust-code-runner/generated.rs b/exts/rust-code-runner/generated.rs index c7a74cc..5b29a57 100644 --- a/exts/rust-code-runner/generated.rs +++ b/exts/rust-code-runner/generated.rs @@ -1,43 +1,3 @@ -// ==== Code Block 1 ==== -#[test] -fn test_block_coding_guidelines_concurrency_1() { - test ga -} - -// ==== Code Block 2 ==== -#[test] -fn test_block_coding_guidelines_concurrency_2() { - test ga -} - -// ==== Code Block 3 ==== -#[test] -fn test_block_coding_guidelines_concurrency_3() { - .. compliant_example:: - :id: compl_ex_yp7aQuEi3Sag - :status: draft -} - -// ==== Code Block 4 ==== -#[test] -fn test_block_coding_guidelines_concurrency_4() { - test ga -} - -// ==== Code Block 5 ==== -#[test] -fn test_block_coding_guidelines_concurrency_5() { - .. compliant_example:: - :id: compl_ex_gqeLAg0YBu9P - :status: draft -} - -// ==== Code Block 6 ==== -#[test] -fn test_block_coding_guidelines_concurrency_6() { - test ga -} - // ==== Code Block 1 ==== #[test] fn test_block_coding_guidelines_expressions_1() { @@ -87,9 +47,6 @@ fn test_block_coding_guidelines_macros_4() { // ==== Code Block 5 ==== #[test] fn test_block_coding_guidelines_macros_5() { - // HIDDEN START - use std::vec::*; - // HIDDEN END macro_rules! increment_and_double { ($x:expr) => { { @@ -99,7 +56,6 @@ fn test_block_coding_guidelines_macros_5() { }; } let mut num = 5; - let vv = Vec![]; let result = increment_and_double!(num); println!("Result: {}, Num: {}", result, num); // Result: 12, Num: 6 @@ -109,7 +65,7 @@ fn test_block_coding_guidelines_macros_5() { #[test] fn test_block_coding_guidelines_macros_6() { fn increment_and_double(x: &mut i32) -> i32 { - *x += 1; // mutation is explicit + *x += 1; // mutation is explicit *x * 2 } let mut num = 5; @@ -186,9 +142,7 @@ fn test_block_coding_guidelines_macros_14() { #[test] fn test_block_coding_guidelines_macros_15() { #[tokio::main] // non-compliant - async fn maind() { - //dd - } + async fn main() { } // ==== Code Block 16 ==== @@ -210,10 +164,43 @@ fn test_block_coding_guidelines_macros_17() { // ==== Code Block 18 ==== #[test] fn test_block_coding_guidelines_macros_18() { - // HIDDEN START - use std::fs; - use std::io::{self, Read}; - // HIDDEN END + fn example_function() { + // Compliant implementation + } +} + +// ==== Code Block 19 ==== +#[test] +fn test_block_coding_guidelines_macros_19() { + #[macro_export] + macro_rules! vec { + ( $( $x:expr ),* ) => { + { + let mut temp_vec = Vec::new(); // non-global path + $( + temp_vec.push($x); + )* + temp_vec + } + }; + } +} + +// ==== Code Block 20 ==== +#[test] +fn test_block_coding_guidelines_macros_20() { + #[macro_export] + macro_rules! vec { + ( $( $x:expr ),* ) => { + { + let mut temp_vec = ::std::vec::Vec::new(); // global path + $( + temp_vec.push($x); + )* + temp_vec + } + }; + } } // ==== Code Block 1 ==== diff --git a/exts/rust-code-runner/rust_examples_aggregate.py b/exts/rust-code-runner/rust_examples_aggregate.py index 8c821c1..c209b03 100644 --- a/exts/rust-code-runner/rust_examples_aggregate.py +++ b/exts/rust-code-runner/rust_examples_aggregate.py @@ -60,9 +60,9 @@ def replacer(match): prefix = match.group(1) code_content = match.group(2) cleaned_code, hidden_code = strip_hidden(code_content) - print("============") - print(hidden_code) - print("============") + # print("============") + # print(hidden_code) + # print("============") return prefix + cleaned_code modified_text = code_block_re.sub(replacer, source_text) @@ -76,12 +76,11 @@ def preprocess_rst_for_rust_code(app, docname, source): modified_content = remove_hidden_blocks_from_document(original_content) source[0] = modified_content - print(f"Original content length: {len(original_content)}") - print(f"Extracted {len(code_blocks)} code blocks") + # print(f"Original content length: {len(original_content)}") + # print(f"Extracted {len(code_blocks)} code blocks") - output_path = "exts/rust-code-runner/generated.rs" safe_docname = docname.replace("/", "_").replace("-", "_") - with open(output_path, "a", encoding="utf-8") as f: + with open(app.output_rust_file, "a", encoding="utf-8") as f: for i, block in enumerate(code_blocks, start=1): f.write(f"// ==== Code Block {i} ====\n") f.write("#[test]\n") diff --git a/exts/rust-code-runner/rustc.py b/exts/rust-code-runner/rustc.py index 0f55b56..f875429 100644 --- a/exts/rust-code-runner/rustc.py +++ b/exts/rust-code-runner/rustc.py @@ -31,54 +31,56 @@ def print_code_snippet(file_path, line_num, context=3): print(f"Could not read file {file_path}: {e}") -def parse_rustc_json(stderr: str, file): +def parse_rustc_json(stderr: str, file_path): """ - Parses the JSON diagnostics output from `rustc`. - - This function takes the standard error output (in JSON format) from the Rust compiler (`rustc`) - and processes it, possibly filtering or reporting diagnostics relevant to the specified file. + Parses rustc's JSON output and prints only the first error with a single snippet. Args: - stderr (str): The JSON-formatted stderr output from `rustc`. - file: The file object or path that the diagnostics should relate to. + stderr (str): JSON-formatted stderr output from rustc. + file_path: Path to the Rust file. Returns: - Any + None """ for line in stderr.splitlines(): line = line.strip() if not line: continue + try: - obj = json.loads(line) + diagnostic = json.loads(line) except json.JSONDecodeError: continue - if obj.get("$message_type") != "diagnostic": + + if diagnostic.get("$message_type") != "diagnostic": continue - level = obj.get("level") - msg = obj.get("message") - spans = obj.get("spans", []) + if diagnostic.get("level") != "error": + continue # skip warnings and notes - line_num = None - for span in spans: - if span.get("is_primary"): - line_num = span.get("line_start") - break - - if line_num is not None: - print(f"{level}: line {line_num}: {msg}") - diag_f = print_code_snippet(file, line_num) - if level == "error": - print("=========================") - print(f" {diag_f}") - print("=========================\n\n") - else: - print(f"{level}: {msg}") + message = diagnostic.get("message", "") + spans = diagnostic.get("spans", []) + # Try to find a span in the current file + for span in spans: + if span["file_name"] == file_path: + line_num = span["line_start"] + label = span.get("label", "") + print(f"error: line {line_num}: {message}") + if label: + print(f"--> {label}") + print("=" * 25) + snippet = print_code_snippet(file_path, line_num, context=3) + print(snippet) + print("=" * 25) + return # we return because we only print the first error--in json format there can be multiple error messages for 1 error-- if you want to see them comment this line. + + # If no span in the file, still print the error + print(f"error: {message}") + return def check_rust_test_errors(app, exception): - """ + """ Sphinx 'build-finished' event handler that compiles the generated Rust file in test mode. This function is connected to the Sphinx build lifecycle and is executed after the build finishes. @@ -89,13 +91,13 @@ def check_rust_test_errors(app, exception): app: The Sphinx application object. Must have an `output_rust_file` attribute containing the path to the generated Rust source file. exception: Exception raised during the build process, or None if the build completed successfully. - """ rs_path = app.output_rust_file # Run the Rust compiler in test mode with JSON error output format. # capturing stdout and stderr as text. result = subprocess.run( - ["rustc", "--test", "--edition=2024", "--error-format=json", rs_path], + ["rustc", "--test", "--edition=2024", "--error-format=json", "--emit=metadata", rs_path], + # --emit=metadata or else rustc will produce a binary ./generated capture_output=True, text=True ) diff --git a/src/coding-guidelines/macros.rst b/src/coding-guidelines/macros.rst index 7d3d2fd..03751a0 100644 --- a/src/coding-guidelines/macros.rst +++ b/src/coding-guidelines/macros.rst @@ -137,9 +137,6 @@ Macros .. code-block:: rust - // HIDDEN START - use std::vec::*; - // HIDDEN END macro_rules! increment_and_double { ($x:expr) => { { @@ -148,9 +145,7 @@ Macros } }; } - let mut num = 5; - let vv = Vec![]; let result = increment_and_double!(num); println!("Result: {}, Num: {}", result, num); // Result: 12, Num: 6 @@ -375,8 +370,8 @@ Macros .. code-block:: rust #[tokio::main] // non-compliant - async fn maind() { - //dd + async fn main() { + } .. compliant_example:: @@ -407,7 +402,7 @@ Macros :id: rat_WJubG7KuUDLW :status: draft - Test rust code blocks test + Explanation of why this guideline is important. .. non_compliant_example:: :id: non_compl_ex_AyFnP0lJLHxi @@ -429,18 +424,8 @@ Macros .. code-block:: rust - // HIDDEN START - use std::fs; - use std::io::{self, Read}; - // HIDDEN END - - pub fn read_file_to_string(file_path: &str) -> io::Result { - fs::read_to_string(file_path) - - let mut file = fs::File::open(file_path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - Ok(contents) + fn example_function() { + // Compliant implementation } .. guideline:: Names in a macro definition shall use a fully qualified path From 97509eb74987de30260733e4c25235b09531aff1 Mon Sep 17 00:00:00 2001 From: x0rw Date: Thu, 26 Jun 2025 15:17:27 +0100 Subject: [PATCH 7/7] Ensure build/rust-code-blocks directory exists before use --- exts/rust-code-runner/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exts/rust-code-runner/__init__.py b/exts/rust-code-runner/__init__.py index 7ed4d29..efb8361 100644 --- a/exts/rust-code-runner/__init__.py +++ b/exts/rust-code-runner/__init__.py @@ -5,6 +5,10 @@ def setup(app): app.output_rust_file = "build/rust-code-blocks/generated.rs" + + # create build dir + if not os.path.exists("build/rust-code-blocks"): + os.makedirs("build/rust-code-blocks") if os.path.isfile(app.output_rust_file): with open(app.output_rust_file, 'w'): pass pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy