1#!/usr/bin/env python3
2# QEMU library
3#
4# Copyright (C) 2020 Red Hat Inc.
5#
6# Authors:
7#  Eduardo Habkost <ehabkost@redhat.com>
8#
9# This work is licensed under the terms of the GNU GPL, version 2.  See
10# the COPYING file in the top-level directory.
11#
12import sys
13import argparse
14import os
15import os.path
16import re
17from typing import *
18
19from codeconverter.patching import FileInfo, match_class_dict, FileList
20import codeconverter.qom_macros
21from codeconverter.qom_type_info import TI_FIELDS, type_infos, TypeInfoVar
22
23import logging
24logger = logging.getLogger(__name__)
25DBG = logger.debug
26INFO = logger.info
27WARN = logger.warning
28
29def process_all_files(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
30    DBG("filenames: %r", args.filenames)
31
32    files = FileList()
33    files.extend(FileInfo(files, fn, args.force) for fn in args.filenames)
34    for f in files:
35        DBG('opening %s', f.filename)
36        f.load()
37
38    if args.table:
39        fields = ['filename', 'variable_name'] + TI_FIELDS
40        print('\t'.join(fields))
41        for f in files:
42            for t in f.matches_of_type(TypeInfoVar):
43                assert isinstance(t, TypeInfoVar)
44                values = [f.filename, t.name] + \
45                         [t.get_initializer_value(f).raw
46                          for f in TI_FIELDS]
47                DBG('values: %r', values)
48                assert all('\t' not in v for v in values)
49                values = [v.replace('\n', ' ').replace('"', '') for v in values]
50                print('\t'.join(values))
51        return
52
53    match_classes = match_class_dict()
54    if not args.patterns:
55        parser.error("--pattern is required")
56
57    classes = [p for arg in args.patterns
58                for p in re.split(r'[\s,]', arg)]
59    for c in classes:
60        if c not in match_classes:
61            print("Invalid pattern name: %s" % (c), file=sys.stderr)
62            print("Valid patterns:", file=sys.stderr)
63            print(PATTERN_HELP, file=sys.stderr)
64            sys.exit(1)
65
66    DBG("classes: %r", classes)
67    for f in files:
68        DBG("patching contents of %s", f.filename)
69        f.patch_content(max_passes=args.passes, class_names=classes)
70
71    for f in files:
72        #alltypes.extend(f.type_infos)
73        #full_types.extend(f.full_types())
74
75        if not args.dry_run:
76            if args.inplace:
77                f.patch_inplace()
78            if args.diff:
79                f.show_diff()
80            if not args.diff and not args.inplace:
81                f.write_to_file(sys.stdout)
82                sys.stdout.flush()
83
84
85PATTERN_HELP = ('\n'.join("  %s: %s" % (n, str(c.__doc__).strip())
86                for (n,c) in sorted(match_class_dict().items())
87                if c.has_replacement_rule()))
88
89def main() -> None:
90    p = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
91    p.add_argument('filenames', nargs='+')
92    p.add_argument('--passes', type=int, default=1,
93                   help="Number of passes (0 means unlimited)")
94    p.add_argument('--pattern', required=True, action='append',
95                   default=[], dest='patterns',
96                   help="Pattern to scan for")
97    p.add_argument('--inplace', '-i', action='store_true',
98                   help="Patch file in place")
99    p.add_argument('--dry-run', action='store_true',
100                   help="Don't patch files or print patching results")
101    p.add_argument('--force', '-f', action='store_true',
102                   help="Perform changes even if not completely safe")
103    p.add_argument('--diff', action='store_true',
104                   help="Print diff output on stdout")
105    p.add_argument('--debug', '-d', action='store_true',
106                   help="Enable debugging")
107    p.add_argument('--verbose', '-v', action='store_true',
108                   help="Verbose logging on stderr")
109    p.add_argument('--table', action='store_true',
110                   help="Print CSV table of type information")
111    p.add_argument_group("Valid pattern names",
112                         PATTERN_HELP)
113    args = p.parse_args()
114
115    loglevel = (logging.DEBUG if args.debug
116             else logging.INFO if args.verbose
117             else logging.WARN)
118    logging.basicConfig(format='%(levelname)s: %(message)s', level=loglevel)
119    DBG("args: %r", args)
120    process_all_files(p, args)
121
122if __name__ == '__main__':
123    main()