1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
# 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"])
|