Loading...
#!/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)