1# Copyright 2017 The Abseil Authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Contains Flag class - information about single command-line flag. 16 17Do NOT import this module directly. Import the flags package and use the 18aliases defined at the package level instead. 19""" 20 21from collections import abc 22import copy 23import functools 24 25from absl.flags import _argument_parser 26from absl.flags import _exceptions 27from absl.flags import _helpers 28 29 30@functools.total_ordering 31class Flag(object): 32 """Information about a command-line flag. 33 34 Attributes: 35 name: the name for this flag 36 default: the default value for this flag 37 default_unparsed: the unparsed default value for this flag. 38 default_as_str: default value as repr'd string, e.g., "'true'" 39 (or None) 40 value: the most recent parsed value of this flag set by :meth:`parse` 41 help: a help string or None if no help is available 42 short_name: the single letter alias for this flag (or None) 43 boolean: if 'true', this flag does not accept arguments 44 present: true if this flag was parsed from command line flags 45 parser: an :class:`~absl.flags.ArgumentParser` object 46 serializer: an ArgumentSerializer object 47 allow_override: the flag may be redefined without raising an error, 48 and newly defined flag overrides the old one. 49 allow_override_cpp: use the flag from C++ if available the flag 50 definition is replaced by the C++ flag after init 51 allow_hide_cpp: use the Python flag despite having a C++ flag with 52 the same name (ignore the C++ flag) 53 using_default_value: the flag value has not been set by user 54 allow_overwrite: the flag may be parsed more than once without 55 raising an error, the last set value will be used 56 allow_using_method_names: whether this flag can be defined even if 57 it has a name that conflicts with a FlagValues method. 58 validators: list of the flag validators. 59 60 The only public method of a ``Flag`` object is :meth:`parse`, but it is 61 typically only called by a :class:`~absl.flags.FlagValues` object. The 62 :meth:`parse` method is a thin wrapper around the 63 :meth:`ArgumentParser.parse()<absl.flags.ArgumentParser.parse>` method. The 64 parsed value is saved in ``.value``, and the ``.present`` attribute is 65 updated. If this flag was already present, an Error is raised. 66 67 :meth:`parse` is also called during ``__init__`` to parse the default value 68 and initialize the ``.value`` attribute. This enables other python modules to 69 safely use flags even if the ``__main__`` module neglects to parse the 70 command line arguments. The ``.present`` attribute is cleared after 71 ``__init__`` parsing. If the default value is set to ``None``, then the 72 ``__init__`` parsing step is skipped and the ``.value`` attribute is 73 initialized to None. 74 75 Note: The default value is also presented to the user in the help 76 string, so it is important that it be a legal value for this flag. 77 """ 78 79 def __init__(self, parser, serializer, name, default, help_string, 80 short_name=None, boolean=False, allow_override=False, 81 allow_override_cpp=False, allow_hide_cpp=False, 82 allow_overwrite=True, allow_using_method_names=False): 83 self.name = name 84 85 if not help_string: 86 help_string = '(no help available)' 87 88 self.help = help_string 89 self.short_name = short_name 90 self.boolean = boolean 91 self.present = 0 92 self.parser = parser 93 self.serializer = serializer 94 self.allow_override = allow_override 95 self.allow_override_cpp = allow_override_cpp 96 self.allow_hide_cpp = allow_hide_cpp 97 self.allow_overwrite = allow_overwrite 98 self.allow_using_method_names = allow_using_method_names 99 100 self.using_default_value = True 101 self._value = None 102 self.validators = [] 103 if self.allow_hide_cpp and self.allow_override_cpp: 104 raise _exceptions.Error( 105 "Can't have both allow_hide_cpp (means use Python flag) and " 106 'allow_override_cpp (means use C++ flag after InitGoogle)') 107 108 self._set_default(default) 109 110 @property 111 def value(self): 112 return self._value 113 114 @value.setter 115 def value(self, value): 116 self._value = value 117 118 def __hash__(self): 119 return hash(id(self)) 120 121 def __eq__(self, other): 122 return self is other 123 124 def __lt__(self, other): 125 if isinstance(other, Flag): 126 return id(self) < id(other) 127 return NotImplemented 128 129 def __bool__(self): 130 raise TypeError('A Flag instance would always be True. ' 131 'Did you mean to test the `.value` attribute?') 132 133 def __getstate__(self): 134 raise TypeError("can't pickle Flag objects") 135 136 def __copy__(self): 137 raise TypeError('%s does not support shallow copies. ' 138 'Use copy.deepcopy instead.' % type(self).__name__) 139 140 def __deepcopy__(self, memo): 141 result = object.__new__(type(self)) 142 result.__dict__ = copy.deepcopy(self.__dict__, memo) 143 return result 144 145 def _get_parsed_value_as_string(self, value): 146 """Returns parsed flag value as string.""" 147 if value is None: 148 return None 149 if self.serializer: 150 return repr(self.serializer.serialize(value)) 151 if self.boolean: 152 if value: 153 return repr('true') 154 else: 155 return repr('false') 156 return repr(str(value)) 157 158 def parse(self, argument): 159 """Parses string and sets flag value. 160 161 Args: 162 argument: str or the correct flag value type, argument to be parsed. 163 """ 164 if self.present and not self.allow_overwrite: 165 raise _exceptions.IllegalFlagValueError( 166 'flag --%s=%s: already defined as %s' % ( 167 self.name, argument, self.value)) 168 self.value = self._parse(argument) 169 self.present += 1 170 171 def _parse(self, argument): 172 """Internal parse function. 173 174 It returns the parsed value, and does not modify class states. 175 176 Args: 177 argument: str or the correct flag value type, argument to be parsed. 178 179 Returns: 180 The parsed value. 181 """ 182 try: 183 return self.parser.parse(argument) 184 except (TypeError, ValueError) as e: # Recast as IllegalFlagValueError. 185 raise _exceptions.IllegalFlagValueError( 186 'flag --%s=%s: %s' % (self.name, argument, e)) 187 188 def unparse(self): 189 self.value = self.default 190 self.using_default_value = True 191 self.present = 0 192 193 def serialize(self): 194 """Serializes the flag.""" 195 return self._serialize(self.value) 196 197 def _serialize(self, value): 198 """Internal serialize function.""" 199 if value is None: 200 return '' 201 if self.boolean: 202 if value: 203 return '--%s' % self.name 204 else: 205 return '--no%s' % self.name 206 else: 207 if not self.serializer: 208 raise _exceptions.Error( 209 'Serializer not present for flag %s' % self.name) 210 return '--%s=%s' % (self.name, self.serializer.serialize(value)) 211 212 def _set_default(self, value): 213 """Changes the default value (and current value too) for this Flag.""" 214 self.default_unparsed = value 215 if value is None: 216 self.default = None 217 else: 218 self.default = self._parse_from_default(value) 219 self.default_as_str = self._get_parsed_value_as_string(self.default) 220 if self.using_default_value: 221 self.value = self.default 222 223 # This is split out so that aliases can skip regular parsing of the default 224 # value. 225 def _parse_from_default(self, value): 226 return self._parse(value) 227 228 def flag_type(self): 229 """Returns a str that describes the type of the flag. 230 231 NOTE: we use strings, and not the types.*Type constants because 232 our flags can have more exotic types, e.g., 'comma separated list 233 of strings', 'whitespace separated list of strings', etc. 234 """ 235 return self.parser.flag_type() 236 237 def _create_xml_dom_element(self, doc, module_name, is_key=False): 238 """Returns an XML element that contains this flag's information. 239 240 This is information that is relevant to all flags (e.g., name, 241 meaning, etc.). If you defined a flag that has some other pieces of 242 info, then please override _ExtraXMLInfo. 243 244 Please do NOT override this method. 245 246 Args: 247 doc: minidom.Document, the DOM document it should create nodes from. 248 module_name: str,, the name of the module that defines this flag. 249 is_key: boolean, True iff this flag is key for main module. 250 251 Returns: 252 A minidom.Element instance. 253 """ 254 element = doc.createElement('flag') 255 if is_key: 256 element.appendChild(_helpers.create_xml_dom_element(doc, 'key', 'yes')) 257 element.appendChild(_helpers.create_xml_dom_element( 258 doc, 'file', module_name)) 259 # Adds flag features that are relevant for all flags. 260 element.appendChild(_helpers.create_xml_dom_element(doc, 'name', self.name)) 261 if self.short_name: 262 element.appendChild(_helpers.create_xml_dom_element( 263 doc, 'short_name', self.short_name)) 264 if self.help: 265 element.appendChild(_helpers.create_xml_dom_element( 266 doc, 'meaning', self.help)) 267 # The default flag value can either be represented as a string like on the 268 # command line, or as a Python object. We serialize this value in the 269 # latter case in order to remain consistent. 270 if self.serializer and not isinstance(self.default, str): 271 if self.default is not None: 272 default_serialized = self.serializer.serialize(self.default) 273 else: 274 default_serialized = '' 275 else: 276 default_serialized = self.default 277 element.appendChild(_helpers.create_xml_dom_element( 278 doc, 'default', default_serialized)) 279 value_serialized = self._serialize_value_for_xml(self.value) 280 element.appendChild(_helpers.create_xml_dom_element( 281 doc, 'current', value_serialized)) 282 element.appendChild(_helpers.create_xml_dom_element( 283 doc, 'type', self.flag_type())) 284 # Adds extra flag features this flag may have. 285 for e in self._extra_xml_dom_elements(doc): 286 element.appendChild(e) 287 return element 288 289 def _serialize_value_for_xml(self, value): 290 """Returns the serialized value, for use in an XML help text.""" 291 return value 292 293 def _extra_xml_dom_elements(self, doc): 294 """Returns extra info about this flag in XML. 295 296 "Extra" means "not already included by _create_xml_dom_element above." 297 298 Args: 299 doc: minidom.Document, the DOM document it should create nodes from. 300 301 Returns: 302 A list of minidom.Element. 303 """ 304 # Usually, the parser knows the extra details about the flag, so 305 # we just forward the call to it. 306 return self.parser._custom_xml_dom_elements(doc) # pylint: disable=protected-access 307 308 309class BooleanFlag(Flag): 310 """Basic boolean flag. 311 312 Boolean flags do not take any arguments, and their value is either 313 ``True`` (1) or ``False`` (0). The false value is specified on the command 314 line by prepending the word ``'no'`` to either the long or the short flag 315 name. 316 317 For example, if a Boolean flag was created whose long name was 318 ``'update'`` and whose short name was ``'x'``, then this flag could be 319 explicitly unset through either ``--noupdate`` or ``--nox``. 320 """ 321 322 def __init__(self, name, default, help, short_name=None, **args): # pylint: disable=redefined-builtin 323 p = _argument_parser.BooleanParser() 324 super(BooleanFlag, self).__init__( 325 p, None, name, default, help, short_name, 1, **args) 326 327 328class EnumFlag(Flag): 329 """Basic enum flag; its value can be any string from list of enum_values.""" 330 331 def __init__(self, name, default, help, enum_values, # pylint: disable=redefined-builtin 332 short_name=None, case_sensitive=True, **args): 333 p = _argument_parser.EnumParser(enum_values, case_sensitive) 334 g = _argument_parser.ArgumentSerializer() 335 super(EnumFlag, self).__init__( 336 p, g, name, default, help, short_name, **args) 337 self.help = '<%s>: %s' % ('|'.join(enum_values), self.help) 338 339 def _extra_xml_dom_elements(self, doc): 340 elements = [] 341 for enum_value in self.parser.enum_values: 342 elements.append(_helpers.create_xml_dom_element( 343 doc, 'enum_value', enum_value)) 344 return elements 345 346 347class EnumClassFlag(Flag): 348 """Basic enum flag; its value is an enum class's member.""" 349 350 def __init__( 351 self, 352 name, 353 default, 354 help, # pylint: disable=redefined-builtin 355 enum_class, 356 short_name=None, 357 case_sensitive=False, 358 **args): 359 p = _argument_parser.EnumClassParser( 360 enum_class, case_sensitive=case_sensitive) 361 g = _argument_parser.EnumClassSerializer(lowercase=not case_sensitive) 362 super(EnumClassFlag, self).__init__( 363 p, g, name, default, help, short_name, **args) 364 self.help = '<%s>: %s' % ('|'.join(p.member_names), self.help) 365 366 def _extra_xml_dom_elements(self, doc): 367 elements = [] 368 for enum_value in self.parser.enum_class.__members__.keys(): 369 elements.append(_helpers.create_xml_dom_element( 370 doc, 'enum_value', enum_value)) 371 return elements 372 373 374class MultiFlag(Flag): 375 """A flag that can appear multiple time on the command-line. 376 377 The value of such a flag is a list that contains the individual values 378 from all the appearances of that flag on the command-line. 379 380 See the __doc__ for Flag for most behavior of this class. Only 381 differences in behavior are described here: 382 383 * The default value may be either a single value or an iterable of values. 384 A single value is transformed into a single-item list of that value. 385 386 * The value of the flag is always a list, even if the option was 387 only supplied once, and even if the default value is a single 388 value 389 """ 390 391 def __init__(self, *args, **kwargs): 392 super(MultiFlag, self).__init__(*args, **kwargs) 393 self.help += ';\n repeat this option to specify a list of values' 394 395 def parse(self, arguments): 396 """Parses one or more arguments with the installed parser. 397 398 Args: 399 arguments: a single argument or a list of arguments (typically a 400 list of default values); a single argument is converted 401 internally into a list containing one item. 402 """ 403 new_values = self._parse(arguments) 404 if self.present: 405 self.value.extend(new_values) 406 else: 407 self.value = new_values 408 self.present += len(new_values) 409 410 def _parse(self, arguments): 411 if (isinstance(arguments, abc.Iterable) and 412 not isinstance(arguments, str)): 413 arguments = list(arguments) 414 415 if not isinstance(arguments, list): 416 # Default value may be a list of values. Most other arguments 417 # will not be, so convert them into a single-item list to make 418 # processing simpler below. 419 arguments = [arguments] 420 421 return [super(MultiFlag, self)._parse(item) for item in arguments] 422 423 def _serialize(self, value): 424 """See base class.""" 425 if not self.serializer: 426 raise _exceptions.Error( 427 'Serializer not present for flag %s' % self.name) 428 if value is None: 429 return '' 430 431 serialized_items = [ 432 super(MultiFlag, self)._serialize(value_item) for value_item in value 433 ] 434 435 return '\n'.join(serialized_items) 436 437 def flag_type(self): 438 """See base class.""" 439 return 'multi ' + self.parser.flag_type() 440 441 def _extra_xml_dom_elements(self, doc): 442 elements = [] 443 if hasattr(self.parser, 'enum_values'): 444 for enum_value in self.parser.enum_values: 445 elements.append(_helpers.create_xml_dom_element( 446 doc, 'enum_value', enum_value)) 447 return elements 448 449 450class MultiEnumClassFlag(MultiFlag): 451 """A multi_enum_class flag. 452 453 See the __doc__ for MultiFlag for most behaviors of this class. In addition, 454 this class knows how to handle enum.Enum instances as values for this flag 455 type. 456 """ 457 458 def __init__(self, 459 name, 460 default, 461 help_string, 462 enum_class, 463 case_sensitive=False, 464 **args): 465 p = _argument_parser.EnumClassParser( 466 enum_class, case_sensitive=case_sensitive) 467 g = _argument_parser.EnumClassListSerializer( 468 list_sep=',', lowercase=not case_sensitive) 469 super(MultiEnumClassFlag, self).__init__( 470 p, g, name, default, help_string, **args) 471 self.help = ( 472 '<%s>: %s;\n repeat this option to specify a list of values' % 473 ('|'.join(p.member_names), help_string or '(no help available)')) 474 475 def _extra_xml_dom_elements(self, doc): 476 elements = [] 477 for enum_value in self.parser.enum_class.__members__.keys(): 478 elements.append(_helpers.create_xml_dom_element( 479 doc, 'enum_value', enum_value)) 480 return elements 481 482 def _serialize_value_for_xml(self, value): 483 """See base class.""" 484 if value is not None: 485 value_serialized = self.serializer.serialize(value) 486 else: 487 value_serialized = '' 488 return value_serialized 489