diff --git a/codebasin/coverage/__init__.py b/codebasin/coverage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codebasin/coverage/__main__.py b/codebasin/coverage/__main__.py index 63971b5..c7d5359 100755 --- a/codebasin/coverage/__main__.py +++ b/codebasin/coverage/__main__.py @@ -12,26 +12,56 @@ from codebasin import CodeBase, config, finder, util # TODO: Refactor to avoid imports from __main__ -from codebasin.__main__ import Formatter, WarningAggregator, _help_string +from codebasin.__main__ import ( + Formatter, + WarningAggregator, + _help_string, + version, +) from codebasin.preprocessor import CodeNode log = logging.getLogger("codebasin") -def cli(argv: list[str]): - # Read command-line arguments +def _build_parser() -> argparse.ArgumentParser: + """ + Build argument parser. + """ parser = argparse.ArgumentParser( - description="Code Base Investigator Coverage Tool", + description="CBI Coverage Tool " + version, formatter_class=argparse.RawTextHelpFormatter, add_help=False, ) + parser.set_defaults(func=None) parser.add_argument( "-h", "--help", action="help", - help=_help_string("Display help message and exit."), + help="Display help message and exit.", ) parser.add_argument( + "--version", + action="version", + version=f"CBI Coverage Tool {version}", + help="Display version information and exit.", + ) + + subparsers = parser.add_subparsers(title="commands") + + compute_parser = subparsers.add_parser( + "compute", + help="Compute coverage.", + formatter_class=argparse.RawTextHelpFormatter, + add_help=False, + ) + compute_parser.set_defaults(func=_compute) + compute_parser.add_argument( + "-h", + "--help", + action="help", + help=_help_string("Display help message and exit."), + ) + compute_parser.add_argument( "-S", "--source-dir", metavar="", @@ -39,7 +69,7 @@ def cli(argv: list[str]): help=_help_string("Path to source directory.", is_long=True), default=os.getcwd(), ) - parser.add_argument( + compute_parser.add_argument( "-x", "--exclude", dest="excludes", @@ -50,21 +80,34 @@ def cli(argv: list[str]): "Exclude files matching this pattern from the code base.", "May be specified multiple times.", is_long=True, + ), + ) + compute_parser.add_argument( + "-o", + "--output", + dest="ofile", + metavar="", + default="coverage.json", + help=_help_string( + "Path to coverage JSON file.", + "If not specified, defaults to 'coverage.json'.", + is_long=True, is_last=True, ), ) - parser.add_argument( + compute_parser.add_argument( "ifile", metavar="", - help=_help_string("Path to compilation database JSON file."), - ) - parser.add_argument( - "ofile", - metavar="", - help=_help_string("Path to coverage JSON file.", is_last=True), + help=_help_string( + "Path to compilation database JSON file.", + is_last=True, + ), ) - args = parser.parse_args(argv) + return parser + + +def _compute(args: argparse.Namespace): dbpath = os.path.realpath(args.ifile) covpath = os.path.realpath(args.ofile) for path in [dbpath, covpath]: @@ -75,29 +118,6 @@ def cli(argv: list[str]): source_dir = os.path.realpath(args.source_dir) - # Configure logging such that: - # - All messages are written to a log file - # - Only errors are written to the terminal - # - Meta-warnings and statistics are generated by a WarningAggregator - aggregator = WarningAggregator() - log.setLevel(logging.DEBUG) - - file_handler = logging.FileHandler("cbi.log", mode="w") - file_handler.setLevel(logging.INFO) - file_handler.setFormatter(Formatter()) - file_handler.addFilter(aggregator) - log.addHandler(file_handler) - - # Inform the user that a log file has been created. - # 'print' instead of 'log' to ensure the message is visible in the output. - log_path = os.path.abspath("cbi.log") - print(f"Log file created at {log_path}") - - stderr_handler = logging.StreamHandler(sys.stderr) - stderr_handler.setLevel(logging.ERROR) - stderr_handler.setFormatter(Formatter(colors=sys.stderr.isatty())) - log.addHandler(stderr_handler) - # Run CBI configured as-if: # - configuration contains a single (dummy) platform # - codebase contains all files in the specified compilation database @@ -135,6 +155,41 @@ def cli(argv: list[str]): sys.exit(0) +def cli(argv: list[str]) -> int: + parser = _build_parser() + args = parser.parse_args(argv) + command = args.func + + if command is None: + parser.print_help() + sys.exit(2) + + # Configure logging such that: + # - All messages are written to a log file + # - Only errors are written to the terminal + # - Meta-warnings and statistics are generated by a WarningAggregator + aggregator = WarningAggregator() + log.setLevel(logging.DEBUG) + + file_handler = logging.FileHandler("cbi.log", mode="w") + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(Formatter()) + file_handler.addFilter(aggregator) + log.addHandler(file_handler) + + # Inform the user that a log file has been created. + # 'print' instead of 'log' to ensure the message is visible in the output. + log_path = os.path.abspath("cbi.log") + print(f"Log file created at {log_path}") + + stderr_handler = logging.StreamHandler(sys.stderr) + stderr_handler.setLevel(logging.ERROR) + stderr_handler.setFormatter(Formatter(colors=sys.stderr.isatty())) + log.addHandler(stderr_handler) + + return command(args) + + def main(): try: cli(sys.argv[1:])