1#  Licensed under the Apache License, Version 2.0 (the "License"); you may
2#  not use this file except in compliance with the License. You may obtain
3#  a copy of the License at
4#
5#       http://www.apache.org/licenses/LICENSE-2.0
6#
7#  Unless required by applicable law or agreed to in writing, software
8#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10#  License for the specific language governing permissions and limitations
11#  under the License.
12
13"""Overrides of standard argparse behavior."""
14
15import argparse
16import sys
17import warnings
18
19
20class _ArgumentContainerMixIn(object):
21
22    # NOTE(dhellmann): We have to override the methods for creating
23    # groups to return our objects that know how to deal with the
24    # special conflict handler.
25
26    def add_argument_group(self, *args, **kwargs):
27        group = _ArgumentGroup(self, *args, **kwargs)
28        self._action_groups.append(group)
29        return group
30
31    def add_mutually_exclusive_group(self, **kwargs):
32        group = _MutuallyExclusiveGroup(self, **kwargs)
33        self._mutually_exclusive_groups.append(group)
34        return group
35
36    def _handle_conflict_ignore(self, action, conflicting_actions):
37        _handle_conflict_ignore(
38            self,
39            self._option_string_actions,
40            action,
41            conflicting_actions,
42        )
43
44
45class ArgumentParser(_ArgumentContainerMixIn, argparse.ArgumentParser):
46
47    if sys.version_info < (3, 5):
48        def __init__(self, *args, **kwargs):
49            self.allow_abbrev = kwargs.pop("allow_abbrev", True)
50            super(ArgumentParser, self).__init__(*args, **kwargs)
51
52        def _get_option_tuples(self, option_string):
53            if self.allow_abbrev:
54                return super(ArgumentParser, self)._get_option_tuples(
55                    option_string)
56            return ()
57
58
59def _handle_conflict_ignore(container, option_string_actions,
60                            new_action, conflicting_actions):
61
62    # Remember the option strings the new action starts with so we can
63    # restore them as part of error reporting if we need to.
64    original_option_strings = new_action.option_strings
65
66    # Remove all of the conflicting option strings from the new action
67    # and report an error if none are left at the end.
68    for option_string, action in conflicting_actions:
69
70        # remove the conflicting option from the new action
71        new_action.option_strings.remove(option_string)
72        warnings.warn(
73            ('Ignoring option string {} for new action '
74             'because it conflicts with an existing option.').format(
75                 option_string))
76
77        # if the option now has no option string, remove it from the
78        # container holding it
79        if not new_action.option_strings:
80            new_action.option_strings = original_option_strings
81            raise argparse.ArgumentError(
82                new_action,
83                ('Cannot resolve conflicting option string, '
84                 'all names conflict.'),
85            )
86
87
88class _ArgumentGroup(_ArgumentContainerMixIn, argparse._ArgumentGroup):
89    pass
90
91
92class _MutuallyExclusiveGroup(_ArgumentContainerMixIn,
93                              argparse._MutuallyExclusiveGroup):
94    pass
95
96
97class SmartHelpFormatter(argparse.HelpFormatter):
98    """Smart help formatter to output raw help message if help contain \n.
99
100    Some command help messages maybe have multiple line content, the built-in
101    argparse.HelpFormatter wrap and split the content according to width, and
102    ignore \n in the raw help message, it merge multiple line content in one
103    line to output, that looks messy. SmartHelpFormatter keep the raw help
104    message format if it contain \n, and wrap long line like HelpFormatter
105    behavior.
106    """
107
108    def _split_lines(self, text, width):
109        lines = text.splitlines() if '\n' in text else [text]
110        wrap_lines = []
111        for each_line in lines:
112            wrap_lines.extend(
113                super(SmartHelpFormatter, self)._split_lines(each_line, width)
114            )
115        return wrap_lines
116