154 lines
5.3 KiB
Python
154 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"])
|