From 7bc8efbe7764099c62b6f7d01abb6ba617c15199 Mon Sep 17 00:00:00 2001 From: ljalil Date: Mon, 15 Dec 2025 15:01:44 -0300 Subject: [PATCH 1/2] add new arguments style and heading_level --- CHANGELOG.md | 8 ++ pyproject.toml | 2 +- src/peakrdl_markdown/__peakrdl__.py | 18 ++- src/peakrdl_markdown/exporter.py | 200 +++++++++++++++++----------- 4 files changed, 149 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1287692..ae9c221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.0] + +### Added + +- Add new parameters: + - **heading_level:** Starting heading level for top-level node + - **style:** 'flat' or 'hierarchy' + ## [1.0.3] ### Fixed diff --git a/pyproject.toml b/pyproject.toml index 1794e7b..f5b9eb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "peakrdl_markdown" -version = "1.0.3" +version = "1.1.0" description = "Export Markdown description from the systemrdl-compiler register model" authors = ["Marek Pikuła "] maintainers = ["Marek Pikuła "] diff --git a/src/peakrdl_markdown/__peakrdl__.py b/src/peakrdl_markdown/__peakrdl__.py index e852781..17f9d4f 100644 --- a/src/peakrdl_markdown/__peakrdl__.py +++ b/src/peakrdl_markdown/__peakrdl__.py @@ -30,6 +30,20 @@ def add_exporter_arguments(self, arg_group: "argparse._ActionsContainer"): # ty type=int, help="Depth of generation (0 means all)", ) + arg_group.add_argument( + "--heading_level", + dest="heading_level", + default=2, + type=int, + help="Starting heading level for top-level node.", + ) + arg_group.add_argument( + "--style", + dest="style", + choices=["flat", "hierarchy"], + default="flat", + help="Generation style: 'flat' or 'hierarchy'." + ) def do_export( self, top_node: "Union[AddrmapNode, RootNode]", options: "argparse.Namespace" @@ -46,4 +60,6 @@ def do_export( input_files=options.input_files, rename=options.inst_name, depth=options.depth, - ) + heading_level=options.heading_level, + style=options.style, + ) \ No newline at end of file diff --git a/src/peakrdl_markdown/exporter.py b/src/peakrdl_markdown/exporter.py index f54b53e..aa01274 100644 --- a/src/peakrdl_markdown/exporter.py +++ b/src/peakrdl_markdown/exporter.py @@ -10,7 +10,7 @@ from functools import reduce from operator import mul from pathlib import Path -from typing import List, Optional, Union +from typing import List, Optional, Union, Literal from py_markdown_table.markdown_table import markdown_table # type:ignore from systemrdl.messages import MessageHandler # type: ignore @@ -25,39 +25,47 @@ RootNode, ) - class MarkdownExporter: # pylint: disable=too-few-public-methods """PeakRDL Markdown exporter main class.""" + def __init__(self): + """Initialize the exporter.""" + self.style = "flat" + @dataclass class GenStageOutput: """Generation stage output.""" - node: Node - """Node on which generation has been performed.""" - table_row: "OrderedDict[str, Union[str, int]]" - """Row for the parent table.""" - generated: str - """Markdown generated during this stage.""" @staticmethod def _heading(depth: int, title: str): - """Generate Markdown heading of a given depth with newline envelope. + """Generate Markdown heading. Arguments: - depth -- heading depth (number of hashes) - title -- heading title + depth -- Heading depth (number of hashes). + title -- Heading title text. Returns: - Formatted Markdown heading. + Formatted Markdown heading string. """ - return "\n" + "#" * depth + f" {title}\n\n" + # Safety check: Markdown only supports up to level 6 + safe_depth = min(depth, 6) + if safe_depth < 1: safe_depth = 1 # Prevent 0 or negative + + return "\n" + "#" * safe_depth + f" {title}\n\n" @staticmethod def _addrnode_info(node: AddressableNode): - """Generate AddressableNode basic information dictionary.""" + """Generate AddressableNode basic information dictionary. + + Arguments: + node -- The addressable node (Addrmap, Regfile, Mem) to extract info from. + + Returns: + OrderedDict containing address, offset, size, etc. + """ ret: "OrderedDict[str, str]" = OrderedDict() set_index = False @@ -79,7 +87,14 @@ def _addrnode_info(node: AddressableNode): return ret def _addrnode_info_md(self, node: AddressableNode) -> str: - """Generate AddressableNode basic information as a Markdown list.""" + """Generate AddressableNode basic information as a Markdown list. + + Arguments: + node -- The addressable node to format. + + Returns: + String containing the markdown list of properties. + """ return "- " + "\n- ".join( f"{key}: {value}" for key, value in self._addrnode_info(node).items() ) @@ -88,7 +103,11 @@ def _addrnode_info_md(self, node: AddressableNode) -> str: def _node_name_sanitized(node: Node) -> str: """Get the Node name as HTML without newlines. - Needed for proper inclusion in tables. + Arguments: + node -- The node to get the name from. + + Returns: + Sanitized name string suitable for table inclusion. """ name = node.get_html_name() if name is None: @@ -103,9 +122,12 @@ def _addrnode_header( """Get the AddressableNode header. Arguments: - node -- node to generate the header for. - msg -- message handler from top-level. - heading_level -- Markdown heading level. + node -- Node to generate the header for. + msg -- Message handler from top-level for warnings. + heading_level -- The Markdown heading depth level to use. + + Returns: + String containing the header, info list, and description. """ if isinstance(node, AddrmapNode): node_type_name = "address map" @@ -131,8 +153,11 @@ def _addrnode_table_row( ) -> "OrderedDict[str, Union[str, int]]": """Generate AddressableNode table row. - The "Offset" is an integer so that it can be formatted in the parent - node. + Arguments: + node -- The node to generate a summary row for. + + Returns: + OrderedDict representing the table row (Offset, Identifier, Name). """ offset = node.address_offset identifier = node.inst_name @@ -155,17 +180,22 @@ def export( # pylint: disable=too-many-arguments input_files: Optional[List[str]] = None, rename: Optional[str] = None, depth: int = 0, + heading_level: int = 2, + style: Literal["flat", "hierarchy"] = "flat", ): - """Export the `node` to generated Python interface file. + """Export the `node` to generated Markdown file. Arguments: - node -- node to export. - input_files -- list of input files. - output_path -- path to the exported file. - rename -- name to rename the top-level to. - depth -- depth of generation (0 means all) + node -- Node to export (Root or Addrmap). + output_path -- Path to the exported .md file. + input_files -- List of input RDL files used for annotation. + rename -- Optional name to rename the top-level to. + depth -- Depth of generation (0 means all). + heading_level -- Starting heading level for top-level node. + style -- Generation style: 'flat' or 'hierarchy'. """ - # Get the top node. + self.style = style + top = node.top if isinstance(node, RootNode) else node top_name = rename if rename is not None else node.inst_name @@ -173,15 +203,12 @@ def export( # pylint: disable=too-many-arguments if input_files is not None: generated_from += "\n - " + "\n - ".join(f for f in input_files) - # Ensure proper format of the output path and that the directory exists. if not output_path.endswith(".md"): raise ValueError("The output file is not Markdown file.") Path(output_path).parent.mkdir(parents=True, exist_ok=True) - # Run generation. - gen = self._add_addrmap_regfile_mem(top, node.env.msg, depth - 1).generated + gen = self._add_addrmap_regfile_mem(top, node.env.msg, depth - 1, heading_level).generated - # Write to the file. with open(output_path, "w", encoding="UTF-8") as output: output.write( "