diff --git a/tools/md_from_h_generator/README.md b/tools/md_from_h_generator/README.md
new file mode 100644
index 0000000..6e33dab
--- /dev/null
+++ b/tools/md_from_h_generator/README.md
@@ -0,0 +1,426 @@
+# Guidelines for Tagging C++ Methods with Doxygen Comments
+
+## 1. Introduction
+
+This document provides guidelines for using the `md_from_h` documentation tool. Use of the tool requires tagging C++ methods and data structures with Doxygen comments to ensure proper documentation generation. The `HeaderFileStructure` class in the provided Python file first parses C++ header files, extracting information from Doxygen tags, declared structs, enums, iterators, methods, events, and properties, then uses this information to generate a markdown file documenting the headerfile. Many aspects of the markdown are automatically generated without relying on Doxygen comments (such as the table of contents), but method/property/event and parameter/result/value descriptions and JSON examples rely on Doxygen tags. This is intended for use on RDK Services and EntServices plugin APIs. Below are the details of the supported tags and additional features.
+
+---
+## 2. Running the Tool
+
+The md_from_h tool is currently run automatically on a weekly basis from a Jenkins job. However, to run the tool locally on individual header files, run:
+
+`python3 generate_md_from_header.py [path_to_header_file]`
+
+This will create a folder in the current directory named "generated_docs", where "generated_docs" contains the generated markdown file for the header file.
+
+---
+
+## 3. Supported Doxygen Tags
+
+### 1. `@text` or `@alt`
+- **Purpose**: Used to tell the program that all tags declared below the `@text` or `@alt` tag belong to the method
+- **Required**: Yes (Mandatory tag for all methods/properties/events)
+- **Usage**:
+ - Use this tag to by following it with the name of the method/property/event.
+ - Ensure the name following the tag matches the respective method/property/event name exactly.
+
+
+### Example:
+
+***Header File Example:***
+```cpp
+/**
+ * @text initialize
+ */
+virtual uint32_t initialize();
+```
+
+---
+
+### 2. `@brief`
+- **Purpose**: Provides a concise summary of the method's functionality.
+- **Required**: Yes (Mandatory tag for all methods/properties/events)
+- **Usage**:
+ - Use this tag for a short, one-line description of the method.
+ - The description following this tag will be shown on the method/property/event table of contents
+
+### Example:
+
+***Header File Example:***
+```cpp
+/**
+ * @text initialize
+ * @brief This method initializes the system.
+ */
+virtual uint32_t initialize();
+```
+
+***Generated Markdown Example:***
+
+> | Method | Description |
+> | :-------- | :-------- |
+> | [initialize](#method.initialize) | This method initializes the system. |
+
+
+---
+
+### 3. `@details`
+- **Purpose**: Provides a detailed explanation of the method's behavior.
+- **Required**: No (Optional tag)
+- **Usage**:
+ - Use this tag to elaborate on the method's functionality, including edge cases, assumptions, and any additional context.
+ - The description following this tag will be shown in the method/property/event details.
+ - Multi-line comments are supported for the details tag
+ - This tag is optional but highly recommended for complex methods. If it is absent, the description from the `@brief` tag will be used in its place.
+
+### Example:
+
+***Header File Example:***
+```cpp
+/**
+ * @text initialize
+ * @brief This method initializes the system.
+ * @details This method sets up the system by initializing all required components.
+ * It must be called before any other method in the class.
+ */
+virtual uint32_t initialize();
+```
+
+***Generated Markdown Example:***
+>
+> ## *initialize [method](#head.Methods)*
+> This method sets up the system by initializing all required components. It must be called before any other method in the class.
+
+---
+
+### 4. `@see`
+- **Purpose**: Links the method to related methods or documentation.
+- **Required**: No (Optional tag)
+- **Usage**:
+ - Use this tag to reference related methods, classes, or external documentation.
+ - The linked event name should appear exactly as it is declared, without parenthesis
+ - This tag is optional, but should be used if a corresponding event is defined in INotifications
+
+### Example:
+
+***Usage in the header file:***
+```cpp
+/**
+ * @text initialize
+ * @brief This method initializes the system.
+ * @see onInitialize
+ */
+virtual uint32_t initialize();
+
+...
+
+/**
+ * @text onInitialize
+ * @brief Event triggered when system initialized
+ */
+virtual void onInitialize();
+```
+
+***Result in the markdown:***
+>## *initialize [method](#head.Methods)*
+> This method initializes the system.
+> ### Events
+> | Event | Description |
+> | :-------- | :-------- |
+> | [initialize](#event.OnAudioDescriptionChanged) | Event triggered when system initialized. |
+
+---
+
+### 5. `@param`
+- **Purpose**: Documents the parameters of the method.
+- **Required**: Yes (Mandatory tag for all methods/properties/events with params/results)
+- **Usage**:
+ - Use this tag for each parameter of the method. Each parameter and tag must be declared on a new line.
+ - The description following the tag shall be listed in the parameters/results table
+ - Parameter/symbol examples should be defined here (see [Providing Symbol Examples](#providing_examples))
+ - Specify the parameter name and its description. Format can include colon i.e. `@param [param_name]: [description]` or `@param [para_name] [description]`
+ - IMPORTANTLY, in addition to using the param tag, mark each parameter with inline 'in/out' information in the parameter list. If a parameter does not have inline in/out information, it defaults to 'in'.
+
+### Example:
+
+***Header File Example:***
+```cpp
+/**
+ ...
+ * @param configPath: The config file path for initialization e.g. "../build/test.conf"
+ * @param status The status of the initialization. Set to true if completed.
+ */
+virtual uint32_t initialize(const string& configPath /* @in */, bool status /* @out */);
+```
+
+***Generated Markdown Example:***
+> ### Parameters
+> | Name | Type | Description |
+> | :-------- | :-------- | :-------- |
+> | config | string | The config file path for initialization |
+> ### Results
+> | Name | Type | Description |
+> | :-------- | :-------- | :-------- |
+> | status | bool | The status of the initialization. Set to true if completed. |
+
+---
+
+### 6. `@returns`
+- **Purpose**: Describes the return value of the method.
+- **Required**: No (Optional tag)
+- **Usage**:
+ - Use this tag to explain what the method returns.
+
+**Example**:
+```cpp
+/**
+ * @text initialize
+ * @brief This method initializes the system.
+ * @returns NULL if initialization was successful.
+ */
+virtual uint32_t initialize();
+```
+
+---
+
+### 7. `@omit`
+- **Purpose**: Indicates that the method should be omitted from the markdown.
+- **Required**: No (Optional tag)
+- **Usage**:
+ - Use this tag for methods that should not appear in the generated documentation.
+ - Omitted methods do not need to have any tags, like the `@text` or `@brief` tags
+ - This tag is optional.
+
+**Example**:
+```cpp
+/**
+ * @omit
+ */
+virtual uint32_t internalMethod();
+```
+
+---
+
+### 8. `@property`
+- **Purpose**: Marks the method as a property (getter or setter).
+- **Required**: No (Mandatory tag if method is a property)
+- **Usage**:
+ - Use this tag for methods that act as properties.
+
+**Example**:
+```cpp
+/*
+ * @property
+ * @brief Video output port on the STB used for connection to TV
+ * @param name: video output port name
+ */
+virtual uint32_t PortName (string& name /* @out */) const = 0;
+```
+
+---
+
+## 4. Additional Features and Guidelines
+
+
+
+### Providing Symbol Examples
+In the RDK Services and Entservices APIs, plugins communicate using RPC. To facilitate this, the documentation includes relevant examples of request and response JSON structures. The md_from_h tool automates the creation of these examples by parsing enums, structs, iterators, and basic types declared in the header file, as well as extracting examples from @param Doxygen tags.
+
+The tool maintains a global symbol registry to track the names and types of parameters declared in methods, properties, events, enums, and struct members. The goal of the global symbol registry is to make it easier and more consistent to provide examples for symbols which appear multiple times in a header file (such as preferredLanguages in IUserSettings.h). Examples are generated by analyzing the @param tags, where the tool uses a regular expression to extract text following the pattern `e.g. "(.*)"` in the parameter description. The value inside the quotes is then used as the example for that symbol. The pattern `ex: (.*)` is also matched in cases where examples have double-quotes. Additionally, examples can be derived from structs if their members include descriptive comments.
+
+### Setting Examples for Method Parameters
+The following demonstrates how examples are set for method parameters:
+
+***Header File Example:***
+```cpp
+ /** Binds the respective driver for the device */
+ // @text bindDriver
+ // @brief Bind the respective driver for the device
+ // @param deviceName: Name of the device e.g. "001/003"
+ virtual uint32_t BindDriver(const string &deviceName /* @in */) const = 0;
+```
+
+***Generated Markdown Example:***
+>#### Request
+>```json
+>{
+> "jsonrpc": "2.0",
+> "id": 42,
+> "method": "org.rdk.IUSBDevice.BindDriver",
+> "params": {
+> "deviceName": "001/003"
+> }
+>}
+>```
+
+### Setting Examples for Struct Members
+The following demonstrates how examples are set for struct members:
+
+***Header File Example:***
+```cpp
+struct USBDevice {
+ uint8_t deviceClass /* @brief USB class of the device as per USB specification e.g. "10" */ ;
+ uint8_t deviceSubclass /* @brief USB sub class of the device as per USB specification e.g. "6" */;
+ string deviceName /* @brief Name of the USB device e.g. "001/003"*/;
+ string devicePath /* @brief the path to be used for the USB device e.g."/dev/sdX" */;
+};
+```
+
+***Generated Markdown Example:***
+>```json
+>{
+> "jsonrpc": "2.0",
+> "id": 42,
+> "results": {
+> "devices": [
+> {
+> "deviceClass": "10",
+> "deviceSubclass": "6",
+> "deviceName": "001/003",
+> "devicePath": "/dev/sdX"
+> }
+> ]
+> }
+>}
+>```
+
+#### Global and Greedy Example Generation
+The process of generating examples is both global and greedy:
+- Global: A global symbol registry ensures that once an example is defined for a symbol, it is reused wherever that symbol appears.
+- Greedy: The first instance of a symbol in a header file with an example defined takes precedence globally. However, this can be overridden if a subsequent functions provide a new example for the same symbol using the @param tag with `e.g. "(.*)`.
+For example, consider the preferredLanguages symbol:
+
+***Header File Example***
+```cpp
+// @text onPreferredAudioLanguagesChanged
+// @brief The preferredLanguages setting has changed.
+// @param preferredLanguages: The preferred language for the device in ISO 639-2/B e.g. "fra"
+virtual void OnPreferredAudioLanguagesChanged(const string& preferredLanguages /* in */) = 0;
+
+// @text getPreferredAudioLanguages
+// @brief Gets the preferred audio languages, in ISO 639-2/B code
+// @param preferredLanguages: The preferred language for the device e.g. "eng"
+virtual uint32_t GetPreferredAudioLanguages(string &preferredLanguages /* @out */) const = 0;
+
+// @text getPreferredCaptionsLanguages
+// @brief Gets the current PreferredCaptionsLanguages setting.
+// @param preferredLanguages: The preferred language for the device
+virtual uint32_t GetPreferredCaptionsLanguages(string &preferredLanguages /* @out */) const = 0;
+```
+
+***Generated Markdown Example:***
+>## *OnPreferredAudioLanguagesChanged [event](#head.Notifications)*
+>The preferredLanguages setting has changed.
+>
+>...
+>#### Request
+>```json
+>{
+> "jsonrpc": "2.0",
+> "id": 42,
+> "method": "org.rdk.IUserSettings.OnPreferredAudioLanguagesChanged",
+> "params": {
+> "preferredLanguages": "fra" // first instance of symbol encountered, "fra" set as global example
+> }
+>}
+>```
+>## *GetPreferredAudioLanguages [method](#head.Methods)*
+>Gets the preferred audio languages, in ISO 639-2/B code
+>
+> ...
+>#### Response
+>```json
+>{
+> "jsonrpc": "2.0",
+> "id": 42,
+> "results": {
+> "preferredLanguages": "eng" // Global example for preferredLanguages overridden
+> }
+>}
+>```
+>## *GetPreferredCaptionsLanguages [method](#head.Methods)*
+>Gets the current PreferredCaptionsLanguages setting.
+>
+> ...
+>#### Response
+>```json
+>{
+> "jsonrpc": "2.0",
+> "id": 42,
+> "results": {
+> "preferredLanguages": "fra" // No e.g. provided in @param, so global example used
+> }
+>}
+>```
+
+---
+
+### General Guidelines
+1. **Consistency**: All methods/properties/events should be tagged consistently, and should match their respective regexes mentioned above.
+2. **Mandatory Tags**: Always include `@text`, `@brief` and `@params` (if applicable).
+3. **Optional Tags**: Use `@details`, `@see`, and `@returns` as needed for clarity and completeness.
+4. **Property and Event Methods**: Use `@property` for getter and setter methods to distinguish them from regular methods. Events are automatically distinguished because they are defined in the INotification struct in the headerfile.
+5. **Parameter Documentation**: In addition to using `@param` for every parameter, specify whether it is an input or output parameter with inline comments in the method/property/event's parameter list. Make sure the parameter name in the Doxygen tag description matches exactly the name of the parameter declared in the parameter list.
+#### Correct
+```cpp
+ ...
+ /* @param configFile: path to config file for initialization */
+ /* @param status true if initialization complete */
+ virtual uint32_t initialize(const string& configFile /* @in */, bool status /* @out */)
+```
+#### Incorrect
+```cpp
+ ...
+ /* @param string: path to config file */ # references type, not param name
+ /* @param Status true if complete */ # does not match lower case of param name
+ virtual uint32_t initialize(const string& configFile /* @in */, bool status) # missing `/* out */`
+```
+6. **Example Generation**: For generating examples for methods/properties/events which use iterators or structs as parameters or results, be sure to follow the guidelines stated in [Providing Symbol Examples](#providing_examples)
+7. **Defining Structs in Header Files**: Nested structs and enums can complicate parsing and documentation generation. So, structs and enums should not be defined within another struct, as keeping them separate ensures better compatibility with the `md_to_h` tool (future versions may support nested definitions).
+#### Correct - enums & structs not nested in definition
+```cpp
+struct USBDevice {
+ uint8_t deviceClass /* @brief USB class of the device */ ;
+ ...
+};
+
+enum USBDeviceFlags : uint8_t {
+ DEVICE_FLAGS_DRIVER_AVAILABLE = 1 /* @brief AVAILABLE */,
+ DEVICE_FLAGS_SWITCHABLE = 2 /* @brief SWITCHABLE */
+};
+
+struct USBDeviceInfo {
+ ...
+ string serialNumber /* @brief Serial number of the device */;
+ USBDevice device /* @brief Basic device information included */;
+ USBDeviceFlags flags /* @brief Flags of the device */;
+ ...
+};
+```
+#### Incorrect - definitions for structs and enums should not be nested
+```cpp
+struct USBDeviceInfo {
+ ...
+ string serialNumber /* @brief Serial number of the device */;
+ struct USBDevice {
+ uint8_t deviceClass /* @brief USB class of the device */;
+ ...
+ } device;
+ enum USBDeviceFlags : uint8_t {
+ DEVICE_FLAGS_DRIVER_AVAILABLE = 1 /* @brief AVAILABLE */,
+ DEVICE_FLAGS_SWITCHABLE = 2 /* @brief SWITCHABLE */
+ } flags;
+ ...
+};
+```
+
+---
+
+### Miscellaneous Features
+1. Multiline comments are supported.
+2. Both `// ...` and `/* ... */` comments are supported for doxygen tags
+3. Multi-line method parameter declaration is supported
+4. The order in which Doxygen tags appear above a method (except for `@text`, which should always be the first tag) does not affect how the document is generated
+5. All tags appearing above a method (between two methods) will be associated with that method
+
+---
diff --git a/tools/md_from_h_generator/generate_md_from_header.py b/tools/md_from_h_generator/generate_md_from_header.py
new file mode 100644
index 0000000..88be6fd
--- /dev/null
+++ b/tools/md_from_h_generator/generate_md_from_header.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+
+# If not stated otherwise in this file or this component's LICENSE file the
+# following copyright and licenses apply:
+
+# Copyright 2024 RDK Management
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generates the .md files from the .h files, and logs any warnings or errors.
+
+import os
+import argparse
+from header_file_parser import HeaderFileParser
+from logger import Logger
+from markdown_templates import generate_header_description_markdown, generate_header_toc, generate_methods_toc, generate_method_markdown, generate_notifications_toc, generate_notification_markdown, generate_properties_toc, generate_property_markdown
+
+def generate_md_from_header(header_file):
+ """
+ Writes the markdown documentation from the header file.
+
+ Args:
+ header_file (str): Path to the header file.
+ """
+ filename = os.path.basename(header_file)
+ classname, _ = os.path.splitext(filename)
+
+ # Remove the leading 'I' from the api's class name
+ output_file_path = 'generated_docs/' + classname[1:] + '.md'
+
+ log_file_path = 'logs/' + classname + '.txt'
+ logger = Logger(log_file_path)
+
+ header_structure = HeaderFileParser(header_file, logger)
+
+ with open(output_file_path, 'w', encoding='utf-8') as file:
+ file.write(generate_header_toc(classname, header_structure))
+ file.write(generate_header_description_markdown(classname))
+ if len(header_structure.methods.values()) > 0:
+ file.write(generate_methods_toc(header_structure.methods, classname))
+ for method_name, method_info in header_structure.methods.items():
+ file.write(generate_method_markdown(
+ method_name, method_info, header_structure.symbols_registry))
+ file.write("\n")
+ if len(header_structure.properties.values()) > 0:
+ file.write(generate_properties_toc(header_structure.properties, classname))
+ for prop_name, prop_info in header_structure.properties.items():
+ file.write(generate_property_markdown(
+ prop_name,prop_info, header_structure.symbols_registry))
+ file.write("\n")
+ if len(header_structure.events.values()) > 0:
+ file.write(generate_notifications_toc(header_structure.events, classname))
+ for event_name, event_info in header_structure.events.items():
+ file.write(generate_notification_markdown(
+ event_name, event_info, header_structure.symbols_registry))
+ logger.write_log()
+ logger.close()
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description='Process header file.')
+ parser.add_argument('header_file', type=str, help='Path to header file')
+ args = parser.parse_args()
+ generate_md_from_header(args.header_file)
diff --git a/tools/md_from_h_generator/header_file_parser.py b/tools/md_from_h_generator/header_file_parser.py
new file mode 100644
index 0000000..2b1d7c7
--- /dev/null
+++ b/tools/md_from_h_generator/header_file_parser.py
@@ -0,0 +1,790 @@
+#!/usr/bin/env python3
+
+# If not stated otherwise in this file or this component's LICENSE file the
+# following copyright and licenses apply:
+
+# Copyright 2024 RDK Management
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# header_file_parser.py
+
+import re
+import os
+from logger import Logger
+
+class HeaderFileParser:
+ """
+ Parses a C++ header file to extract methods, properties, events, structs, enums, and iterators.
+ It also generates request and response JSONs for each method and event, and fills in missing
+ symbol information.
+ """
+ # List of regexes to match different components of the header file
+ REGEX_LINE_LIST = [
+ ('text', 'doxygen', re.compile(r'(?:\/\*+|\*|//) (?:@text|@alt)\s+(.*?)(?=\s*\*\/|$)')),
+ ('brief', 'doxygen', re.compile(r'(?:\/\*+|\*|//) @brief\s*(.*?)(?=\s*\*\/|$)')),
+ ('details', 'doxygen', re.compile(r'(?:\/\*+|\*|//) @details\s*(.*?)(?=\s*\*\/|$)')),
+ ('params', 'doxygen', re.compile(r'(?:\/\*+|\*|//) @param(?:\[.*\])?\s+(\w+)\s*\:?\s*(.*?)(?=\s*\*\/|$)')),
+ ('return', 'doxygen', re.compile(r'(?:\/\*+|\*|//) @return(?:s)?\s*(.*?)(?=\s*\*\/|$)')),
+ ('see', 'doxygen', re.compile(r'(?:\/\*+|\*|//) @see\s*(.*?)(?=\s*\*\/|$)')),
+ ('omit', 'doxygen', re.compile(r'(?:\/\*+|\*|//)\s*(@json:omit|@omit)')),
+ ('property','doxygen', re.compile(r'(?:\/\*+|\*|//) @property\s*(.*)')),
+ ('comment', 'doxygen', re.compile(r'(?:\/\*+|\*|//)\s*(.*)')),
+ ('enum', 'cpp_obj', re.compile(r'enum\s+([\w\d]+)\s*(?:\:\s*([\w\d\:\*]*))?\s*\{?')),
+ ('struct', 'cpp_obj', re.compile(r'struct\s+(EXTERNAL\s+)?([\w\d]+)\s*(?:\{)?(?!.*:)')),
+ ('method', 'cpp_obj', re.compile(r'virtual\s+([\w\d\:]+)\s+([\w\d\:]+)\s*\((.*)')),
+ ('iterator','cpp_obj', re.compile(r'(.*)\s+RPC::IIteratorType\s*(.*)'))
+ ]
+ # Basic type examples for generating missing symbol examples
+ BASIC_TYPE_EXAMPLES = {
+ 'int32_t': '0',
+ 'uint32_t': '0',
+ 'int64_t': '0',
+ 'uint64_t': '0',
+ 'int': '0',
+ 'float': '0.0',
+ 'double': '0.0',
+ 'bool': 'true',
+ 'char': 'a',
+ 'string': ''
+ }
+ # List of regexes to match different cpp components of the header file
+ CPP_COMPONENT_REGEX = {
+ 'iter_using': re.compile(r'using\s+([\w\d]+)\s*=\s*RPC::IIteratorType\s*\<\s*([\w\d\:]+)\s*\,\s*(?:[\w\d\:]+)\s*\>\s*;'),
+ 'iter_typedef': re.compile(r'typedef\s+RPC::IIteratorType\s*\<\s*([\w\d\:]+)\s*\,\s*(?:[\w\d\:]+)\s*\>\s*([\w\d]+)\s*;'),
+ 'enum': re.compile(r'enum\s+([\w\d]+)\s*(?:\:\s*([\w\d\:\*]*))?\s*\{(.*)\}\;?'),
+ 'enum_mem': re.compile(r'([\w\d\[\]]+)\s*(?:\=\s*([\w\d]+))?\s*(?:(?:(?:\/\*)|(?:\/\/))(.*)(?:\*\/)?)?'),
+ 'struct': re.compile(r'struct\s+(?:EXTERNAL\s+)?([\w\d]+)\s*\{(.*)\}\;?'),
+ 'struct_mem': re.compile(r'([\w\d\:\*]+)\s+([\w\d\[\]]+)\s*(?:(?:(?:\/\*)|(?:\/\/))(.*)(?:\*\/)?)?'),
+ 'method': re.compile(r'virtual\s+([\w\d\:]+)\s+([\w\d\:]+)\s*\((.*)\)\s*(?:(?:(?:const\s*)?\=\s*0)|(?:{\s*})\s*)\;?'),
+ 'method_param': re.compile(r'([\w\d\:\*]+)\s+([\w\d\[\]]+)\s*(?:\/\*(.*)\*\/)?')
+ }
+
+ def __init__(self, header_file_path: str, logger: Logger):
+ """
+ Initializes data structures to track different components of a C++ header file, then
+ parses said header file to extract methods, structs, enums, and iterators.
+
+ Args:
+ header_file_path (str): path to the header file
+ logger (Logger): list of regex matching different components of the header file
+ """
+ # objects to hold the different components and properties of the header file
+ self.header_file_path = header_file_path
+ self.classname = os.path.splitext(os.path.basename(self.header_file_path))[0]
+ self.methods = {}
+ self.properties = {}
+ self.events = {}
+ self.structs_registry = {}
+ self.iterators_registry = {}
+ self.enums_registry = {}
+ self.symbols_registry = {}
+ self.logger = logger
+
+ # helper objects for holding doxygen tag information while parsing
+ self.doxy_tags = {}
+ self.latest_param = ''
+ self.latest_tag = ''
+
+ # main logic to create header file structure
+ self.process_header_file()
+ self.post_process()
+
+
+ def process_header_file(self):
+ """
+ Processes the header file by parsing it, sorting methods/properties/events alphabetically,
+ and linking methods to events.
+ """
+ self.parse_header_file()
+ self.methods = self.sort_dict(self.methods)
+ self.properties = self.sort_dict(self.properties)
+ self.events = self.sort_dict(self.events)
+ self.link_method_to_event()
+
+
+ def post_process(self):
+ """
+ Post-processes the header file structure by generating missing symbol examples, request &
+ response examples, and logging which information is missing from the headerfile.
+ """
+ self.generate_missing_examples_for_symbol_registry()
+ self.generate_flattened_descriptions_for_symbol_registry()
+ self.generate_request_response_objects()
+ self.fill_and_log_missing_symbol_descriptions()
+ self.log_unassociated_events()
+ self.log_missing_method_info()
+
+ def parse_header_file(self):
+ """
+ Parses the header file line-by-line to track and record the file's components, such as
+ methods, properties, events, structs, enums, and iterators. Keeps track of these components'
+ associated doxygen tags.
+ """
+ # flags and structures to help track the current state of the parser
+ within_enum_def = False
+ enum_braces_count = 0
+ enum_object = ''
+ within_struct_def = False
+ struct_braces_count = 0
+ struct_object = ''
+ within_method_def = False
+ method_parens_count = 0
+ method_object = ''
+
+ scope = ['Exchange']
+ brace_count = [1]
+
+ with open(self.header_file_path, 'r', encoding='utf-8') as header_file:
+ line_num = 0
+ for line in header_file:
+ line_num += 1
+ line = line.strip()
+ # keeps track of the current scope (the nested/external struct) of the current line
+ scope, brace_count = self.external_struct_tracker(line, scope, brace_count)
+ groups, line_tag, line_type = self.match_line_with_regex(line, self.REGEX_LINE_LIST)
+
+ if line_type == 'doxygen':
+ self.update_doxy_tags(groups, line_tag)
+ if line_type == 'cpp_obj':
+ if line_tag == 'enum':
+ within_enum_def = True
+ elif line_tag == 'struct':
+ within_struct_def = True
+ elif line_tag == 'method':
+ within_method_def = True
+ elif line_tag == 'iterator':
+ self.register_iterator(line)
+
+ if within_enum_def:
+ enum_object, enum_braces_count, within_enum_def = self.process_enum(
+ line, enum_object, within_enum_def, enum_braces_count, line_num)
+ if within_struct_def:
+ struct_object, struct_braces_count, within_struct_def = self.process_struct(
+ line, struct_object, within_struct_def, struct_braces_count, line_num)
+ if within_method_def:
+ method_object, method_parens_count, within_method_def = self.process_method(
+ line, method_object, within_method_def, method_parens_count, line_num, scope)
+
+ def match_line_with_regex(self, line, line_regex_list):
+ """
+ Matches a line with a regex from the line_regex_list.
+ """
+ for (tag, l_type, regex) in line_regex_list:
+ match = regex.match(line)
+ if match:
+ return match.groups(), tag, l_type
+ return None, None, None
+
+ def process_method(self, line, method_object, within_method_def, method_paren_count,
+ curr_line_num, scope):
+ """
+ Processes a line within a method definition.
+ """
+ method_paren_count += self.count_parentheses(line)
+ # accumulate the method's parameters until the closing parenthesis is reached
+ if method_paren_count != 0:
+ line = self.clean_and_validate_cpp_obj_line(line, ',', curr_line_num, 'Method param')
+ method_object += line
+ elif method_paren_count == 0:
+ method_object += line
+ self.register_method(method_object, self.doxy_tags, scope)
+ # reset the method object and doxygen tag helper objects once the method is registered
+ within_method_def = False
+ method_object = ''
+ self.doxy_tags = {}
+ self.latest_param = ''
+ self.latest_tag = ''
+ return method_object, method_paren_count, within_method_def
+
+ def process_enum(self, line, enum_object, within_enum_def, enum_braces_count, curr_line_num):
+ """
+ Processes a line within an enum definition.
+ """
+ enum_braces_count += self.count_braces(line)
+ if 'enum' in line and enum_braces_count == 0:
+ return line, 0, within_enum_def
+ # accumulate the enum's data members until the closing brace is reached
+ if enum_braces_count > 0:
+ line = self.clean_and_validate_cpp_obj_line(line, ',', curr_line_num, 'Enumerator')
+ enum_object += line
+ elif enum_braces_count <= 0:
+ enum_object += line
+ self.register_enum(enum_object)
+ # reset the enum helper object once the enum is registered
+ within_enum_def = False
+ enum_object = ''
+ return enum_object, enum_braces_count, within_enum_def
+
+ def process_struct(self, line, struct_object, within_struct_def, struct_braces_count,
+ curr_line_num):
+ """
+ Processes a line within a struct definition.
+ """
+ struct_braces_count += self.count_braces(line)
+ if 'struct' in line and struct_braces_count == 0:
+ return line, 0, within_struct_def
+ # accumulate the struct's data members until the closing brace is reached
+ if struct_braces_count > 0:
+ line = self.clean_and_validate_cpp_obj_line(line, ';', curr_line_num, 'Struct member')
+ struct_object += line
+ elif struct_braces_count <= 0:
+ struct_object += line
+ self.register_struct(struct_object)
+ # reset the struct helper object once the struct is registered
+ within_struct_def = False
+ struct_object = ''
+ return struct_object, struct_braces_count, within_struct_def
+
+ def update_doxy_tags(self, groups, line_tag):
+ """
+ Updates the doxygen tag object with the given line's information.
+ """
+ if line_tag == 'text':
+ # self.doxy_tags = {}
+ self.doxy_tags['text'] = groups[0]
+ elif line_tag == 'params':
+ self.latest_param = groups[0]
+ self.latest_tag = 'params'
+ self.doxy_tags.setdefault('params', {})[self.latest_param] = groups[1]
+ elif line_tag == 'see':
+ self.doxy_tags.setdefault('see', {})[groups[0]] = ''
+ elif line_tag == 'comment':
+ if groups[0] == '/':
+ return
+ elif self.latest_tag == 'params':
+ self.doxy_tags['params'][self.latest_param] += (' ' + groups[0])
+ elif self.latest_tag:
+ self.doxy_tags[self.latest_tag] += (' ' + groups[0])
+ line_tag = self.latest_tag
+ else:
+ self.doxy_tags[line_tag] = groups[0]
+ self.latest_tag = line_tag
+
+ def clean_and_validate_cpp_obj_line(self, line, delimiter, line_num, data_type):
+ """
+ Validates a line of a multi-line cpp object by checking that data members are defined on
+ separate lines and that comments are formed before the delimiter.
+ """
+ delim_index = line.find(delimiter)
+ # if a comment is defined after the delimiter, log a warning
+ if delim_index != -1 and ('//' in line[delim_index:] or '/*' in line[delim_index:]):
+ line = self.remove_inline_comments(line)
+ self.logger.log("WARNING",
+ f"Comment on line {line_num + 1} should come before comma/semicolon.")
+ # if the delimiter is found more than once, log a warning
+ if line.count(delimiter) > 1:
+ line = self.remove_inline_comments(line)
+ self.logger.log("WARNING",
+ f"Line {line_num + 1} should have only one {data_type} per line.")
+ return line
+
+ def remove_inline_comments(self, line):
+ """
+ Removes inline comments from a line.
+ """
+ line = re.sub(r"/\*.*?\*/", "", line)
+ line = re.sub(r"//.*", "", line)
+ return line.strip()
+
+ def register_iterator(self, iterator_object):
+ """
+ Registers an iterator.
+ """
+ match_typedef = self.CPP_COMPONENT_REGEX['iter_typedef'].match(iterator_object)
+ match_using = self.CPP_COMPONENT_REGEX['iter_using'].match(iterator_object)
+
+ if iterator_object == "RPC::IStringIterator":
+ self.iterators_registry[iterator_object] = 'string'
+ elif match_typedef:
+ groups = match_typedef.groups()
+ iterator_name = groups[1]
+ self.iterators_registry[iterator_name] = groups[0]
+ elif match_using:
+ groups = match_using.groups()
+ iterator_name = groups[0]
+ self.iterators_registry[iterator_name] = groups[1]
+ else:
+ self.logger.log("ERROR", f"Could not register iterator: {iterator_object}")
+
+ def register_enum(self, enum_object):
+ """
+ Registers an enum by processing the enum's enumerator definitions.
+ """
+ match = self.CPP_COMPONENT_REGEX['enum'].match(enum_object)
+ if match:
+ enum_name, _, enum_body = match.groups()
+ self.enums_registry[enum_name] = {}
+ # process each enumerator definition
+ for enumerator_def in enum_body.split(','):
+ enumerator_def = enumerator_def.strip()
+ enumerator_match = self.CPP_COMPONENT_REGEX['enum_mem'].match(enumerator_def)
+ if enumerator_match:
+ enumerator_name, enumerator_value, description = enumerator_match.groups()
+ description = self.clean_description(description)
+ enumerator_value = enumerator_value or len(self.enums_registry[enum_name])
+ self.enums_registry[enum_name][enumerator_name] = {
+ 'value': enumerator_value,
+ 'description': description.strip() if description else ''
+ }
+ else:
+ self.logger.log("ERROR", f"Could not register enum: {enum_object}")
+
+ def register_struct(self, struct_object):
+ """
+ Registers a struct by processing the struct's data members.
+ """
+ match = self.CPP_COMPONENT_REGEX['struct'].match(struct_object)
+ if match:
+ struct_name, struct_body = match.groups()
+ self.structs_registry[struct_name] = {}
+ # process each data member
+ for member_def in struct_body.split(';'):
+ member_def = member_def.strip()
+ member_match = self.CPP_COMPONENT_REGEX['struct_mem'].match(member_def)
+ if member_match:
+ member_type, member_name, description = member_match.groups()
+ description = self.clean_description(description)
+ self.structs_registry[struct_name][member_name] = {
+ 'type': member_type,
+ 'description': description.strip() if description else ''
+ }
+ # register each data member in the global symbol registry
+ self.register_symbol(member_name, member_type, description)
+ else:
+ self.logger.log("ERROR", f"Could not register struct: {struct_object}")
+
+ def register_method(self, method_object, doxy_tags, scope):
+ """
+ Registers a method, property, or event.
+ """
+ if doxy_tags == {}:
+ self.logger.log("WARNING",f"{method_object} has no doxygen tags.")
+ match = self.CPP_COMPONENT_REGEX['method'].match(method_object)
+ if match:
+ method_return_type, method_name, method_parameters = match.groups()
+ # ignore these methods
+ if method_name in ['Register', 'Unregister'] or 'omit' in doxy_tags:
+ return
+ # encountering the getter/setter version definition of a property
+ if method_name in self.properties:
+ self.properties[method_name]['property'] = 'read write'
+ return
+ method_info = self.build_method_info(method_return_type, method_parameters, doxy_tags)
+ if 'property' in doxy_tags:
+ self.properties[method_name] = method_info
+ elif scope[-1] == 'INotification':
+ self.events[method_name] = method_info
+ else:
+ self.methods[method_name] = method_info
+ else:
+ self.logger.log("ERROR", f"Could not register method: {method_object}")
+
+ def build_method_info(self, method_return_type, method_parameters, doxy_tags):
+ """
+ Helper to build a method info object. Also registers method parameters in the symbol
+ registry.
+ """
+ doxy_tag_param_info = doxy_tags.get('params', {})
+ params, results = self.process_and_register_params(method_parameters, doxy_tag_param_info)
+ method_info = {
+ 'text': doxy_tags.get('text', ''),
+ 'brief': doxy_tags.get('brief', ''),
+ 'details': doxy_tags.get('details', ''),
+ 'events': doxy_tags.get('see', {}),
+ 'params': params,
+ 'results': results,
+ 'return_type': method_return_type
+ }
+ if 'property' in doxy_tags:
+ if 'const' in method_parameters or '@in' in method_parameters:
+ method_info['property'] = 'write'
+ else:
+ method_info['property'] = 'read'
+ return method_info
+
+ def process_and_register_params(self, method_parameters, doxy_tag_param_info):
+ """
+ Helper to build params and results data structures, using the parameter declaration list
+ and doxygen tags.
+ """
+ param_list_info = self.get_info_from_param_declaration(method_parameters)
+ params = []
+ results = []
+ # build the params and results lists using the parameter delcaration list and doxygen tags
+ for symbol_name, (symbol_type, symbol_inline_comment) in param_list_info.items():
+ # register string iterators here b/c they are seldom defined outside of a method param
+ if symbol_type == 'RPC::IStringIterator':
+ self.register_iterator(symbol_type)
+ symbol_description = doxy_tag_param_info.get(symbol_name, '')
+ self.register_symbol(symbol_name, symbol_type, symbol_description)
+ symbol_info = {
+ 'name': symbol_name,
+ 'type': symbol_type,
+ 'description': symbol_description
+ }
+ # determine whether the symbol is a result or a parameter
+ if symbol_inline_comment and '@inout' in symbol_inline_comment:
+ params.append(symbol_info)
+ results.append(symbol_info)
+ elif symbol_inline_comment and '@out' in symbol_inline_comment:
+ results.append(symbol_info)
+ else: # Includes '@in', other, or empty
+ params.append(symbol_info)
+ return params, results
+
+ def get_info_from_param_declaration(self, parameters):
+ """
+ Helper to extract parameter information from a parameter list string.
+ """
+ parameters = parameters.strip('()')
+ param_info = {}
+ for param in parameters.split(','):
+ # clean up the parameter string
+ param = param.strip().replace('&', '').replace('const ', '')
+ param = re.sub(r'(? 1:
+ brace_count[-2] += brace_count[-1]
+ scope.pop()
+ brace_count.pop()
+ return scope, brace_count
+
+ def generate_request_response_objects(self):
+ """
+ Generates request and response JSONs for each method and event. Directly modifies the
+ methods, properties, and events registries.
+ """
+ for method_name, method_info in self.methods.items():
+ method_info['request'] = self.generate_request_object(method_name, method_info)
+ method_info['response'] = self.generate_response_object(method_info)
+ for event_name, event_info in self.events.items():
+ event_info['request'] = self.generate_request_object(event_name, event_info)
+ for prop_name, prop_info in self.properties.items():
+ # properties can have both get and set requests and responses
+ if 'read' in prop_info['property']:
+ if prop_info['params'] != []:
+ prop_info['results'] = prop_info['params']
+ prop_info['params'] = []
+ prop_info['get_request'] = self.generate_request_object(prop_name, prop_info)
+ prop_info['get_response'] = self.generate_response_object(prop_info)
+ if 'write' in prop_info['property']:
+ if prop_info['results'] != []:
+ prop_info['params'] = prop_info['results']
+ prop_info['results'] = []
+ prop_info['set_request'] = self.generate_request_object(prop_name, prop_info)
+ prop_info['set_response'] = self.generate_response_object(prop_info)
+
+ def generate_request_object(self, method_name, method_info):
+ """
+ Makes a request JSON. Creates an example dynamically.
+ """
+ request = {
+ "jsonrpc": "2.0",
+ "id": 42,
+ "method": f"org.rdk.{self.classname}.{method_name}",
+ }
+ if method_info['params'] != []:
+ request["params"] = {}
+ for param in method_info['params']:
+ param_name = param.get('name')
+ param_type = param.get('type')
+ param_desc = param.get('description')
+ request["params"][param_name] = self.get_symbol_example(
+ f"{param_name}-{param_type}", param_desc)
+ return request
+
+ def generate_response_object(self, method_info):
+ """
+ Makes a response JSON. Creates an example dynamically.
+ """
+ response = {
+ "jsonrpc": "2.0",
+ "id": 42,
+ "result": "null"
+ }
+ if method_info['results'] != []:
+ response['result'] = {}
+ for result in method_info['results']:
+ result_name = result.get('name')
+ result_type = result.get('type')
+ result_desc = result.get('description')
+ response['result'][result_name] = self.get_symbol_example(
+ f"{result_name}-{result_type}", result_desc)
+ return response
+
+ def get_symbol_example(self, unique_id, description):
+ """
+ Used in generating request/response JSONs. Pulls an example from either the @param tag
+ description or the symbols registry.
+ """
+ example_from_description = self.generate_example_from_description(description)
+ if example_from_description:
+ return self.wrap_example_if_iterator(unique_id, example_from_description)
+ if unique_id in self.symbols_registry:
+ return self.symbols_registry[unique_id].get('example')
+ return None
+
+ def generate_missing_examples_for_symbol_registry(self):
+ """
+ Generate examples for symbols in the symbols registry that lack examples.
+ """
+ for unique_id, symbol_data in self.symbols_registry.items():
+ if not symbol_data.get('example'):
+ description = symbol_data.get('description')
+ symbol_data['example'] = self.generate_example_for_individual_symbol(
+ unique_id, description)
+ self.logger.log("INFO", f"Generated missing example for {unique_id}")
+
+ def generate_example_for_individual_symbol(self, unique_id, description):
+ """
+ Generate an example for an individual symbol based on its description or type.
+ """
+ example = self.generate_example_from_description(description)
+ if example:
+ return self.wrap_example_if_iterator(unique_id, example)
+ if unique_id in self.symbols_registry:
+ symbol_type = self.symbols_registry[unique_id]['type']
+ return self.generate_example_from_symbol_type(symbol_type)
+ return None
+
+ def generate_example_from_description(self, param_description):
+ """
+ Extracts an example from a parameter description.
+ """
+ if param_description is None:
+ return None
+ match = re.search(r'e\.g\.\s*\"([^\"]+)', param_description) or re.search(r'ex:\s*(.*)', param_description)
+ return match.group(1) if match else None
+
+ def generate_example_from_symbol_type(self, symbol_type):
+ """
+ Creates an example parameter based on the symbol type.
+ """
+ if symbol_type in self.structs_registry:
+ struct = self.structs_registry[symbol_type]
+ return {member_name: self.generate_example_for_individual_symbol(f"{member_name}-{struct[member_name]['type']}", struct[member_name]['description']) for member_name in struct}
+ if symbol_type in self.enums_registry:
+ return list(self.enums_registry[symbol_type])[0]
+ if symbol_type in self.BASIC_TYPE_EXAMPLES:
+ return self.BASIC_TYPE_EXAMPLES[symbol_type]
+ if symbol_type in self.iterators_registry:
+ underlying_type = self.iterators_registry[symbol_type]
+ return [self.generate_example_from_symbol_type(underlying_type)]
+ return ''
+
+ def wrap_example_if_iterator(self, unique_id, example):
+ """
+ Wrap the example in a list if the symbol is an iterator, otherwise simply return the
+ example.
+ """
+ if self.symbols_registry[unique_id]['type'] in self.iterators_registry:
+ return [example]
+ return example
+
+ def link_method_to_event(self):
+ """
+ Links methods to their associated events. Directly modifies the methods dictionary.
+ """
+ for method_name, method_info in self.methods.items():
+ method_events = method_info['events']
+ for event in method_events:
+ if event in self.events:
+ self.methods[method_name]['events'][event] = self.events[event].get('brief')
+ self.events[event]['associated_method'] = method_name
+ else:
+ self.logger.log("ERROR",
+ f"Event {event} tagged with {method_name} does not exist.")
+
+ def log_unassociated_events(self):
+ """
+ Logs events that are not associated with any method.
+ """
+ for event_name, event_info in self.events.items():
+ if not event_info.get('associated_method'):
+ self.logger.log("WARNING", f"Event {event_name} is not associated with a method.")
+
+ def fill_and_log_missing_symbol_descriptions(self):
+ """
+ Fills missing symbol information for methods, events, and properties.
+ """
+ for method_name, method_info in self.methods.items():
+ for param in method_info['params']:
+ if not param.get('description'):
+ param['description'] = self.symbols_registry[f"{param['name']}-{param['type']}"].get('description', '')
+ self.logger.log("INFO",
+ f"Filled missing desc for {param['name']} in method {method_name}")
+ for result in method_info['results']:
+ if not result.get('description'):
+ result['description'] = self.symbols_registry[f"{result['name']}-{result['type']}"].get('description', '')
+ self.logger.log("INFO",
+ f"Filled missing desc for {result['name']} in method {method_name}")
+ for event_name, event_info in self.events.items():
+ for param in event_info['params']:
+ if not param.get('description'):
+ param['description'] = self.symbols_registry[f"{param['name']}-{param['type']}"].get('description', '')
+ self.logger.log("INFO",
+ f"Filled missing desc for {param['name']} in event {event_name}")
+ for result in event_info['results']:
+ if not result.get('description'):
+ result['description'] = self.symbols_registry[f"{result['name']}-{result['type']}"].get('description', '')
+ self.logger.log("INFO",
+ f"Filled missing desc for {result['name']} in event {event_name}")
+ for prop_name, prop_info in self.properties.items():
+ for param in prop_info['params']:
+ if not param.get('description'):
+ param['description'] = self.symbols_registry[f"{param['name']}-{param['type']}"].get('description', '')
+ self.logger.log("INFO",
+ f"Filled missing desc for {param['name']} in property {prop_name}")
+ for result in prop_info['results']:
+ if not result.get('description'):
+ result['description'] = self.symbols_registry[f"{result['name']}-{result['type']}"].get('description', '')
+ self.logger.log("INFO",
+ f"Filled missing desc for {result['name']} in property {prop_name}")
+
+ def log_missing_method_info(self):
+ """
+ At the end of parsing, if there is still information missing for methods, events, and
+ symbols, log it.
+ """
+ for method_name, method_info in self.methods.items():
+ if not method_info.get('brief') and not method_info.get('details'):
+ self.logger.log("INFO", f"Missing description: {method_name}")
+ for event_name, event_info in self.events.items():
+ if not event_info.get('brief') and not event_info.get('details'):
+ self.logger.log("INFO", f"Missing description: {event_name}")
+ for prop_name, prop_info in self.properties.items():
+ if not prop_info.get('brief') and not prop_info.get('details'):
+ self.logger.log("INFO", f"Missing description: {prop_name}")
+ for symbol_name, symbol_info in self.symbols_registry.items():
+ if not symbol_info.get('description'):
+ self.logger.log("INFO", f"Missing description: {symbol_name}")
+ if symbol_info.get('example') == "":
+ self.logger.log("INFO", f"Missing example: {symbol_name}")
+
+ def count_parentheses(self, line):
+ """
+ Counts the number of opening and closing parentheses in a line.
+ """
+ return line.count('(') - line.count(')')
+
+ def count_braces(self, line):
+ """
+ Counts the number of opening and closing braces in a line.
+ """
+ return line.count('{') - line.count('}')
+
+ def sort_dict(self, dictionary):
+ """
+ Sorts a dictionary by its keys and returns a new dictionary.
+ """
+ return dict(sorted(dictionary.items()))
+
+ def generate_flattened_descriptions_for_symbol_registry(self):
+ """
+ Builds flattened descriptions for all symbols in the symbols registry.
+ """
+ for symbol_name, symbol_info in self.symbols_registry.items():
+ symbol_info['flattened_description'] = self.get_description_from_individual_symbol('', symbol_name)
+
+ def get_description_from_individual_symbol(self, parent_key, unqiue_id):
+ """
+ Used to flatten descriptions for params/results/values in the symbol registry.
+ """
+ if unqiue_id in self.symbols_registry:
+ symbol_name = unqiue_id.split('-')[0]
+ symbol_type = self.symbols_registry[unqiue_id]['type']
+ symbol_desc = self.symbols_registry[unqiue_id]['description']
+ curr_key = f"{parent_key}.{symbol_name}"
+ flattened_descriptions = {curr_key: {'type': symbol_type, 'description': symbol_desc}}
+ flattened_descriptions.update(self.flatten_description(curr_key, symbol_type))
+ return flattened_descriptions
+ return {}
+
+ def flatten_description(self, parent_key, symbol_type):
+ """
+ Mirrors logic in generate_example_from_symbol_type.
+ """
+ flattened_descriptions = {}
+ if symbol_type in self.structs_registry:
+ struct = self.structs_registry[symbol_type]
+ for member_name in struct:
+ member_type = struct[member_name]['type']
+ member_desc = struct[member_name]['description']
+ curr_key = f"{parent_key}.{member_name}"
+ flattened_descriptions[curr_key] = {'type': member_type, 'description': member_desc}
+ flattened_descriptions.update(
+ self.flatten_description(curr_key, member_type))
+ return flattened_descriptions
+ elif symbol_type in self.enums_registry:
+ if parent_key[-3:] == '[#]':
+ flattened_descriptions.update(
+ {parent_key: {'type': 'string', 'description': ''}})
+ return flattened_descriptions
+ elif symbol_type in self.BASIC_TYPE_EXAMPLES:
+ if parent_key[-3:] == '[#]':
+ flattened_descriptions.update(
+ {parent_key: {'type': symbol_type, 'description': ''}})
+ return flattened_descriptions
+ elif symbol_type in self.iterators_registry:
+ underlying_type = self.iterators_registry[symbol_type]
+ flattened_descriptions.update(
+ self.flatten_description(f"{parent_key}[#]", underlying_type))
+ return flattened_descriptions
+ return {}
+
+ def clean_description(self, description):
+ """
+ Cleans a description by removing doxygen tag and unnecessary characters.
+ """
+ if description:
+ description = description.strip()
+ description = re.sub(r'^@\S+', '', description)
+ description = description[:-2] if description.endswith("*/") else description
+ return description
diff --git a/tools/md_from_h_generator/logger.py b/tools/md_from_h_generator/logger.py
new file mode 100644
index 0000000..2d715f4
--- /dev/null
+++ b/tools/md_from_h_generator/logger.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+
+# If not stated otherwise in this file or this component's LICENSE file the
+# following copyright and licenses apply:
+
+# Copyright 2024 RDK Management
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# logger.py
+
+class Logger:
+ """
+ A simple logger class to log messages to a file.
+ It categorizes messages into warnings, errors, and info.
+ """
+ def __init__(self, log_file_path):
+ """
+ Initializes the logger with a log file path.
+
+ Args:
+ log_file_path (str): Path to the log file.
+ """
+ self.log_file_path = log_file_path
+ self.log_file = open(log_file_path, 'w', encoding='utf-8')
+ self.warning_msgs = []
+ self.error_msgs = []
+ self.info_msgs = []
+
+ def log(self, level, message):
+ """
+ Logs a message with a specific level.
+
+ Args:
+ level (str): The log level (e.g., "WARNING", "ERROR", "INFO").
+ message (str): The message to log.
+ """
+ if level == "WARNING":
+ self.warning_msgs.append(message)
+ elif level == "ERROR":
+ self.error_msgs.append(message)
+ elif level == "INFO":
+ self.info_msgs.append(message)
+
+ def write_log(self):
+ """
+ Writes the logged messages to the log file.
+ """
+ self.log_file.write("### Warnings\n")
+ for msg in self.warning_msgs:
+ self.log_file.write(f"- {msg}\n")
+ self.log_file.write("\n### Errors\n")
+ for msg in self.error_msgs:
+ self.log_file.write(f"- {msg}\n")
+ self.log_file.write("\n### Info\n")
+ for msg in self.info_msgs:
+ self.log_file.write(f"- {msg}\n")
+ self.log_file.write("\n")
+ self.log_file.write("### End of Log\n")
+
+ def close(self):
+ """
+ Closes the log file.
+ """
+ self.log_file.close()
diff --git a/tools/md_from_h_generator/logs/test.txt b/tools/md_from_h_generator/logs/test.txt
new file mode 100644
index 0000000..2f47da1
--- /dev/null
+++ b/tools/md_from_h_generator/logs/test.txt
@@ -0,0 +1 @@
+test log
\ No newline at end of file
diff --git a/tools/md_from_h_generator/markdown_templates.py b/tools/md_from_h_generator/markdown_templates.py
new file mode 100644
index 0000000..ba3d1c4
--- /dev/null
+++ b/tools/md_from_h_generator/markdown_templates.py
@@ -0,0 +1,313 @@
+#!/usr/bin/env python3
+
+# If not stated otherwise in this file or this component's LICENSE file the
+# following copyright and licenses apply:
+
+# Copyright 2024 RDK Management
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import re
+
+# Templates
+HEADER_TOC_TEMPLATE = """
+
+# {classname} Plugin
+
+**Version: [{version}](https://github.com/rdkcentral/rdkservices/blob/main/{classname}/CHANGELOG.md)**
+
+A {classname} plugin for Thunder framework.
+
+### Table of Contents
+
+- [Abbreviation, Acronyms and Terms](#head.Abbreviation,_Acronyms_and_Terms)
+- [Description](#head.Description)
+- [Configuration](#head.Configuration)
+"""
+
+HEADER_DESCRIPTION_TEMPLATE = """
+
+# Abbreviation, Acronyms and Terms
+
+[[Refer to this link](userguide/aat.md)]
+
+
+# Description
+
+The `{classname}` plugin provides an interface for {classname}.
+
+The plugin is designed to be loaded and executed within the Thunder framework. For more information about the framework refer to [[Thunder](#ref.Thunder)].
+
+
+# Configuration
+
+The table below lists configuration options of the plugin.
+
+| Name | Type | Description |
+| :-------- | :-------- | :-------- |
+| callsign | string | Plugin instance name (default: *{classname}*) |
+| classname | string | Class name: *{classname}* |
+| locator | string | Library name: *libWPEFramework{classname}.so* |
+| autostart | boolean | Determines if the plugin shall be started automatically along with the framework |
+"""
+
+METHODS_TOC_TEMPLATE = """
+
+# Methods
+
+The following methods are provided by the {classname} plugin:
+
+{classname} interface methods:
+
+| Method | Description |
+| :-------- | :-------- |
+"""
+
+METHOD_MARKDOWN_TEMPLATE = """
+
+## *{method_name} [method](#head.Methods)*
+
+{method_description}
+
+"""
+
+PROPERTIES_TOC_TEMPLATE = """
+
+# Properties
+The following properties are provided by the {classname} plugin:
+
+{classname} interface properties:
+
+| Method | Description |
+| :-------- | :-------- |
+"""
+
+PROPERTY_MARKDOWN_TEMPLATE = """
+
+## *{property_name} [property](#head.Properties)*
+
+{property_description}
+
+"""
+
+EVENTS_TOC_TEMPLATE = """
+
+# Notifications
+
+Notifications are autonomous events, triggered by the internals of the implementation, and broadcasted via JSON-RPC to all registered observers. Refer to [[Thunder](#ref.Thunder)] for information on how to register for a notification.
+
+The following events are provided by the {classname} plugin:
+
+{classname} interface events:
+
+| Method | Description |
+| :-------- | :-------- |
+"""
+
+EVENT_MARKDOWN_TEMPLATE = """
+
+## *{event_name} [event](#head.Notifications)*
+
+{event_description}
+
+"""
+
+EXAMPLE_REQUEST_TEMPLATE = """
+
+#### {method_type}Request
+
+```json
+{request_json}
+"""
+
+EXAMPLE_RESPONSE_TEMPLATE = """
+
+#### {method_type}Response
+
+```json
+{response_json}
+"""
+
+def generate_header_toc(classname, document_object, version="1.0.0"):
+ """
+ Generate the header table of contents for the markdown file.
+ """
+ toc = HEADER_TOC_TEMPLATE.format(classname=classname, version=version)
+ if len(document_object.methods.values()) > 0:
+ toc += "- [Methods](#head.Methods)\n"
+ if len(document_object.properties.values()) > 0:
+ toc += "- [Properties](#head.Properties)\n"
+ if len(document_object.events.values()) > 0:
+ toc += "- [Notifications](#head.Notifications)\n"
+ return toc
+
+def generate_header_description_markdown(classname):
+ """
+ Generate the header description markdown for the file.
+ """
+ return HEADER_DESCRIPTION_TEMPLATE.format(classname=classname)
+
+def generate_methods_toc(methods, classname):
+ """
+ Generate the methods table of contents for the markdown file.
+ """
+ toc = METHODS_TOC_TEMPLATE.format(classname=classname)
+ for method in methods:
+ method_body = methods[method]
+ toc += f"| [{method}](#method.{method}) | {method_body['brief'] or method_body['details']} |\n"
+ return toc
+
+def generate_method_markdown(method_name, method_info, symbol_registry):
+ """
+ Generate the markdown for a specific method.
+ """
+ markdown = METHOD_MARKDOWN_TEMPLATE.format(method_name=method_name, method_description=method_info['brief'] or method_info['details'])
+ markdown += generate_events_section(method_info['events'])
+ markdown += generate_parameters_section(method_info['params'], symbol_registry)
+ markdown += generate_results_section(method_info['results'], symbol_registry)
+ markdown += "\n### Examples\n"
+ markdown += generate_request_section(method_info['request'], '')
+ markdown += generate_response_section(method_info['response'], '')
+ return markdown
+
+def generate_events_section(events):
+ """
+ Generate the events section for a method.
+ """
+ markdown = "### Events\n"
+ if events:
+ markdown += """| Event | Description |\n| :-------- | :-------- |\n"""
+ for event in events:
+ markdown += f"| [{event}](#event.{event}) | {events[event]} |\n"
+ else:
+ markdown += "No events are associated with this method.\n"
+ return markdown
+
+def generate_parameters_section(params, symbol_registry):
+ """
+ Generate the parameters section for a method.
+ """
+ markdown = "### Parameters\n"
+ if params:
+ markdown += """| Name | Type | Description |\n| :-------- | :-------- | :-------- |\n"""
+ for param in params:
+ flattened_params = symbol_registry[f"{param['name']}-{param['type']}"]['flattened_description']
+ for param_name, param_data in flattened_params.items():
+ markdown += f"| params{param_name} | {param_data['type']} | {re.sub(r'e\.g\.\s*".*?(?RO"
+ elif property_body['property'] == 'write':
+ super_script = "WO"
+ toc += f"| [{prop}](#property.{prop}){super_script} | {property_body['brief'] or property_body['details']} |\n"
+ return toc
+
+def generate_property_markdown(property_name, property_info, symbol_registry):
+ """
+ Generate the markdown for a specific property.
+ """
+ markdown = PROPERTY_MARKDOWN_TEMPLATE.format(property_name=property_name, property_description=property_info['brief'] or property_info['details'])
+ if property_info['property'] == 'read':
+ markdown += "> This property is read-only.\n"
+ elif property_info['property'] == 'write':
+ markdown += "> This property is write-only.\n"
+ markdown += generate_events_section(property_info['events'])
+ markdown += generate_values_section((property_info['results'] + property_info['params']), symbol_registry)
+ markdown += "\n### Examples\n"
+ if 'read' in property_info['property']:
+ markdown += generate_request_section(property_info['get_request'], 'Get ')
+ markdown += generate_response_section(property_info['get_response'], 'Get ')
+ if 'write' in property_info['property']:
+ markdown += generate_request_section(property_info['set_request'], 'Set ')
+ markdown += generate_response_section(property_info['set_response'], 'Set ')
+ return markdown
+
+def generate_values_section(values, symbol_registry):
+ """
+ Generate the values section for a property.
+ """
+ markdown = "### Values\n"
+ if values:
+ markdown += """| Name | Type | Description |\n| :-------- | :-------- | :-------- |\n"""
+ for value in values:
+ flattened_values = symbol_registry[f"{value['name']}-{value['type']}"]['flattened_description']
+ for value_name, value_data in flattened_values.items():
+ markdown += f"| (property){value_name} | {value_data['type']} | {re.sub(r'e\.g\.\s*".*?(?