1#!/usr/bin/env python 2 3""" 4This script is converting the misra documentation RST file into a text file 5that can be used as text-rules for cppcheck. 6Usage: 7 convert_misra_doc.py -i INPUT [-o OUTPUT] [-j JSON] [-s RULES,[...,RULES]] 8 9 INPUT - RST file containing the list of misra rules. 10 OUTPUT - file to store the text output to be used by cppcheck. 11 If not specified, the result will be printed to stdout. 12 JSON - cppcheck json file to be created (optional). 13 RULES - list of rules to skip during the analysis, comma separated 14 (e.g. 1.1,1.2,1.3,...) 15""" 16 17import sys, getopt, re 18 19# MISRA rule are identified by two numbers, e.g. Rule 1.2, the main rule number 20# and a sub-number. This dictionary contains the number of the MISRA rule as key 21# and the maximum sub-number for that rule as value. 22misra_c2012_rules = { 23 1:4, 24 2:7, 25 3:2, 26 4:2, 27 5:9, 28 6:2, 29 7:4, 30 8:14, 31 9:5, 32 10:8, 33 11:9, 34 12:5, 35 13:6, 36 14:4, 37 15:7, 38 16:7, 39 17:8, 40 18:8, 41 19:2, 42 20:14, 43 21:21, 44 22:10 45} 46 47def main(argv): 48 infile = '' 49 outfile = '' 50 outstr = sys.stdout 51 jsonfile = '' 52 force_skip = '' 53 54 try: 55 opts, args = getopt.getopt(argv,"hi:o:j:s:", 56 ["input=","output=","json=","skip="]) 57 except getopt.GetoptError: 58 print('convert-misra.py -i <input> [-o <output>] [-j <json>] [-s <rules>]') 59 sys.exit(2) 60 for opt, arg in opts: 61 if opt == '-h': 62 print('convert-misra.py -i <input> [-o <output>] [-j <json>] [-s <rules>]') 63 print(' If output is not specified, print to stdout') 64 sys.exit(1) 65 elif opt in ("-i", "--input"): 66 infile = arg 67 elif opt in ("-o", "--output"): 68 outfile = arg 69 elif opt in ("-s", "--skip"): 70 force_skip = arg 71 elif opt in ("-j", "--json"): 72 jsonfile = arg 73 74 try: 75 file_stream = open(infile, 'rt') 76 except: 77 print('Error opening ' + infile) 78 sys.exit(1) 79 80 if outfile: 81 try: 82 outstr = open(outfile, "w") 83 except: 84 print('Error creating ' + outfile) 85 sys.exit(1) 86 87 # Each rule start with ' * - `[Dir|Rule]' and is followed by the 88 # severity, the summary and then notes 89 # Only the summary can be multi line 90 pattern_dir = re.compile(r'^ \* - `Dir ([0-9]+.[0-9]+).*$') 91 pattern_rule = re.compile(r'^ \* - `Rule ([0-9]+.[0-9]+).*$') 92 pattern_col = re.compile(r'^ - (.*)$') 93 # allow empty notes 94 pattern_notes = re.compile(r'^ -.*$') 95 pattern_cont = re.compile(r'^ (.*)$') 96 97 rule_number = '' 98 rule_severity = '' 99 rule_summary = '' 100 rule_state = 0 101 rule_list = [] 102 103 # Start search by cppcheck misra 104 outstr.write('Appendix A Summary of guidelines\n') 105 106 for line in file_stream: 107 108 line = line.replace('\r', '').replace('\n', '') 109 110 if len(line) == 0: 111 continue 112 113 # New Rule or Directive 114 if rule_state == 0: 115 # new Rule 116 res = pattern_rule.match(line) 117 if res: 118 rule_number = res.group(1) 119 rule_list.append(rule_number) 120 rule_state = 1 121 continue 122 123 # new Directive 124 res = pattern_dir.match(line) 125 if res: 126 rule_number = res.group(1) 127 rule_list.append(rule_number) 128 rule_state = 1 129 continue 130 continue 131 132 # Severity 133 elif rule_state == 1: 134 res =pattern_col.match(line) 135 if res: 136 rule_severity = res.group(1) 137 rule_state = 2 138 continue 139 140 print('No severity for rule ' + rule_number) 141 sys.exit(1) 142 143 # Summary 144 elif rule_state == 2: 145 res = pattern_col.match(line) 146 if res: 147 rule_summary = res.group(1) 148 rule_state = 3 149 continue 150 151 print('No summary for rule ' + rule_number) 152 sys.exit(1) 153 154 # Notes or summary continuation 155 elif rule_state == 3: 156 res = pattern_cont.match(line) 157 if res: 158 rule_summary += res.group(1) 159 continue 160 res = pattern_notes.match(line) 161 if res: 162 outstr.write('Rule ' + rule_number + ' ' + rule_severity 163 + '\n') 164 outstr.write(rule_summary + ' (Misra rule ' + rule_number 165 + ')\n') 166 rule_state = 0 167 rule_number = '' 168 continue 169 print('No notes for rule ' + rule_number) 170 sys.exit(1) 171 172 else: 173 print('Impossible case in state machine') 174 sys.exit(1) 175 176 skip_list = [] 177 178 # Add rules to be skipped anyway 179 for r in force_skip.split(','): 180 skip_list.append(r) 181 182 # Search for missing rules and add a dummy text with the rule number 183 for i in misra_c2012_rules: 184 for j in list(range(1,misra_c2012_rules[i]+1)): 185 rule_str = str(i) + '.' + str(j) 186 if (rule_str not in rule_list) and (rule_str not in skip_list): 187 outstr.write('Rule ' + rule_str + '\n') 188 outstr.write('No description for rule ' + rule_str + '\n') 189 skip_list.append(rule_str) 190 191 # Make cppcheck happy by starting the appendix 192 outstr.write('Appendix B\n') 193 outstr.write('\n') 194 if outfile: 195 outstr.close() 196 197 if jsonfile: 198 with open(jsonfile, "w") as f: 199 f.write('{\n') 200 f.write(' "script": "misra.py",\n') 201 f.write(' "args": [\n') 202 if outfile: 203 f.write(' "--rule-texts=' + outfile + '",\n') 204 205 f.write(' "--suppress-rules=' + ",".join(skip_list) + '"\n') 206 f.write(' ]\n') 207 f.write('}\n') 208 f.close() 209 210if __name__ == "__main__": 211 main(sys.argv[1:]) 212