summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Silverstein <csilvers+gflags@google.com>2007-03-22 00:18:13 +0000
committerCraig Silverstein <csilvers+gflags@google.com>2007-03-22 00:18:13 +0000
commit573580dcd64b95a01cf5babec6031ef664c8248d (patch)
treee4cffe60252f3f0ffa4d5b854dd19ca473d99a16
parentb9f23483e1569615bb0801f1763e8b3a33989284 (diff)
downloadgflags-573580dcd64b95a01cf5babec6031ef664c8248d.tar.gz
gflags-573580dcd64b95a01cf5babec6031ef664c8248d.tar.bz2
gflags-573580dcd64b95a01cf5babec6031ef664c8248d.zip
gflags 0.2
git-svn-id: https://gflags.googlecode.com/svn/trunk@9 6586e3c6-dcc4-952a-343f-ff74eb82781d
-rw-r--r--ChangeLog6
-rw-r--r--README17
-rwxr-xr-xconfigure20
-rw-r--r--configure.ac2
-rw-r--r--packages/rpm/rpm.spec5
-rwxr-xr-xpython/gflags.py1368
-rwxr-xr-xpython/gflags2man.py (renamed from contrib/gflags2man.py)239
-rwxr-xr-xpython/gflags_unittest.py800
-rwxr-xr-xpython/setup.py42
-rw-r--r--src/google/gflags.h.in1
10 files changed, 2392 insertions, 108 deletions
diff --git a/ChangeLog b/ChangeLog
index 6574aaa..e5f126b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,3 +6,9 @@ Wed Dec 13 12:37:19 2006 Google Inc. <opensource@google.com>
has increased flexibility, including built-in support for C++
types like string, and the ability to define flags in the source
file in which they're used.
+
+Mon Jan 22 15:33:06 2007 Google Inc. <opensource@google.com>
+
+ * google-gflags: version 0.2
+ * added support for python commandlineflags, as well as c++
+ * gflags2man, a script to turn flags into a man page (dchristian)
diff --git a/README b/README
index 1333ed7..d6dd30a 100644
--- a/README
+++ b/README
@@ -1 +1,16 @@
-TODO
+This repository contains both a C++ and a python implementation of the
+Google commandline flags module. Documentation for the C++
+implementation is in doc/. Documentation for the python
+implementation is at the top of gflags/flags.py.
+
+See INSTALL for (generic) installation instructions for C++: basically
+ ./configure && make && make install
+
+To install the python module, run
+ cd python; python ./setup.py install
+
+When you install the python library, you also get a helper
+application, gflags2man.py, installed into /usr/local/bin. You can
+run gflags2man.py to create an instant man page, with all the
+commandline flags and their docs, for any C++ or python program you've
+written using the gflags library.
diff --git a/configure b/configure
index d2a8453..590bdff 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.59 for gflags 0.1.
+# Generated by GNU Autoconf 2.59 for gflags 0.2.
#
# Report bugs to <opensource@google.com>.
#
@@ -423,8 +423,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
# Identity of this package.
PACKAGE_NAME='gflags'
PACKAGE_TARNAME='gflags'
-PACKAGE_VERSION='0.1'
-PACKAGE_STRING='gflags 0.1'
+PACKAGE_VERSION='0.2'
+PACKAGE_STRING='gflags 0.2'
PACKAGE_BUGREPORT='opensource@google.com'
ac_unique_file="README"
@@ -954,7 +954,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures gflags 0.1 to adapt to many kinds of systems.
+\`configure' configures gflags 0.2 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1020,7 +1020,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of gflags 0.1:";;
+ short | recursive ) echo "Configuration of gflags 0.2:";;
esac
cat <<\_ACEOF
@@ -1163,7 +1163,7 @@ fi
test -n "$ac_init_help" && exit 0
if $ac_init_version; then
cat <<\_ACEOF
-gflags configure 0.1
+gflags configure 0.2
generated by GNU Autoconf 2.59
Copyright (C) 2003 Free Software Foundation, Inc.
@@ -1177,7 +1177,7 @@ cat >&5 <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by gflags $as_me 0.1, which was
+It was created by gflags $as_me 0.2, which was
generated by GNU Autoconf 2.59. Invocation command line was
$ $0 $@
@@ -1823,7 +1823,7 @@ fi
# Define the identity of the package.
PACKAGE='gflags'
- VERSION='0.1'
+ VERSION='0.2'
cat >>confdefs.h <<_ACEOF
@@ -21158,7 +21158,7 @@ _ASBOX
} >&5
cat >&5 <<_CSEOF
-This file was extended by gflags $as_me 0.1, which was
+This file was extended by gflags $as_me 0.2, which was
generated by GNU Autoconf 2.59. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -21221,7 +21221,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF
ac_cs_version="\\
-gflags config.status 0.1
+gflags config.status 0.2
configured by $0, generated by GNU Autoconf 2.59,
with options \\"`echo "$ac_configure_args" | sed 's/[\\""\`\$]/\\\\&/g'`\\"
diff --git a/configure.ac b/configure.ac
index ae5c126..98a07c2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -10,7 +10,7 @@
# make sure we're interpreted by some minimal autoconf
AC_PREREQ(2.57)
-AC_INIT(gflags, 0.1, opensource@google.com)
+AC_INIT(gflags, 0.2, opensource@google.com)
# The argument here is just something that should be in the current directory
# (for sanity checking)
AC_CONFIG_SRCDIR(README)
diff --git a/packages/rpm/rpm.spec b/packages/rpm/rpm.spec
index f47e23c..35d177d 100644
--- a/packages/rpm/rpm.spec
+++ b/packages/rpm/rpm.spec
@@ -8,11 +8,11 @@ Summary: A commandline flags library that allows for distributed flags
Version: %ver
Release: %rel
Group: Development/Libraries
-URL: http://goog-gflags.sourceforge.net
+URL: http://code.google.com/p/google-gflags
License: BSD
Vendor: Google
Packager: Google Inc. <opensource@google.com>
-Source: http://goog-gflags.sourceforge.net/%{NAME}-%{PACKAGE_VERSION}.tar.gz
+Source: http://google-gflags.googlecode.com/files/%{NAME}-%{PACKAGE_VERSION}.tar.gz
Distribution: Redhat 7 and above.
Buildroot: %{_tmppath}/%{name}-root
Prefix: %prefix
@@ -61,7 +61,6 @@ rm -rf $RPM_BUILD_ROOT
%defattr(-,root,root)
%{prefix}/include/google
-%{prefix}/lib/debug
%{prefix}/lib/libgflags.a
%{prefix}/lib/libgflags.la
%{prefix}/lib/libgflags.so
diff --git a/python/gflags.py b/python/gflags.py
new file mode 100755
index 0000000..257b6f5
--- /dev/null
+++ b/python/gflags.py
@@ -0,0 +1,1368 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2007, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# ---
+# Author: Chad Lester
+# Design and style contributions by:
+# Amit Patel, Bogdan Cocosel, Daniel Dulitz, Eric Tiedemann,
+# Eric Veach, Laurence Gonsalves, Matthew Springer
+# Code reorganized a bit by Craig Silverstein
+
+"""
+This module is used to define and parse command line flags.
+
+This module defines a *distributed* flag-definition policy: rather
+than an application having to define all flags in or near main(), each
+python module defines flags that are useful to it. When one python
+module imports another, it gains access to the other's flags. (This
+is implemented by having all modules share a common, global registry
+object containing all the flag information.)
+
+Flags are defined through the use of one of the DEFINE_xxx functions.
+The specific function used determines how the flag is parsed, checked,
+and optionally type-converted, when it's seen on the command line.
+
+
+IMPLEMENTATION: DEFINE_* creates a 'Flag' object and registers it with
+a 'FlagValues' object (typically the global FlagValues FLAGS, defined
+here). The 'FlagValues' object can scan the command line arguments
+and pass flag arguments to the corresponding 'Flag' objects for
+value-checking and type conversion. The converted flag values are
+available as members of the 'FlagValues' object.
+
+Code can access the flag through a FlagValues object, for instancee
+gflags.FLAGS.myflag. Typically, the __main__ module passes the
+command line arguments to gflags.FLAGS for parsing.
+
+At bottom, this module calls getopt(), so getopt functionality is
+supported, including short- and long-style flags, and the use of -- to
+terminate flags.
+
+Methods defined by the flag module will throw 'FlagsError' exceptions.
+The exception argument will be a human-readable string.
+
+
+FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All
+flags take a name, default value, help-string, and optional 'short'
+name (one-letter name). Some flags have other arguments, which are
+described with the flag.
+
+DEFINE_string: takes any input, and interprets it as a string.
+
+DEFINE_boolean: typically does not take an argument: say --myflag to
+ set FLAGS.myflag to true, or --nomyflag to set
+ FLAGS.myflag to false. Alternately, you can say
+ --myflag=true or --myflag=t or --myflag=1 or
+ --myflag=false or --myflag=f or --myflag=0
+
+DEFINE_float: takes an input and interprets it as a floating point
+ number. Takes optional args lower_bound and
+ upper_bound; if the number specified on the command line
+ is out of range, it will raise a FlagError.
+
+DEFINE_integer: takes an input and interprets it as an integer. Takes
+ optional args lower_bound and upper_bound as for floats.
+
+DEFINE_enum: takes a list of strings which represents legal values. If
+ the command-line value is not in this list, raise a flag
+ error. Otherwise, assign to FLAGS.flag as a string.
+
+DEFINE_list: Takes a comma-separated list of strings on the commandline.
+ Stores them in a python list object.
+
+DEFINE_spaceseplist: Takes a space-separated list of strings on the
+ commandline. Stores them in a python list object.
+
+DEFINE_multistring: The same as DEFINE_string, except the flag can be
+ specified more than once on the commandline. The
+ result is a python list object (list of strings),
+ even if the flag is only on the command line once.
+
+DEFINE_multi_int: The same as DEFINE_integer, except the flag can be
+ specified more than once on the commandline. The
+ result is a python list object (list of ints),
+ even if the flag is only on the command line once.
+
+
+SPECIAL FLAGS: There are a few flags that have special meaning:
+ --help (or -?) prints a list of all the flags in a human-readable fashion
+ --helpshort prints a list of all the flags in the 'main' .py file only
+ --flagfile=foo read flags from foo.
+ -- as in getopt(), terminates flag-processing
+
+Note on --flagfile:
+
+Flags may be loaded from text files in addition to being specified on
+the commandline.
+
+Any flags you don't feel like typing, throw them in a file, one flag
+per line, for instance:
+ --myflag=myvalue
+ --nomyboolean_flag
+You then specify your file with the special flag
+'--flagfile=somefile'. You CAN recursively nest flagfile= tokens OR
+use multiple files on the command line. Lines beginning with a single
+hash '#' or a double slash '//' are comments in your flagfile.
+
+Any flagfile=<file> will be interpreted as having a relative path from
+the current working directory rather than from the place the file was
+included from:
+ myPythonScript.py --flagfile=config/somefile.cfg
+
+If somefile.cfg includes further --flagfile= directives, these will be
+referenced relative to the original CWD, not from the directory the
+including flagfile was found in!
+
+The caveat applies to people who are including a series of nested
+files in a different dir than they are executing out of. Relative
+path names are always from CWD, not from the directory of the parent
+include flagfile. We do now support '~' expanded directory names.
+
+Absolute path names ALWAYS work!
+
+
+EXAMPLE USAGE:
+
+ import gflags
+ FLAGS = gflags.FLAGS
+
+ # Flag names are globally defined! So in general, we need to be
+ # careful to pick names that are unlikely to be used by other libraries.
+ # If there is a conflict, we'll get an error at import time.
+ gflags.DEFINE_string("name", "Mr. President" "NAME: your name")
+ gflags.DEFINE_integer("age", None, "AGE: your age in years", lower_bound=0)
+ gflags.DEFINE_boolean("debug", 0, "produces debugging output")
+ gflags.DEFINE_enum("gender", "male", ["male", "female"],
+ "GENDER: your gender")
+
+ def main(argv):
+ try:
+ argv = FLAGS(argv) # parse flags
+ except gflags.FlagsError, e:
+ print '%s\\nUsage: %s ARGS\\n%s' % (e, sys.argv[0], FLAGS)
+ sys.exit(1)
+ if FLAGS.debug: print 'non-flag arguments:', argv
+ print 'Happy Birthday', FLAGS.name
+ if FLAGS.age != None:
+ print "You are a %s, who is %d years old" % (FLAGS.gender, FLAGS.age)
+
+ if __name__ == '__main__': main(sys.argv)
+"""
+
+import getopt
+import os
+import sys
+
+# Are we running at least python 2.2?
+try:
+ if tuple(sys.version_info[:3]) < (2,2,0):
+ raise NotImplementedError("requires python 2.2.0 or later")
+except AttributeError: # a very old python, that lacks sys.version_info
+ raise NotImplementedError("requires python 2.2.0 or later")
+
+# Are we running under pychecker?
+_RUNNING_PYCHECKER = 'pychecker.python' in sys.modules
+
+# module exceptions:
+class FlagsError(Exception): "The base class for all flags errors"
+class DuplicateFlag(FlagsError): "Thrown if there is a flag naming conflict"
+class IllegalFlagValue(FlagsError): "The flag command line argument is illegal"
+
+# Global variable used by expvar
+_exported_flags = {}
+
+
+def __GetModuleName(globals_dict):
+ """Given a globals dict, find the module in which it's defined."""
+ for name, module in sys.modules.iteritems():
+ if getattr(module, '__dict__', None) is globals_dict:
+ if name == '__main__':
+ return sys.argv[0]
+ return name
+ raise AssertionError, "No module was found"
+
+def __GetCallingModule():
+ """Get the name of the module that's calling into this module; e.g.,
+ the module calling a DEFINE_foo... function.
+ """
+ # Walk down the stack to find the first globals dict that's not ours.
+ for depth in range(1, sys.getrecursionlimit()):
+ if not sys._getframe(depth).f_globals is globals():
+ return __GetModuleName(sys._getframe(depth).f_globals)
+ raise AssertionError, "No module was found"
+
+def _GetMainModule():
+ """Get the module name from which execution started."""
+ for depth in range(1, sys.getrecursionlimit()):
+ try:
+ globals_of_main = sys._getframe(depth).f_globals
+ except ValueError:
+ return __GetModuleName(globals_of_main)
+ raise AssertionError, "No module was found"
+
+
+class FlagValues:
+ """
+ Used as a registry for '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: gflags.FLAGS
+
+ This class is heavily overloaded:
+
+ 'Flag' objects are registered via __setitem__:
+ FLAGS['longname'] = x # register a new flag
+
+ The .value member of the registered 'Flag' objects can be accessed as
+ members 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.
+ """
+ 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.
+ self.__dict__['__flags'] = {}
+ self.__dict__['__flags_by_module'] = {} # A dict module -> list of flag
+
+ def FlagDict(self):
+ return self.__dict__['__flags']
+
+ def _RegisterFlagByModule(self, module_name, flag):
+ """We keep track of which flag is defined by which module so that
+ we can later sort the flags by module.
+ """
+ flags_by_module = self.__dict__['__flags_by_module']
+ flags_by_module.setdefault(module_name, []).append(flag)
+
+ def __setitem__(self, name, flag):
+ """
+ Register a new flag variable.
+ """
+ fl = self.FlagDict()
+ if not isinstance(flag, Flag):
+ raise IllegalFlagValue, flag
+ if not isinstance(name, type("")):
+ raise FlagsError, "Flag name must be a string"
+ if len(name) == 0:
+ raise FlagsError, "Flag name cannot be empty"
+ # If running under pychecker, duplicate keys are likely to be defined.
+ # Disable check for duplicate keys when pycheck'ing.
+ if (fl.has_key(name) and not flag.allow_override and
+ not fl[name].allow_override and not _RUNNING_PYCHECKER):
+ raise DuplicateFlag, name
+ short_name = flag.short_name
+ if short_name is not None:
+ if (fl.has_key(short_name) and not flag.allow_override and
+ not fl[short_name].allow_override and not _RUNNING_PYCHECKER):
+ raise DuplicateFlag, short_name
+ fl[short_name] = flag
+ fl[name] = flag
+ global _exported_flags
+ _exported_flags[name] = flag
+
+ def __getitem__(self, name):
+ """
+ Retrieve the flag object.
+ """
+ return self.FlagDict()[name]
+
+ def __getattr__(self, name):
+ """
+ Retrieve the .value member of a flag object.
+ """
+ fl = self.FlagDict()
+ if not fl.has_key(name):
+ raise AttributeError, name
+ return fl[name].value
+
+ def __setattr__(self, name, value):
+ """
+ Set the .value member of a flag object.
+ """
+ fl = self.FlagDict()
+ fl[name].value = value
+ return value
+
+ def __delattr__(self, name):
+ """
+ Delete a previously-defined flag from a flag object.
+ """
+ fl = self.FlagDict()
+ if not fl.has_key(name):
+ raise AttributeError, name
+ del fl[name]
+
+ def SetDefault(self, name, value):
+ """
+ Change the default value of the named flag object.
+ """
+ fl = self.FlagDict()
+ if not fl.has_key(name):
+ raise AttributeError, name
+ fl[name].SetDefault(value)
+
+ def __contains__(self, name):
+ """
+ Return True if name is a value (flag) in the dict.
+ """
+ return name in self.FlagDict()
+
+ has_key = __contains__ # a synonym for __contains__()
+
+ def __iter__(self):
+ return self.FlagDict().iterkeys()
+
+ def __call__(self, argv):
+ """
+ Searches argv for flag arguments, parses them and then sets the flag
+ values as attributes of this FlagValues object. All unparsed
+ arguments are returned. Flags are parsed using the GNU Program
+ Argument Syntax Conventions, using getopt:
+
+ http://www.gnu.org/software/libc/manual/html_mono/libc.html#Getopt
+ """
+
+ # Support any sequence type that can be converted to a list
+ argv = list(argv)
+
+ shortopts = ""
+ longopts = []
+
+ fl = self.FlagDict()
+
+ # This pre parses the argv list for --flagfile=<> options.
+ argv = self.ReadFlagsFromFiles(argv)
+
+ # Correct the argv to support the google style of passing boolean
+ # parameters. Boolean parameters may be passed by using --mybool,
+ # --nomybool, --mybool=(true|false|1|0). getopt does not support
+ # having options that may or may not have a parameter. We replace
+ # instances of the short form --mybool and --nomybool with their
+ # full forms: --mybool=(true|false).
+ original_argv = list(argv)
+ shortest_matches = None
+ for name, flag in fl.items():
+ if not flag.boolean:
+ continue
+ if shortest_matches is None:
+ # Determine the smallest allowable prefix for all flag names
+ shortest_matches = self.ShortestUniquePrefixes(fl)
+ no_name = 'no' + name
+ prefix = shortest_matches[name]
+ no_prefix = shortest_matches[no_name]
+
+ # Replace all occurences of this boolean with extended forms
+ for arg_idx in range(1, len(argv)):
+ arg = argv[arg_idx]
+ if arg.find('=') >= 0: continue
+ if arg.startswith('--'+prefix) and ('--'+name).startswith(arg):
+ argv[arg_idx] = ('--%s=true' % name)
+ elif arg.startswith('--'+no_prefix) and ('--'+no_name).startswith(arg):
+ argv[arg_idx] = ('--%s=false' % name)
+
+ # Loop over all of the flags, building up the lists of short options and
+ # long options that will be passed to getopt. Short options are
+ # specified as a string of letters, each letter followed by a colon if it
+ # takes an argument. Long options are stored in an array of strings.
+ # Each string ends with an '=' if it takes an argument.
+ for name, flag in fl.items():
+ longopts.append(name + "=")
+ if len(name) == 1: # one-letter option: allow short flag type also
+ shortopts += name
+ if not flag.boolean:
+ shortopts += ":"
+
+ try:
+ optlist, unparsed_args = getopt.getopt(argv[1:], shortopts, longopts)
+ except getopt.GetoptError, e:
+ raise FlagsError, e
+ for name, arg in optlist:
+ if name.startswith('--'):
+ # long option
+ name = name[2:]
+ short_option = 0
+ else:
+ # short option
+ name = name[1:]
+ short_option = 1
+ if fl.has_key(name):
+ flag = fl[name]
+ if flag.boolean and short_option: arg = 1
+ flag.Parse(arg)
+
+ if unparsed_args:
+ # unparsed_args becomes the first non-flag detected by getopt to
+ # the end of argv. Because argv may have been modified above,
+ # return original_argv for this region.
+ return argv[:1] + original_argv[-len(unparsed_args):]
+ else:
+ return argv[:1]
+
+ def Reset(self):
+ """
+ Reset the values to the point before FLAGS(argv) was called.
+ """
+ for f in self.FlagDict().values():
+ f.Unparse()
+
+ def RegisteredFlags(self):
+ """
+ Return a list of all registered flags.
+ """
+ return self.FlagDict().keys()
+
+ def FlagValuesDict(self):
+ """
+ Return a dictionary with flag names as keys and flag values as values.
+ """
+ flag_values = {}
+
+ for flag_name in self.RegisteredFlags():
+ flag = self.FlagDict()[flag_name]
+ flag_values[flag_name] = flag.value
+
+ return flag_values
+
+ def __str__(self):
+ """
+ Generate a help string for all known flags.
+ """
+ helplist = []
+
+ flags_by_module = self.__dict__['__flags_by_module']
+ if flags_by_module:
+
+ modules = flags_by_module.keys()
+ modules.sort()
+
+ # Print the help for the main module first, if possible.
+ main_module = _GetMainModule()
+ if main_module in modules:
+ modules.remove(main_module)
+ modules = [ main_module ] + modules
+
+ for module in modules:
+ self.__RenderModuleFlags(module, helplist)
+
+ else:
+ # Just print one long list of flags.
+ self.__RenderFlagList(self.FlagDict().values(), helplist)
+
+ return '\n'.join(helplist)
+
+ def __RenderModuleFlags(self, module, output_lines):
+ """
+ Generate a help string for a given module.
+ """
+ flags_by_module = self.__dict__['__flags_by_module']
+ if module in flags_by_module:
+ output_lines.append('\n%s:' % module)
+ self.__RenderFlagList(flags_by_module[module], output_lines)
+
+ def MainModuleHelp(self):
+ """
+ Generate a help string for all known flags of the main module.
+ """
+ helplist = []
+ self.__RenderModuleFlags(_GetMainModule(), helplist)
+ return '\n'.join(helplist)
+
+ def __RenderFlagList(self, flaglist, output_lines):
+ fl = self.FlagDict()
+ 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 FlagDict.
+ if fl.get(name, None) != flag: # a different flag is using this name now
+ continue
+ # only print help once
+ if flagset.has_key(flag): 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
+ if flag.default_as_str:
+ flaghelp += "\n (default: %s)" % flag.default_as_str
+ if flag.parser.syntactic_help:
+ flaghelp += "\n (%s)" % flag.parser.syntactic_help
+ output_lines.append(flaghelp)
+
+ def get(self, name, default):
+ """
+ Retrieve the .value member of a flag object, or default if .value is None
+ """
+
+ 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 ShortestUniquePrefixes(self, fl):
+ """
+ Returns a dictionary mapping flag names to their shortest unique prefix.
+ """
+ # Sort the list of flag names
+ sorted_flags = []
+ for name, flag in fl.items():
+ sorted_flags.append(name)
+ if flag.boolean:
+ sorted_flags.append('no%s' % name)
+ sorted_flags.sort()
+
+ # For each name in the sorted list, determine the shortest unique prefix
+ # by comparing itself to the next name and to the previous name (the latter
+ # check uses cached info from the previous loop).
+ shortest_matches = {}
+ prev_idx = 0
+ for flag_idx in range(len(sorted_flags)):
+ curr = sorted_flags[flag_idx]
+ if flag_idx == (len(sorted_flags) - 1):
+ next = None
+ else:
+ next = sorted_flags[flag_idx+1]
+ next_len = len(next)
+ for curr_idx in range(len(curr)):
+ if (next is None
+ or curr_idx >= next_len
+ or curr[curr_idx] != next[curr_idx]):
+ # curr longer than next or no more chars in common
+ shortest_matches[curr] = curr[:max(prev_idx, curr_idx) + 1]
+ prev_idx = curr_idx
+ break
+ else:
+ # curr shorter than (or equal to) next
+ shortest_matches[curr] = curr
+ prev_idx = curr_idx + 1 # next will need at least one more char
+ return shortest_matches
+
+ def __IsFlagFileDirective(self, flag_string):
+ """ Detects the --flagfile= token.
+ Takes a string which might contain a '--flagfile=<foo>' directive.
+ Returns a Boolean.
+ """
+ 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 ExtractFilename(self, flagfile_str):
+ """Function to remove the --flagfile= (or variant) and return just the
+ filename part. We can get strings that look like:
+ --flagfile=foo, -flagfile=foo.
+ The case of --flagfile foo and -flagfile foo shouldn't be hitting this
+ function, as they are dealt with in the level above this funciton.
+ """
+ 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 FlagsError('Hit illegal --flagfile type: %s' % flagfile_str)
+ return ''
+
+
+ def __GetFlagFileLines(self, filename, parsed_file_list):
+ """Function to open a flag file, return its useful (!=comments,etc) lines.
+ Takes:
+ A filename to open and read
+ A list of files we have already read THAT WILL BE CHANGED
+ 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 off
+ all the lines that _could_ contain command flags. This is
+ EVERYTHING except whitespace lines and comments (lines starting
+ with '#' or '//').
+ """
+ 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, e_msg:
+ print e_msg
+ print 'ERROR:: Unable to open flagfile: %s' % (filename)
+ return flag_line_list
+
+ line_list = file_obj.readlines()
+ file_obj.close()
+ parsed_file_list.append(filename)
+
+ # 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.__IsFlagFileDirective(line):
+ sub_filename = self.ExtractFilename(line)
+ # We do a little safety check for reparsing a file we've already done.
+ if not sub_filename in parsed_file_list:
+ included_flags = self.__GetFlagFileLines(sub_filename, parsed_file_list)
+ flag_line_list.extend(included_flags)
+ else: # Case of hitting a circularly included file.
+ print >>sys.stderr, ('Warning: Hit circular flagfile dependency: %s'
+ % sub_filename)
+ else:
+ # Any line that's not a comment or a nested flagfile should
+ # get copied into 2nd position, this leaves earlier arguements
+ # further back in the list, which makes them have higher priority.
+ flag_line_list.append(line.strip())
+ return flag_line_list
+
+ def ReadFlagsFromFiles(self, argv):
+ """Process command line args, but also allow args to be read from file
+ Usage:
+ Takes: a list of strings, usually sys.argv, which may contain one or more
+ flagfile directives of the form --flagfile="./filename"
+ References: Global gflags.FLAG class instance
+ Returns: a new list which has the original list combined with what we
+ read from any flagfile(s).
+
+ This function should be called before the normal FLAGS(argv) call.
+ This function simply 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 between the first item of the list and any
+ subsequent items in the list.
+ Note that your application's flags are still defined the usual way using
+ gflags DEFINE_flag() type functions.
+
+ Notes (assuming we're getting a commandline of some sort as our input):
+ --> Any flags on the command line we were passed in _should_ always take
+ precedence!!!
+ --> a further "--flagfile=<otherfile.cfg>" CAN be nested in a flagfile.
+ It will be processed after the parent flag file is done.
+ --> For duplicate flags, first one we hit should "win".
+ --> In a flagfile, a line beginning with # or // is a comment
+ --> Entirely blank lines _should_ be ignored
+ """
+ parsed_file_list = []
+ 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.__IsFlagFileDirective(current_arg):
+ # This handles the case of -(-)flagfile foo. Inthis case the next arg
+ # really is part of this one.
+ if current_arg == '--flagfile' or current_arg =='-flagfile':
+ if not rest_of_args:
+ raise IllegalFlagValue, '--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.ExtractFilename(current_arg)
+ new_argv = (new_argv[:1] +
+ self.__GetFlagFileLines(flag_filename, parsed_file_list) +
+ new_argv[1:])
+ else:
+ new_argv.append(current_arg)
+
+ return new_argv
+
+ def FlagsIntoString(self):
+ """
+ Retreive a string version of all the flags with assignments stored
+ in this FlagValues object. Should mirror the behavior of the c++
+ version of FlagsIntoString. Each flag assignment is seperated by
+ a newline.
+ """
+ s = ''
+ for flag in self.FlagDict().values():
+ if flag.value is not None:
+ s += flag.Serialize() + '\n'
+ return s
+
+ def AppendFlagsIntoFile(self, filename):
+ """
+ Appends all flags found in this FlagInfo object to the file
+ specified. Output will be in the format of a flagfile. This
+ should mirror the behavior of the c++ version of
+ AppendFlagsIntoFile.
+ """
+ out_file = open(filename, 'a')
+ out_file.write(self.FlagsIntoString())
+ out_file.close()
+
+#end of the FLAGS registry class
+
+
+# The global FlagValues instance
+FLAGS = FlagValues()
+
+
+class Flag:
+ """
+ 'Flag' objects define the following fields:
+ .name - the name for this flag
+ .default - the 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
+
+ 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 member is updated. If this
+ flag was already present, a FlagsError is raised.
+
+ Parse() is also called during __init__ to parse the default value and
+ initialize the .value member. This enables other python modules to
+ safely use flags even if the __main__ module neglects to parse the
+ command line arguments. The .present member is cleared after __init__
+ parsing. If the default value is set to None, then the __init__
+ parsing step is skipped and the .value member 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=0, allow_override=0):
+ self.name = name
+ self.default = default
+
+ 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.value = None
+
+ # We can't allow a None override because it may end up not being
+ # passed to C++ code when we're overriding C++ flags. So we
+ # cowardly bail out until someone fixes the semantics of trying to
+ # pass None to a C++ flag. See swig_flags.Init() for details on
+ # this behavior.
+ if default is None and allow_override:
+ raise DuplicateFlag, name
+
+ self.Unparse()
+
+ self.default_as_str = self.__GetParsedValueAsString(self.value)
+
+ def __GetParsedValueAsString(self, value):
+ if self.boolean:
+ if value:
+ return repr('true')
+ else:
+ return repr('false')
+ if value is None:
+ return None
+ return repr(str(value))
+
+ def Parse(self, argument):
+ try:
+ self.value = self.parser.Parse(argument)
+ except ValueError, e: # recast ValueError as IllegalFlagValue
+ raise IllegalFlagValue, ("flag --%s: " % self.name) + str(e)
+ self.present += 1
+
+ def Unparse(self):
+ if self.default != None:
+ self.Parse(self.default)
+ 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 FlagsError, "Serializer not present for flag %s" % self.name
+ return "--%s=%s" % (self.name, self.serializer.Serialize(self.value))
+
+ def SetDefault(self, value):
+ """
+ Change the default value, and current value, of this flag object
+ """
+ if value: # See __init__ for logic details
+ self.Parse(value)
+ self.present -= 1 # reset .present after parsing new default value
+ else:
+ self.value = None
+ self.default = value
+ self.default_as_str = self.__GetParsedValueAsString(value)
+
+class ArgumentParser:
+ """
+ This is a base class used to parse and convert arguments.
+
+ The Parse() method checks to make sure that the string argument is a
+ legal value and convert it to a native type. If the value cannot be
+ converted, it should throw a 'ValueError' exception with a human
+ readable explanation of why the value is illegal.
+
+ Subclasses should also define a syntactic_help string which may be
+ presented to the user to describe the form of the legal values.
+ """
+ syntactic_help = ""
+ def Parse(self, argument):
+ """
+ The default implementation of Parse() accepts any value of argument,
+ simply returning it unmodified.
+ """
+ return argument
+
+class ArgumentSerializer:
+ """
+ This is the base class for generating string representations of a
+ flag value
+ """
+ def Serialize(self, value):
+ return str(value)
+
+class ListSerializer(ArgumentSerializer):
+ def __init__(self, list_sep):
+ self.list_sep = list_sep
+
+ def Serialize(self, value):
+ return self.list_sep.join([str(x) for x in value])
+
+
+# The DEFINE functions are explained in the module doc string.
+
+def DEFINE(parser, name, default, help, flag_values=FLAGS, serializer=None,
+ **args):
+ """
+ This creates a generic 'Flag' object that parses its arguments with a
+ 'Parser' and registers it with a 'FlagValues' object.
+
+ Developers who need to create their own 'Parser' classes should call
+ this module function. to register their flags. For example:
+
+ DEFINE(DatabaseSpec(), "dbspec", "mysql:db0:readonly:hr",
+ "The primary database")
+ """
+ DEFINE_flag(Flag(parser, serializer, name, default, help, **args),
+ flag_values)
+
+def DEFINE_flag(flag, flag_values=FLAGS):
+ """
+ This registers a 'Flag' object with a 'FlagValues' object. By
+ default, the global FLAGS 'FlagValue' object is used.
+
+ Typical users will use one of the more specialized DEFINE_xxx
+ functions, such as DEFINE_string or DEFINEE_integer. But developers
+ who need to create Flag objects themselves should use this function to
+ register their flags.
+ """
+ # copying the reference to flag_values prevents pychecker warnings
+ fv = flag_values
+ fv[flag.name] = flag
+
+ if flag_values == FLAGS:
+ # We are using the global flags dictionary, so we'll want to sort the
+ # usage output by calling module in FlagValues.__str__ (FLAGS is an
+ # instance of FlagValues). This requires us to keep track
+ # of which module is creating the flags.
+
+ # Tell FLAGS who's defining flag.
+ FLAGS._RegisterFlagByModule(__GetCallingModule(), flag)
+
+
+###############################
+################# STRING FLAGS
+###############################
+
+def DEFINE_string(name, default, help, flag_values=FLAGS, **args):
+ """
+ This registers a flag whose value can be any string.
+ """
+ parser = ArgumentParser()
+ serializer = ArgumentSerializer()
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+
+###############################
+################ BOOLEAN FLAGS
+###############################
+#### and the special HELP flag
+###############################
+
+class BooleanParser(ArgumentParser):
+ """
+ A boolean value
+ """
+
+ def Convert(self, argument):
+ """
+ convert the argument to a boolean (integer); raise ValueError on errors
+ """
+ if type(argument) == str:
+ if argument.lower() in ['true', 't', '1']:
+ return 1
+ elif argument.lower() in ['false', 'f', '0']:
+ return 0
+ return int(argument)
+
+ def Parse(self, argument):
+ val = self.Convert(argument)
+ return val
+
+class BooleanFlag(Flag):
+ """
+ A basic boolean flag. Boolean flags do not take any arguments, and
+ their value is either 0 (false) or 1 (true). The false value is
+ specified on the command line by prepending the word 'no' to either
+ the long or 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):
+ p = BooleanParser()
+ g = ArgumentSerializer()
+ Flag.__init__(self, p, g, name, default, help, short_name, 1, **args)
+ if not self.help: self.help = "a boolean value"
+
+def DEFINE_boolean(name, default, help, flag_values=FLAGS, **args):
+ """
+ This registers a boolean flag - one that does not take an argument.
+ If a user wants to specify a false value explicitly, the long option
+ beginning with 'no' must be used: i.e. --noflag
+
+ This flag will have a value of None, 0 or 1. None is possible if
+ default=None and the user does not specify the flag on the command
+ line.
+ """
+ DEFINE_flag(BooleanFlag(name, default, help, **args), flag_values)
+
+class HelpFlag(BooleanFlag):
+ """
+ HelpFlag is a special boolean flag that prints usage information and
+ raises a SystemExit exception if it is ever found in the command
+ line arguments. Note this is called with allow_override=1, so other
+ apps can define their own --help flag, replacing this one, if they want.
+ """
+ def __init__(self):
+ BooleanFlag.__init__(self, "help", 0, "show this help",
+ short_name="?", allow_override=1)
+ def Parse(self, arg):
+ if arg:
+ doc = sys.modules["__main__"].__doc__
+ flags = str(FLAGS)
+ print doc or ("\nUSAGE: %s [flags]\n" % sys.argv[0])
+ if flags:
+ print "flags:"
+ print flags
+ sys.exit(1)
+
+class HelpshortFlag(BooleanFlag):
+ """
+ HelpshortFlag is a special boolean flag that prints usage
+ information for the "main" module, and rasies a SystemExit exception
+ if it is ever found in the command line arguments. Note this is
+ called with allow_override=1, so other apps can define their own
+ --helpshort flag, replacing this one, if they want.
+ """
+ def __init__(self):
+ BooleanFlag.__init__(self, "helpshort", 0,
+ "show usage only for this module", allow_override=1)
+ def Parse(self, arg):
+ if arg:
+ doc = sys.modules["__main__"].__doc__
+ flags = FLAGS.MainModuleHelp()
+ print doc or ("\nUSAGE: %s [flags]\n" % sys.argv[0])
+ if flags:
+ print "flags:"
+ print flags
+ sys.exit(1)
+
+
+###############################
+################## FLOAT FLAGS
+###############################
+
+class FloatParser(ArgumentParser):
+ """
+ A floating point value; optionally bounded to a given upper and lower
+ bound.
+ """
+ number_article = "a"
+ number_name = "number"
+ syntactic_help = " ".join((number_article, number_name))
+
+ def __init__(self, lower_bound=None, upper_bound=None):
+ self.lower_bound = lower_bound
+ self.upper_bound = upper_bound
+ sh = self.syntactic_help
+ if lower_bound != None and upper_bound != None:
+ sh = ("%s in the range [%s, %s]" % (sh, lower_bound, upper_bound))
+ elif lower_bound == 1:
+ sh = "a positive %s" % self.number_name
+ elif upper_bound == -1:
+ sh = "a negative %s" % self.number_name
+ elif lower_bound == 0:
+ sh = "a non-negative %s" % self.number_name
+ elif upper_bound != None:
+ sh = "%s <= %s" % (self.number_name, upper_bound)
+ elif lower_bound != None:
+ sh = "%s >= %s" % (self.number_name, lower_bound)
+ self.syntactic_help = sh
+
+ def Convert(self, argument):
+ """
+ convert the argument to a float; raise ValueError on errors
+ """
+ return float(argument)
+
+ def Parse(self, argument):
+ val = self.Convert(argument)
+ if ((self.lower_bound != None and val < self.lower_bound) or
+ (self.upper_bound != None and val > self.upper_bound)):
+ raise ValueError, "%s is not %s" % (val, self.syntactic_help)
+ return val
+
+def DEFINE_float(name, default, help, lower_bound=None, upper_bound=None,
+ flag_values = FLAGS, **args):
+ """
+ This registers a flag whose value must be a float. If lower_bound,
+ or upper_bound are set, then this flag must be within the given range.
+ """
+ parser = FloatParser(lower_bound, upper_bound)
+ serializer = ArgumentSerializer()
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+
+###############################
+################ INTEGER FLAGS
+###############################
+
+class IntegerParser(FloatParser):
+ """
+ An integer value; optionally bounded to a given upper or lower bound.
+ """
+ number_article = "an"
+ number_name = "integer"
+ syntactic_help = " ".join((number_article, number_name))
+ def Convert(self, argument):
+ __pychecker__ = 'no-returnvalues'
+ if type(argument) == str:
+ base = 10
+ if len(argument) > 2 and argument[0] == "0" and argument[1] == "x":
+ base=16
+ try:
+ return int(argument, base)
+ # ValueError is thrown when argument is a string, and overflows an int.
+ except ValueError:
+ return long(argument, base)
+ else:
+ try:
+ return int(argument)
+ # OverflowError is thrown when argument is numeric, and overflows an int.
+ except OverflowError:
+ return long(argument)
+
+def DEFINE_integer(name, default, help, lower_bound=None, upper_bound=None,
+ flag_values = FLAGS, **args):
+ """
+ This registers a flag whose value must be an integer. If lower_bound,
+ or upper_bound are set, then this flag must be within the given range.
+ """
+ parser = IntegerParser(lower_bound, upper_bound)
+ serializer = ArgumentSerializer()
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+
+###############################
+################### ENUM FLAGS
+###############################
+
+class EnumParser(ArgumentParser):
+ """
+ A string enum value
+ """
+
+ def __init__(self, enum_values=None):
+ self.enum_values = enum_values
+
+ def Parse(self, argument):
+ """
+ If enum_values is not specified, any string is allowed
+ """
+ if self.enum_values and argument not in self.enum_values:
+ raise ValueError, ("value should be one of <%s>"
+ % "|".join(self.enum_values))
+ return argument
+
+class EnumFlag(Flag):
+ """
+ A basic enum flag. The flag's value can be any string from the list
+ of enum_values.
+ """
+ def __init__(self, name, default, help, enum_values=[],
+ short_name=None, **args):
+ p = EnumParser(enum_values)
+ g = ArgumentSerializer()
+ Flag.__init__(self, p, g, name, default, help, short_name, **args)
+ if not self.help: self.help = "an enum string"
+ self.help = "<%s>: %s" % ("|".join(enum_values), self.help)
+
+def DEFINE_enum(name, default, enum_values, help, flag_values=FLAGS,
+ **args):
+ """
+ This registers a flag whose value can be a string from a set of
+ specified values.
+ """
+ DEFINE_flag(EnumFlag(name, default, help, enum_values, ** args),
+ flag_values)
+
+
+###############################
+################### LIST FLAGS
+###############################
+
+class BaseListParser(ArgumentParser):
+ """
+ A base class for a string list parser.
+ To extend, inherit from this class, and call
+
+ BaseListParser.__init__(self, token, name)
+
+ where token is a character used to tokenize, and
+ name is a description of the separator
+ """
+
+ def __init__(self, token=None, name=None):
+ assert name
+ self._token = token
+ self._name = name
+ self.syntactic_help = "a %s separated list" % self._name
+
+ def Parse(self, argument):
+ if argument == '':
+ return []
+ else:
+ return [s.strip() for s in argument.split(self._token)]
+
+
+class ListParser(BaseListParser):
+ """
+ A string list parser (comma-separated)
+ """
+
+ def __init__(self):
+ BaseListParser.__init__(self, ',', 'comma')
+
+class WhitespaceSeparatedListParser(BaseListParser):
+ """
+ A string list parser (whitespace-separated)
+ """
+
+ def __init__(self):
+ BaseListParser.__init__(self, None, 'whitespace')
+
+
+def DEFINE_list(name, default, help, flag_values=FLAGS, **args):
+ """
+ This registers a flag whose value is a list of strings, separated by commas
+ """
+ parser = ListParser()
+ serializer = ListSerializer(',')
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+def DEFINE_spaceseplist(name, default, help, flag_values=FLAGS, **args):
+ """
+ This registers a flag whose value is a list of strings, separated by any
+ whitespace
+ """
+ parser = WhitespaceSeparatedListParser()
+ serializer = ListSerializer(' ')
+ DEFINE(parser, name, default, help, flag_values, serializer, **args)
+
+
+###############################
+################## MULTI FLAGS
+###############################
+
+class MultiFlag(Flag):
+ """
+ MultiFlag is a specialized subclass of Flag that accumulates
+ multiple values in a list when a command-line option appears
+ multiple times.
+
+ See the __doc__ for Flag for most behavior of this class. Only
+ differences in behavior are described here:
+ * the default value may be a single value -OR- a list of values
+ * 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):
+ Flag.__init__(self, *args, **kwargs)
+
+ self.help = (self.help +
+ ';\n repeat this option to specify a list of values')
+
+ def Parse(self, arguments):
+ """Parse one or more arguments with the installed parser.
+
+ Arguments:
+ arguments: a single argument or a list of arguments (typically a list
+ of default values); single arguments will be converted internally into
+ a list containing one item
+ """
+ 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 ]
+
+ if self.present:
+ # keep a backup reference to list of previously supplied option values
+ values = self.value
+ else:
+ # "erase" the defaults with an empty list
+ values = []
+
+ for item in arguments:
+ # have Flag superclass parse argument, overwriting self.value reference
+ Flag.Parse(self, item) # also increments self.present
+ values.append(self.value)
+
+ # put list of option values back in member variable
+ self.value = values
+
+ def Serialize(self):
+ if not self.serializer:
+ raise FlagsError, "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 DEFINE_multi(parser, serializer, name, default, help, flag_values=FLAGS,
+ **args):
+ """
+ This creates a generic 'MultiFlag' object that parses its arguments with a
+ 'Parser' and registers it with a 'FlagValues' object.
+
+ Developers who need to create their own 'Parser' classes for options which
+ can appear multiple times can call this module function to register their
+ flags.
+ """
+ DEFINE_flag(MultiFlag(parser, serializer, name, default, help, **args), flag_values)
+
+def DEFINE_multistring(name, default, help, flag_values=FLAGS, **args):
+ """
+ This registers a flag whose value can be a list of any strings. Use the flag
+ on the command line multiple times to place multiple string values into the
+ list. The 'default' may be a single string (which will be converted into a
+ single-element list) or a list of strings.
+ """
+ parser = ArgumentParser()
+ serializer = ArgumentSerializer()
+ DEFINE_multi(parser, serializer, name, default, help, flag_values, **args)
+
+def DEFINE_multi_int(name, default, help, lower_bound=None, upper_bound=None,
+ flag_values=FLAGS, **args):
+ """
+ This registers a flag whose value can be a list of any integers. Use the
+ flag on the command line multiple times to place multiple integer values
+ into the list. The 'default' may be a single integer (which will be
+ converted into a single-element list) or a list of integers.
+ """
+ parser = IntegerParser(lower_bound, upper_bound)
+ serializer = ArgumentSerializer()
+ DEFINE_multi(parser, serializer, name, default, help, flag_values, **args)
+
+
+# Now register the flags that we want to exist in all applications.
+# These are all defined with allow_override=1, so user-apps can use
+# these flagnames for their own purposes, if they want.
+DEFINE_flag(HelpFlag())
+DEFINE_flag(HelpshortFlag())
diff --git a/contrib/gflags2man.py b/python/gflags2man.py
index 9074559..4edac1c 100755
--- a/contrib/gflags2man.py
+++ b/python/gflags2man.py
@@ -1,6 +1,33 @@
-#!/usr/bin/python2.4
+#!/usr/bin/env python
#
-# Copyright 2006 Google Inc. All Rights Reserved.
+# Copyright (c) 2007, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""gflags2man runs a Google flags base program and generates a man page.
@@ -8,26 +35,29 @@ Run the program, parse the output, and then format that into a man
page.
Usage:
- gflags2man program...
+ gflags2man <program> [program] ...
"""
+# TODO(csilvers): work with windows paths (\) as well as unix (/)
+
# This may seem a bit of an end run, but it: doesn't bloat flags, can
# support python/java/C++, supports older executables, and can be
# extended to other document formats.
# Inspired by help2man.
-__author__ = 'dchristian@google.com (Dan Christian)'
+__author__ = 'Dan Christian'
import os
import re
import sys
import stat
-import datetime
+import time
import subprocess
-from google3.pyglib import app
-from google3.pyglib import flags
-from google3.pyglib import logging
+import gflags
+
+_VERSION = '0.1'
+
def _GetDefaultDestDir():
home = os.environ.get('HOME', '')
@@ -35,17 +65,29 @@ def _GetDefaultDestDir():
if home and os.path.exists(homeman):
return homeman
else:
- return '/tmp'
+ return os.environ.get('TMPDIR', '/tmp')
-FLAGS = flags.FLAGS
-flags.DEFINE_string('dest_dir', _GetDefaultDestDir(),
- 'Directory to write resulting manpage to.'
- ' Specify \'-\' for stdout')
-flags.DEFINE_string('help_flag', '--help',
- 'Option to pass to target program in to get help')
+FLAGS = gflags.FLAGS
+gflags.DEFINE_string('dest_dir', _GetDefaultDestDir(),
+ 'Directory to write resulting manpage to.'
+ ' Specify \'-\' for stdout')
+gflags.DEFINE_string('help_flag', '--help',
+ 'Option to pass to target program in to get help')
+gflags.DEFINE_integer('v', 0, 'verbosity level to use for output')
+
+_MIN_VALID_USAGE_MSG = 9 # if fewer lines than this, help is suspect
+
+
+class Logging:
+ """A super-simple logging class"""
+ def error(self, msg): print >>sys.stderr, "ERROR: ", msg
+ def warn(self, msg): print >>sys.stderr, "WARNING: ", msg
+ def info(self, msg): print msg
+ def debug(self, msg): self.vlog(1, msg)
+ def vlog(self, level, msg):
+ if FLAGS.v >= level: print msg
+logging = Logging()
-MIN_VALID_USAGE_MSG = 9 # minimum output likely to be valid
-_version = '0.1'
def GetRealPath(filename):
"""Given an executable filename, find in the PATH or find absolute path.
@@ -55,7 +97,7 @@ def GetRealPath(filename):
Absolute version of filename.
None if filename could not be found locally, absolutely, or in PATH
"""
- if '/' == filename[0]: # already absolute
+ if os.path.isabs(filename): # already absolute
return filename
if filename.startswith('./') or filename.startswith('../'): # relative
@@ -65,7 +107,7 @@ def GetRealPath(filename):
for directory in path.split(':'):
tryname = os.path.join(directory, filename)
if os.path.exists(tryname):
- if not directory or '/' != directory[0]: # directory is relative
+ if not os.path.isabs(directory): # relative directory
return os.path.abspath(tryname)
return tryname
if os.path.exists(filename):
@@ -88,31 +130,31 @@ class Flag(object):
class ProgramInfo(object):
- """All the information gleened from running a program with --help."""
+ """All the information gleaned from running a program with --help."""
- # Match a module block start
- # google3.pyglib.logging:
+ # Match a module block start, for python scripts --help
+ # "goopy.logging:"
module_py_re = re.compile(r'(\S.+):$')
# match the start of a flag listing
- # -v,--verbosity: Logging verbosity
+ # " -v,--verbosity: Logging verbosity"
flag_py_re = re.compile(r'\s+(-\S+):\s+(.*)$')
- # (default: '0')
+ # " (default: '0')"
flag_default_py_re = re.compile(r'\s+\(default:\s+\'(.*)\'\)$')
- # (an integer)
+ # " (an integer)"
flag_tips_py_re = re.compile(r'\s+\((.*)\)$')
- # Match a module block start
- # google3/base/commandlineflags
+ # Match a module block start, for c++ programs --help
+ # "google/base/commandlineflags"
module_c_re = re.compile(r'\s+Flags from (\S.+):$')
# match the start of a flag listing
- # -v,--verbosity: Logging verbosity
+ # " -v,--verbosity: Logging verbosity"
flag_c_re = re.compile(r'\s+(-\S+)\s+(.*)$')
- # Match a module block start
- # com.google.common.flags
+ # Match a module block start, for java programs --help
+ # "com.google.common.flags"
module_java_re = re.compile(r'\s+Flags for (\S.+):$')
# match the start of a flag listing
- # -v,--verbosity: Logging verbosity
+ # " -v,--verbosity: Logging verbosity"
flag_java_re = re.compile(r'\s+(-\S+)\s+(.*)$')
def __init__(self, executable):
@@ -121,29 +163,29 @@ class ProgramInfo(object):
executable Program to execute (string)
"""
self.long_name = executable
- self.name = os.path.basename(executable) # name
+ self.name = os.path.basename(executable) # name
# Get name without extension (PAR files)
- self.short_name, self.ext = os.path.splitext(self.name)
- self.executable = GetRealPath(executable) # name of the program
- self.output = [] # output from the program. List of lines.
- self.desc = [] # top level description. List of lines
- self.modules = {} # { section_name(string), [ flags ] }
- self.module_list = [] # list of module names in their original order
- self.date = datetime.date.today() # default date info
+ (self.short_name, self.ext) = os.path.splitext(self.name)
+ self.executable = GetRealPath(executable) # name of the program
+ self.output = [] # output from the program. List of lines.
+ self.desc = [] # top level description. List of lines
+ self.modules = {} # { section_name(string), [ flags ] }
+ self.module_list = [] # list of module names in their original order
+ self.date = time.localtime(time.time()) # default date info
def Run(self):
"""Run it and collect output.
Returns:
- True If everything went well.
- False If there were problems.
+ 1 (true) If everything went well.
+ 0 (false) If there were problems.
"""
if not self.executable:
logging.error('Could not locate "%s"' % self.long_name)
- return False
+ return 0
finfo = os.stat(self.executable)
- self.date = datetime.date.fromtimestamp(finfo[stat.ST_MTIME])
+ self.date = time.localtime(finfo[stat.ST_MTIME])
logging.info('Running: %s %s </dev/null 2>&1'
% (self.executable, FLAGS.help_flag))
@@ -157,70 +199,71 @@ class ProgramInfo(object):
stdin=open('/dev/null', 'r'))
except OSError, msg:
logging.error('Error executing "%s": %s' % (self.name, msg))
- return False
+ return 0
- #read output progressively so the pipe doesn't fill up (fileutil).
+ # read output progressively so the pipe doesn't fill up (fileutil).
self.output = runstate.stdout.readlines()
status = runstate.wait()
logging.debug('Program exited with %s' % status)
output = runstate.communicate()[0]
if output:
self.output = output.splitlines()
- if len(self.output) < MIN_VALID_USAGE_MSG:
+ if len(self.output) < _MIN_VALID_USAGE_MSG:
logging.error(
'Error: "%s %s" returned %d and only %d lines: %s'
% (self.name, FLAGS.help_flag, status, len(self.output), output))
- return False
- return True
+ return 0
+ return 1
def Parse(self):
"""Parse program output."""
- cnt, lang = self.ParseDesc()
- if cnt < 0:
+ (start_line, lang) = self.ParseDesc()
+ if start_line < 0:
return
if 'python' == lang:
- self.ParsePythonFlags(cnt)
+ self.ParsePythonFlags(start_line)
elif 'c' == lang:
- self.ParseCFlags(cnt)
+ self.ParseCFlags(start_line)
elif 'java' == lang:
- self.ParseJavaFlags(cnt)
+ self.ParseJavaFlags(start_line)
- def ParseDesc(self, cnt=0):
+ def ParseDesc(self, start_line=0):
"""Parse the initial description.
This could be Python or C++.
Returns:
- (line_count, lang_type)
- line_count Line to start parsing flags on (int)
+ (start_line, lang_type)
+ start_line Line to start parsing flags on (int)
lang_type Either 'python' or 'c'
(-1, '') if the flags start could not be found
"""
exec_mod_start = self.executable + ':'
- after_blank = False
- cnt = 0
- for cnt in range(cnt, len(self.output)): # collect top description
- line = self.output[cnt].rstrip()
+ after_blank = 0
+ start_line = 0 # ignore the passed-in arg for now (?)
+ for start_line in range(start_line, len(self.output)): # collect top description
+ line = self.output[start_line].rstrip()
# Python flags start with 'flags:\n'
if ('flags:' == line
- and len(self.output) > cnt+1 and '' == self.output[cnt+1].rstrip()):
- cnt += 2
+ and len(self.output) > start_line+1
+ and '' == self.output[start_line+1].rstrip()):
+ start_line += 2
logging.debug('Flags start (python): %s' % line)
- return (cnt, 'python')
+ return (start_line, 'python')
# SWIG flags just have the module name followed by colon.
if exec_mod_start == line:
logging.debug('Flags start (swig): %s' % line)
- return (cnt, 'python')
+ return (start_line, 'python')
# C++ flags begin after a blank line and with a constant string
if after_blank and line.startswith(' Flags from '):
logging.debug('Flags start (c): %s' % line)
- return (cnt, 'c')
+ return (start_line, 'c')
# java flags begin with a constant string
if line == 'where flags are':
logging.debug('Flags start (java): %s' % line)
- cnt += 2 # skip "Standard flags:"
- return (cnt, 'java')
+ start_line += 2 # skip "Standard flags:"
+ return (start_line, 'java')
logging.debug('Desc: %s' % line)
self.desc.append(line)
@@ -230,12 +273,13 @@ class ProgramInfo(object):
% self.long_name)
return (-1, '')
- def ParsePythonFlags(self, cnt=0):
+ def ParsePythonFlags(self, start_line=0):
"""Parse python/swig style flags."""
modname = None # name of current module
+ modlist = []
flag = None
- for cnt in range(cnt, len(self.output)): # collect flags
- line = self.output[cnt].rstrip()
+ for line_num in range(start_line, len(self.output)): # collect flags
+ line = self.output[line_num].rstrip()
if not line: # blank
continue
@@ -278,12 +322,13 @@ class ProgramInfo(object):
if flag:
modlist.append(flag)
- def ParseCFlags(self, cnt=0):
+ def ParseCFlags(self, start_line=0):
"""Parse C style flags."""
modname = None # name of current module
+ modlist = []
flag = None
- for cnt in range(cnt, len(self.output)): # collect flags
- line = self.output[cnt].rstrip()
+ for line_num in range(start_line, len(self.output)): # collect flags
+ line = self.output[line_num].rstrip()
if not line: # blank lines terminate flags
if flag: # save last flag
modlist.append(flag)
@@ -318,7 +363,7 @@ class ProgramInfo(object):
if flag:
modlist.append(flag)
- def ParseJavaFlags(self, cnt=0):
+ def ParseJavaFlags(self, start_line=0):
"""Parse Java style flags (com.google.common.flags)."""
# The java flags prints starts with a "Standard flags" "module"
# that doesn't follow the standard module syntax.
@@ -328,8 +373,8 @@ class ProgramInfo(object):
modlist = self.modules[modname]
flag = None
- for cnt in range(cnt, len(self.output)): # collect flags
- line = self.output[cnt].rstrip()
+ for line_num in range(start_line, len(self.output)): # collect flags
+ line = self.output[line_num].rstrip()
logging.vlog(2, 'Line: "%s"' % line)
if not line: # blank lines terminate module
if flag: # save last flag
@@ -371,9 +416,9 @@ class ProgramInfo(object):
self.short_desc = ''
return
- for cnt in range(len(self.desc)): # replace full path with name
- if self.desc[cnt].find(self.executable) >= 0:
- self.desc[cnt] = self.desc[cnt].replace(self.executable, self.name)
+ for i in range(len(self.desc)): # replace full path with name
+ if self.desc[i].find(self.executable) >= 0:
+ self.desc[i] = self.desc[i].replace(self.executable, self.name)
self.short_desc = self.desc[0]
word_list = self.short_desc.split(' ')
@@ -407,6 +452,11 @@ class GenerateDoc(object):
self.Body()
self.Footer()
+ def Open(self): raise NotImplementedError # define in subclass
+ def Header(self): raise NotImplementedError # define in subclass
+ def Body(self): raise NotImplementedError # define in subclass
+ def Footer(self): raise NotImplementedError # define in subclass
+
class GenerateMan(GenerateDoc):
"""Output a man page."""
@@ -431,10 +481,10 @@ class GenerateMan(GenerateDoc):
def Header(self):
self.fp.write(
'.\\" DO NOT MODIFY THIS FILE! It was generated by gflags2man %s\n'
- % _version)
+ % _VERSION)
self.fp.write(
'.TH %s "1" "%s" "%s" "User Commands"\n'
- % (self.info.name, self.info.date.strftime('%x'), self.info.name))
+ % (self.info.name, time.strftime('%x', self.info.date), self.info.name))
self.fp.write(
'.SH NAME\n%s \\- %s\n' % (self.info.name, self.info.short_desc))
self.fp.write(
@@ -455,30 +505,33 @@ class GenerateMan(GenerateDoc):
mod = modname
self.fp.write('\n.P\n.I %s\n' % mod)
for flag in self.info.modules[modname]:
- help = flag.help
+ help_string = flag.help
if flag.default or flag.tips:
- help += '\n.br\n'
+ help_string += '\n.br\n'
if flag.default:
- help += ' (default: \'%s\')' % flag.default
+ help_string += ' (default: \'%s\')' % flag.default
if flag.tips:
- help += ' (%s)' % flag.tips
+ help_string += ' (%s)' % flag.tips
self.fp.write(
- '.TP\n%s\n%s\n' % (flag.desc, help))
+ '.TP\n%s\n%s\n' % (flag.desc, help_string))
def Footer(self):
self.fp.write(
'.SH COPYRIGHT\nCopyright \(co %s Google.\n'
- % self.info.date.strftime('%Y'))
- self.fp.write('Gflags2man.par created this page from "%s %s" output.\n'
+ % time.strftime('%Y', self.info.date))
+ self.fp.write('Gflags2man created this page from "%s %s" output.\n'
% (self.info.name, FLAGS.help_flag))
- self.fp.write('\nGflags2man.par was written by Dan Christian'
- ' (dchristian@google.com). Note that the date on this'
+ self.fp.write('\nGflags2man was written by Dan Christian. '
+ ' Note that the date on this'
' page is the modification date of %s.\n' % self.info.name)
def main(argv):
+ argv = FLAGS(argv) # handles help as well
if len(argv) <= 1:
- app.usage(shorthelp=1)
+ print >>sys.stderr, __doc__
+ print >>sys.stderr, "flags:"
+ print >>sys.stderr, str(FLAGS)
return 1
for arg in argv[1:]:
@@ -489,7 +542,7 @@ def main(argv):
prog.Filter()
doc = GenerateMan(prog, FLAGS.dest_dir)
doc.Output()
-
+ return 0
if __name__ == '__main__':
- app.run()
+ main(sys.argv)
diff --git a/python/gflags_unittest.py b/python/gflags_unittest.py
new file mode 100755
index 0000000..d55a26a
--- /dev/null
+++ b/python/gflags_unittest.py
@@ -0,0 +1,800 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2007, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"Unittest for flags.py module"
+
+__pychecker__ = "no-local" # for unittest
+
+
+import sys
+import os
+import shutil
+import unittest
+
+# We use the name 'flags' internally in this test, for historical reasons.
+# Don't do this yourself! :-) Just do 'import gflags; FLAGS=gflags.FLAGS; etc'
+import gflags as flags
+FLAGS=flags.FLAGS
+
+class FlagsUnitTest(unittest.TestCase):
+ "Flags Unit Test"
+
+ def test_flags(self):
+
+ ##############################################
+ # Test normal usage with no (expected) errors.
+
+ # Define flags
+ number_test_framework_flags = len(FLAGS.RegisteredFlags())
+ repeatHelp = "how many times to repeat (0-5)"
+ flags.DEFINE_integer("repeat", 4, repeatHelp,
+ lower_bound=0, short_name='r')
+ flags.DEFINE_string("name", "Bob", "namehelp")
+ flags.DEFINE_boolean("debug", 0, "debughelp")
+ flags.DEFINE_boolean("q", 1, "quiet mode")
+ flags.DEFINE_boolean("quack", 0, "superstring of 'q'")
+ flags.DEFINE_boolean("noexec", 1, "boolean flag with no as prefix")
+ flags.DEFINE_integer("x", 3, "how eXtreme to be")
+ flags.DEFINE_integer("l", 0x7fffffff00000000L, "how long to be")
+ assert FLAGS.repeat == 4, "integer default values not set:" + FLAGS.repeat
+ assert FLAGS.name == 'Bob', "default values not set:" + FLAGS.name
+ assert FLAGS.debug == 0, "boolean default values not set:" + FLAGS.debug
+ assert FLAGS.q == 1, "boolean default values not set:" + FLAGS.q
+ assert FLAGS.x == 3, "integer default values not set:" + FLAGS.x
+ assert FLAGS.l == 0x7fffffff00000000L, "integer default values not set:" + FLAGS.l
+
+ flag_values = FLAGS.FlagValuesDict()
+ assert flag_values['repeat'] == 4
+ assert flag_values['name'] == 'Bob'
+ assert flag_values['debug'] == 0
+ assert flag_values['r'] == 4 # short for of repeat
+ assert flag_values['q'] == 1
+ assert flag_values['quack'] == 0
+ assert flag_values['x'] == 3
+ assert flag_values['l'] == 0x7fffffff00000000L
+
+ # Verify string form of defaults
+ assert FLAGS['repeat'].default_as_str == "'4'"
+ assert FLAGS['name'].default_as_str == "'Bob'"
+ assert FLAGS['debug'].default_as_str == "'false'"
+ assert FLAGS['q'].default_as_str == "'true'"
+ assert FLAGS['quack'].default_as_str == "'false'"
+ assert FLAGS['noexec'].default_as_str == "'true'"
+ assert FLAGS['x'].default_as_str == "'3'"
+ assert FLAGS['l'].default_as_str == "'9223372032559808512'"
+
+ # Verify that the iterator for flags yields all the keys
+ keys = list(FLAGS)
+ keys.sort()
+ reg_flags = FLAGS.RegisteredFlags()
+ reg_flags.sort()
+ self.assertEqual(keys, reg_flags)
+
+ # Parse flags
+ # .. empty command line
+ argv = ('./program',)
+ argv = FLAGS(argv)
+ assert len(argv) == 1, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+
+ # .. non-empty command line
+ argv = ('./program', '--debug', '--name=Bob', '-q', '--x=8')
+ argv = FLAGS(argv)
+ assert len(argv) == 1, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert FLAGS['debug'].present == 1
+ FLAGS['debug'].present = 0 # Reset
+ assert FLAGS['name'].present == 1
+ FLAGS['name'].present = 0 # Reset
+ assert FLAGS['q'].present == 1
+ FLAGS['q'].present = 0 # Reset
+ assert FLAGS['x'].present == 1
+ FLAGS['x'].present = 0 # Reset
+
+ # Flags list
+
+ assert len(FLAGS.RegisteredFlags()) == 9 + number_test_framework_flags
+ assert 'name' in FLAGS.RegisteredFlags()
+ assert 'debug' in FLAGS.RegisteredFlags()
+ assert 'repeat' in FLAGS.RegisteredFlags()
+ assert 'r' in FLAGS.RegisteredFlags()
+ assert 'q' in FLAGS.RegisteredFlags()
+ assert 'quack' in FLAGS.RegisteredFlags()
+ assert 'x' in FLAGS.RegisteredFlags()
+ assert 'l' in FLAGS.RegisteredFlags()
+
+ # has_key
+ assert FLAGS.has_key('name')
+ assert not FLAGS.has_key('name2')
+ assert 'name' in FLAGS
+ assert 'name2' not in FLAGS
+
+ # try deleting a flag
+ del FLAGS.r
+ assert len(FLAGS.RegisteredFlags()) == 8 + number_test_framework_flags
+ assert not 'r' in FLAGS.RegisteredFlags()
+
+ # .. command line with extra stuff
+ argv = ('./program', '--debug', '--name=Bob', 'extra')
+ argv = FLAGS(argv)
+ assert len(argv) == 2, "wrong number of arguments pulled"
+ assert argv[0]=='./program', "program name not preserved"
+ assert argv[1]=='extra', "extra argument not preserved"
+ assert FLAGS['debug'].present == 1
+ FLAGS['debug'].present = 0 # Reset
+ assert FLAGS['name'].present == 1
+ FLAGS['name'].present = 0 # Reset
+
+ # Test reset
+ argv = ('./program', '--debug')
+ argv = FLAGS(argv)
+ assert len(argv) == 1, "wrong number of arguments pulled"
+ assert argv[0] == './program', "program name not preserved"
+ assert FLAGS['debug'].present == 1
+ assert FLAGS['debug'].value == True
+ FLAGS.Reset()
+ assert FLAGS['debug'].present == 0
+ assert FLAGS['debug'].value == False
+
+ # Test integer argument passing
+ argv = ('./program', '--x', '0x12345')
+ argv = FLAGS(argv)
+ # 0x12345 == 74565
+ self.assertEquals(FLAGS.x, 74565)
+ self.assertEquals(type(FLAGS.x), int)
+
+ argv = ('./program', '--x', '0x123456789A')
+ argv = FLAGS(argv)
+ # 0x123456789A == 78187493530L
+ self.assertEquals(FLAGS.x, 78187493530L)
+ self.assertEquals(type(FLAGS.x), long)
+
+ # Treat 0-prefixed parameters as base-10, not base-8
+ argv = ('./program', '--x', '012345')
+ argv = FLAGS(argv)
+ self.assertEquals(FLAGS.x, 12345)
+ self.assertEquals(type(FLAGS.x), int)
+
+ argv = ('./program', '--x', '0123459')
+ argv = FLAGS(argv)
+ self.assertEquals(FLAGS.x, 123459)
+ self.assertEquals(type(FLAGS.x), int)
+
+ argv = ('./program', '--x', '0x123efg')
+ try:
+ argv = FLAGS(argv)
+ raise AssertionError("failed to detect invalid hex argument")
+ except flags.IllegalFlagValue:
+ pass
+
+ argv = ('./program', '--x', '0X123efg')
+ try:
+ argv = FLAGS(argv)
+ raise AssertionError("failed to detect invalid hex argument")
+ except flags.IllegalFlagValue:
+ pass
+
+ # Test boolean argument parsing
+ flags.DEFINE_boolean("test0", None, "test boolean parsing")
+ argv = ('./program', '--notest0')
+ argv = FLAGS(argv)
+ assert FLAGS.test0 == 0
+
+ flags.DEFINE_boolean("test1", None, "test boolean parsing")
+ argv = ('./program', '--test1')
+ argv = FLAGS(argv)
+ assert FLAGS.test1 == 1
+
+ FLAGS.test0 = None
+ argv = ('./program', '--test0=false')
+ argv = FLAGS(argv)
+ assert FLAGS.test0 == 0
+
+ FLAGS.test1 = None
+ argv = ('./program', '--test1=true')
+ argv = FLAGS(argv)
+ assert FLAGS.test1 == 1
+
+ FLAGS.test0 = None
+ argv = ('./program', '--test0=0')
+ argv = FLAGS(argv)
+ assert FLAGS.test0 == 0
+
+ FLAGS.test1 = None
+ argv = ('./program', '--test1=1')
+ argv = FLAGS(argv)
+ assert FLAGS.test1 == 1
+
+ # Test booleans that already have 'no' as a prefix
+ FLAGS.noexec = None
+ argv = ('./program', '--nonoexec', '--name', 'Bob')
+ argv = FLAGS(argv)
+ assert FLAGS.noexec == 0
+
+ FLAGS.noexec = None
+ argv = ('./program', '--name', 'Bob', '--noexec')
+ argv = FLAGS(argv)
+ assert FLAGS.noexec == 1
+
+ # Test unassigned booleans
+ flags.DEFINE_boolean("testnone", None, "test boolean parsing")
+ argv = ('./program',)
+ argv = FLAGS(argv)
+ assert FLAGS.testnone == None
+
+ # Test get with default
+ flags.DEFINE_boolean("testget1", None, "test parsing with defaults")
+ flags.DEFINE_boolean("testget2", None, "test parsing with defaults")
+ flags.DEFINE_boolean("testget3", None, "test parsing with defaults")
+ flags.DEFINE_integer("testget4", None, "test parsing with defaults")
+ argv = ('./program','--testget1','--notestget2')
+ argv = FLAGS(argv)
+ assert FLAGS.get('testget1', 'foo') == 1
+ assert FLAGS.get('testget2', 'foo') == 0
+ assert FLAGS.get('testget3', 'foo') == 'foo'
+ assert FLAGS.get('testget4', 'foo') == 'foo'
+
+ # test list code
+ lists = [['hello','moo','boo','1'],
+ [],]
+
+ flags.DEFINE_list('testlist', '', 'test lists parsing')
+ flags.DEFINE_spaceseplist('testspacelist', '', 'tests space lists parsing')
+
+ for name, sep in (('testlist', ','), ('testspacelist', ' '),
+ ('testspacelist', '\n')):
+ for lst in lists:
+ argv = ('./program', '--%s=%s' % (name, sep.join(lst)))
+ argv = FLAGS(argv)
+ self.assertEquals(getattr(FLAGS, name), lst)
+
+ # Test help text
+ flagsHelp = str(FLAGS)
+ assert flagsHelp.find("repeat") != -1, "cannot find flag in help"
+ assert flagsHelp.find(repeatHelp) != -1, "cannot find help string in help"
+
+ # Test flag specified twice
+ argv = ('./program', '--repeat=4', '--repeat=2', '--debug', '--nodebug')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('repeat', None), 2)
+ self.assertEqual(FLAGS.get('debug', None), 0)
+
+ # Test MultiFlag with single default value
+ flags.DEFINE_multistring('s_str', 'sing1',
+ 'string option that can occur multiple times',
+ short_name='s')
+ self.assertEqual(FLAGS.get('s_str', None), [ 'sing1', ])
+
+ # Test MultiFlag with list of default values
+ multi_string_defs = [ 'def1', 'def2', ]
+ flags.DEFINE_multistring('m_str', multi_string_defs,
+ 'string option that can occur multiple times',
+ short_name='m')
+ self.assertEqual(FLAGS.get('m_str', None), multi_string_defs)
+
+ # Test flag specified multiple times with a MultiFlag
+ argv = ('./program', '--m_str=str1', '-m', 'str2')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('m_str', None), [ 'str1', 'str2', ])
+
+ # Test single-letter flags; should support both single and double dash
+ argv = ('./program', '-q', '-x8')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('q', None), 1)
+ self.assertEqual(FLAGS.get('x', None), 8)
+
+ argv = ('./program', '--q', '--x', '9', '--noqu')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('q', None), 1)
+ self.assertEqual(FLAGS.get('x', None), 9)
+ # --noqu should match '--noquack since it's a unique prefix
+ self.assertEqual(FLAGS.get('quack', None), 0)
+
+ argv = ('./program', '--noq', '--x=10', '--qu')
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.get('q', None), 0)
+ self.assertEqual(FLAGS.get('x', None), 10)
+ self.assertEqual(FLAGS.get('quack', None), 1)
+
+ ####################################
+ # Test flag serialization code:
+
+ oldtestlist = FLAGS.testlist
+ oldtestspacelist = FLAGS.testspacelist
+
+ argv = ('./program',
+ FLAGS['test0'].Serialize(),
+ FLAGS['test1'].Serialize(),
+ FLAGS['testnone'].Serialize(),
+ FLAGS['s_str'].Serialize())
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS['test0'].Serialize(), '--notest0')
+ self.assertEqual(FLAGS['test1'].Serialize(), '--test1')
+ self.assertEqual(FLAGS['testnone'].Serialize(), '')
+ self.assertEqual(FLAGS['s_str'].Serialize(), '--s_str=sing1')
+
+ testlist1 = ['aa', 'bb']
+ testspacelist1 = ['aa', 'bb', 'cc']
+ FLAGS.testlist = list(testlist1)
+ FLAGS.testspacelist = list(testspacelist1)
+ argv = ('./program',
+ FLAGS['testlist'].Serialize(),
+ FLAGS['testspacelist'].Serialize())
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.testlist, testlist1)
+ self.assertEqual(FLAGS.testspacelist, testspacelist1)
+
+ testlist1 = ['aa some spaces', 'bb']
+ testspacelist1 = ['aa', 'bb,some,commas,', 'cc']
+ FLAGS.testlist = list(testlist1)
+ FLAGS.testspacelist = list(testspacelist1)
+ argv = ('./program',
+ FLAGS['testlist'].Serialize(),
+ FLAGS['testspacelist'].Serialize())
+ argv = FLAGS(argv)
+ self.assertEqual(FLAGS.testlist, testlist1)
+ self.assertEqual(FLAGS.testspacelist, testspacelist1)
+
+ FLAGS.testlist = oldtestlist
+ FLAGS.testspacelist = oldtestspacelist
+
+ ####################################
+ # Test flag-update:
+
+ def ArgsString():
+ flagnames = FLAGS.RegisteredFlags()
+ flagnames.sort()
+ nonbool_flags = ['--%s %s' % (name, FLAGS.get(name, None))
+ for name in flagnames
+ if not isinstance(FLAGS[name], flags.BooleanFlag)]
+
+ truebool_flags = ['--%s' % (name)
+ for name in flagnames
+ if isinstance(FLAGS[name], flags.BooleanFlag) and
+ FLAGS.get(name, None)]
+ falsebool_flags = ['--no%s' % (name)
+ for name in flagnames
+ if isinstance(FLAGS[name], flags.BooleanFlag) and
+ not FLAGS.get(name, None)]
+ return ' '.join(nonbool_flags + truebool_flags + falsebool_flags)
+
+ argv = ('./program', '--repeat=3', '--name=giants', '--nodebug')
+ FLAGS(argv)
+ self.assertEqual(FLAGS.get('repeat', None), 3)
+ self.assertEqual(FLAGS.get('name', None), 'giants')
+ self.assertEqual(FLAGS.get('debug', None), 0)
+ self.assertEqual(ArgsString(),
+ "--l 9223372032559808512 "
+ "--m ['str1', 'str2'] --m_str ['str1', 'str2'] "
+ "--name giants "
+ "--repeat 3 "
+ "--s ['sing1'] --s_str ['sing1'] "
+ "--testget4 None --testlist [] "
+ "--testspacelist [] --x 10 "
+ "--noexec --quack "
+ "--test1 "
+ "--testget1 --no? --nodebug --nohelp --nohelpshort "
+ "--noq --notest0 --notestget2 "
+ "--notestget3 --notestnone")
+
+ argv = ('./program', '--debug', '--m_str=upd1', '-s', 'upd2')
+ FLAGS(argv)
+ self.assertEqual(FLAGS.get('repeat', None), 3)
+ self.assertEqual(FLAGS.get('name', None), 'giants')
+ self.assertEqual(FLAGS.get('debug', None), 1)
+
+ # items appended to existing non-default value lists for --m/--m_str
+ # new value overwrites default value (not appended to it) for --s/--s_str
+ self.assertEqual(ArgsString(),
+ "--l 9223372032559808512 "
+ "--m ['str1', 'str2', 'upd1'] "
+ "--m_str ['str1', 'str2', 'upd1'] "
+ "--name giants "
+ "--repeat 3 "
+ "--s ['upd2'] --s_str ['upd2'] "
+ "--testget4 None --testlist [] "
+ "--testspacelist [] --x 10 "
+ "--debug --noexec --quack "
+ "--test1 "
+ "--testget1 --no? --nohelp --nohelpshort "
+ "--noq --notest0 --notestget2 "
+ "--notestget3 --notestnone")
+
+
+ ####################################
+ # Test all kind of error conditions.
+
+ # Duplicate flag detection
+ try:
+ flags.DEFINE_boolean("run", 0, "runhelp", short_name='q')
+ raise AssertionError("duplicate flag detection failed")
+ except flags.DuplicateFlag, e:
+ pass
+
+ try:
+ flags.DEFINE_boolean("zoom1", 0, "runhelp z1", short_name='z')
+ flags.DEFINE_boolean("zoom2", 0, "runhelp z2", short_name='z')
+ raise AssertionError("duplicate flag detection failed")
+ except flags.DuplicateFlag, e:
+ pass
+
+ # Make sure allow_override works
+ try:
+ flags.DEFINE_boolean("dup1", 0, "runhelp d11", short_name='u',
+ allow_override=0)
+ flag = FLAGS.FlagDict()['dup1']
+ self.assertEqual(flag.default, 0)
+
+ flags.DEFINE_boolean("dup1", 1, "runhelp d12", short_name='u',
+ allow_override=1)
+ flag = FLAGS.FlagDict()['dup1']
+ self.assertEqual(flag.default, 1)
+ except flags.DuplicateFlag, e:
+ raise AssertionError("allow_override did not permit a flag duplication")
+
+ # Make sure allow_override works
+ try:
+ flags.DEFINE_boolean("dup2", 0, "runhelp d21", short_name='u',
+ allow_override=1)
+ flag = FLAGS.FlagDict()['dup2']
+ self.assertEqual(flag.default, 0)
+
+ flags.DEFINE_boolean("dup2", 1, "runhelp d22", short_name='u',
+ allow_override=0)
+ flag = FLAGS.FlagDict()['dup2']
+ self.assertEqual(flag.default, 1)
+ except flags.DuplicateFlag, e:
+ raise AssertionError("allow_override did not permit a flag duplication")
+
+ # Make sure allow_override doesn't work with None default
+ try:
+ flags.DEFINE_boolean("dup3", 0, "runhelp d31", short_name='u',
+ allow_override=0)
+ flag = FLAGS.FlagDict()['dup3']
+ self.assertEqual(flag.default, 0)
+
+ flags.DEFINE_boolean("dup3", None, "runhelp d32", short_name='u',
+ allow_override=1)
+ raise AssertionError('Cannot override a flag with a default of None')
+ except flags.DuplicateFlag, e:
+ pass
+
+ # Make sure that when we override, the help string gets updated correctly
+ flags.DEFINE_boolean("dup3", 0, "runhelp d31", short_name='u',
+ allow_override=1)
+ flags.DEFINE_boolean("dup3", 1, "runhelp d32", short_name='u',
+ allow_override=1)
+ self.assert_(str(FLAGS).find('runhelp d31') == -1)
+ self.assert_(str(FLAGS).find('runhelp d32') != -1)
+
+ # Integer out of bounds
+ try:
+ argv = ('./program', '--repeat=-4')
+ FLAGS(argv)
+ raise AssertionError('integer bounds exception not thrown:'
+ + str(FLAGS.repeat))
+ except flags.IllegalFlagValue:
+ pass
+
+ # Non-integer
+ try:
+ argv = ('./program', '--repeat=2.5')
+ FLAGS(argv)
+ raise AssertionError("malformed integer value exception not thrown")
+ except flags.IllegalFlagValue:
+ pass
+
+ # Missing required arugment
+ try:
+ argv = ('./program', '--name')
+ FLAGS(argv)
+ raise AssertionError("Flag argument required exception not thrown")
+ except flags.FlagsError:
+ pass
+
+ # Argument erroneously supplied for boolean
+ try:
+ argv = ('./program', '--debug=goofup')
+ FLAGS(argv)
+ raise AssertionError("No argument allowed exception not thrown")
+ except flags.FlagsError:
+ pass
+
+ # Unknown argument --nosuchflag
+ try:
+ argv = ('./program', '--nosuchflag', '--name=Bob', 'extra')
+ FLAGS(argv)
+ raise AssertionError("Unknown argument exception not thrown")
+ except flags.FlagsError:
+ pass
+
+ # Non-numeric argument for integer flag --repeat
+ try:
+ argv = ('./program', '--repeat', 'Bob', 'extra')
+ FLAGS(argv)
+ raise AssertionError("Illegal flag value exception not thrown")
+ except flags.IllegalFlagValue:
+ pass
+
+ ################################################
+ # Code to test the flagfile=<> loading behavior
+ ################################################
+ def _SetupTestFiles(self):
+ """ Creates and sets up some dummy flagfile files with bogus flags"""
+
+ # Figure out where to create temporary files
+ tmp_path = '/tmp/flags_unittest'
+ if os.path.exists(tmp_path):
+ shutil.rmtree(tmp_path)
+ os.makedirs(tmp_path)
+
+ try:
+ tmp_flag_file_1 = open((tmp_path + '/UnitTestFile1.tst'), 'w')
+ tmp_flag_file_2 = open((tmp_path + '/UnitTestFile2.tst'), 'w')
+ tmp_flag_file_3 = open((tmp_path + '/UnitTestFile3.tst'), 'w')
+ except IOError, e_msg:
+ print e_msg
+ print 'FAIL\n File Creation problem in Unit Test'
+ sys.exit(1)
+
+ # put some dummy flags in our test files
+ tmp_flag_file_1.write('#A Fake Comment\n')
+ tmp_flag_file_1.write('--UnitTestMessage1=tempFile1!\n')
+ tmp_flag_file_1.write('\n')
+ tmp_flag_file_1.write('--UnitTestNumber=54321\n')
+ tmp_flag_file_1.write('--noUnitTestBoolFlag\n')
+ file_list = [tmp_flag_file_1.name]
+ # this one includes test file 1
+ tmp_flag_file_2.write('//A Different Fake Comment\n')
+ tmp_flag_file_2.write('--flagfile=%s\n' % tmp_flag_file_1.name)
+ tmp_flag_file_2.write('--UnitTestMessage2=setFromTempFile2\n')
+ tmp_flag_file_2.write('\t\t\n')
+ tmp_flag_file_2.write('--UnitTestNumber=6789a\n')
+ file_list.append(tmp_flag_file_2.name)
+ # this file points to itself
+ tmp_flag_file_3.write('--flagfile=%s\n' % tmp_flag_file_3.name)
+ tmp_flag_file_3.write('--UnitTestMessage1=setFromTempFile3\n')
+ tmp_flag_file_3.write('#YAFC\n')
+ tmp_flag_file_3.write('--UnitTestBoolFlag\n')
+ file_list.append(tmp_flag_file_3.name)
+
+ tmp_flag_file_1.close()
+ tmp_flag_file_2.close()
+ tmp_flag_file_3.close()
+
+ return file_list # these are just the file names
+ # end SetupFiles def
+
+ def _RemoveTestFiles(self, tmp_file_list):
+ """Closes the files we just created. tempfile deletes them for us """
+ for file_name in tmp_file_list:
+ try:
+ os.remove(file_name)
+ except OSError, e_msg:
+ print '%s\n, Problem deleting test file' % e_msg
+ #end RemoveTestFiles def
+
+ def __DeclareSomeFlags(self):
+ flags.DEFINE_string('UnitTestMessage1', 'Foo!', 'You Add Here.')
+ flags.DEFINE_string('UnitTestMessage2', 'Bar!', 'Hello, Sailor!')
+ flags.DEFINE_boolean('UnitTestBoolFlag', 0, 'Some Boolean thing')
+ flags.DEFINE_integer('UnitTestNumber', 12345, 'Some integer',
+ lower_bound=0)
+
+ def _UndeclareSomeFlags(self):
+ FLAGS.__delattr__('UnitTestMessage1')
+ FLAGS.__delattr__('UnitTestMessage2')
+ FLAGS.__delattr__('UnitTestBoolFlag')
+ FLAGS.__delattr__('UnitTestNumber')
+
+ #### Flagfile Unit Tests ####
+ def testMethod_flagfiles_1(self):
+ """ Test trivial case with no flagfile based options. """
+ self.__DeclareSomeFlags()
+ fake_cmd_line = 'fooScript --UnitTestBoolFlag'
+ fake_argv = fake_cmd_line.split(' ')
+ FLAGS(fake_argv)
+ self.assertEqual( FLAGS.UnitTestBoolFlag, 1)
+ self.assertEqual( fake_argv, FLAGS.ReadFlagsFromFiles(fake_argv))
+ self._UndeclareSomeFlags()
+ # end testMethodOne
+
+ def testMethod_flagfiles_2(self):
+ """Tests parsing one file + arguments off simulated argv"""
+ self.__DeclareSomeFlags()
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = 'fooScript --q --flagfile=%s' % tmp_files[0]
+ fake_argv = fake_cmd_line.split(' ')
+
+ # We should see the original cmd line with the file's contents spliced in.
+ # Note that these will be in REVERSE order from order encountered in file
+ # This is done so arguements we encounter sooner will have priority.
+ expected_results = ['fooScript',
+ '--UnitTestMessage1=tempFile1!',
+ '--UnitTestNumber=54321',
+ '--noUnitTestBoolFlag',
+ '--q']
+ test_results = FLAGS.ReadFlagsFromFiles(fake_argv)
+ self.assertEqual(expected_results, test_results)
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+ # end testTwo def
+
+ def testMethod_flagfiles_3(self):
+ """Tests parsing nested files + arguments of simulated argv"""
+ self.__DeclareSomeFlags()
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = ('fooScript --UnitTestNumber=77 --flagfile=%s'
+ % tmp_files[1])
+ fake_argv = fake_cmd_line.split(' ')
+
+ expected_results = ['fooScript',
+ '--UnitTestMessage1=tempFile1!',
+ '--UnitTestNumber=54321',
+ '--noUnitTestBoolFlag',
+ '--UnitTestMessage2=setFromTempFile2',
+ '--UnitTestNumber=6789a',
+ '--UnitTestNumber=77']
+ test_results = FLAGS.ReadFlagsFromFiles(fake_argv)
+ self.assertEqual(expected_results, test_results)
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+ # end testThree def
+
+ def testMethod_flagfiles_4(self):
+ """Tests parsing self referetial files + arguments of simulated argv.
+ This test should print a warning to stderr of some sort.
+ """
+ self.__DeclareSomeFlags()
+ tmp_files = self._SetupTestFiles()
+ # specify our temp file on the fake cmd line
+ fake_cmd_line = ('fooScript --flagfile=%s --noUnitTestBoolFlag'
+ % tmp_files[2])
+ fake_argv = fake_cmd_line.split(' ')
+ expected_results = ['fooScript',
+ '--UnitTestMessage1=setFromTempFile3',
+ '--UnitTestBoolFlag',
+ '--noUnitTestBoolFlag' ]
+
+ test_results = FLAGS.ReadFlagsFromFiles(fake_argv)
+ self.assertEqual(expected_results, test_results)
+ self._RemoveTestFiles(tmp_files)
+ self._UndeclareSomeFlags()
+
+ def test_flagfiles_user_path_expansion(self):
+ """Test that user directory referenced paths (ie. ~/foo) are correctly
+ expanded. This test depends on whatever account's running the unit test
+ to have read/write access to their own home directory, otherwise it'll
+ FAIL.
+ """
+ self.__DeclareSomeFlags()
+ fake_flagfile_item_style_1 = '--flagfile=~/foo.file'
+ fake_flagfile_item_style_2 = '-flagfile=~/foo.file'
+
+ expected_results = os.path.expanduser('~/foo.file')
+
+ test_results = FLAGS.ExtractFilename(fake_flagfile_item_style_1)
+ self.assertEqual(expected_results, test_results)
+
+ test_results = FLAGS.ExtractFilename(fake_flagfile_item_style_2)
+ self.assertEqual(expected_results, test_results)
+
+ self._UndeclareSomeFlags()
+
+ # end testFour def
+
+ def test_no_touchy_non_flags(self):
+ """
+ Test that the flags parser does not mutilate arguments which are
+ not supposed to be flags
+ """
+ self.__DeclareSomeFlags()
+ fake_argv = ['fooScript', '--UnitTestBoolFlag',
+ 'command', '--command_arg1', '--UnitTestBoom', '--UnitTestB']
+ argv = FLAGS(fake_argv)
+ self.assertEqual(argv, fake_argv[:1] + fake_argv[2:])
+ self._UndeclareSomeFlags()
+
+ def test_SetDefault(self):
+ """
+ Test changing flag defaults.
+ """
+ self.__DeclareSomeFlags()
+ # Test that SetDefault changes both the default and the value,
+ # and that the value is changed when one is given as an option.
+ FLAGS['UnitTestMessage1'].SetDefault('New value')
+ self.assertEqual(FLAGS.UnitTestMessage1, 'New value')
+ self.assertEqual(FLAGS['UnitTestMessage1'].default_as_str,"'New value'")
+ FLAGS([ 'dummyscript', '--UnitTestMessage1=Newer value' ])
+ self.assertEqual(FLAGS.UnitTestMessage1, 'Newer value')
+ # Test that setting the default to None works correctly.
+ FLAGS['UnitTestNumber'].SetDefault(None)
+ self.assertEqual(FLAGS.UnitTestNumber, None)
+ self.assertEqual(FLAGS['UnitTestNumber'].default_as_str, None)
+ FLAGS([ 'dummyscript', '--UnitTestNumber=56' ])
+ self.assertEqual(FLAGS.UnitTestNumber, 56)
+ # Test that setting invalid defaults raises exceptions
+ self.assertRaises(flags.IllegalFlagValue,
+ FLAGS['UnitTestNumber'].SetDefault, 'oops')
+ self.assertRaises(flags.IllegalFlagValue,
+ FLAGS['UnitTestNumber'].SetDefault, -1)
+ self.assertRaises(flags.IllegalFlagValue,
+ FLAGS['UnitTestBoolFlag'].SetDefault, 'oops')
+
+ self._UndeclareSomeFlags()
+
+ def testMethod_ShortestUniquePrefixes(self):
+ """
+ Test FlagValues.ShortestUniquePrefixes
+ """
+ flags.DEFINE_string('a', '', '')
+ flags.DEFINE_string('abc', '', '')
+ flags.DEFINE_string('common_a_string', '', '')
+ flags.DEFINE_boolean('common_b_boolean', 0, '')
+ flags.DEFINE_boolean('common_c_boolean', 0, '')
+ flags.DEFINE_boolean('common', 0, '')
+ flags.DEFINE_integer('commonly', 0, '')
+ flags.DEFINE_boolean('zz', 0, '')
+ flags.DEFINE_integer('nozz', 0, '')
+
+ shorter_flags = FLAGS.ShortestUniquePrefixes(FLAGS.FlagDict())
+
+ expected_results = {'nocommon_b_boolean': 'nocommon_b',
+ 'common_c_boolean': 'common_c',
+ 'common_b_boolean': 'common_b',
+ 'a': 'a',
+ 'abc': 'ab',
+ 'zz': 'z',
+ 'nozz': 'nozz',
+ 'common_a_string': 'common_a',
+ 'commonly': 'commonl',
+ 'nocommon_c_boolean': 'nocommon_c',
+ 'nocommon': 'nocommon',
+ 'common': 'common'}
+
+ for name, shorter in expected_results.iteritems():
+ self.assertEquals(shorter_flags[name], shorter)
+
+ FLAGS.__delattr__('a')
+ FLAGS.__delattr__('abc')
+ FLAGS.__delattr__('common_a_string')
+ FLAGS.__delattr__('common_b_boolean')
+ FLAGS.__delattr__('common_c_boolean')
+ FLAGS.__delattr__('common')
+ FLAGS.__delattr__('commonly')
+ FLAGS.__delattr__('zz')
+ FLAGS.__delattr__('nozz')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/python/setup.py b/python/setup.py
new file mode 100755
index 0000000..c139338
--- /dev/null
+++ b/python/setup.py
@@ -0,0 +1,42 @@
+#!/usr/bin/python2.2
+
+# Copyright (c) 2007, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from distutils.core import setup
+
+setup(name='gflags',
+ version='0.2',
+ description='Google Commandline Flags Module',
+ license='BSD',
+ author='Google Inc.',
+ author_email='opensource@google.com',
+ url='http://code.google.com/p/google-gflags',
+ py_modules=["gflags"],
+ data_files=["/usr/local/bin", "gflags2man.py"])
diff --git a/src/google/gflags.h.in b/src/google/gflags.h.in
index 832a64f..13043f5 100644
--- a/src/google/gflags.h.in
+++ b/src/google/gflags.h.in
@@ -55,6 +55,7 @@
#define BASE_COMMANDLINEFLAGS_H__
#include <string>
+#include <vector>
// We care a lot about number of bits things take up. Unfortunately,
// systems define their bit-specific ints in a lot of different ways.