From 4e4ed8648353092f844078d179f860879df2578c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me?= Date: Wed, 4 Feb 2026 14:09:41 +0100 Subject: [PATCH] Initial commit. --- .gitignore | 2 + README.md | 73 ++++++++++++++ example/include/factorial.h | 5 + example/src/example_code.cpp | 24 +++++ example/src/factorial.cpp | 8 ++ markdown_snippet_injector.py | 189 +++++++++++++++++++++++++++++++++++ processed_markdown.md | 53 ++++++++++ source_markdown.md | 38 +++++++ 8 files changed, 392 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 example/include/factorial.h create mode 100644 example/src/example_code.cpp create mode 100644 example/src/factorial.cpp create mode 100644 markdown_snippet_injector.py create mode 100644 processed_markdown.md create mode 100644 source_markdown.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bbe4bf4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode/ +**/__pycache__/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4196cb8 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Code snippet markdown insertion tool + +This tool allows the definition of tags within any code base to be referred to in a markdown documentation file. + +The specific XML tags present in the markdown file are replaced by the corresponding code snippet, pulled directly from the linked source code file. + +This allows the documentation to evolve automatically when the source files are modified, thus reducing the maintenance effort of such documentation, especially in cases where minor changes in the code do not necesitate updating the documentation, but that the snippets must be kept up-to-date with the actual code. + +## Usage + +```bash +python markdown_snippet_injector.py source_markdown.md processed_markdown.md +``` + +## Example + +Here are links to examples of an [unprocessed](source_markdown.md) and the corresponding [processed](processed_markdown.md) markdown files. + +## Markdown XML tags + +The source markdown file has specific XML tags `` embedded that will be replaced by the actual code contained within the corresponding tags in the related source files. + +Two parameters must be specified : + +- name : unique name of the snippet +- file : file path in which to find the snippet + +```xml + +``` + +Here is a simple example of a markdown file containing a tag : + +```markdown + ## The includes + + The includes are important to tell the compiler what to expect later. In the example file, the includes are : + + ```cpp + + ``` +``` + +## Code file tags + +In the code files, the tags must be under the following form : + +``` +BEGIN CODE SNIPPET snippet_name +Some code that will be included in the snippet +END CODE SNIPPET +``` + +Note that the entire line where BEGIN CODE SNIPPET and END CODE SNIPPET is removed from the output markdown file. +It is recommended to put the tags in separate lines around the snippet you want to capture. + +Depending on the language of the source file, the appropriate comment format must be used, so that the tag's presence does not change the behavior of the program. + +Example in python : + +```python +# BEGIN CODE SNIPPET snippet_name +v1 = np.array([1.0, 2.0, 3.0]) +# END CODE SNIPPET +``` + +Example in C++ : + +```cpp +// BEGIN CODE SNIPPET snippet_name +const auto v1 = {1.0, 2.0, 3.0}; +// END CODE SNIPPET +``` \ No newline at end of file diff --git a/example/include/factorial.h b/example/include/factorial.h new file mode 100644 index 0000000..f604c53 --- /dev/null +++ b/example/include/factorial.h @@ -0,0 +1,5 @@ +// BEGIN CODE SNIPPET header_factorial +#pragma once + +int factorial(int n); +// END CODE SNIPPET diff --git a/example/src/example_code.cpp b/example/src/example_code.cpp new file mode 100644 index 0000000..e7f8ff8 --- /dev/null +++ b/example/src/example_code.cpp @@ -0,0 +1,24 @@ +// BEGIN CODE SNIPPET includes +#include +#include +// END CODE SNIPPET + +int some_function(int x) { + int y = x*x; + + // BEGIN CODE SNIPPET some_function + int z = y*y; + return z; + // END CODE SNIPPET +} + +// BEGIN CODE SNIPPET main_function +int main() { + // Call the factorial function + std::cout << factorial(5) << std::endl; + + // Call some_function + std::cout << some_function(3) << std::endl; + return 0; +} +// END CODE SNIPPET diff --git a/example/src/factorial.cpp b/example/src/factorial.cpp new file mode 100644 index 0000000..b153490 --- /dev/null +++ b/example/src/factorial.cpp @@ -0,0 +1,8 @@ +// BEGIN CODE SNIPPET factorial_function +int factorial(int n) { + if (n <= 1) { + return 1; + } + return factorial(n-1) * n; +} +// END CODE SNIPPET \ No newline at end of file diff --git a/markdown_snippet_injector.py b/markdown_snippet_injector.py new file mode 100644 index 0000000..f5f2ee3 --- /dev/null +++ b/markdown_snippet_injector.py @@ -0,0 +1,189 @@ +import argparse +import re +import xml.etree.ElementTree as ET + + +def extract_code_snippets(file_path: str) -> dict: + """ + Extracts code snippets delimited by "BEGIN CODE SNIPPET XYZ" and "END CODE SNIPPET" from the given file, with XYZ being some user-defined name. + + All snippets are extracted and returned as a dictionary with the snippet name as the key, and the snippet contents as the value. + + :param file_path: File path in which the tag should be found. + """ + snippets = dict() + with open(file_path) as f_in: + lines = f_in.readlines() + snippet_name = None + snippet_start_line = None + for i, line in enumerate(lines): + if "BEGIN CODE SNIPPET" in line: + snippet_name = line.split("BEGIN CODE SNIPPET")[1].strip() + snippet_start_line = i + 1 + if "END CODE SNIPPET" in line: + snippets[snippet_name] = "".join(lines[snippet_start_line:i]) + return snippets + + +def decode_xml_snippet_tag(line): + """ + Extracts and decodes the XML snippet tag under the following form : + ``` + + ``` + + :param line: Line containing the XML tag. + """ + # Extract the XML tag from the line + the_match = re.match("()", line) + if the_match: + xml_string = the_match.group(1) + else: + return None + + # Parse the XML string + root = ET.fromstring(f'{xml_string}') # Wrap in a root tag if needed + + # Iterate over all include_snippet tags + for elem in root.findall('include_snippet'): + name = elem.get('name') + file = elem.get('file') + + return {"name": name, "file": file} + + +def extract_snippet_infos(lines) -> dict: + """ + Extracts the names of the snippets required from the source markdown file. + + Returns a dictionary of lists : + - file_name_1 + - snippet_name_1 + - snippet_name_2 + - ... + - file_name_2 + - snippet_name_1 + - snippet_name_2 + - ... + - ... + """ + snippet_infos = dict() + snippet_infos_list = [] + + for line in lines: + if "` tags under the following form: + + ``` + + ``` + + Beware that the `` tag is case-sensitive ! It must be all lowercase to be correctly detected. + + :param args: Parsed program arguments. + """ + with open(args.source) as f_in: + with open(args.destination, "w+") as f_out: + # Read source markdown file + source_lines = f_in.readlines() + + # Extract required code snippets from markdown file + snippet_infos = extract_snippet_infos(source_lines) + print_snippet_information(snippet_infos) + + # Extract actual snippets + snippets = dict() + for file_path in snippet_infos.keys(): + snippets[file_path] = extract_code_snippets(file_path) + print_snippet_summary(snippets) + + # Replace the snippet calls by the actual content + output_lines = insert_snippets_in_markdown(source_lines, snippets) + + # Write the processed file + f_out.writelines(output_lines) + + +def parse_args(): + """ + Parses the arguments of the program. + """ + # Create the argument parser + parser = argparse.ArgumentParser(description='Markdown code snippet injector.') + + # Add the positional arguments + parser.add_argument('source', type=str, help='Source path (mandatory)') + parser.add_argument('destination', type=str, help='Destination path (mandatory)') + + return parser.parse_args() + + +if __name__ == "__main__": + args = parse_args() + process_markdown_file(args) diff --git a/processed_markdown.md b/processed_markdown.md new file mode 100644 index 0000000..c32d481 --- /dev/null +++ b/processed_markdown.md @@ -0,0 +1,53 @@ +# Example markdown + +Compare the processed version of this file with its unprocessed form. + +## The includes + +The includes are important to tell the compiler what to expect later. In the example file, the includes are : + +```cpp +#include +#include +``` + +## The factorial function + +The factorial function is implemented recursively. It is defined in another file called `factorial.cpp` : + +```cpp +int factorial(int n) { + if (n <= 1) { + return 1; + } + return factorial(n-1) * n; +} +``` + +The corresponding header file is : + +```cpp +#pragma once + +int factorial(int n); +``` + +## Some other function + +Some other function is implemented in the `example_code.cpp` file. +While the snippet is defined in the C++ file, we can decide to not use it in the documentation. + +## Main function + +In order to call all the functions defined above, the main function is implemented as such : + +```cpp +int main() { + // Call the factorial function + std::cout << factorial(5) << std::endl; + + // Call some_function + std::cout << some_function(3) << std::endl; + return 0; +} +``` diff --git a/source_markdown.md b/source_markdown.md new file mode 100644 index 0000000..976211d --- /dev/null +++ b/source_markdown.md @@ -0,0 +1,38 @@ +# Example markdown + +Compare the processed version of this file with its unprocessed form. + +## The includes + +The includes are important to tell the compiler what to expect later. In the example file, the includes are : + +```cpp + +``` + +## The factorial function + +The factorial function is implemented recursively. It is defined in another file called `factorial.cpp` : + +```cpp + +``` + +The corresponding header file is : + +```cpp + +``` + +## Some other function + +Some other function is implemented in the `example_code.cpp` file. +While the snippet is defined in the C++ file, we can decide to not use it in the documentation. + +## Main function + +In order to call all the functions defined above, the main function is implemented as such : + +```cpp + +```