summaryrefslogtreecommitdiffstats
path: root/debuginfo-tests/dexter/dex/utils/ExtArgParse.py
blob: 9fa08fb066e08c88863df9c4aba27f6784676179 (plain)
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
# 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'])
OpenPOWER on IntegriCloud