scripts: utils: add guidelines converter script

Add a guidelines rst converter script
to convert the rst document written coding guidelines
into another format.
First supported format is the format for cppcheck

Signed-off-by: Simon Hein <Shein@baumer.com>
diff --git a/scripts/utils/convert_guidelines.py b/scripts/utils/convert_guidelines.py
new file mode 100755
index 0000000..a0530b9
--- /dev/null
+++ b/scripts/utils/convert_guidelines.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2023 Baumer (www.baumer.com)
+# SPDX-License-Identifier: Apache-2.0
+
+"""This script converting the Zephyr coding guideline rst file to a output file,
+or print the output to the console. Which than can be used by a tool which
+needs to have that information in a specific format (e.g. for cppcheck).
+Or simply use the rule list to generate a filter to suppress all other rules
+used by default from such a tool.
+"""
+
+import sys
+import re
+import argparse
+from pathlib import Path
+
+class RuleFormatter:
+    """
+    Base class for the different output formats
+    """
+    def table_start_print(self, outputfile):
+        pass
+    def severity_print(self, outputfile, guideline_number, severity):
+        pass
+    def description_print(self, outputfile, guideline_number, description):
+        pass
+    def closing_print(self, outputfile):
+        pass
+
+class CppCheckFormatter(RuleFormatter):
+    """
+    Formatter class to print the rules in a format which can be used by cppcheck
+    """
+    def table_start_print(self, outputfile):
+        # Start search by cppcheck misra addon
+        print('Appendix A Summary of guidelines', file=outputfile)
+
+    def severity_print(self, outputfile, guideline_number, severity):
+        print('Rule ' + guideline_number + ' ' + severity, file=outputfile)
+
+    def description_print(self, outputfile, guideline_number, description):
+        print(description + '(Misra rule ' + guideline_number + ')', file=outputfile)
+
+    def closing_print(self, outputfile):
+        # Make cppcheck happy by starting the appendix
+        print('Appendix B', file=outputfile)
+        print('', file=outputfile)
+
+def convert_guidelines(args):
+    inputfile = args.input
+    outputfile = sys.stdout
+    formatter = None
+
+    # If the output is not empty, open the given file for writing
+    if args.output is not None:
+        outputfile = open(args.output, "w")
+
+    try:
+        file_stream = open(inputfile, 'rt', errors='ignore')
+    except Exception:
+        print('Error opening ' + inputfile +'.')
+        sys.exit()
+
+    # Set formatter according to the used format
+    if args.format == 'cppcheck':
+        formatter = CppCheckFormatter()
+
+    # Search for table named Main rules
+    pattern_table_start = re.compile(r'.*list-table:: Main rules')
+    # Each Rule is a new table column so start with '[tab]* -  Rule'
+    # Ignore directives here
+    pattern_new_line = re.compile(r'^    \* -  Rule ([0-9]+.[0-9]+).*$')
+    # Each table column start with '[tab]-  '
+    pattern_new_col = re.compile(r'^      -  (.*)$')
+
+    table_start = False
+    guideline_number = ''
+    guideline_state  = 0
+    guideline_list = []
+
+    for line in file_stream:
+
+        line = line.replace('\r', '').replace('\n', '')
+
+        # Done if we find the Additional rules table start
+        if line.find('Additional rules') >= 0:
+            break
+
+        if len(line) == 0:
+            continue
+
+        if not table_start:
+            res = pattern_table_start.match(line)
+            if res:
+                table_start = True
+                formatter.table_start_print(outputfile)
+            continue
+
+        res = pattern_new_line.match(line)
+        if res:
+            guideline_state = "severity"
+            guideline_number = res.group(1)
+            guideline_list.append(guideline_number)
+            continue
+        elif guideline_number == '':
+            continue
+
+        res = pattern_new_col.match(line)
+        if res:
+            if guideline_state == "severity":
+                # Severity
+                formatter.severity_print(outputfile, guideline_number, res.group(1))
+                guideline_state = "description"
+                continue
+            if guideline_state == "description":
+                # Description
+                formatter.description_print(outputfile, guideline_number, res.group(1))
+                guideline_state = "None"
+                # We stop here for now, we do not handle the CERT C col
+                guideline_number = ''
+                continue
+
+    formatter.closing_print(outputfile)
+
+if __name__ == "__main__":
+    supported_formats = ['cppcheck']
+
+    parser = argparse.ArgumentParser(allow_abbrev=False)
+    parser.add_argument(
+        "-i", "--input", metavar="RST_FILE", type=Path, required=True,
+        help="Path to rst input source file, where the guidelines are written down."
+    )
+    parser.add_argument(
+        "-f", "--format", metavar="FORMAT", choices=supported_formats, required=True,
+        help="Format to convert guidlines to. Supported formats are: " + str(supported_formats)
+    )
+    parser.add_argument(
+        "-o", "--output", metavar="OUTPUT_FILE", type=Path, required=False,
+        help="Path to output file, where the converted guidelines are written to. If outputfile is not specified, print to stdout."
+    )
+    args = parser.parse_args()
+
+    convert_guidelines(args)