1#!/usr/bin/env python3
2
3# Copyright (c) 2023 Baumer (www.baumer.com)
4# SPDX-License-Identifier: Apache-2.0
5
6"""This script converting the Zephyr coding guideline rst file to a output file,
7or print the output to the console. Which than can be used by a tool which
8needs to have that information in a specific format (e.g. for cppcheck).
9Or simply use the rule list to generate a filter to suppress all other rules
10used by default from such a tool.
11"""
12
13import sys
14import re
15import argparse
16from pathlib import Path
17
18class RuleFormatter:
19    """
20    Base class for the different output formats
21    """
22    def table_start_print(self, outputfile):
23        pass
24    def severity_print(self, outputfile, guideline_number, severity):
25        pass
26    def description_print(self, outputfile, guideline_number, description):
27        pass
28    def closing_print(self, outputfile):
29        pass
30
31class CppCheckFormatter(RuleFormatter):
32    """
33    Formatter class to print the rules in a format which can be used by cppcheck
34    """
35    def table_start_print(self, outputfile):
36        # Start search by cppcheck misra addon
37        print('Appendix A Summary of guidelines', file=outputfile)
38
39    def severity_print(self, outputfile, guideline_number, severity):
40        print('Rule ' + guideline_number + ' ' + severity, file=outputfile)
41
42    def description_print(self, outputfile, guideline_number, description):
43        print(description + '(Misra rule ' + guideline_number + ')', file=outputfile)
44
45    def closing_print(self, outputfile):
46        # Make cppcheck happy by starting the appendix
47        print('Appendix B', file=outputfile)
48        print('', file=outputfile)
49
50def convert_guidelines(args):
51    inputfile = args.input
52    outputfile = sys.stdout
53    formatter = None
54
55    # If the output is not empty, open the given file for writing
56    if args.output is not None:
57        outputfile = open(args.output, "w")
58
59    try:
60        file_stream = open(inputfile, 'rt', errors='ignore')
61    except Exception:
62        print('Error opening ' + inputfile +'.')
63        sys.exit()
64
65    # Set formatter according to the used format
66    if args.format == 'cppcheck':
67        formatter = CppCheckFormatter()
68
69    # Search for table named Main rules
70    pattern_table_start = re.compile(r'.*list-table:: Main rules')
71    # Each Rule is a new table column so start with '[tab]* -  Rule'
72    # Ignore directives here
73    pattern_new_line = re.compile(r'^    \* -  Rule ([0-9]+.[0-9]+).*$')
74    # Each table column start with '[tab]-  '
75    pattern_new_col = re.compile(r'^      -  (.*)$')
76
77    table_start = False
78    guideline_number = ''
79    guideline_state  = 0
80    guideline_list = []
81
82    for line in file_stream:
83
84        line = line.replace('\r', '').replace('\n', '')
85
86        # Done if we find the Additional rules table start
87        if line.find('Additional rules') >= 0:
88            break
89
90        if len(line) == 0:
91            continue
92
93        if not table_start:
94            res = pattern_table_start.match(line)
95            if res:
96                table_start = True
97                formatter.table_start_print(outputfile)
98            continue
99
100        res = pattern_new_line.match(line)
101        if res:
102            guideline_state = "severity"
103            guideline_number = res.group(1)
104            guideline_list.append(guideline_number)
105            continue
106        elif guideline_number == '':
107            continue
108
109        res = pattern_new_col.match(line)
110        if res:
111            if guideline_state == "severity":
112                # Severity
113                formatter.severity_print(outputfile, guideline_number, res.group(1))
114                guideline_state = "description"
115                continue
116            if guideline_state == "description":
117                # Description
118                formatter.description_print(outputfile, guideline_number, res.group(1))
119                guideline_state = "None"
120                # We stop here for now, we do not handle the CERT C col
121                guideline_number = ''
122                continue
123
124    formatter.closing_print(outputfile)
125
126if __name__ == "__main__":
127    supported_formats = ['cppcheck']
128
129    parser = argparse.ArgumentParser(allow_abbrev=False)
130    parser.add_argument(
131        "-i", "--input", metavar="RST_FILE", type=Path, required=True,
132        help="Path to rst input source file, where the guidelines are written down."
133    )
134    parser.add_argument(
135        "-f", "--format", metavar="FORMAT", choices=supported_formats, required=True,
136        help="Format to convert guidlines to. Supported formats are: " + str(supported_formats)
137    )
138    parser.add_argument(
139        "-o", "--output", metavar="OUTPUT_FILE", type=Path, required=False,
140        help="Path to output file, where the converted guidelines are written to. If outputfile is not specified, print to stdout."
141    )
142    args = parser.parse_args()
143
144    convert_guidelines(args)
145