# DExTer : Debugging Experience Tester # ~~~~~~ ~ ~~ ~ ~~ # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception """Extended Argument Parser. Extends the argparse module with some extra functionality, to hopefully aid user-friendliness. """ import argparse import difflib import unittest from dex.utils import PrettyOutput from dex.utils.Exceptions import Error # re-export all of argparse for argitem in argparse.__all__: vars()[argitem] = getattr(argparse, argitem) def _did_you_mean(val, possibles): close_matches = difflib.get_close_matches(val, possibles) did_you_mean = '' if close_matches: did_you_mean = 'did you mean {}?'.format(' or '.join( "'{}'".format(c) for c in close_matches[:2])) return did_you_mean def _colorize(message): lines = message.splitlines() for i, line in enumerate(lines): lines[i] = lines[i].replace('usage:', 'usage:') if line.endswith(':'): lines[i] = '{}'.format(line) return '\n'.join(lines) class ExtArgumentParser(argparse.ArgumentParser): def error(self, message): """Use the Dexception Error mechanism (including auto-colored output). """ raise Error('{}\n\n{}'.format(message, self.format_usage())) # pylint: disable=redefined-builtin def _print_message(self, message, file=None): if message: if file and file.name == '': file = PrettyOutput.stdout else: file = PrettyOutput.stderr self.context.o.auto(message, file) # pylint: enable=redefined-builtin def format_usage(self): return _colorize(super(ExtArgumentParser, self).format_usage()) def format_help(self): return _colorize(super(ExtArgumentParser, self).format_help() + '\n\n') @property def _valid_visible_options(self): """A list of all non-suppressed command line flags.""" return [ item for sublist in vars(self)['_actions'] for item in sublist.option_strings if sublist.help != argparse.SUPPRESS ] def parse_args(self, args=None, namespace=None): """Add 'did you mean' output to errors.""" args, argv = self.parse_known_args(args, namespace) if argv: errors = [] for arg in argv: if arg in self._valid_visible_options: error = "unexpected argument: '{}'".format(arg) else: error = "unrecognized argument: '{}'".format(arg) dym = _did_you_mean(arg, self._valid_visible_options) if dym: error += ' ({})'.format(dym) errors.append(error) self.error('\n '.join(errors)) return args def add_argument(self, *args, **kwargs): """Automatically add the default value to help text.""" if 'default' in kwargs: default = kwargs['default'] if default is None: default = kwargs.pop('display_default', None) if (default and isinstance(default, (str, int, float)) and default != argparse.SUPPRESS): assert ( 'choices' not in kwargs or default in kwargs['choices']), ( "default value '{}' is not one of allowed choices: {}". format(default, kwargs['choices'])) if 'help' in kwargs and kwargs['help'] != argparse.SUPPRESS: assert isinstance(kwargs['help'], str), type(kwargs['help']) kwargs['help'] = ('{} (default:{})'.format( kwargs['help'], default)) super(ExtArgumentParser, self).add_argument(*args, **kwargs) def __init__(self, context, *args, **kwargs): self.context = context super(ExtArgumentParser, self).__init__(*args, **kwargs) class TestExtArgumentParser(unittest.TestCase): def test_did_you_mean(self): parser = ExtArgumentParser(None) parser.add_argument('--foo') parser.add_argument('--qoo', help=argparse.SUPPRESS) parser.add_argument('jam', nargs='?') parser.parse_args(['--foo', '0']) expected = (r"^unrecognized argument\: '\-\-doo'\s+" r"\(did you mean '\-\-foo'\?\)\n" r"\s*usage:") with self.assertRaisesRegex(Error, expected): parser.parse_args(['--doo']) parser.add_argument('--noo') expected = (r"^unrecognized argument\: '\-\-doo'\s+" r"\(did you mean '\-\-noo' or '\-\-foo'\?\)\n" r"\s*usage:") with self.assertRaisesRegex(Error, expected): parser.parse_args(['--doo']) expected = (r"^unrecognized argument\: '\-\-bar'\n" r"\s*usage:") with self.assertRaisesRegex(Error, expected): parser.parse_args(['--bar']) expected = (r"^unexpected argument\: '\-\-foo'\n" r"\s*usage:") with self.assertRaisesRegex(Error, expected): parser.parse_args(['--', 'x', '--foo'])