This reverts commit cb935f3456.
Discussion in D68708 advises that green dragon is being briskly
refurbished, and it's good to have this patch up testing it.
149 lines
5.3 KiB
Python
149 lines
5.3 KiB
Python
# 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'])
|