Balint Dobszay | 9e2573d | 2022-08-10 15:15:21 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # SPDX-License-Identifier: BSD-3-Clause |
| 3 | # |
Gyorgy Szing | cacf18d | 2023-01-18 19:08:58 +0000 | [diff] [blame^] | 4 | # Copyright (c) 2022-2023, Arm Limited. All rights reserved. |
Balint Dobszay | 9e2573d | 2022-08-10 15:15:21 +0200 | [diff] [blame] | 5 | |
Gyorgy Szing | cacf18d | 2023-01-18 19:08:58 +0000 | [diff] [blame^] | 6 | """Merge json files and print the result to STDOUT. |
| 7 | |
| 8 | Source files are specified with a list of file names. Any name in the list can |
| 9 | be a glob pattern. Glob patterns act if the returned list of file names were |
| 10 | passed instead. The list returned by glob is not sorted. All files are |
| 11 | processed in the order being found in the argument list. |
| 12 | |
| 13 | Note: do not forget to quote globing patterns when running the tool from a |
| 14 | shell. |
Balint Dobszay | 9e2573d | 2022-08-10 15:15:21 +0200 | [diff] [blame] | 15 | """ |
| 16 | |
Gyorgy Szing | cacf18d | 2023-01-18 19:08:58 +0000 | [diff] [blame^] | 17 | import argparse |
| 18 | import errno |
| 19 | import glob |
Balint Dobszay | 9e2573d | 2022-08-10 15:15:21 +0200 | [diff] [blame] | 20 | import json |
Gyorgy Szing | cacf18d | 2023-01-18 19:08:58 +0000 | [diff] [blame^] | 21 | import logging |
Balint Dobszay | 9e2573d | 2022-08-10 15:15:21 +0200 | [diff] [blame] | 22 | import os.path |
| 23 | import sys |
| 24 | |
Gyorgy Szing | cacf18d | 2023-01-18 19:08:58 +0000 | [diff] [blame^] | 25 | # initialize logger |
| 26 | logging.getLogger('merge_json') |
| 27 | logging.basicConfig(level=logging.ERROR) |
Balint Dobszay | 9e2573d | 2022-08-10 15:15:21 +0200 | [diff] [blame] | 28 | |
Balint Dobszay | 9e2573d | 2022-08-10 15:15:21 +0200 | [diff] [blame] | 29 | |
Gyorgy Szing | cacf18d | 2023-01-18 19:08:58 +0000 | [diff] [blame^] | 30 | def parse_arguments(args): |
| 31 | parser = argparse.ArgumentParser( |
| 32 | prog=os.path.basename(args[0]), |
| 33 | description=__doc__, |
| 34 | formatter_class=argparse.RawDescriptionHelpFormatter) |
| 35 | parser.add_argument( |
| 36 | "-e", "--exclude", |
| 37 | default=None, |
| 38 | metavar="<exclude pattern>", |
| 39 | help="Exclude files matching this pattern.") |
| 40 | parser.add_argument( |
| 41 | "-o", "--output_file", |
| 42 | default=None, |
| 43 | metavar="<path to output file>", |
| 44 | help="Write result to this file instead of STDOUT") |
| 45 | parser.add_argument( |
| 46 | "-v", |
| 47 | action='append_const', const='v', |
| 48 | metavar="<verbosity level>", |
| 49 | help="Set the amount of information printed to STDERR." + |
| 50 | " Passing more times gives more info.") |
| 51 | parser.add_argument( |
| 52 | "-i", "--ignore-missing", |
| 53 | dest="ignore_missing", |
| 54 | action='store_const', const=True, default=False, |
| 55 | help="Ignore missing source files or source globs returning" + |
| 56 | " empty result.") |
| 57 | parser.add_argument( |
| 58 | 'source_list', |
| 59 | nargs="+", |
| 60 | metavar="<source file>", |
| 61 | help="List of source files (file name can be glob pattern).") |
| 62 | parsed_args = parser.parse_args(args[1:]) |
| 63 | # Count -v arguments to logging level |
| 64 | if parsed_args.v: |
| 65 | llv = len(parsed_args.v) |
| 66 | if llv > 3: |
| 67 | llv = 3 |
| 68 | else: |
| 69 | llv = 0 |
| 70 | parsed_args.log_level = [logging.ERROR, logging.WARNING, logging.INFO, |
| 71 | logging.DEBUG][llv] |
| 72 | return (parsed_args) |
| 73 | |
| 74 | |
| 75 | def merge_files(parsed_args): |
| 76 | logger = logging.getLogger('merge_json') |
| 77 | |
| 78 | logger.info( |
| 79 | "Merging " + str(parsed_args.source_list) + " to " + |
| 80 | (parsed_args.output_file if parsed_args.output_file else "STDOUT")) |
| 81 | |
| 82 | result = {} |
| 83 | exclude_list = None |
| 84 | |
| 85 | if parsed_args.exclude: |
| 86 | exclude_list = glob.glob(parsed_args.exclude, recursive=True) |
| 87 | if exclude_list: |
| 88 | logger.debug("Excluding files: %s" % exclude_list) |
| 89 | else: |
| 90 | logger.warning("Exclude pattern matches no files.") |
| 91 | |
| 92 | for pattern in parsed_args.source_list: |
| 93 | file_list = glob.glob(pattern, recursive=True) |
| 94 | logger.debug("Globing " + pattern + " = " + str(file_list)) |
| 95 | if not file_list: |
| 96 | logger.error("Pattern \"%s\" does not match any file" % pattern) |
| 97 | if not parsed_args.ignore_missing: |
| 98 | raise (FileNotFoundError( |
| 99 | errno.ENOENT, |
| 100 | "Pattern does not match any file", |
| 101 | pattern)) |
| 102 | |
| 103 | for file in list(file_list): |
| 104 | if exclude_list and file in list(exclude_list): |
| 105 | logger.debug("excluding file " + file) |
| 106 | continue |
| 107 | logger.debug("Reding source file " + file) |
| 108 | with open(file, "rt", encoding="utf8", errors="strict") as f: |
| 109 | result.update(json.load(f)) |
| 110 | |
| 111 | if parsed_args.output_file is not None: |
| 112 | path = os.path.dirname(parsed_args.output_file) |
| 113 | if path: |
| 114 | os.makedirs(path, exist_ok=True) |
| 115 | with open(parsed_args.output_file, "w", encoding="utf8") as f: |
| 116 | json.dump(result, f, indent=4) |
| 117 | else: |
| 118 | print(json.dumps(result, indent=4)) |
| 119 | |
| 120 | |
| 121 | if __name__ == "__main__": |
| 122 | parsed_args = parse_arguments(sys.argv) |
| 123 | logger = logging.getLogger('merge_json') |
| 124 | logger.setLevel(parsed_args.log_level) |
| 125 | merge_files(parsed_args) |