1import logging
2import sys
3
4from c_common.scriptutil import (
5    CLIArgSpec as Arg,
6    add_verbosity_cli,
7    add_traceback_cli,
8    add_kind_filtering_cli,
9    add_files_cli,
10    add_failure_filtering_cli,
11    add_commands_cli,
12    process_args_by_key,
13    configure_logger,
14    get_prog,
15    main_for_filenames,
16)
17from . import (
18    errors as _errors,
19    get_preprocessor as _get_preprocessor,
20)
21
22
23FAIL = {
24    'err': _errors.ErrorDirectiveError,
25    'deps': _errors.MissingDependenciesError,
26    'os': _errors.OSMismatchError,
27}
28FAIL_DEFAULT = tuple(v for v in FAIL if v != 'os')
29
30
31logger = logging.getLogger(__name__)
32
33
34##################################
35# CLI helpers
36
37def add_common_cli(parser, *, get_preprocessor=_get_preprocessor):
38    parser.add_argument('--macros', action='append')
39    parser.add_argument('--incldirs', action='append')
40    parser.add_argument('--same', action='append')
41    process_fail_arg = add_failure_filtering_cli(parser, FAIL)
42
43    def process_args(args, *, argv):
44        ns = vars(args)
45
46        process_fail_arg(args, argv)
47        ignore_exc = ns.pop('ignore_exc')
48        # We later pass ignore_exc to _get_preprocessor().
49
50        args.get_file_preprocessor = get_preprocessor(
51            file_macros=ns.pop('macros'),
52            file_incldirs=ns.pop('incldirs'),
53            file_same=ns.pop('same'),
54            ignore_exc=ignore_exc,
55            log_err=print,
56        )
57    return process_args
58
59
60def _iter_preprocessed(filename, *,
61                       get_preprocessor,
62                       match_kind=None,
63                       pure=False,
64                       ):
65    preprocess = get_preprocessor(filename)
66    for line in preprocess(tool=not pure) or ():
67        if match_kind is not None and not match_kind(line.kind):
68            continue
69        yield line
70
71
72#######################################
73# the commands
74
75def _cli_preprocess(parser, excluded=None, **prepr_kwargs):
76    parser.add_argument('--pure', action='store_true')
77    parser.add_argument('--no-pure', dest='pure', action='store_const', const=False)
78    process_kinds = add_kind_filtering_cli(parser)
79    process_common = add_common_cli(parser, **prepr_kwargs)
80    parser.add_argument('--raw', action='store_true')
81    process_files = add_files_cli(parser, excluded=excluded)
82
83    return [
84        process_kinds,
85        process_common,
86        process_files,
87    ]
88
89
90def cmd_preprocess(filenames, *,
91                   raw=False,
92                   iter_filenames=None,
93                   **kwargs
94                   ):
95    if 'get_file_preprocessor' not in kwargs:
96        kwargs['get_file_preprocessor'] = _get_preprocessor()
97    if raw:
98        def show_file(filename, lines):
99            for line in lines:
100                print(line)
101                #print(line.raw)
102    else:
103        def show_file(filename, lines):
104            for line in lines:
105                linefile = ''
106                if line.filename != filename:
107                    linefile = f' ({line.filename})'
108                text = line.data
109                if line.kind == 'comment':
110                    text = '/* ' + line.data.splitlines()[0]
111                    text += ' */' if '\n' in line.data else r'\n... */'
112                print(f' {line.lno:>4} {line.kind:10} | {text}')
113
114    filenames = main_for_filenames(filenames, iter_filenames)
115    for filename in filenames:
116        lines = _iter_preprocessed(filename, **kwargs)
117        show_file(filename, lines)
118
119
120def _cli_data(parser):
121    ...
122
123    return None
124
125
126def cmd_data(filenames,
127             **kwargs
128             ):
129    # XXX
130    raise NotImplementedError
131
132
133COMMANDS = {
134    'preprocess': (
135        'preprocess the given C source & header files',
136        [_cli_preprocess],
137        cmd_preprocess,
138    ),
139    'data': (
140        'check/manage local data (e.g. excludes, macros)',
141        [_cli_data],
142        cmd_data,
143    ),
144}
145
146
147#######################################
148# the script
149
150def parse_args(argv=sys.argv[1:], prog=sys.argv[0], *,
151               subset='preprocess',
152               excluded=None,
153               **prepr_kwargs
154               ):
155    import argparse
156    parser = argparse.ArgumentParser(
157        prog=prog or get_prog(),
158    )
159
160    processors = add_commands_cli(
161        parser,
162        commands={k: v[1] for k, v in COMMANDS.items()},
163        commonspecs=[
164            add_verbosity_cli,
165            add_traceback_cli,
166        ],
167        subset=subset,
168    )
169
170    args = parser.parse_args(argv)
171    ns = vars(args)
172
173    cmd = ns.pop('cmd')
174
175    verbosity, traceback_cm = process_args_by_key(
176        args,
177        argv,
178        processors[cmd],
179        ['verbosity', 'traceback_cm'],
180    )
181
182    return cmd, ns, verbosity, traceback_cm
183
184
185def main(cmd, cmd_kwargs):
186    try:
187        run_cmd = COMMANDS[cmd][0]
188    except KeyError:
189        raise ValueError(f'unsupported cmd {cmd!r}')
190    run_cmd(**cmd_kwargs)
191
192
193if __name__ == '__main__':
194    cmd, cmd_kwargs, verbosity, traceback_cm = parse_args()
195    configure_logger(verbosity)
196    with traceback_cm:
197        main(cmd, cmd_kwargs)
198