1e5c31af7Sopenharmony_ci"""Provides a reusable command-line interface to a MacroChecker.""" 2e5c31af7Sopenharmony_ci 3e5c31af7Sopenharmony_ci# Copyright (c) 2018-2019 Collabora, Ltd. 4e5c31af7Sopenharmony_ci# 5e5c31af7Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0 6e5c31af7Sopenharmony_ci# 7e5c31af7Sopenharmony_ci# Author(s): Ryan Pavlik <ryan.pavlik@collabora.com> 8e5c31af7Sopenharmony_ci 9e5c31af7Sopenharmony_ci 10e5c31af7Sopenharmony_ciimport argparse 11e5c31af7Sopenharmony_ciimport logging 12e5c31af7Sopenharmony_ciimport re 13e5c31af7Sopenharmony_cifrom pathlib import Path 14e5c31af7Sopenharmony_ci 15e5c31af7Sopenharmony_cifrom .shared import MessageId 16e5c31af7Sopenharmony_ci 17e5c31af7Sopenharmony_ci 18e5c31af7Sopenharmony_cidef checkerMain(default_enabled_messages, make_macro_checker, 19e5c31af7Sopenharmony_ci all_docs, available_messages=None): 20e5c31af7Sopenharmony_ci """Perform the bulk of the work for a command-line interface to a MacroChecker. 21e5c31af7Sopenharmony_ci 22e5c31af7Sopenharmony_ci Arguments: 23e5c31af7Sopenharmony_ci default_enabled_messages -- The MessageId values that should be enabled by default. 24e5c31af7Sopenharmony_ci make_macro_checker -- A function that can be called with a set of enabled MessageId to create a 25e5c31af7Sopenharmony_ci properly-configured MacroChecker. 26e5c31af7Sopenharmony_ci all_docs -- A list of all spec documentation files. 27e5c31af7Sopenharmony_ci available_messages -- a list of all MessageId values that can be generated for this project. 28e5c31af7Sopenharmony_ci Defaults to every value. (e.g. some projects don't have MessageId.LEGACY) 29e5c31af7Sopenharmony_ci """ 30e5c31af7Sopenharmony_ci enabled_messages = set(default_enabled_messages) 31e5c31af7Sopenharmony_ci if not available_messages: 32e5c31af7Sopenharmony_ci available_messages = list(MessageId) 33e5c31af7Sopenharmony_ci 34e5c31af7Sopenharmony_ci disable_args = [] 35e5c31af7Sopenharmony_ci enable_args = [] 36e5c31af7Sopenharmony_ci 37e5c31af7Sopenharmony_ci parser = argparse.ArgumentParser() 38e5c31af7Sopenharmony_ci parser.add_argument( 39e5c31af7Sopenharmony_ci "--scriptlocation", 40e5c31af7Sopenharmony_ci help="Append the script location generated a message to the output.", 41e5c31af7Sopenharmony_ci action="store_true") 42e5c31af7Sopenharmony_ci parser.add_argument( 43e5c31af7Sopenharmony_ci "--verbose", 44e5c31af7Sopenharmony_ci "-v", 45e5c31af7Sopenharmony_ci help="Output 'info'-level development logging messages.", 46e5c31af7Sopenharmony_ci action="store_true") 47e5c31af7Sopenharmony_ci parser.add_argument( 48e5c31af7Sopenharmony_ci "--debug", 49e5c31af7Sopenharmony_ci "-d", 50e5c31af7Sopenharmony_ci help="Output 'debug'-level development logging messages (more verbose than -v).", 51e5c31af7Sopenharmony_ci action="store_true") 52e5c31af7Sopenharmony_ci parser.add_argument( 53e5c31af7Sopenharmony_ci "-Werror", 54e5c31af7Sopenharmony_ci "--warning_error", 55e5c31af7Sopenharmony_ci help="Make warnings act as errors, exiting with non-zero error code", 56e5c31af7Sopenharmony_ci action="store_true") 57e5c31af7Sopenharmony_ci parser.add_argument( 58e5c31af7Sopenharmony_ci "--include_warn", 59e5c31af7Sopenharmony_ci help="List all expected but unseen include files, not just those that are referenced.", 60e5c31af7Sopenharmony_ci action='store_true') 61e5c31af7Sopenharmony_ci parser.add_argument( 62e5c31af7Sopenharmony_ci "-Wmissing_refpages", 63e5c31af7Sopenharmony_ci help="List all entities with expected but unseen ref page blocks. NOT included in -Wall!", 64e5c31af7Sopenharmony_ci action='store_true') 65e5c31af7Sopenharmony_ci parser.add_argument( 66e5c31af7Sopenharmony_ci "--include_error", 67e5c31af7Sopenharmony_ci help="Make expected but unseen include files cause exiting with non-zero error code", 68e5c31af7Sopenharmony_ci action='store_true') 69e5c31af7Sopenharmony_ci parser.add_argument( 70e5c31af7Sopenharmony_ci "--broken_error", 71e5c31af7Sopenharmony_ci help="Make missing include/anchor for linked-to entities cause exiting with non-zero error code. Weaker version of --include_error.", 72e5c31af7Sopenharmony_ci action='store_true') 73e5c31af7Sopenharmony_ci parser.add_argument( 74e5c31af7Sopenharmony_ci "--dump_entities", 75e5c31af7Sopenharmony_ci help="Just dump the parsed entity data to entities.json and exit.", 76e5c31af7Sopenharmony_ci action='store_true') 77e5c31af7Sopenharmony_ci parser.add_argument( 78e5c31af7Sopenharmony_ci "--html", 79e5c31af7Sopenharmony_ci help="Output messages to the named HTML file instead of stdout.") 80e5c31af7Sopenharmony_ci parser.add_argument( 81e5c31af7Sopenharmony_ci "file", 82e5c31af7Sopenharmony_ci help="Only check the indicated file(s). By default, all chapters and extensions are checked.", 83e5c31af7Sopenharmony_ci nargs="*") 84e5c31af7Sopenharmony_ci parser.add_argument( 85e5c31af7Sopenharmony_ci "--ignore_count", 86e5c31af7Sopenharmony_ci type=int, 87e5c31af7Sopenharmony_ci help="Ignore up to the given number of errors without exiting with a non-zero error code.") 88e5c31af7Sopenharmony_ci parser.add_argument("-Wall", 89e5c31af7Sopenharmony_ci help="Enable all warning categories.", 90e5c31af7Sopenharmony_ci action='store_true') 91e5c31af7Sopenharmony_ci 92e5c31af7Sopenharmony_ci for message_id in MessageId: 93e5c31af7Sopenharmony_ci enable_arg = message_id.enable_arg() 94e5c31af7Sopenharmony_ci enable_args.append((message_id, enable_arg)) 95e5c31af7Sopenharmony_ci 96e5c31af7Sopenharmony_ci disable_arg = message_id.disable_arg() 97e5c31af7Sopenharmony_ci disable_args.append((message_id, disable_arg)) 98e5c31af7Sopenharmony_ci if message_id in enabled_messages: 99e5c31af7Sopenharmony_ci parser.add_argument('-' + disable_arg, action="store_true", 100e5c31af7Sopenharmony_ci help="Disable message category {}: {}".format(str(message_id), message_id.desc())) 101e5c31af7Sopenharmony_ci # Don't show the enable flag in help since it's enabled by default 102e5c31af7Sopenharmony_ci parser.add_argument('-' + enable_arg, action="store_true", 103e5c31af7Sopenharmony_ci help=argparse.SUPPRESS) 104e5c31af7Sopenharmony_ci else: 105e5c31af7Sopenharmony_ci parser.add_argument('-' + enable_arg, action="store_true", 106e5c31af7Sopenharmony_ci help="Enable message category {}: {}".format(str(message_id), message_id.desc())) 107e5c31af7Sopenharmony_ci # Don't show the disable flag in help since it's disabled by 108e5c31af7Sopenharmony_ci # default 109e5c31af7Sopenharmony_ci parser.add_argument('-' + disable_arg, action="store_true", 110e5c31af7Sopenharmony_ci help=argparse.SUPPRESS) 111e5c31af7Sopenharmony_ci 112e5c31af7Sopenharmony_ci args = parser.parse_args() 113e5c31af7Sopenharmony_ci 114e5c31af7Sopenharmony_ci arg_dict = vars(args) 115e5c31af7Sopenharmony_ci for message_id, arg in enable_args: 116e5c31af7Sopenharmony_ci if args.Wall or (arg in arg_dict and arg_dict[arg]): 117e5c31af7Sopenharmony_ci enabled_messages.add(message_id) 118e5c31af7Sopenharmony_ci 119e5c31af7Sopenharmony_ci for message_id, arg in disable_args: 120e5c31af7Sopenharmony_ci if arg in arg_dict and arg_dict[arg]: 121e5c31af7Sopenharmony_ci enabled_messages.discard(message_id) 122e5c31af7Sopenharmony_ci 123e5c31af7Sopenharmony_ci if args.verbose: 124e5c31af7Sopenharmony_ci logging.basicConfig(level='INFO') 125e5c31af7Sopenharmony_ci 126e5c31af7Sopenharmony_ci if args.debug: 127e5c31af7Sopenharmony_ci logging.basicConfig(level='DEBUG') 128e5c31af7Sopenharmony_ci 129e5c31af7Sopenharmony_ci checker = make_macro_checker(enabled_messages) 130e5c31af7Sopenharmony_ci 131e5c31af7Sopenharmony_ci if args.dump_entities: 132e5c31af7Sopenharmony_ci with open('entities.json', 'w', encoding='utf-8') as f: 133e5c31af7Sopenharmony_ci f.write(checker.getEntityJson()) 134e5c31af7Sopenharmony_ci exit(0) 135e5c31af7Sopenharmony_ci 136e5c31af7Sopenharmony_ci if args.file: 137e5c31af7Sopenharmony_ci files = (str(Path(f).resolve()) for f in args.file) 138e5c31af7Sopenharmony_ci else: 139e5c31af7Sopenharmony_ci files = all_docs 140e5c31af7Sopenharmony_ci 141e5c31af7Sopenharmony_ci for fn in files: 142e5c31af7Sopenharmony_ci checker.processFile(fn) 143e5c31af7Sopenharmony_ci 144e5c31af7Sopenharmony_ci if args.html: 145e5c31af7Sopenharmony_ci from .html_printer import HTMLPrinter 146e5c31af7Sopenharmony_ci printer = HTMLPrinter(args.html) 147e5c31af7Sopenharmony_ci else: 148e5c31af7Sopenharmony_ci from .console_printer import ConsolePrinter 149e5c31af7Sopenharmony_ci printer = ConsolePrinter() 150e5c31af7Sopenharmony_ci 151e5c31af7Sopenharmony_ci if args.scriptlocation: 152e5c31af7Sopenharmony_ci printer.show_script_location = True 153e5c31af7Sopenharmony_ci 154e5c31af7Sopenharmony_ci if args.file: 155e5c31af7Sopenharmony_ci printer.output("Only checked specified files.") 156e5c31af7Sopenharmony_ci for f in args.file: 157e5c31af7Sopenharmony_ci printer.output(f) 158e5c31af7Sopenharmony_ci else: 159e5c31af7Sopenharmony_ci printer.output("Checked all chapters and extensions.") 160e5c31af7Sopenharmony_ci 161e5c31af7Sopenharmony_ci if args.warning_error: 162e5c31af7Sopenharmony_ci numErrors = checker.numDiagnostics() 163e5c31af7Sopenharmony_ci else: 164e5c31af7Sopenharmony_ci numErrors = checker.numErrors() 165e5c31af7Sopenharmony_ci 166e5c31af7Sopenharmony_ci check_includes = args.include_warn 167e5c31af7Sopenharmony_ci check_broken = not args.file 168e5c31af7Sopenharmony_ci 169e5c31af7Sopenharmony_ci if args.file and check_includes: 170e5c31af7Sopenharmony_ci print('Note: forcing --include_warn off because only checking supplied files.') 171e5c31af7Sopenharmony_ci check_includes = False 172e5c31af7Sopenharmony_ci 173e5c31af7Sopenharmony_ci printer.outputResults(checker, broken_links=(not args.file), 174e5c31af7Sopenharmony_ci missing_includes=check_includes) 175e5c31af7Sopenharmony_ci 176e5c31af7Sopenharmony_ci if check_broken: 177e5c31af7Sopenharmony_ci numErrors += len(checker.getBrokenLinks()) 178e5c31af7Sopenharmony_ci 179e5c31af7Sopenharmony_ci if args.file and args.include_error: 180e5c31af7Sopenharmony_ci print('Note: forcing --include_error off because only checking supplied files.') 181e5c31af7Sopenharmony_ci args.include_error = False 182e5c31af7Sopenharmony_ci if args.include_error: 183e5c31af7Sopenharmony_ci numErrors += len(checker.getMissingUnreferencedApiIncludes()) 184e5c31af7Sopenharmony_ci 185e5c31af7Sopenharmony_ci check_missing_refpages = args.Wmissing_refpages 186e5c31af7Sopenharmony_ci if args.file and check_missing_refpages: 187e5c31af7Sopenharmony_ci print('Note: forcing -Wmissing_refpages off because only checking supplied files.') 188e5c31af7Sopenharmony_ci check_missing_refpages = False 189e5c31af7Sopenharmony_ci 190e5c31af7Sopenharmony_ci if check_missing_refpages: 191e5c31af7Sopenharmony_ci missing = checker.getMissingRefPages() 192e5c31af7Sopenharmony_ci if missing: 193e5c31af7Sopenharmony_ci printer.output("Expected, but did not find, ref page blocks for the following {} entities: {}".format( 194e5c31af7Sopenharmony_ci len(missing), 195e5c31af7Sopenharmony_ci ', '.join(missing) 196e5c31af7Sopenharmony_ci )) 197e5c31af7Sopenharmony_ci if args.warning_error: 198e5c31af7Sopenharmony_ci numErrors += len(missing) 199e5c31af7Sopenharmony_ci 200e5c31af7Sopenharmony_ci printer.close() 201e5c31af7Sopenharmony_ci 202e5c31af7Sopenharmony_ci if args.broken_error and not args.file: 203e5c31af7Sopenharmony_ci numErrors += len(checker.getBrokenLinks()) 204e5c31af7Sopenharmony_ci 205e5c31af7Sopenharmony_ci if checker.hasFixes(): 206e5c31af7Sopenharmony_ci fixFn = 'applyfixes.sh' 207e5c31af7Sopenharmony_ci print('Saving shell script to apply fixes as {}'.format(fixFn)) 208e5c31af7Sopenharmony_ci with open(fixFn, 'w', encoding='utf-8') as f: 209e5c31af7Sopenharmony_ci f.write('#!/bin/sh -e\n') 210e5c31af7Sopenharmony_ci for fileChecker in checker.files: 211e5c31af7Sopenharmony_ci wroteComment = False 212e5c31af7Sopenharmony_ci for msg in fileChecker.messages: 213e5c31af7Sopenharmony_ci if msg.fix is not None: 214e5c31af7Sopenharmony_ci if not wroteComment: 215e5c31af7Sopenharmony_ci f.write('\n# {}\n'.format(fileChecker.filename)) 216e5c31af7Sopenharmony_ci wroteComment = True 217e5c31af7Sopenharmony_ci search, replace = msg.fix 218e5c31af7Sopenharmony_ci f.write( 219e5c31af7Sopenharmony_ci r"sed -i -r 's~\b{}\b~{}~g' {}".format( 220e5c31af7Sopenharmony_ci re.escape(search), 221e5c31af7Sopenharmony_ci replace, 222e5c31af7Sopenharmony_ci fileChecker.filename)) 223e5c31af7Sopenharmony_ci f.write('\n') 224e5c31af7Sopenharmony_ci 225e5c31af7Sopenharmony_ci print('Total number of errors with this run: {}'.format(numErrors)) 226e5c31af7Sopenharmony_ci 227e5c31af7Sopenharmony_ci if args.ignore_count: 228e5c31af7Sopenharmony_ci if numErrors > args.ignore_count: 229e5c31af7Sopenharmony_ci # Exit with non-zero error code so that we "fail" CI, etc. 230e5c31af7Sopenharmony_ci print('Exceeded specified limit of {}, so exiting with error'.format( 231e5c31af7Sopenharmony_ci args.ignore_count)) 232e5c31af7Sopenharmony_ci exit(1) 233e5c31af7Sopenharmony_ci else: 234e5c31af7Sopenharmony_ci print('At or below specified limit of {}, so exiting with success'.format( 235e5c31af7Sopenharmony_ci args.ignore_count)) 236e5c31af7Sopenharmony_ci exit(0) 237e5c31af7Sopenharmony_ci 238e5c31af7Sopenharmony_ci if numErrors: 239e5c31af7Sopenharmony_ci # Exit with non-zero error code so that we "fail" CI, etc. 240e5c31af7Sopenharmony_ci print('Exiting with error') 241e5c31af7Sopenharmony_ci exit(1) 242e5c31af7Sopenharmony_ci else: 243e5c31af7Sopenharmony_ci print('Exiting with success') 244e5c31af7Sopenharmony_ci exit(0) 245