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