aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/py/abseil/absl/flags/_flagvalues.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/py/abseil/absl/flags/_flagvalues.py')
-rw-r--r--third_party/py/abseil/absl/flags/_flagvalues.py1244
1 files changed, 1244 insertions, 0 deletions
diff --git a/third_party/py/abseil/absl/flags/_flagvalues.py b/third_party/py/abseil/absl/flags/_flagvalues.py
new file mode 100644
index 0000000000..61a5bb4566
--- /dev/null
+++ b/third_party/py/abseil/absl/flags/_flagvalues.py
@@ -0,0 +1,1244 @@
+# 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.
+
+"""Defines the FlagValues class - registry of 'Flag' objects.
+
+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 itertools
+import logging
+import os
+import sys
+import warnings
+from xml.dom import minidom
+
+from absl.flags import _exceptions
+from absl.flags import _flag
+from absl.flags import _helpers
+import six
+
+# Add flagvalues module to disclaimed module ids.
+_helpers.disclaim_module_ids.add(id(sys.modules[__name__]))
+
+
+class FlagValues(object):
+ """Registry of 'Flag' objects.
+
+ A 'FlagValues' can then scan command line arguments, passing flag
+ arguments through to the 'Flag' objects that it owns. It also
+ provides easy access to the flag values. Typically only one
+ 'FlagValues' object is needed by an application: flags.FLAGS
+
+ This class is heavily overloaded:
+
+ 'Flag' objects are registered via __setitem__:
+ FLAGS['longname'] = x # register a new flag
+
+ The .value attribute of the registered 'Flag' objects can be accessed
+ as attributes of this 'FlagValues' object, through __getattr__. Both
+ the long and short name of the original 'Flag' objects can be used to
+ access its value:
+ FLAGS.longname # parsed flag value
+ FLAGS.x # parsed flag value (short name)
+
+ Command line arguments are scanned and passed to the registered 'Flag'
+ objects through the __call__ method. Unparsed arguments, including
+ argv[0] (e.g. the program name) are returned.
+ argv = FLAGS(sys.argv) # scan command line arguments
+
+ The original registered Flag objects can be retrieved through the use
+ of the dictionary-like operator, __getitem__:
+ x = FLAGS['longname'] # access the registered Flag object
+
+ The str() operator of a 'FlagValues' object provides help for all of
+ the registered 'Flag' objects.
+ """
+
+ # A note on collections.abc.Mapping:
+ # FlagValues defines __getitem__, __iter__, and __len__. It makes perfect
+ # sense to let it be a collections.abc.Mapping class. However, we are not
+ # able to do so. The mixin methods, e.g. keys, values, are not uncommon flag
+ # names. Those flag values would not be accessible via the FLAGS.xxx form.
+
+ def __init__(self):
+ # Since everything in this class is so heavily overloaded, the only
+ # way of defining and using fields is to access __dict__ directly.
+
+ # Dictionary: flag name (string) -> Flag object.
+ self.__dict__['__flags'] = {}
+
+ # Set: name of hidden flag (string).
+ # Holds flags that should not be directly accessible from Python.
+ self.__dict__['__hiddenflags'] = set()
+
+ # Dictionary: module name (string) -> list of Flag objects that are defined
+ # by that module.
+ self.__dict__['__flags_by_module'] = {}
+ # Dictionary: module id (int) -> list of Flag objects that are defined by
+ # that module.
+ self.__dict__['__flags_by_module_id'] = {}
+ # Dictionary: module name (string) -> list of Flag objects that are
+ # key for that module.
+ self.__dict__['__key_flags_by_module'] = {}
+
+ # Bool: True if flags were parsed.
+ self.__dict__['__flags_parsed'] = False
+
+ # Bool: True if unparse_flags() was called.
+ self.__dict__['__unparse_flags_called'] = False
+
+ # None or Method(name, value) to call from __setattr__ for an unknown flag.
+ self.__dict__['__set_unknown'] = None
+
+ # A set of banned flag names. This is to prevent users from accidentally
+ # defining a flag that has the same name as a method on this class.
+ # Users can still allow defining the flag by passing
+ # allow_using_method_names=True in DEFINE_xxx functions.
+ self.__dict__['__banned_flag_names'] = frozenset(dir(FlagValues))
+
+ # Bool: Whether to use GNU style scanning.
+ self.__dict__['__use_gnu_getopt'] = True
+
+ # Bool: Whether use_gnu_getopt has been explicitly set by the user.
+ self.__dict__['__use_gnu_getopt_explicitly_set'] = False
+
+ # Function: Takes a flag name as parameter, returns a tuple
+ # (is_retired, type_is_bool).
+ self.__dict__['__is_retired_flag_func'] = None
+
+ def set_gnu_getopt(self, gnu_getopt=True):
+ """Sets whether or not to use GNU style scanning.
+
+ GNU style allows mixing of flag and non-flag arguments. See
+ http://docs.python.org/library/getopt.html#getopt.gnu_getopt
+
+ Args:
+ gnu_getopt: bool, whether or not to use GNU style scanning.
+ """
+ self.__dict__['__use_gnu_getopt'] = gnu_getopt
+ self.__dict__['__use_gnu_getopt_explicitly_set'] = True
+
+ def is_gnu_getopt(self):
+ return self.__dict__['__use_gnu_getopt']
+
+ def _flags(self):
+ return self.__dict__['__flags']
+
+ def flags_by_module_dict(self):
+ """Returns the dictionary of module_name -> list of defined flags.
+
+ Returns:
+ A dictionary. Its keys are module names (strings). Its values
+ are lists of Flag objects.
+ """
+ return self.__dict__['__flags_by_module']
+
+ def flags_by_module_id_dict(self):
+ """Returns the dictionary of module_id -> list of defined flags.
+
+ Returns:
+ A dictionary. Its keys are module IDs (ints). Its values
+ are lists of Flag objects.
+ """
+ return self.__dict__['__flags_by_module_id']
+
+ def key_flags_by_module_dict(self):
+ """Returns the dictionary of module_name -> list of key flags.
+
+ Returns:
+ A dictionary. Its keys are module names (strings). Its values
+ are lists of Flag objects.
+ """
+ return self.__dict__['__key_flags_by_module']
+
+ def register_flag_by_module(self, module_name, flag):
+ """Records the module that defines a specific flag.
+
+ We keep track of which flag is defined by which module so that we
+ can later sort the flags by module.
+
+ Args:
+ module_name: str, the name of a Python module.
+ flag: Flag, the Flag instance that is key to the module.
+ """
+ flags_by_module = self.flags_by_module_dict()
+ flags_by_module.setdefault(module_name, []).append(flag)
+
+ def register_flag_by_module_id(self, module_id, flag):
+ """Records the module that defines a specific flag.
+
+ Args:
+ module_id: int, the ID of the Python module.
+ flag: Flag, the Flag instance that is key to the module.
+ """
+ flags_by_module_id = self.flags_by_module_id_dict()
+ flags_by_module_id.setdefault(module_id, []).append(flag)
+
+ def register_key_flag_for_module(self, module_name, flag):
+ """Specifies that a flag is a key flag for a module.
+
+ Args:
+ module_name: str, the name of a Python module.
+ flag: Flag, the Flag instance that is key to the module.
+ """
+ key_flags_by_module = self.key_flags_by_module_dict()
+ # The list of key flags for the module named module_name.
+ key_flags = key_flags_by_module.setdefault(module_name, [])
+ # Add flag, but avoid duplicates.
+ if flag not in key_flags:
+ key_flags.append(flag)
+
+ def _flag_is_registered(self, flag_obj):
+ """Checks whether a Flag object is registered under long name or short name.
+
+ Args:
+ flag_obj: Flag, the Flag instance to check for.
+
+ Returns:
+ bool, True iff flag_obj is registered under long name or short name.
+ """
+ flag_dict = self._flags()
+ # Check whether flag_obj is registered under its long name.
+ name = flag_obj.name
+ if flag_dict.get(name, None) == flag_obj:
+ return True
+ # Check whether flag_obj is registered under its short name.
+ short_name = flag_obj.short_name
+ if (short_name is not None and
+ flag_dict.get(short_name, None) == flag_obj):
+ return True
+ return False
+
+ def _cleanup_unregistered_flag_from_module_dicts(self, flag_obj):
+ """Cleans up unregistered flags from all module -> [flags] dictionaries.
+
+ If flag_obj is registered under either its long name or short name, it
+ won't be removed from the dictionaries.
+
+ Args:
+ flag_obj: Flag, the Flag instance to clean up for.
+ """
+ if self._flag_is_registered(flag_obj):
+ return
+ for flags_by_module_dict in (self.flags_by_module_dict(),
+ self.flags_by_module_id_dict(),
+ self.key_flags_by_module_dict()):
+ for flags_in_module in six.itervalues(flags_by_module_dict):
+ # While (as opposed to if) takes care of multiple occurrences of a
+ # flag in the list for the same module.
+ while flag_obj in flags_in_module:
+ flags_in_module.remove(flag_obj)
+
+ def _get_flags_defined_by_module(self, module):
+ """Returns the list of flags defined by a module.
+
+ Args:
+ module: module|str, the module to get flags from.
+
+ Returns:
+ [Flag], a new list of Flag instances. Caller may update this list as
+ desired: none of those changes will affect the internals of this
+ FlagValue instance.
+ """
+ if not isinstance(module, str):
+ module = module.__name__
+
+ return list(self.flags_by_module_dict().get(module, []))
+
+ def get_key_flags_for_module(self, module):
+ """Returns the list of key flags for a module.
+
+ Args:
+ module: module|str, the module to get key flags from.
+
+ Returns:
+ [Flag], a new list of Flag instances. Caller may update this list as
+ desired: none of those changes will affect the internals of this
+ FlagValue instance.
+ """
+ if not isinstance(module, str):
+ module = module.__name__
+
+ # Any flag is a key flag for the module that defined it. NOTE:
+ # key_flags is a fresh list: we can update it without affecting the
+ # internals of this FlagValues object.
+ key_flags = self._get_flags_defined_by_module(module)
+
+ # Take into account flags explicitly declared as key for a module.
+ for flag in self.key_flags_by_module_dict().get(module, []):
+ if flag not in key_flags:
+ key_flags.append(flag)
+ return key_flags
+
+ def find_module_defining_flag(self, flagname, default=None):
+ """Return the name of the module defining this flag, or default.
+
+ Args:
+ flagname: str, name of the flag to lookup.
+ default: Value to return if flagname is not defined. Defaults
+ to None.
+
+ Returns:
+ The name of the module which registered the flag with this name.
+ If no such module exists (i.e. no flag with this name exists),
+ we return default.
+ """
+ registered_flag = self._flags().get(flagname)
+ if registered_flag is None:
+ return default
+ for module, flags in six.iteritems(self.flags_by_module_dict()):
+ for flag in flags:
+ # It must compare the flag with the one in _flags. This is because a
+ # flag might be overridden only for its long name (or short name),
+ # and only its short name (or long name) is considered registered.
+ if (flag.name == registered_flag.name and
+ flag.short_name == registered_flag.short_name):
+ return module
+ return default
+
+ def find_module_id_defining_flag(self, flagname, default=None):
+ """Return the ID of the module defining this flag, or default.
+
+ Args:
+ flagname: str, name of the flag to lookup.
+ default: Value to return if flagname is not defined. Defaults
+ to None.
+
+ Returns:
+ The ID of the module which registered the flag with this name.
+ If no such module exists (i.e. no flag with this name exists),
+ we return default.
+ """
+ registered_flag = self._flags().get(flagname)
+ if registered_flag is None:
+ return default
+ for module_id, flags in six.iteritems(self.flags_by_module_id_dict()):
+ for flag in flags:
+ # It must compare the flag with the one in _flags. This is because a
+ # flag might be overridden only for its long name (or short name),
+ # and only its short name (or long name) is considered registered.
+ if (flag.name == registered_flag.name and
+ flag.short_name == registered_flag.short_name):
+ return module_id
+ return default
+
+ def _register_unknown_flag_setter(self, setter):
+ """Allow set default values for undefined flags.
+
+ Args:
+ setter: Method(name, value) to call to __setattr__ an unknown flag.
+ Must raise NameError or ValueError for invalid name/value.
+ """
+ self.__dict__['__set_unknown'] = setter
+
+ def _set_unknown_flag(self, name, value):
+ """Returns value if setting flag |name| to |value| returned True.
+
+ Args:
+ name: str, name of the flag to set.
+ value: Value to set.
+
+ Returns:
+ Flag value on successful call.
+
+ Raises:
+ UnrecognizedFlagError
+ IllegalFlagValueError
+ """
+ setter = self.__dict__['__set_unknown']
+ if setter:
+ try:
+ setter(name, value)
+ return value
+ except (TypeError, ValueError): # Flag value is not valid.
+ raise _exceptions.IllegalFlagValueError(
+ '"{1}" is not valid for --{0}' .format(name, value))
+ except NameError: # Flag name is not valid.
+ pass
+ raise _exceptions.UnrecognizedFlagError(name, value)
+
+ def append_flag_values(self, flag_values):
+ """Appends flags registered in another FlagValues instance.
+
+ Args:
+ flag_values: FlagValues, the FlagValues instance from which to copy flags.
+ """
+ for flag_name, flag in six.iteritems(flag_values._flags()): # pylint: disable=protected-access
+ # Each flags with short_name appears here twice (once under its
+ # normal name, and again with its short name). To prevent
+ # problems (DuplicateFlagError) with double flag registration, we
+ # perform a check to make sure that the entry we're looking at is
+ # for its normal name.
+ if flag_name == flag.name:
+ try:
+ self[flag_name] = flag
+ except _exceptions.DuplicateFlagError:
+ raise _exceptions.DuplicateFlagError.from_flag(
+ flag_name, self, other_flag_values=flag_values)
+
+ def remove_flag_values(self, flag_values):
+ """Remove flags that were previously appended from another FlagValues.
+
+ Args:
+ flag_values: FlagValues, the FlagValues instance containing flags to
+ remove.
+ """
+ for flag_name in flag_values:
+ self.__delattr__(flag_name)
+
+ def __setitem__(self, name, flag):
+ """Registers a new flag variable."""
+ fl = self._flags()
+ if not isinstance(flag, _flag.Flag):
+ raise _exceptions.IllegalFlagValueError(flag)
+ if str is bytes and isinstance(name, unicode):
+ # When using Python 2 with unicode_literals, allow it but encode it
+ # into the bytes type we require.
+ name = name.encode('utf-8')
+ if not isinstance(name, type('')):
+ raise _exceptions.Error('Flag name must be a string')
+ if not name:
+ raise _exceptions.Error('Flag name cannot be empty')
+ self._check_method_name_conflicts(name, flag)
+ if name in fl and not flag.allow_override and not fl[name].allow_override:
+ module, module_name = _helpers.get_calling_module_object_and_name()
+ if (self.find_module_defining_flag(name) == module_name and
+ id(module) != self.find_module_id_defining_flag(name)):
+ # If the flag has already been defined by a module with the same name,
+ # but a different ID, we can stop here because it indicates that the
+ # module is simply being imported a subsequent time.
+ return
+ raise _exceptions.DuplicateFlagError.from_flag(name, self)
+ short_name = flag.short_name
+ # If a new flag overrides an old one, we need to cleanup the old flag's
+ # modules if it's not registered.
+ flags_to_cleanup = set()
+ if short_name is not None:
+ if (short_name in fl and not flag.allow_override and
+ not fl[short_name].allow_override):
+ raise _exceptions.DuplicateFlagError.from_flag(short_name, self)
+ if short_name in fl and fl[short_name] != flag:
+ flags_to_cleanup.add(fl[short_name])
+ fl[short_name] = flag
+ if (name not in fl # new flag
+ or fl[name].using_default_value
+ or not flag.using_default_value):
+ if name in fl and fl[name] != flag:
+ flags_to_cleanup.add(fl[name])
+ fl[name] = flag
+ for f in flags_to_cleanup:
+ self._cleanup_unregistered_flag_from_module_dicts(f)
+
+ def __dir__(self):
+ """Returns list of names of all defined flags.
+
+ Useful for TAB-completion in ipython.
+
+ Returns:
+ [str], a list of names of all defined flags.
+ """
+ return sorted(self.__dict__['__flags'])
+
+ def __getitem__(self, name):
+ """Returns the Flag object for the flag --name."""
+ return self._flags()[name]
+
+ def _hide_flag(self, name):
+ """Marks the flag --name as hidden."""
+ self.__dict__['__hiddenflags'].add(name)
+
+ # This exists for legacy reasons, and will be removed in the future.
+ def _is_unparsed_flag_access_allowed(self, name):
+ """Determine whether to allow unparsed flag access or not."""
+ del name
+ return False
+
+ def __getattr__(self, name):
+ """Retrieves the 'value' attribute of the flag --name."""
+ fl = self._flags()
+ if name not in fl:
+ raise AttributeError(name)
+ if name in self.__dict__['__hiddenflags']:
+ raise AttributeError(name)
+
+ if self.__dict__['__flags_parsed'] or fl[name].present:
+ return fl[name].value
+ else:
+ error_message = (
+ 'Trying to access flag --%s before flags were parsed.' % name)
+ if self._is_unparsed_flag_access_allowed(name):
+ # Print warning to stderr. Messages in logs are often ignored/unnoticed.
+ warnings.warn(
+ error_message + ' This will raise an exception in the future.',
+ RuntimeWarning,
+ stacklevel=2)
+ # Force logging.exception() to behave realistically, but don't propagate
+ # exception up. Allow flag value to be returned (for now).
+ try:
+ raise _exceptions.UnparsedFlagAccessError(error_message)
+ except _exceptions.UnparsedFlagAccessError:
+ logging.exception(error_message)
+ return fl[name].value
+ else:
+ if six.PY2:
+ # In Python 2, hasattr returns False if getattr raises any exception.
+ # That means if someone calls hasattr(FLAGS, 'flag'), it returns False
+ # instead of raises UnparsedFlagAccessError even if --flag is already
+ # defined. To make the error more visible, the best we can do is to
+ # log an error message before raising the exception.
+ # Don't log a full stacktrace here since that makes other callers
+ # get too much noise.
+ logging.error(error_message)
+ raise _exceptions.UnparsedFlagAccessError(error_message)
+
+ def __setattr__(self, name, value):
+ """Sets the 'value' attribute of the flag --name."""
+ fl = self._flags()
+ if name in self.__dict__['__hiddenflags']:
+ raise AttributeError(name)
+ if name not in fl:
+ return self._set_unknown_flag(name, value)
+ fl[name].value = value
+ self._assert_validators(fl[name].validators)
+ fl[name].using_default_value = False
+ return value
+
+ def _assert_all_validators(self):
+ all_validators = set()
+ for flag in six.itervalues(self._flags()):
+ for validator in flag.validators:
+ all_validators.add(validator)
+ self._assert_validators(all_validators)
+
+ def _assert_validators(self, validators):
+ """Asserts if all validators in the list are satisfied.
+
+ It asserts validators in the order they were created.
+
+ Args:
+ validators: Iterable(validators.Validator), validators to be
+ verified.
+ Raises:
+ AttributeError: Raised if validators work with a non-existing flag.
+ IllegalFlagValueError: Raised if validation fails for at least one
+ validator.
+ """
+ for validator in sorted(
+ validators, key=lambda validator: validator.insertion_index):
+ try:
+ validator.verify(self)
+ except _exceptions.ValidationError as e:
+ message = validator.print_flags_with_values(self)
+ raise _exceptions.IllegalFlagValueError('%s: %s' % (message, str(e)))
+
+ def __delattr__(self, flag_name):
+ """Deletes a previously-defined flag from a flag object.
+
+ This method makes sure we can delete a flag by using
+
+ del FLAGS.<flag_name>
+
+ E.g.,
+
+ flags.DEFINE_integer('foo', 1, 'Integer flag.')
+ del flags.FLAGS.foo
+
+ If a flag is also registered by its the other name (long name or short
+ name), the other name won't be deleted.
+
+ Args:
+ flag_name: str, the name of the flag to be deleted.
+
+ Raises:
+ AttributeError: Raised when there is no registered flag named flag_name.
+ """
+ fl = self._flags()
+ if flag_name not in fl:
+ raise AttributeError(flag_name)
+
+ flag_obj = fl[flag_name]
+ del fl[flag_name]
+
+ self._cleanup_unregistered_flag_from_module_dicts(flag_obj)
+
+ def set_default(self, name, value):
+ """Changes the default value of the named flag object.
+
+ The flag's current value is also updated if the flag is currently using
+ the default value, i.e. not specified in the command line, and not set
+ by FLAGS.name = value.
+
+ Args:
+ name: str, the name of the flag to modify.
+ value: The new default value.
+
+ Raises:
+ UnrecognizedFlagError: Raised when there is no registered flag named name.
+ IllegalFlagValueError: Raised when value is not valid.
+ """
+ fl = self._flags()
+ if name not in fl:
+ self._set_unknown_flag(name, value)
+ return
+ fl[name]._set_default(value) # pylint: disable=protected-access
+ self._assert_validators(fl[name].validators)
+
+ def __contains__(self, name):
+ """Returns True if name is a value (flag) in the dict."""
+ return name in self._flags()
+
+ def __len__(self):
+ return len(self.__dict__['__flags'])
+
+ def __iter__(self):
+ return iter(self._flags())
+
+ def __call__(self, argv):
+ """Parses flags from argv; stores parsed flags into this FlagValues object.
+
+ All unparsed arguments are returned.
+
+ Args:
+ argv: a tuple/list of strings.
+
+ Returns:
+ The list of arguments not parsed as options, including argv[0].
+
+ Raises:
+ Error: Raised on any parsing error.
+ TypeError: Raised on passing wrong type of arguments.
+ ValueError: Raised on flag value parsing error.
+ """
+ if _helpers.is_bytes_or_string(argv):
+ raise TypeError(
+ 'argv should be a tuple/list of strings, not bytes or string.')
+ if not argv:
+ raise ValueError(
+ 'argv cannot be an empty list, and must contain the program name as '
+ 'the first element.')
+
+ # This pre parses the argv list for --flagfile=<> options.
+ program_name = argv[0]
+ args = self.read_flags_from_files(argv[1:], force_gnu=False)
+
+ # Parse the arguments.
+ unknown_flags, unparsed_args, undefok = self._parse_args(args)
+
+ # Handle unknown flags by raising UnrecognizedFlagError.
+ # Note some users depend on us raising this particular error.
+ for name, value in unknown_flags:
+ if name in undefok:
+ continue
+
+ suggestions = _helpers.get_flag_suggestions(name, list(self))
+ raise _exceptions.UnrecognizedFlagError(
+ name, value, suggestions=suggestions)
+
+ self.mark_as_parsed()
+ self._assert_all_validators()
+ return [program_name] + unparsed_args
+
+ def _set_is_retired_flag_func(self, is_retired_flag_func):
+ """Sets a function for checking retired flags.
+
+ Do not use it. This is a private absl API used to check retired flags
+ registered by the absl C++ flags library.
+
+ Args:
+ is_retired_flag_func: Callable(str) -> (bool, bool), a function takes flag
+ name as parameter, returns a tuple (is_retired, type_is_bool).
+ """
+ self.__dict__['__is_retired_flag_func'] = is_retired_flag_func
+
+ def _parse_args(self, args):
+ """Helper function to do the main argument parsing.
+
+ This function goes through args and does the bulk of the flag parsing.
+ It will find the corresponding flag in our flag dictionary, and call its
+ .parse() method on the flag value.
+
+ Args:
+ args: [str], a list of strings with the arguments to parse.
+
+ Returns:
+ A tuple with the following:
+ unknown_flags: List of (flag name, arg) for flags we don't know about.
+ unparsed_args: List of arguments we did not parse.
+ undefok: Set of flags that were given via --undefok.
+
+ Raises:
+ Error: Raised on any parsing error.
+ ValueError: Raised on flag value parsing error.
+ """
+ unknown_flags, unparsed_args, undefok = [], [], set()
+ retired_flag_func = self.__dict__['__is_retired_flag_func']
+
+ flag_dict = self._flags()
+ args = iter(args)
+ for arg in args:
+ value = None
+
+ def get_value():
+ # pylint: disable=cell-var-from-loop
+ try:
+ return next(args) if value is None else value
+ except StopIteration:
+ raise _exceptions.Error('Missing value for flag ' + arg) # pylint: disable=undefined-loop-variable
+
+ if not arg.startswith('-'):
+ # A non-argument: default is break, GNU is skip.
+ unparsed_args.append(arg)
+ if self.is_gnu_getopt():
+ continue
+ else:
+ break
+
+ if arg == '--':
+ break
+
+ # At this point, arg must start with '-'.
+ if arg.startswith('--'):
+ arg_without_dashes = arg[2:]
+ else:
+ arg_without_dashes = arg[1:]
+
+ if '=' in arg_without_dashes:
+ name, value = arg_without_dashes.split('=', 1)
+ else:
+ name, value = arg_without_dashes, None
+
+ if not name:
+ # The argument is all dashes (including one dash).
+ unparsed_args.append(arg)
+ if self.is_gnu_getopt():
+ continue
+ else:
+ break
+
+ # --undefok is a special case.
+ if name == 'undefok':
+ value = get_value()
+ undefok.update(v.strip() for v in value.split(','))
+ undefok.update('no' + v.strip() for v in value.split(','))
+ continue
+
+ flag = flag_dict.get(name)
+ if flag:
+ if flag.boolean and value is None:
+ value = 'true'
+ else:
+ value = get_value()
+ elif name.startswith('no') and len(name) > 2:
+ # Boolean flags can take the form of --noflag, with no value.
+ noflag = flag_dict.get(name[2:])
+ if noflag and noflag.boolean:
+ if value is not None:
+ raise ValueError(arg + ' does not take an argument')
+ flag = noflag
+ value = 'false'
+
+ if retired_flag_func and not flag:
+ is_retired, is_bool = retired_flag_func(name)
+
+ # If we didn't recognize that flag, but it starts with
+ # "no" then maybe it was a boolean flag specified in the
+ # --nofoo form.
+ if not is_retired and name.startswith('no'):
+ is_retired, is_bool = retired_flag_func(name[2:])
+ is_retired = is_retired and is_bool
+
+ if is_retired:
+ if not is_bool and value is None:
+ # This happens when a non-bool retired flag is specified
+ # in format of "--flag value".
+ get_value()
+ logging.error('Flag "%s" is retired and should no longer '
+ 'be specified. See go/totw/90.', name)
+ continue
+
+ if flag:
+ flag.parse(value)
+ flag.using_default_value = False
+ else:
+ unknown_flags.append((name, arg))
+
+ unparsed_args.extend(list(args))
+ return unknown_flags, unparsed_args, undefok
+
+ def is_parsed(self):
+ """Returns whether flags were parsed."""
+ return self.__dict__['__flags_parsed']
+
+ def mark_as_parsed(self):
+ """Explicitly marks flags as parsed.
+
+ Use this when the caller knows that this FlagValues has been parsed as if
+ a __call__() invocation has happened. This is only a public method for
+ use by things like appcommands which do additional command like parsing.
+ """
+ self.__dict__['__flags_parsed'] = True
+
+ def unparse_flags(self):
+ """Unparses all flags to the point before any FLAGS(argv) was called."""
+ for f in self._flags().values():
+ f.unparse()
+ # We log this message before marking flags as unparsed to avoid a
+ # problem when the logging library causes flags access.
+ logging.info('unparse_flags() called; flags access will now raise errors.')
+ self.__dict__['__flags_parsed'] = False
+ self.__dict__['__unparse_flags_called'] = True
+
+ def flag_values_dict(self):
+ """Returns a dictionary that maps flag names to flag values."""
+ return {name: flag.value for name, flag in six.iteritems(self._flags())}
+
+ def __str__(self):
+ """Returns a help string for all known flags."""
+ return self.get_help()
+
+ def get_help(self, prefix='', include_special_flags=True):
+ """Returns a help string for all known flags.
+
+ Args:
+ prefix: str, per-line output prefix.
+ include_special_flags: bool, whether to include description of
+ _SPECIAL_FLAGS, i.e. --flagfile and --undefok.
+
+ Returns:
+ str, formatted help message.
+ """
+ helplist = []
+
+ flags_by_module = self.flags_by_module_dict()
+ if flags_by_module:
+ modules = sorted(flags_by_module)
+
+ # Print the help for the main module first, if possible.
+ main_module = sys.argv[0]
+ if main_module in modules:
+ modules.remove(main_module)
+ modules = [main_module] + modules
+
+ for module in modules:
+ self._render_our_module_flags(module, helplist, prefix)
+ if include_special_flags:
+ self._render_module_flags(
+ 'absl.flags',
+ _helpers.SPECIAL_FLAGS._flags().values(), # pylint: disable=protected-access
+ helplist,
+ prefix)
+ else:
+ # Just print one long list of flags.
+ values = six.itervalues(self._flags())
+ if include_special_flags:
+ values = itertools.chain(
+ values, six.itervalues(_helpers.SPECIAL_FLAGS._flags())) # pylint: disable=protected-access
+ self._render_flag_list(values, helplist, prefix)
+
+ return '\n'.join(helplist)
+
+ def _render_module_flags(self, module, flags, output_lines, prefix=''):
+ """Returns a help string for a given module."""
+ if not isinstance(module, str):
+ module = module.__name__
+ output_lines.append('\n%s%s:' % (prefix, module))
+ self._render_flag_list(flags, output_lines, prefix + ' ')
+
+ def _render_our_module_flags(self, module, output_lines, prefix=''):
+ """Returns a help string for a given module."""
+ flags = self._get_flags_defined_by_module(module)
+ if flags:
+ self._render_module_flags(module, flags, output_lines, prefix)
+
+ def _render_our_module_key_flags(self, module, output_lines, prefix=''):
+ """Returns a help string for the key flags of a given module.
+
+ Args:
+ module: module|str, the module to render key flags for.
+ output_lines: [str], a list of strings. The generated help message
+ lines will be appended to this list.
+ prefix: str, a string that is prepended to each generated help line.
+ """
+ key_flags = self.get_key_flags_for_module(module)
+ if key_flags:
+ self._render_module_flags(module, key_flags, output_lines, prefix)
+
+ def module_help(self, module):
+ """Describes the key flags of a module.
+
+ Args:
+ module: module|str, the module to describe the key flags for.
+
+ Returns:
+ str, describing the key flags of a module.
+ """
+ helplist = []
+ self._render_our_module_key_flags(module, helplist)
+ return '\n'.join(helplist)
+
+ def main_module_help(self):
+ """Describes the key flags of the main module.
+
+ Returns:
+ str, describing the key flags of the main module.
+ """
+ return self.module_help(sys.argv[0])
+
+ def _render_flag_list(self, flaglist, output_lines, prefix=' '):
+ fl = self._flags()
+ special_fl = _helpers.SPECIAL_FLAGS._flags() # pylint: disable=protected-access
+ flaglist = [(flag.name, flag) for flag in flaglist]
+ flaglist.sort()
+ flagset = {}
+ for (name, flag) in flaglist:
+ # It's possible this flag got deleted or overridden since being
+ # registered in the per-module flaglist. Check now against the
+ # canonical source of current flag information, the _flags.
+ if fl.get(name, None) != flag and special_fl.get(name, None) != flag:
+ # a different flag is using this name now
+ continue
+ # only print help once
+ if flag in flagset: continue
+ flagset[flag] = 1
+ flaghelp = ''
+ if flag.short_name: flaghelp += '-%s,' % flag.short_name
+ if flag.boolean:
+ flaghelp += '--[no]%s:' % flag.name
+ else:
+ flaghelp += '--%s:' % flag.name
+ flaghelp += ' '
+ if flag.help:
+ flaghelp += flag.help
+ flaghelp = _helpers.text_wrap(
+ flaghelp, indent=prefix+' ', firstline_indent=prefix)
+ if flag.default_as_str:
+ flaghelp += '\n'
+ flaghelp += _helpers.text_wrap(
+ '(default: %s)' % flag.default_as_str, indent=prefix+' ')
+ if flag.parser.syntactic_help:
+ flaghelp += '\n'
+ flaghelp += _helpers.text_wrap(
+ '(%s)' % flag.parser.syntactic_help, indent=prefix+' ')
+ output_lines.append(flaghelp)
+
+ def get_flag_value(self, name, default): # pylint: disable=invalid-name
+ """Returns the value of a flag (if not None) or a default value.
+
+ Args:
+ name: str, the name of a flag.
+ default: Default value to use if the flag value is None.
+
+ Returns:
+ Requested flag value or default.
+ """
+
+ value = self.__getattr__(name)
+ if value is not None: # Can't do if not value, b/c value might be '0' or ""
+ return value
+ else:
+ return default
+
+ def _is_flag_file_directive(self, flag_string):
+ """Checks whether flag_string contain a --flagfile=<foo> directive."""
+ if isinstance(flag_string, type('')):
+ if flag_string.startswith('--flagfile='):
+ return 1
+ elif flag_string == '--flagfile':
+ return 1
+ elif flag_string.startswith('-flagfile='):
+ return 1
+ elif flag_string == '-flagfile':
+ return 1
+ else:
+ return 0
+ return 0
+
+ def _extract_filename(self, flagfile_str):
+ """Returns filename from a flagfile_str of form -[-]flagfile=filename.
+
+ The cases of --flagfile foo and -flagfile foo shouldn't be hitting
+ this function, as they are dealt with in the level above this
+ function.
+
+ Args:
+ flagfile_str: str, the flagfile string.
+
+ Returns:
+ str, the filename from a flagfile_str of form -[-]flagfile=filename.
+
+ Raises:
+ Error: Raised when illegal --flagfile is provided.
+ """
+ if flagfile_str.startswith('--flagfile='):
+ return os.path.expanduser((flagfile_str[(len('--flagfile=')):]).strip())
+ elif flagfile_str.startswith('-flagfile='):
+ return os.path.expanduser((flagfile_str[(len('-flagfile=')):]).strip())
+ else:
+ raise _exceptions.Error(
+ 'Hit illegal --flagfile type: %s' % flagfile_str)
+
+ def _get_flag_file_lines(self, filename, parsed_file_stack=None):
+ """Returns the useful (!=comments, etc) lines from a file with flags.
+
+ Args:
+ filename: str, the name of the flag file.
+ parsed_file_stack: [str], a list of the names of the files that we have
+ recursively encountered at the current depth. MUTATED BY THIS FUNCTION
+ (but the original value is preserved upon successfully returning from
+ function call).
+
+ Returns:
+ List of strings. See the note below.
+
+ NOTE(springer): This function checks for a nested --flagfile=<foo>
+ tag and handles the lower file recursively. It returns a list of
+ all the lines that _could_ contain command flags. This is
+ EVERYTHING except whitespace lines and comments (lines starting
+ with '#' or '//').
+ """
+ if parsed_file_stack is None:
+ parsed_file_stack = []
+ # We do a little safety check for reparsing a file we've already encountered
+ # at a previous depth.
+ if filename in parsed_file_stack:
+ sys.stderr.write('Warning: Hit circular flagfile dependency. Ignoring'
+ ' flagfile: %s\n' % (filename,))
+ return []
+ else:
+ parsed_file_stack.append(filename)
+
+ line_list = [] # All line from flagfile.
+ flag_line_list = [] # Subset of lines w/o comments, blanks, flagfile= tags.
+ try:
+ file_obj = open(filename, 'r')
+ except IOError as e_msg:
+ raise _exceptions.CantOpenFlagFileError(
+ 'ERROR:: Unable to open flagfile: %s' % e_msg)
+
+ with file_obj:
+ line_list = file_obj.readlines()
+
+ # This is where we check each line in the file we just read.
+ for line in line_list:
+ if line.isspace():
+ pass
+ # Checks for comment (a line that starts with '#').
+ elif line.startswith('#') or line.startswith('//'):
+ pass
+ # Checks for a nested "--flagfile=<bar>" flag in the current file.
+ # If we find one, recursively parse down into that file.
+ elif self._is_flag_file_directive(line):
+ sub_filename = self._extract_filename(line)
+ included_flags = self._get_flag_file_lines(
+ sub_filename, parsed_file_stack=parsed_file_stack)
+ flag_line_list.extend(included_flags)
+ else:
+ # Any line that's not a comment or a nested flagfile should get
+ # copied into 2nd position. This leaves earlier arguments
+ # further back in the list, thus giving them higher priority.
+ flag_line_list.append(line.strip())
+
+ parsed_file_stack.pop()
+ return flag_line_list
+
+ def read_flags_from_files(self, argv, force_gnu=True):
+ """Processes command line args, but also allow args to be read from file.
+
+ Args:
+ argv: [str], a list of strings, usually sys.argv[1:], which may contain
+ one or more flagfile directives of the form --flagfile="./filename".
+ Note that the name of the program (sys.argv[0]) should be omitted.
+ force_gnu: bool, if False, --flagfile parsing obeys normal flag semantics.
+ If True, --flagfile parsing instead follows gnu_getopt semantics.
+ *** WARNING *** force_gnu=False may become the future default!
+
+ Returns:
+ A new list which has the original list combined with what we read
+ from any flagfile(s).
+
+ Raises:
+ IllegalFlagValueError: Raised when --flagfile is provided with no
+ argument.
+
+ This function is called by FLAGS(argv).
+ It scans the input list for a flag that looks like:
+ --flagfile=<somefile>. Then it opens <somefile>, reads all valid key
+ and value pairs and inserts them into the input list in exactly the
+ place where the --flagfile arg is found.
+
+ Note that your application's flags are still defined the usual way
+ using absl.flags DEFINE_flag() type functions.
+
+ Notes (assuming we're getting a commandline of some sort as our input):
+ --> For duplicate flags, the last one we hit should "win".
+ --> Since flags that appear later win, a flagfile's settings can be "weak"
+ if the --flagfile comes at the beginning of the argument sequence,
+ and it can be "strong" if the --flagfile comes at the end.
+ --> A further "--flagfile=<otherfile.cfg>" CAN be nested in a flagfile.
+ It will be expanded in exactly the spot where it is found.
+ --> In a flagfile, a line beginning with # or // is a comment.
+ --> Entirely blank lines _should_ be ignored.
+ """
+ rest_of_args = argv
+ new_argv = []
+ while rest_of_args:
+ current_arg = rest_of_args[0]
+ rest_of_args = rest_of_args[1:]
+ if self._is_flag_file_directive(current_arg):
+ # This handles the case of -(-)flagfile foo. In this case the
+ # next arg really is part of this one.
+ if current_arg == '--flagfile' or current_arg == '-flagfile':
+ if not rest_of_args:
+ raise _exceptions.IllegalFlagValueError(
+ '--flagfile with no argument')
+ flag_filename = os.path.expanduser(rest_of_args[0])
+ rest_of_args = rest_of_args[1:]
+ else:
+ # This handles the case of (-)-flagfile=foo.
+ flag_filename = self._extract_filename(current_arg)
+ new_argv.extend(self._get_flag_file_lines(flag_filename))
+ else:
+ new_argv.append(current_arg)
+ # Stop parsing after '--', like getopt and gnu_getopt.
+ if current_arg == '--':
+ break
+ # Stop parsing after a non-flag, like getopt.
+ if not current_arg.startswith('-'):
+ if not force_gnu and not self.__dict__['__use_gnu_getopt']:
+ break
+ else:
+ if ('=' not in current_arg and
+ rest_of_args and not rest_of_args[0].startswith('-')):
+ # If this is an occurrence of a legitimate --x y, skip the value
+ # so that it won't be mistaken for a standalone arg.
+ fl = self._flags()
+ name = current_arg.lstrip('-')
+ if name in fl and not fl[name].boolean:
+ current_arg = rest_of_args[0]
+ rest_of_args = rest_of_args[1:]
+ new_argv.append(current_arg)
+
+ if rest_of_args:
+ new_argv.extend(rest_of_args)
+
+ return new_argv
+
+ def flags_into_string(self):
+ """Returns a string with the flags assignments from this FlagValues object.
+
+ This function ignores flags whose value is None. Each flag
+ assignment is separated by a newline.
+
+ NOTE: MUST mirror the behavior of the C++ CommandlineFlagsIntoString
+ from https://github.com/gflags/gflags.
+
+ Returns:
+ str, the string with the flags assignments from this FlagValues object.
+ """
+ s = ''
+ for flag in self._flags().values():
+ if flag.value is not None:
+ s += flag.serialize() + '\n'
+ return s
+
+ def append_flags_into_file(self, filename):
+ """Appends all flags assignments from this FlagInfo object to a file.
+
+ Output will be in the format of a flagfile.
+
+ NOTE: MUST mirror the behavior of the C++ AppendFlagsIntoFile
+ from https://github.com/gflags/gflags.
+
+ Args:
+ filename: str, name of the file.
+ """
+ with open(filename, 'a') as out_file:
+ out_file.write(self.flags_into_string())
+
+ def write_help_in_xml_format(self, outfile=None):
+ """Outputs flag documentation in XML format.
+
+ NOTE: We use element names that are consistent with those used by
+ the C++ command-line flag library, from
+ https://github.com/gflags/gflags.
+ We also use a few new elements (e.g., <key>), but we do not
+ interfere / overlap with existing XML elements used by the C++
+ library. Please maintain this consistency.
+
+ Args:
+ outfile: File object we write to. Default None means sys.stdout.
+ """
+ doc = minidom.Document()
+ all_flag = doc.createElement('AllFlags')
+ doc.appendChild(all_flag)
+
+ all_flag.appendChild(_helpers.create_xml_dom_element(
+ doc, 'program', os.path.basename(sys.argv[0])))
+
+ usage_doc = sys.modules['__main__'].__doc__
+ if not usage_doc:
+ usage_doc = '\nUSAGE: %s [flags]\n' % sys.argv[0]
+ else:
+ usage_doc = usage_doc.replace('%s', sys.argv[0])
+ all_flag.appendChild(_helpers.create_xml_dom_element(
+ doc, 'usage', usage_doc))
+
+ # Get list of key flags for the main module.
+ key_flags = self.get_key_flags_for_module(sys.argv[0])
+
+ # Sort flags by declaring module name and next by flag name.
+ flags_by_module = self.flags_by_module_dict()
+ all_module_names = list(flags_by_module.keys())
+ all_module_names.sort()
+ for module_name in all_module_names:
+ flag_list = [(f.name, f) for f in flags_by_module[module_name]]
+ flag_list.sort()
+ for unused_flag_name, flag in flag_list:
+ is_key = flag in key_flags
+ all_flag.appendChild(flag._create_xml_dom_element( # pylint: disable=protected-access
+ doc, module_name, is_key=is_key))
+
+ outfile = outfile or sys.stdout
+ if six.PY2:
+ outfile.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
+ else:
+ outfile.write(
+ doc.toprettyxml(indent=' ', encoding='utf-8').decode('utf-8'))
+ outfile.flush()
+
+ def _check_method_name_conflicts(self, name, flag):
+ if flag.allow_using_method_names:
+ return
+ short_name = flag.short_name
+ flag_names = {name} if short_name is None else {name, short_name}
+ for flag_name in flag_names:
+ if flag_name in self.__dict__['__banned_flag_names']:
+ raise _exceptions.FlagNameConflictsWithMethodError(
+ 'Cannot define a flag named "{name}". It conflicts with a method '
+ 'on class "{class_name}". To allow defining it, use '
+ 'allow_using_method_names and access the flag value with '
+ "FLAGS['{name}'].value. FLAGS.{name} returns the method, "
+ 'not the flag value.'.format(
+ name=flag_name, class_name=type(self).__name__))
+
+
+
+
+FLAGS = FlagValues()