410 lines
14 KiB
Python
410 lines
14 KiB
Python
|
# Copyright 2017 The Abseil Authors.
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
"""Contains Flag class - information about single command-line flag.
|
||
|
|
||
|
Do NOT import this module directly. Import the flags package and use the
|
||
|
aliases defined at the package level instead.
|
||
|
"""
|
||
|
|
||
|
from __future__ import absolute_import
|
||
|
from __future__ import division
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import functools
|
||
|
|
||
|
from absl.flags import _argument_parser
|
||
|
from absl.flags import _exceptions
|
||
|
from absl.flags import _helpers
|
||
|
|
||
|
|
||
|
@functools.total_ordering
|
||
|
class Flag(object):
|
||
|
"""Information about a command-line flag.
|
||
|
|
||
|
'Flag' objects define the following fields:
|
||
|
.name - the name for this flag;
|
||
|
.default - the default value for this flag;
|
||
|
.default_unparsed - the unparsed default value for this flag.
|
||
|
.default_as_str - default value as repr'd string, e.g., "'true'" (or None);
|
||
|
.value - the most recent parsed value of this flag; set by parse();
|
||
|
.help - a help string or None if no help is available;
|
||
|
.short_name - the single letter alias for this flag (or None);
|
||
|
.boolean - if 'true', this flag does not accept arguments;
|
||
|
.present - true if this flag was parsed from command line flags;
|
||
|
.parser - an ArgumentParser object;
|
||
|
.serializer - an ArgumentSerializer object;
|
||
|
.allow_override - the flag may be redefined without raising an error, and
|
||
|
newly defined flag overrides the old one.
|
||
|
.allow_override_cpp - use the flag from C++ if available; the flag
|
||
|
definition is replaced by the C++ flag after init;
|
||
|
.allow_hide_cpp - use the Python flag despite having a C++ flag with
|
||
|
the same name (ignore the C++ flag);
|
||
|
.using_default_value - the flag value has not been set by user;
|
||
|
.allow_overwrite - the flag may be parsed more than once without raising
|
||
|
an error, the last set value will be used;
|
||
|
.allow_using_method_names - whether this flag can be defined even if it has
|
||
|
a name that conflicts with a FlagValues method.
|
||
|
|
||
|
The only public method of a 'Flag' object is parse(), but it is
|
||
|
typically only called by a 'FlagValues' object. The parse() method is
|
||
|
a thin wrapper around the 'ArgumentParser' parse() method. The parsed
|
||
|
value is saved in .value, and the .present attribute is updated. If
|
||
|
this flag was already present, an Error is raised.
|
||
|
|
||
|
parse() is also called during __init__ to parse the default value and
|
||
|
initialize the .value attribute. This enables other python modules to
|
||
|
safely use flags even if the __main__ module neglects to parse the
|
||
|
command line arguments. The .present attribute is cleared after
|
||
|
__init__ parsing. If the default value is set to None, then the
|
||
|
__init__ parsing step is skipped and the .value attribute is
|
||
|
initialized to None.
|
||
|
|
||
|
Note: The default value is also presented to the user in the help
|
||
|
string, so it is important that it be a legal value for this flag.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, parser, serializer, name, default, help_string,
|
||
|
short_name=None, boolean=False, allow_override=False,
|
||
|
allow_override_cpp=False, allow_hide_cpp=False,
|
||
|
allow_overwrite=True, allow_using_method_names=False):
|
||
|
self.name = name
|
||
|
|
||
|
if not help_string:
|
||
|
help_string = '(no help available)'
|
||
|
|
||
|
self.help = help_string
|
||
|
self.short_name = short_name
|
||
|
self.boolean = boolean
|
||
|
self.present = 0
|
||
|
self.parser = parser
|
||
|
self.serializer = serializer
|
||
|
self.allow_override = allow_override
|
||
|
self.allow_override_cpp = allow_override_cpp
|
||
|
self.allow_hide_cpp = allow_hide_cpp
|
||
|
self.allow_overwrite = allow_overwrite
|
||
|
self.allow_using_method_names = allow_using_method_names
|
||
|
|
||
|
self.using_default_value = True
|
||
|
self._value = None
|
||
|
self.validators = []
|
||
|
if self.allow_hide_cpp and self.allow_override_cpp:
|
||
|
raise _exceptions.Error(
|
||
|
"Can't have both allow_hide_cpp (means use Python flag) and "
|
||
|
'allow_override_cpp (means use C++ flag after InitGoogle)')
|
||
|
|
||
|
self._set_default(default)
|
||
|
|
||
|
@property
|
||
|
def value(self):
|
||
|
return self._value
|
||
|
|
||
|
@value.setter
|
||
|
def value(self, value):
|
||
|
self._value = value
|
||
|
|
||
|
def __hash__(self):
|
||
|
return hash(id(self))
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
return self is other
|
||
|
|
||
|
def __lt__(self, other):
|
||
|
if isinstance(other, Flag):
|
||
|
return id(self) < id(other)
|
||
|
return NotImplemented
|
||
|
|
||
|
def _get_parsed_value_as_string(self, value):
|
||
|
"""Returns parsed flag value as string."""
|
||
|
if value is None:
|
||
|
return None
|
||
|
if self.serializer:
|
||
|
return repr(self.serializer.serialize(value))
|
||
|
if self.boolean:
|
||
|
if value:
|
||
|
return repr('true')
|
||
|
else:
|
||
|
return repr('false')
|
||
|
return repr(_helpers.str_or_unicode(value))
|
||
|
|
||
|
def parse(self, argument):
|
||
|
"""Parses string and sets flag value.
|
||
|
|
||
|
Args:
|
||
|
argument: str or the correct flag value type, argument to be parsed.
|
||
|
"""
|
||
|
if self.present and not self.allow_overwrite:
|
||
|
raise _exceptions.IllegalFlagValueError(
|
||
|
'flag --%s=%s: already defined as %s' % (
|
||
|
self.name, argument, self.value))
|
||
|
self.value = self._parse(argument)
|
||
|
self.present += 1
|
||
|
|
||
|
def _parse(self, argument):
|
||
|
"""Internal parse function.
|
||
|
|
||
|
It returns the parsed value, and does not modify class states.
|
||
|
|
||
|
Args:
|
||
|
argument: str or the correct flag value type, argument to be parsed.
|
||
|
|
||
|
Returns:
|
||
|
The parsed value.
|
||
|
"""
|
||
|
try:
|
||
|
return self.parser.parse(argument)
|
||
|
except (TypeError, ValueError) as e: # Recast as IllegalFlagValueError.
|
||
|
raise _exceptions.IllegalFlagValueError(
|
||
|
'flag --%s=%s: %s' % (self.name, argument, e))
|
||
|
|
||
|
def unparse(self):
|
||
|
self.value = self.default
|
||
|
self.using_default_value = True
|
||
|
self.present = 0
|
||
|
|
||
|
def serialize(self):
|
||
|
if self.value is None:
|
||
|
return ''
|
||
|
if self.boolean:
|
||
|
if self.value:
|
||
|
return '--%s' % self.name
|
||
|
else:
|
||
|
return '--no%s' % self.name
|
||
|
else:
|
||
|
if not self.serializer:
|
||
|
raise _exceptions.Error(
|
||
|
'Serializer not present for flag %s' % self.name)
|
||
|
return '--%s=%s' % (self.name, self.serializer.serialize(self.value))
|
||
|
|
||
|
def _set_default(self, value):
|
||
|
"""Changes the default value (and current value too) for this Flag."""
|
||
|
self.default_unparsed = value
|
||
|
if value is None:
|
||
|
self.default = None
|
||
|
else:
|
||
|
self.default = self._parse(value)
|
||
|
self.default_as_str = self._get_parsed_value_as_string(self.default)
|
||
|
if self.using_default_value:
|
||
|
self.value = self.default
|
||
|
|
||
|
def flag_type(self):
|
||
|
"""Returns a str that describes the type of the flag.
|
||
|
|
||
|
NOTE: we use strings, and not the types.*Type constants because
|
||
|
our flags can have more exotic types, e.g., 'comma separated list
|
||
|
of strings', 'whitespace separated list of strings', etc.
|
||
|
"""
|
||
|
return self.parser.flag_type()
|
||
|
|
||
|
def _create_xml_dom_element(self, doc, module_name, is_key=False):
|
||
|
"""Returns an XML element that contains this flag's information.
|
||
|
|
||
|
This is information that is relevant to all flags (e.g., name,
|
||
|
meaning, etc.). If you defined a flag that has some other pieces of
|
||
|
info, then please override _ExtraXMLInfo.
|
||
|
|
||
|
Please do NOT override this method.
|
||
|
|
||
|
Args:
|
||
|
doc: minidom.Document, the DOM document it should create nodes from.
|
||
|
module_name: str,, the name of the module that defines this flag.
|
||
|
is_key: boolean, True iff this flag is key for main module.
|
||
|
|
||
|
Returns:
|
||
|
A minidom.Element instance.
|
||
|
"""
|
||
|
element = doc.createElement('flag')
|
||
|
if is_key:
|
||
|
element.appendChild(_helpers.create_xml_dom_element(doc, 'key', 'yes'))
|
||
|
element.appendChild(_helpers.create_xml_dom_element(
|
||
|
doc, 'file', module_name))
|
||
|
# Adds flag features that are relevant for all flags.
|
||
|
element.appendChild(_helpers.create_xml_dom_element(doc, 'name', self.name))
|
||
|
if self.short_name:
|
||
|
element.appendChild(_helpers.create_xml_dom_element(
|
||
|
doc, 'short_name', self.short_name))
|
||
|
if self.help:
|
||
|
element.appendChild(_helpers.create_xml_dom_element(
|
||
|
doc, 'meaning', self.help))
|
||
|
# The default flag value can either be represented as a string like on the
|
||
|
# command line, or as a Python object. We serialize this value in the
|
||
|
# latter case in order to remain consistent.
|
||
|
if self.serializer and not isinstance(self.default, str):
|
||
|
if self.default is not None:
|
||
|
default_serialized = self.serializer.serialize(self.default)
|
||
|
else:
|
||
|
default_serialized = ''
|
||
|
else:
|
||
|
default_serialized = self.default
|
||
|
element.appendChild(_helpers.create_xml_dom_element(
|
||
|
doc, 'default', default_serialized))
|
||
|
element.appendChild(_helpers.create_xml_dom_element(
|
||
|
doc, 'current', self.value))
|
||
|
element.appendChild(_helpers.create_xml_dom_element(
|
||
|
doc, 'type', self.flag_type()))
|
||
|
# Adds extra flag features this flag may have.
|
||
|
for e in self._extra_xml_dom_elements(doc):
|
||
|
element.appendChild(e)
|
||
|
return element
|
||
|
|
||
|
def _extra_xml_dom_elements(self, doc):
|
||
|
"""Returns extra info about this flag in XML.
|
||
|
|
||
|
"Extra" means "not already included by _create_xml_dom_element above."
|
||
|
|
||
|
Args:
|
||
|
doc: minidom.Document, the DOM document it should create nodes from.
|
||
|
|
||
|
Returns:
|
||
|
A list of minidom.Element.
|
||
|
"""
|
||
|
# Usually, the parser knows the extra details about the flag, so
|
||
|
# we just forward the call to it.
|
||
|
return self.parser._custom_xml_dom_elements(doc) # pylint: disable=protected-access
|
||
|
|
||
|
|
||
|
class BooleanFlag(Flag):
|
||
|
"""Basic boolean flag.
|
||
|
|
||
|
Boolean flags do not take any arguments, and their value is either
|
||
|
True (1) or False (0). The false value is specified on the command
|
||
|
line by prepending the word 'no' to either the long or the short flag
|
||
|
name.
|
||
|
|
||
|
For example, if a Boolean flag was created whose long name was
|
||
|
'update' and whose short name was 'x', then this flag could be
|
||
|
explicitly unset through either --noupdate or --nox.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, name, default, help, short_name=None, **args): # pylint: disable=redefined-builtin
|
||
|
p = _argument_parser.BooleanParser()
|
||
|
super(BooleanFlag, self).__init__(
|
||
|
p, None, name, default, help, short_name, 1, **args)
|
||
|
|
||
|
|
||
|
class EnumFlag(Flag):
|
||
|
"""Basic enum flag; its value can be any string from list of enum_values."""
|
||
|
|
||
|
def __init__(self, name, default, help, enum_values, # pylint: disable=redefined-builtin
|
||
|
short_name=None, case_sensitive=True, **args):
|
||
|
p = _argument_parser.EnumParser(enum_values, case_sensitive)
|
||
|
g = _argument_parser.ArgumentSerializer()
|
||
|
super(EnumFlag, self).__init__(
|
||
|
p, g, name, default, help, short_name, **args)
|
||
|
self.help = '<%s>: %s' % ('|'.join(enum_values), self.help)
|
||
|
|
||
|
def _extra_xml_dom_elements(self, doc):
|
||
|
elements = []
|
||
|
for enum_value in self.parser.enum_values:
|
||
|
elements.append(_helpers.create_xml_dom_element(
|
||
|
doc, 'enum_value', enum_value))
|
||
|
return elements
|
||
|
|
||
|
|
||
|
class EnumClassFlag(Flag):
|
||
|
"""Basic enum flag; its value is an enum class's member."""
|
||
|
|
||
|
def __init__(self, name, default, help, enum_class, # pylint: disable=redefined-builtin
|
||
|
short_name=None, **args):
|
||
|
p = _argument_parser.EnumClassParser(enum_class)
|
||
|
g = _argument_parser.ArgumentSerializer()
|
||
|
super(EnumClassFlag, self).__init__(
|
||
|
p, g, name, default, help, short_name, **args)
|
||
|
self.help = '<%s>: %s' % ('|'.join(enum_class.__members__), self.help)
|
||
|
|
||
|
def _extra_xml_dom_elements(self, doc):
|
||
|
elements = []
|
||
|
for enum_value in self.parser.enum_class.__members__.keys():
|
||
|
elements.append(_helpers.create_xml_dom_element(
|
||
|
doc, 'enum_value', enum_value))
|
||
|
return elements
|
||
|
|
||
|
|
||
|
class MultiFlag(Flag):
|
||
|
"""A flag that can appear multiple time on the command-line.
|
||
|
|
||
|
The value of such a flag is a list that contains the individual values
|
||
|
from all the appearances of that flag on the command-line.
|
||
|
|
||
|
See the __doc__ for Flag for most behavior of this class. Only
|
||
|
differences in behavior are described here:
|
||
|
|
||
|
* The default value may be either a single value or a list of values.
|
||
|
A single value is interpreted as the [value] singleton list.
|
||
|
|
||
|
* The value of the flag is always a list, even if the option was
|
||
|
only supplied once, and even if the default value is a single
|
||
|
value
|
||
|
"""
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
super(MultiFlag, self).__init__(*args, **kwargs)
|
||
|
self.help += ';\n repeat this option to specify a list of values'
|
||
|
|
||
|
def parse(self, arguments):
|
||
|
"""Parses one or more arguments with the installed parser.
|
||
|
|
||
|
Args:
|
||
|
arguments: a single argument or a list of arguments (typically a
|
||
|
list of default values); a single argument is converted
|
||
|
internally into a list containing one item.
|
||
|
"""
|
||
|
new_values = self._parse(arguments)
|
||
|
if self.present:
|
||
|
self.value.extend(new_values)
|
||
|
else:
|
||
|
self.value = new_values
|
||
|
self.present += len(new_values)
|
||
|
|
||
|
def _parse(self, arguments):
|
||
|
if not isinstance(arguments, list):
|
||
|
# Default value may be a list of values. Most other arguments
|
||
|
# will not be, so convert them into a single-item list to make
|
||
|
# processing simpler below.
|
||
|
arguments = [arguments]
|
||
|
|
||
|
return [super(MultiFlag, self)._parse(item) for item in arguments]
|
||
|
|
||
|
def serialize(self):
|
||
|
"""See base class."""
|
||
|
if not self.serializer:
|
||
|
raise _exceptions.Error(
|
||
|
'Serializer not present for flag %s' % self.name)
|
||
|
if self.value is None:
|
||
|
return ''
|
||
|
|
||
|
s = ''
|
||
|
|
||
|
multi_value = self.value
|
||
|
|
||
|
for self.value in multi_value:
|
||
|
if s: s += ' '
|
||
|
s += Flag.serialize(self)
|
||
|
|
||
|
self.value = multi_value
|
||
|
|
||
|
return s
|
||
|
|
||
|
def flag_type(self):
|
||
|
"""See base class."""
|
||
|
return 'multi ' + self.parser.flag_type()
|
||
|
|
||
|
def _extra_xml_dom_elements(self, doc):
|
||
|
elements = []
|
||
|
if hasattr(self.parser, 'enum_values'):
|
||
|
for enum_value in self.parser.enum_values:
|
||
|
elements.append(_helpers.create_xml_dom_element(
|
||
|
doc, 'enum_value', enum_value))
|
||
|
return elements
|