blob: 9fa08fb066e08c88863df9c4aba27f6784676179 [file] [log] [blame]
# 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(
"<y>'{}'</>".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:', '<g>usage:</>')
if line.endswith(':'):
lines[i] = '<g>{}</>'.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 == '<stdout>':
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: <y>'{}'</>".format(arg)
else:
error = "unrecognized argument: <y>'{}'</>".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\: <y>'\-\-doo'</>\s+"
r"\(did you mean <y>'\-\-foo'</>\?\)\n"
r"\s*<g>usage:</>")
with self.assertRaisesRegex(Error, expected):
parser.parse_args(['--doo'])
parser.add_argument('--noo')
expected = (r"^unrecognized argument\: <y>'\-\-doo'</>\s+"
r"\(did you mean <y>'\-\-noo'</> or <y>'\-\-foo'</>\?\)\n"
r"\s*<g>usage:</>")
with self.assertRaisesRegex(Error, expected):
parser.parse_args(['--doo'])
expected = (r"^unrecognized argument\: <y>'\-\-bar'</>\n"
r"\s*<g>usage:</>")
with self.assertRaisesRegex(Error, expected):
parser.parse_args(['--bar'])
expected = (r"^unexpected argument\: <y>'\-\-foo'</>\n"
r"\s*<g>usage:</>")
with self.assertRaisesRegex(Error, expected):
parser.parse_args(['--', 'x', '--foo'])