• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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