aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/py/gflags/gflags2man.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/py/gflags/gflags2man.py')
-rwxr-xr-xthird_party/py/gflags/gflags2man.py544
1 files changed, 544 insertions, 0 deletions
diff --git a/third_party/py/gflags/gflags2man.py b/third_party/py/gflags/gflags2man.py
new file mode 100755
index 0000000000..3a50f9e19f
--- /dev/null
+++ b/third_party/py/gflags/gflags2man.py
@@ -0,0 +1,544 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2006, 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.
+
+Run the program, parse the output, and then format that into a man
+page.
+
+Usage:
+ 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.
+
+
+
+import os
+import re
+import sys
+import stat
+import time
+
+import gflags
+
+_VERSION = '0.1'
+
+
+def _GetDefaultDestDir():
+ home = os.environ.get('HOME', '')
+ homeman = os.path.join(home, 'man', 'man1')
+ if home and os.path.exists(homeman):
+ return homeman
+ else:
+ return os.environ.get('TMPDIR', '/tmp')
+
+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()
+class App:
+ def usage(self, shorthelp=0):
+ print >>sys.stderr, __doc__
+ print >>sys.stderr, "flags:"
+ print >>sys.stderr, str(FLAGS)
+ def run(self):
+ main(sys.argv)
+app = App()
+
+
+def GetRealPath(filename):
+ """Given an executable filename, find in the PATH or find absolute path.
+ Args:
+ filename An executable filename (string)
+ Returns:
+ Absolute version of filename.
+ None if filename could not be found locally, absolutely, or in PATH
+ """
+ if os.path.isabs(filename): # already absolute
+ return filename
+
+ if filename.startswith('./') or filename.startswith('../'): # relative
+ return os.path.abspath(filename)
+
+ path = os.getenv('PATH', '')
+ for directory in path.split(':'):
+ tryname = os.path.join(directory, filename)
+ if os.path.exists(tryname):
+ if not os.path.isabs(directory): # relative directory
+ return os.path.abspath(tryname)
+ return tryname
+ if os.path.exists(filename):
+ return os.path.abspath(filename)
+ return None # could not determine
+
+class Flag(object):
+ """The information about a single flag."""
+
+ def __init__(self, flag_desc, help):
+ """Create the flag object.
+ Args:
+ flag_desc The command line forms this could take. (string)
+ help The help text (string)
+ """
+ self.desc = flag_desc # the command line forms
+ self.help = help # the help text
+ self.default = '' # default value
+ self.tips = '' # parsing/syntax tips
+
+
+class ProgramInfo(object):
+ """All the information gleaned from running a program with --help."""
+
+ # 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"
+ flag_py_re = re.compile(r'\s+(-\S+):\s+(.*)$')
+ # " (default: '0')"
+ flag_default_py_re = re.compile(r'\s+\(default:\s+\'(.*)\'\)$')
+ # " (an integer)"
+ flag_tips_py_re = re.compile(r'\s+\((.*)\)$')
+
+ # 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"
+ flag_c_re = re.compile(r'\s+(-\S+)\s+(.*)$')
+
+ # 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"
+ flag_java_re = re.compile(r'\s+(-\S+)\s+(.*)$')
+
+ def __init__(self, executable):
+ """Create object with executable.
+ Args:
+ executable Program to execute (string)
+ """
+ self.long_name = executable
+ 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 = time.localtime(time.time()) # default date info
+
+ def Run(self):
+ """Run it and collect output.
+
+ Returns:
+ 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 0
+
+ finfo = os.stat(self.executable)
+ self.date = time.localtime(finfo[stat.ST_MTIME])
+
+ logging.info('Running: %s %s </dev/null 2>&1'
+ % (self.executable, FLAGS.help_flag))
+ # --help output is often routed to stderr, so we combine with stdout.
+ # Re-direct stdin to /dev/null to encourage programs that
+ # don't understand --help to exit.
+ (child_stdin, child_stdout_and_stderr) = os.popen4(
+ [self.executable, FLAGS.help_flag])
+ child_stdin.close() # '</dev/null'
+ self.output = child_stdout_and_stderr.readlines()
+ child_stdout_and_stderr.close()
+ if len(self.output) < _MIN_VALID_USAGE_MSG:
+ logging.error('Error: "%s %s" returned only %d lines: %s'
+ % (self.name, FLAGS.help_flag,
+ len(self.output), self.output))
+ return 0
+ return 1
+
+ def Parse(self):
+ """Parse program output."""
+ (start_line, lang) = self.ParseDesc()
+ if start_line < 0:
+ return
+ if 'python' == lang:
+ self.ParsePythonFlags(start_line)
+ elif 'c' == lang:
+ self.ParseCFlags(start_line)
+ elif 'java' == lang:
+ self.ParseJavaFlags(start_line)
+
+ def ParseDesc(self, start_line=0):
+ """Parse the initial description.
+
+ This could be Python or C++.
+
+ Returns:
+ (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 = 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) > start_line+1
+ and '' == self.output[start_line+1].rstrip()):
+ start_line += 2
+ logging.debug('Flags start (python): %s' % line)
+ 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 (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 (start_line, 'c')
+ # java flags begin with a constant string
+ if line == 'where flags are':
+ logging.debug('Flags start (java): %s' % line)
+ start_line += 2 # skip "Standard flags:"
+ return (start_line, 'java')
+
+ logging.debug('Desc: %s' % line)
+ self.desc.append(line)
+ after_blank = (line == '')
+ else:
+ logging.warn('Never found the start of the flags section for "%s"!'
+ % self.long_name)
+ return (-1, '')
+
+ def ParsePythonFlags(self, start_line=0):
+ """Parse python/swig style flags."""
+ modname = None # name of current module
+ modlist = []
+ flag = None
+ for line_num in range(start_line, len(self.output)): # collect flags
+ line = self.output[line_num].rstrip()
+ if not line: # blank
+ continue
+
+ mobj = self.module_py_re.match(line)
+ if mobj: # start of a new module
+ modname = mobj.group(1)
+ logging.debug('Module: %s' % line)
+ if flag:
+ modlist.append(flag)
+ self.module_list.append(modname)
+ self.modules.setdefault(modname, [])
+ modlist = self.modules[modname]
+ flag = None
+ continue
+
+ mobj = self.flag_py_re.match(line)
+ if mobj: # start of a new flag
+ if flag:
+ modlist.append(flag)
+ logging.debug('Flag: %s' % line)
+ flag = Flag(mobj.group(1), mobj.group(2))
+ continue
+
+ if not flag: # continuation of a flag
+ logging.error('Flag info, but no current flag "%s"' % line)
+ mobj = self.flag_default_py_re.match(line)
+ if mobj: # (default: '...')
+ flag.default = mobj.group(1)
+ logging.debug('Fdef: %s' % line)
+ continue
+ mobj = self.flag_tips_py_re.match(line)
+ if mobj: # (tips)
+ flag.tips = mobj.group(1)
+ logging.debug('Ftip: %s' % line)
+ continue
+ if flag and flag.help:
+ flag.help += line # multiflags tack on an extra line
+ else:
+ logging.info('Extra: %s' % line)
+ if flag:
+ modlist.append(flag)
+
+ def ParseCFlags(self, start_line=0):
+ """Parse C style flags."""
+ modname = None # name of current module
+ modlist = []
+ flag = None
+ 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)
+ flag = None
+ continue
+
+ mobj = self.module_c_re.match(line)
+ if mobj: # start of a new module
+ modname = mobj.group(1)
+ logging.debug('Module: %s' % line)
+ if flag:
+ modlist.append(flag)
+ self.module_list.append(modname)
+ self.modules.setdefault(modname, [])
+ modlist = self.modules[modname]
+ flag = None
+ continue
+
+ mobj = self.flag_c_re.match(line)
+ if mobj: # start of a new flag
+ if flag: # save last flag
+ modlist.append(flag)
+ logging.debug('Flag: %s' % line)
+ flag = Flag(mobj.group(1), mobj.group(2))
+ continue
+
+ # append to flag help. type and default are part of the main text
+ if flag:
+ flag.help += ' ' + line.strip()
+ else:
+ logging.info('Extra: %s' % line)
+ if flag:
+ modlist.append(flag)
+
+ 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.
+ modname = 'Standard flags' # name of current module
+ self.module_list.append(modname)
+ self.modules.setdefault(modname, [])
+ modlist = self.modules[modname]
+ flag = None
+
+ 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
+ modlist.append(flag)
+ flag = None
+ continue
+
+ mobj = self.module_java_re.match(line)
+ if mobj: # start of a new module
+ modname = mobj.group(1)
+ logging.debug('Module: %s' % line)
+ if flag:
+ modlist.append(flag)
+ self.module_list.append(modname)
+ self.modules.setdefault(modname, [])
+ modlist = self.modules[modname]
+ flag = None
+ continue
+
+ mobj = self.flag_java_re.match(line)
+ if mobj: # start of a new flag
+ if flag: # save last flag
+ modlist.append(flag)
+ logging.debug('Flag: %s' % line)
+ flag = Flag(mobj.group(1), mobj.group(2))
+ continue
+
+ # append to flag help. type and default are part of the main text
+ if flag:
+ flag.help += ' ' + line.strip()
+ else:
+ logging.info('Extra: %s' % line)
+ if flag:
+ modlist.append(flag)
+
+ def Filter(self):
+ """Filter parsed data to create derived fields."""
+ if not self.desc:
+ self.short_desc = ''
+ return
+
+ 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(' ')
+ all_names = [ self.name, self.short_name, ]
+ # Since the short_desc is always listed right after the name,
+ # trim it from the short_desc
+ while word_list and (word_list[0] in all_names
+ or word_list[0].lower() in all_names):
+ del word_list[0]
+ self.short_desc = '' # signal need to reconstruct
+ if not self.short_desc and word_list:
+ self.short_desc = ' '.join(word_list)
+
+
+class GenerateDoc(object):
+ """Base class to output flags information."""
+
+ def __init__(self, proginfo, directory='.'):
+ """Create base object.
+ Args:
+ proginfo A ProgramInfo object
+ directory Directory to write output into
+ """
+ self.info = proginfo
+ self.dirname = directory
+
+ def Output(self):
+ """Output all sections of the page."""
+ self.Open()
+ self.Header()
+ 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."""
+
+ def __init__(self, proginfo, directory='.'):
+ """Create base object.
+ Args:
+ proginfo A ProgramInfo object
+ directory Directory to write output into
+ """
+ GenerateDoc.__init__(self, proginfo, directory)
+
+ def Open(self):
+ if self.dirname == '-':
+ logging.info('Writing to stdout')
+ self.fp = sys.stdout
+ else:
+ self.file_path = '%s.1' % os.path.join(self.dirname, self.info.name)
+ logging.info('Writing: %s' % self.file_path)
+ self.fp = open(self.file_path, 'w')
+
+ def Header(self):
+ self.fp.write(
+ '.\\" DO NOT MODIFY THIS FILE! It was generated by gflags2man %s\n'
+ % _VERSION)
+ self.fp.write(
+ '.TH %s "1" "%s" "%s" "User Commands"\n'
+ % (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(
+ '.SH SYNOPSIS\n.B %s\n[\\fIFLAGS\\fR]...\n' % self.info.name)
+
+ def Body(self):
+ self.fp.write(
+ '.SH DESCRIPTION\n.\\" Add any additional description here\n.PP\n')
+ for ln in self.info.desc:
+ self.fp.write('%s\n' % ln)
+ self.fp.write(
+ '.SH OPTIONS\n')
+ # This shows flags in the original order
+ for modname in self.info.module_list:
+ if modname.find(self.info.executable) >= 0:
+ mod = modname.replace(self.info.executable, self.info.name)
+ else:
+ mod = modname
+ self.fp.write('\n.P\n.I %s\n' % mod)
+ for flag in self.info.modules[modname]:
+ help_string = flag.help
+ if flag.default or flag.tips:
+ help_string += '\n.br\n'
+ if flag.default:
+ help_string += ' (default: \'%s\')' % flag.default
+ if flag.tips:
+ help_string += ' (%s)' % flag.tips
+ self.fp.write(
+ '.TP\n%s\n%s\n' % (flag.desc, help_string))
+
+ def Footer(self):
+ self.fp.write(
+ '.SH COPYRIGHT\nCopyright \(co %s Google.\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 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)
+ return 1
+
+ for arg in argv[1:]:
+ prog = ProgramInfo(arg)
+ if not prog.Run():
+ continue
+ prog.Parse()
+ prog.Filter()
+ doc = GenerateMan(prog, FLAGS.dest_dir)
+ doc.Output()
+ return 0
+
+if __name__ == '__main__':
+ app.run()