1import logging 2import os.path 3import sys 4 5from c_common import fsutil 6from c_common.scriptutil import ( 7 CLIArgSpec as Arg, 8 add_verbosity_cli, 9 add_traceback_cli, 10 add_kind_filtering_cli, 11 add_files_cli, 12 add_commands_cli, 13 process_args_by_key, 14 configure_logger, 15 get_prog, 16 main_for_filenames, 17) 18from .preprocessor import get_preprocessor 19from .preprocessor.__main__ import ( 20 add_common_cli as add_preprocessor_cli, 21) 22from .info import KIND 23from . import parse_file as _iter_parsed 24 25 26logger = logging.getLogger(__name__) 27 28 29def _format_vartype(vartype): 30 if isinstance(vartype, str): 31 return vartype 32 33 data = vartype 34 try: 35 vartype = data['vartype'] 36 except KeyError: 37 storage, typequal, typespec, abstract = vartype.values() 38 else: 39 storage = data.get('storage') 40 if storage: 41 _, typequal, typespec, abstract = vartype.values() 42 else: 43 storage, typequal, typespec, abstract = vartype.values() 44 45 vartype = f'{typespec} {abstract}' 46 if typequal: 47 vartype = f'{typequal} {vartype}' 48 if storage: 49 vartype = f'{storage} {vartype}' 50 return vartype 51 52 53def _get_preprocessor(filename, **kwargs): 54 return get_processor(filename, 55 log_err=print, 56 **kwargs 57 ) 58 59 60####################################### 61# the formats 62 63def fmt_raw(filename, item, *, showfwd=None): 64 yield str(tuple(item)) 65 66 67def fmt_summary(filename, item, *, showfwd=None): 68 if item.filename != filename: 69 yield f'> {item.filename}' 70 71 if showfwd is None: 72 LINE = ' {lno:>5} {kind:10} {funcname:40} {fwd:1} {name:40} {data}' 73 else: 74 LINE = ' {lno:>5} {kind:10} {funcname:40} {name:40} {data}' 75 lno = kind = funcname = fwd = name = data = '' 76 MIN_LINE = len(LINE.format(**locals())) 77 78 fileinfo, kind, funcname, name, data = item 79 lno = fileinfo.lno if fileinfo and fileinfo.lno >= 0 else '' 80 funcname = funcname or ' --' 81 name = name or ' --' 82 isforward = False 83 if kind is KIND.FUNCTION: 84 storage, inline, params, returntype, isforward = data.values() 85 returntype = _format_vartype(returntype) 86 data = returntype + params 87 if inline: 88 data = f'inline {data}' 89 if storage: 90 data = f'{storage} {data}' 91 elif kind is KIND.VARIABLE: 92 data = _format_vartype(data) 93 elif kind is KIND.STRUCT or kind is KIND.UNION: 94 if data is None: 95 isforward = True 96 else: 97 fields = data 98 data = f'({len(data)}) {{ ' 99 indent = ',\n' + ' ' * (MIN_LINE + len(data)) 100 data += ', '.join(f.name for f in fields[:5]) 101 fields = fields[5:] 102 while fields: 103 data = f'{data}{indent}{", ".join(f.name for f in fields[:5])}' 104 fields = fields[5:] 105 data += ' }' 106 elif kind is KIND.ENUM: 107 if data is None: 108 isforward = True 109 else: 110 names = [d if isinstance(d, str) else d.name 111 for d in data] 112 data = f'({len(data)}) {{ ' 113 indent = ',\n' + ' ' * (MIN_LINE + len(data)) 114 data += ', '.join(names[:5]) 115 names = names[5:] 116 while names: 117 data = f'{data}{indent}{", ".join(names[:5])}' 118 names = names[5:] 119 data += ' }' 120 elif kind is KIND.TYPEDEF: 121 data = f'typedef {data}' 122 elif kind == KIND.STATEMENT: 123 pass 124 else: 125 raise NotImplementedError(item) 126 if isforward: 127 fwd = '*' 128 if not showfwd and showfwd is not None: 129 return 130 elif showfwd: 131 return 132 kind = kind.value 133 yield LINE.format(**locals()) 134 135 136def fmt_full(filename, item, *, showfwd=None): 137 raise NotImplementedError 138 139 140FORMATS = { 141 'raw': fmt_raw, 142 'summary': fmt_summary, 143 'full': fmt_full, 144} 145 146 147def add_output_cli(parser): 148 parser.add_argument('--format', dest='fmt', default='summary', choices=tuple(FORMATS)) 149 parser.add_argument('--showfwd', action='store_true', default=None) 150 parser.add_argument('--no-showfwd', dest='showfwd', action='store_false', default=None) 151 152 def process_args(args, *, argv=None): 153 pass 154 return process_args 155 156 157####################################### 158# the commands 159 160def _cli_parse(parser, excluded=None, **prepr_kwargs): 161 process_output = add_output_cli(parser) 162 process_kinds = add_kind_filtering_cli(parser) 163 process_preprocessor = add_preprocessor_cli(parser, **prepr_kwargs) 164 process_files = add_files_cli(parser, excluded=excluded) 165 return [ 166 process_output, 167 process_kinds, 168 process_preprocessor, 169 process_files, 170 ] 171 172 173def cmd_parse(filenames, *, 174 fmt='summary', 175 showfwd=None, 176 iter_filenames=None, 177 relroot=None, 178 **kwargs 179 ): 180 if 'get_file_preprocessor' not in kwargs: 181 kwargs['get_file_preprocessor'] = _get_preprocessor() 182 try: 183 do_fmt = FORMATS[fmt] 184 except KeyError: 185 raise ValueError(f'unsupported fmt {fmt!r}') 186 for filename, relfile in main_for_filenames(filenames, iter_filenames, relroot): 187 for item in _iter_parsed(filename, **kwargs): 188 item = item.fix_filename(relroot, fixroot=False, normalize=False) 189 for line in do_fmt(relfile, item, showfwd=showfwd): 190 print(line) 191 192 193def _cli_data(parser): 194 ... 195 196 return [] 197 198 199def cmd_data(filenames, 200 **kwargs 201 ): 202 # XXX 203 raise NotImplementedError 204 205 206COMMANDS = { 207 'parse': ( 208 'parse the given C source & header files', 209 [_cli_parse], 210 cmd_parse, 211 ), 212 'data': ( 213 'check/manage local data (e.g. excludes, macros)', 214 [_cli_data], 215 cmd_data, 216 ), 217} 218 219 220####################################### 221# the script 222 223def parse_args(argv=sys.argv[1:], prog=sys.argv[0], *, subset='parse'): 224 import argparse 225 parser = argparse.ArgumentParser( 226 prog=prog or get_prog, 227 ) 228 229 processors = add_commands_cli( 230 parser, 231 commands={k: v[1] for k, v in COMMANDS.items()}, 232 commonspecs=[ 233 add_verbosity_cli, 234 add_traceback_cli, 235 ], 236 subset=subset, 237 ) 238 239 args = parser.parse_args(argv) 240 ns = vars(args) 241 242 cmd = ns.pop('cmd') 243 244 verbosity, traceback_cm = process_args_by_key( 245 args, 246 argv, 247 processors[cmd], 248 ['verbosity', 'traceback_cm'], 249 ) 250 251 return cmd, ns, verbosity, traceback_cm 252 253 254def main(cmd, cmd_kwargs): 255 try: 256 run_cmd = COMMANDS[cmd][0] 257 except KeyError: 258 raise ValueError(f'unsupported cmd {cmd!r}') 259 run_cmd(**cmd_kwargs) 260 261 262if __name__ == '__main__': 263 cmd, cmd_kwargs, verbosity, traceback_cm = parse_args() 264 configure_logger(verbosity) 265 with traceback_cm: 266 main(cmd, cmd_kwargs) 267