~soren/nova/iptables-security-groups

« back to all changes in this revision

Viewing changes to vendor/python-gflags/gflags2man.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# Copyright (c) 2007, Google Inc.
 
4
# All rights reserved.
 
5
#
 
6
# Redistribution and use in source and binary forms, with or without
 
7
# modification, are permitted provided that the following conditions are
 
8
# met:
 
9
#
 
10
#     * Redistributions of source code must retain the above copyright
 
11
# notice, this list of conditions and the following disclaimer.
 
12
#     * Redistributions in binary form must reproduce the above
 
13
# copyright notice, this list of conditions and the following disclaimer
 
14
# in the documentation and/or other materials provided with the
 
15
# distribution.
 
16
#     * Neither the name of Google Inc. nor the names of its
 
17
# contributors may be used to endorse or promote products derived from
 
18
# this software without specific prior written permission.
 
19
#
 
20
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 
21
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 
22
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 
23
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 
24
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 
25
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 
26
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 
27
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 
28
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 
29
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 
30
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
31
 
 
32
"""gflags2man runs a Google flags base program and generates a man page.
 
33
 
 
34
Run the program, parse the output, and then format that into a man
 
35
page.
 
36
 
 
37
Usage:
 
38
  gflags2man <program> [program] ...
 
39
"""
 
40
 
 
41
# TODO(csilvers): work with windows paths (\) as well as unix (/)
 
42
 
 
43
# This may seem a bit of an end run, but it:  doesn't bloat flags, can
 
44
# support python/java/C++, supports older executables, and can be
 
45
# extended to other document formats.
 
46
# Inspired by help2man.
 
47
 
 
48
__author__ = 'Dan Christian'
 
49
 
 
50
import os
 
51
import re
 
52
import sys
 
53
import stat
 
54
import time
 
55
 
 
56
import gflags
 
57
 
 
58
_VERSION = '0.1'
 
59
 
 
60
 
 
61
def _GetDefaultDestDir():
 
62
  home = os.environ.get('HOME', '')
 
63
  homeman = os.path.join(home, 'man', 'man1')
 
64
  if home and os.path.exists(homeman):
 
65
    return homeman
 
66
  else:
 
67
    return os.environ.get('TMPDIR', '/tmp')
 
68
 
 
69
FLAGS = gflags.FLAGS
 
70
gflags.DEFINE_string('dest_dir', _GetDefaultDestDir(),
 
71
                     'Directory to write resulting manpage to.'
 
72
                     ' Specify \'-\' for stdout')
 
73
gflags.DEFINE_string('help_flag', '--help',
 
74
                     'Option to pass to target program in to get help')
 
75
gflags.DEFINE_integer('v', 0, 'verbosity level to use for output')
 
76
 
 
77
_MIN_VALID_USAGE_MSG = 9         # if fewer lines than this, help is suspect
 
78
 
 
79
 
 
80
class Logging:
 
81
  """A super-simple logging class"""
 
82
  def error(self, msg): print >>sys.stderr, "ERROR: ", msg
 
83
  def warn(self, msg): print >>sys.stderr, "WARNING: ", msg
 
84
  def info(self, msg): print msg
 
85
  def debug(self, msg): self.vlog(1, msg)
 
86
  def vlog(self, level, msg):
 
87
    if FLAGS.v >= level: print msg
 
88
logging = Logging()
 
89
 
 
90
 
 
91
def GetRealPath(filename):
 
92
  """Given an executable filename, find in the PATH or find absolute path.
 
93
  Args:
 
94
    filename  An executable filename (string)
 
95
  Returns:
 
96
    Absolute version of filename.
 
97
    None if filename could not be found locally, absolutely, or in PATH
 
98
  """
 
99
  if os.path.isabs(filename):                # already absolute
 
100
    return filename
 
101
 
 
102
  if filename.startswith('./') or  filename.startswith('../'): # relative
 
103
    return os.path.abspath(filename)
 
104
 
 
105
  path = os.getenv('PATH', '')
 
106
  for directory in path.split(':'):
 
107
    tryname = os.path.join(directory, filename)
 
108
    if os.path.exists(tryname):
 
109
      if not os.path.isabs(directory):  # relative directory
 
110
        return os.path.abspath(tryname)
 
111
      return tryname
 
112
  if os.path.exists(filename):
 
113
    return os.path.abspath(filename)
 
114
  return None                         # could not determine
 
115
 
 
116
class Flag(object):
 
117
  """The information about a single flag."""
 
118
 
 
119
  def __init__(self, flag_desc, help):
 
120
    """Create the flag object.
 
121
    Args:
 
122
      flag_desc  The command line forms this could take. (string)
 
123
      help       The help text (string)
 
124
    """
 
125
    self.desc = flag_desc               # the command line forms
 
126
    self.help = help                    # the help text
 
127
    self.default = ''                   # default value
 
128
    self.tips = ''                      # parsing/syntax tips
 
129
 
 
130
 
 
131
class ProgramInfo(object):
 
132
  """All the information gleaned from running a program with --help."""
 
133
 
 
134
  # Match a module block start, for python scripts --help
 
135
  # "goopy.logging:"
 
136
  module_py_re = re.compile(r'(\S.+):$')
 
137
  # match the start of a flag listing
 
138
  # " -v,--verbosity:  Logging verbosity"
 
139
  flag_py_re         = re.compile(r'\s+(-\S+):\s+(.*)$')
 
140
  # "   (default: '0')"
 
141
  flag_default_py_re = re.compile(r'\s+\(default:\s+\'(.*)\'\)$')
 
142
  # "   (an integer)"
 
143
  flag_tips_py_re    = re.compile(r'\s+\((.*)\)$')
 
144
 
 
145
  # Match a module block start, for c++ programs --help
 
146
  # "google/base/commandlineflags"
 
147
  module_c_re = re.compile(r'\s+Flags from (\S.+):$')
 
148
  # match the start of a flag listing
 
149
  # " -v,--verbosity:  Logging verbosity"
 
150
  flag_c_re         = re.compile(r'\s+(-\S+)\s+(.*)$')
 
151
 
 
152
  # Match a module block start, for java programs --help
 
153
  # "com.google.common.flags"
 
154
  module_java_re = re.compile(r'\s+Flags for (\S.+):$')
 
155
  # match the start of a flag listing
 
156
  # " -v,--verbosity:  Logging verbosity"
 
157
  flag_java_re         = re.compile(r'\s+(-\S+)\s+(.*)$')
 
158
 
 
159
  def __init__(self, executable):
 
160
    """Create object with executable.
 
161
    Args:
 
162
      executable  Program to execute (string)
 
163
    """
 
164
    self.long_name = executable
 
165
    self.name = os.path.basename(executable)  # name
 
166
    # Get name without extension (PAR files)
 
167
    (self.short_name, self.ext) = os.path.splitext(self.name)
 
168
    self.executable = GetRealPath(executable)  # name of the program
 
169
    self.output = []          # output from the program.  List of lines.
 
170
    self.desc = []            # top level description.  List of lines
 
171
    self.modules = {}         # { section_name(string), [ flags ] }
 
172
    self.module_list = []     # list of module names in their original order
 
173
    self.date = time.localtime(time.time())   # default date info
 
174
 
 
175
  def Run(self):
 
176
    """Run it and collect output.
 
177
 
 
178
    Returns:
 
179
      1 (true)   If everything went well.
 
180
      0 (false)  If there were problems.
 
181
    """
 
182
    if not self.executable:
 
183
      logging.error('Could not locate "%s"' % self.long_name)
 
184
      return 0
 
185
 
 
186
    finfo = os.stat(self.executable)
 
187
    self.date = time.localtime(finfo[stat.ST_MTIME])
 
188
 
 
189
    logging.info('Running: %s %s </dev/null 2>&1'
 
190
                 % (self.executable, FLAGS.help_flag))
 
191
    # --help output is often routed to stderr, so we combine with stdout.
 
192
    # Re-direct stdin to /dev/null to encourage programs that
 
193
    # don't understand --help to exit.
 
194
    (child_stdin, child_stdout_and_stderr) = os.popen4(
 
195
      [self.executable, FLAGS.help_flag])
 
196
    child_stdin.close()       # '</dev/null'
 
197
    self.output = child_stdout_and_stderr.readlines()
 
198
    child_stdout_and_stderr.close()
 
199
    if len(self.output) < _MIN_VALID_USAGE_MSG:
 
200
      logging.error('Error: "%s %s" returned only %d lines: %s'
 
201
                    % (self.name, FLAGS.help_flag,
 
202
                       len(self.output), self.output))
 
203
      return 0
 
204
    return 1
 
205
 
 
206
  def Parse(self):
 
207
    """Parse program output."""
 
208
    (start_line, lang) = self.ParseDesc()
 
209
    if start_line < 0:
 
210
      return
 
211
    if 'python' == lang:
 
212
      self.ParsePythonFlags(start_line)
 
213
    elif 'c' == lang:
 
214
      self.ParseCFlags(start_line)
 
215
    elif 'java' == lang:
 
216
      self.ParseJavaFlags(start_line)
 
217
 
 
218
  def ParseDesc(self, start_line=0):
 
219
    """Parse the initial description.
 
220
 
 
221
    This could be Python or C++.
 
222
 
 
223
    Returns:
 
224
      (start_line, lang_type)
 
225
        start_line  Line to start parsing flags on (int)
 
226
        lang_type   Either 'python' or 'c'
 
227
       (-1, '')  if the flags start could not be found
 
228
    """
 
229
    exec_mod_start = self.executable + ':'
 
230
 
 
231
    after_blank = 0
 
232
    start_line = 0             # ignore the passed-in arg for now (?)
 
233
    for start_line in range(start_line, len(self.output)): # collect top description
 
234
      line = self.output[start_line].rstrip()
 
235
      # Python flags start with 'flags:\n'
 
236
      if ('flags:' == line
 
237
          and len(self.output) > start_line+1
 
238
          and '' == self.output[start_line+1].rstrip()):
 
239
        start_line += 2
 
240
        logging.debug('Flags start (python): %s' % line)
 
241
        return (start_line, 'python')
 
242
      # SWIG flags just have the module name followed by colon.
 
243
      if exec_mod_start == line:
 
244
        logging.debug('Flags start (swig): %s' % line)
 
245
        return (start_line, 'python')
 
246
      # C++ flags begin after a blank line and with a constant string
 
247
      if after_blank and line.startswith('  Flags from '):
 
248
        logging.debug('Flags start (c): %s' % line)
 
249
        return (start_line, 'c')
 
250
      # java flags begin with a constant string
 
251
      if line == 'where flags are':
 
252
        logging.debug('Flags start (java): %s' % line)
 
253
        start_line += 2                        # skip "Standard flags:"
 
254
        return (start_line, 'java')
 
255
 
 
256
      logging.debug('Desc: %s' % line)
 
257
      self.desc.append(line)
 
258
      after_blank = (line == '')
 
259
    else:
 
260
      logging.warn('Never found the start of the flags section for "%s"!'
 
261
                   % self.long_name)
 
262
      return (-1, '')
 
263
 
 
264
  def ParsePythonFlags(self, start_line=0):
 
265
    """Parse python/swig style flags."""
 
266
    modname = None                      # name of current module
 
267
    modlist = []
 
268
    flag = None
 
269
    for line_num in range(start_line, len(self.output)): # collect flags
 
270
      line = self.output[line_num].rstrip()
 
271
      if not line:                      # blank
 
272
        continue
 
273
 
 
274
      mobj = self.module_py_re.match(line)
 
275
      if mobj:                          # start of a new module
 
276
        modname = mobj.group(1)
 
277
        logging.debug('Module: %s' % line)
 
278
        if flag:
 
279
          modlist.append(flag)
 
280
        self.module_list.append(modname)
 
281
        self.modules.setdefault(modname, [])
 
282
        modlist = self.modules[modname]
 
283
        flag = None
 
284
        continue
 
285
 
 
286
      mobj = self.flag_py_re.match(line)
 
287
      if mobj:                          # start of a new flag
 
288
        if flag:
 
289
          modlist.append(flag)
 
290
        logging.debug('Flag: %s' % line)
 
291
        flag = Flag(mobj.group(1),  mobj.group(2))
 
292
        continue
 
293
 
 
294
      if not flag:                    # continuation of a flag
 
295
        logging.error('Flag info, but no current flag "%s"' % line)
 
296
      mobj = self.flag_default_py_re.match(line)
 
297
      if mobj:                          # (default: '...')
 
298
        flag.default = mobj.group(1)
 
299
        logging.debug('Fdef: %s' % line)
 
300
        continue
 
301
      mobj = self.flag_tips_py_re.match(line)
 
302
      if mobj:                          # (tips)
 
303
        flag.tips = mobj.group(1)
 
304
        logging.debug('Ftip: %s' % line)
 
305
        continue
 
306
      if flag and flag.help:
 
307
        flag.help += line              # multiflags tack on an extra line
 
308
      else:
 
309
        logging.info('Extra: %s' % line)
 
310
    if flag:
 
311
      modlist.append(flag)
 
312
 
 
313
  def ParseCFlags(self, start_line=0):
 
314
    """Parse C style flags."""
 
315
    modname = None                      # name of current module
 
316
    modlist = []
 
317
    flag = None
 
318
    for line_num in range(start_line, len(self.output)):  # collect flags
 
319
      line = self.output[line_num].rstrip()
 
320
      if not line:                      # blank lines terminate flags
 
321
        if flag:                        # save last flag
 
322
          modlist.append(flag)
 
323
          flag = None
 
324
        continue
 
325
 
 
326
      mobj = self.module_c_re.match(line)
 
327
      if mobj:                          # start of a new module
 
328
        modname = mobj.group(1)
 
329
        logging.debug('Module: %s' % line)
 
330
        if flag:
 
331
          modlist.append(flag)
 
332
        self.module_list.append(modname)
 
333
        self.modules.setdefault(modname, [])
 
334
        modlist = self.modules[modname]
 
335
        flag = None
 
336
        continue
 
337
 
 
338
      mobj = self.flag_c_re.match(line)
 
339
      if mobj:                          # start of a new flag
 
340
        if flag:                        # save last flag
 
341
          modlist.append(flag)
 
342
        logging.debug('Flag: %s' % line)
 
343
        flag = Flag(mobj.group(1),  mobj.group(2))
 
344
        continue
 
345
 
 
346
      # append to flag help.  type and default are part of the main text
 
347
      if flag:
 
348
        flag.help += ' ' + line.strip()
 
349
      else:
 
350
        logging.info('Extra: %s' % line)
 
351
    if flag:
 
352
      modlist.append(flag)
 
353
 
 
354
  def ParseJavaFlags(self, start_line=0):
 
355
    """Parse Java style flags (com.google.common.flags)."""
 
356
    # The java flags prints starts with a "Standard flags" "module"
 
357
    # that doesn't follow the standard module syntax.
 
358
    modname = 'Standard flags'          # name of current module
 
359
    self.module_list.append(modname)
 
360
    self.modules.setdefault(modname, [])
 
361
    modlist = self.modules[modname]
 
362
    flag = None
 
363
 
 
364
    for line_num in range(start_line, len(self.output)): # collect flags
 
365
      line = self.output[line_num].rstrip()
 
366
      logging.vlog(2, 'Line: "%s"' % line)
 
367
      if not line:                      # blank lines terminate module
 
368
        if flag:                        # save last flag
 
369
          modlist.append(flag)
 
370
          flag = None
 
371
        continue
 
372
 
 
373
      mobj = self.module_java_re.match(line)
 
374
      if mobj:                          # start of a new module
 
375
        modname = mobj.group(1)
 
376
        logging.debug('Module: %s' % line)
 
377
        if flag:
 
378
          modlist.append(flag)
 
379
        self.module_list.append(modname)
 
380
        self.modules.setdefault(modname, [])
 
381
        modlist = self.modules[modname]
 
382
        flag = None
 
383
        continue
 
384
 
 
385
      mobj = self.flag_java_re.match(line)
 
386
      if mobj:                          # start of a new flag
 
387
        if flag:                        # save last flag
 
388
          modlist.append(flag)
 
389
        logging.debug('Flag: %s' % line)
 
390
        flag = Flag(mobj.group(1),  mobj.group(2))
 
391
        continue
 
392
 
 
393
      # append to flag help.  type and default are part of the main text
 
394
      if flag:
 
395
        flag.help += ' ' + line.strip()
 
396
      else:
 
397
        logging.info('Extra: %s' % line)
 
398
    if flag:
 
399
      modlist.append(flag)
 
400
 
 
401
  def Filter(self):
 
402
    """Filter parsed data to create derived fields."""
 
403
    if not self.desc:
 
404
      self.short_desc = ''
 
405
      return
 
406
 
 
407
    for i in range(len(self.desc)):   # replace full path with name
 
408
      if self.desc[i].find(self.executable) >= 0:
 
409
        self.desc[i] = self.desc[i].replace(self.executable, self.name)
 
410
 
 
411
    self.short_desc = self.desc[0]
 
412
    word_list = self.short_desc.split(' ')
 
413
    all_names = [ self.name, self.short_name, ]
 
414
    # Since the short_desc is always listed right after the name,
 
415
    #  trim it from the short_desc
 
416
    while word_list and (word_list[0] in all_names
 
417
                         or word_list[0].lower() in all_names):
 
418
      del word_list[0]
 
419
      self.short_desc = ''              # signal need to reconstruct
 
420
    if not self.short_desc and word_list:
 
421
      self.short_desc = ' '.join(word_list)
 
422
 
 
423
 
 
424
class GenerateDoc(object):
 
425
  """Base class to output flags information."""
 
426
 
 
427
  def __init__(self, proginfo, directory='.'):
 
428
    """Create base object.
 
429
    Args:
 
430
      proginfo   A ProgramInfo object
 
431
      directory  Directory to write output into
 
432
    """
 
433
    self.info = proginfo
 
434
    self.dirname = directory
 
435
 
 
436
  def Output(self):
 
437
    """Output all sections of the page."""
 
438
    self.Open()
 
439
    self.Header()
 
440
    self.Body()
 
441
    self.Footer()
 
442
 
 
443
  def Open(self): raise NotImplementedError    # define in subclass
 
444
  def Header(self): raise NotImplementedError  # define in subclass
 
445
  def Body(self): raise NotImplementedError    # define in subclass
 
446
  def Footer(self): raise NotImplementedError  # define in subclass
 
447
 
 
448
 
 
449
class GenerateMan(GenerateDoc):
 
450
  """Output a man page."""
 
451
 
 
452
  def __init__(self, proginfo, directory='.'):
 
453
    """Create base object.
 
454
    Args:
 
455
      proginfo   A ProgramInfo object
 
456
      directory  Directory to write output into
 
457
    """
 
458
    GenerateDoc.__init__(self, proginfo, directory)
 
459
 
 
460
  def Open(self):
 
461
    if self.dirname == '-':
 
462
      logging.info('Writing to stdout')
 
463
      self.fp = sys.stdout
 
464
    else:
 
465
      self.file_path = '%s.1' % os.path.join(self.dirname, self.info.name)
 
466
      logging.info('Writing: %s' % self.file_path)
 
467
      self.fp = open(self.file_path, 'w')
 
468
 
 
469
  def Header(self):
 
470
    self.fp.write(
 
471
      '.\\" DO NOT MODIFY THIS FILE!  It was generated by gflags2man %s\n'
 
472
      % _VERSION)
 
473
    self.fp.write(
 
474
      '.TH %s "1" "%s" "%s" "User Commands"\n'
 
475
      % (self.info.name, time.strftime('%x', self.info.date), self.info.name))
 
476
    self.fp.write(
 
477
      '.SH NAME\n%s \\- %s\n' % (self.info.name, self.info.short_desc))
 
478
    self.fp.write(
 
479
      '.SH SYNOPSIS\n.B %s\n[\\fIFLAGS\\fR]...\n' % self.info.name)
 
480
 
 
481
  def Body(self):
 
482
    self.fp.write(
 
483
      '.SH DESCRIPTION\n.\\" Add any additional description here\n.PP\n')
 
484
    for ln in self.info.desc:
 
485
      self.fp.write('%s\n' % ln)
 
486
    self.fp.write(
 
487
      '.SH OPTIONS\n')
 
488
    # This shows flags in the original order
 
489
    for modname in self.info.module_list:
 
490
      if modname.find(self.info.executable) >= 0:
 
491
        mod = modname.replace(self.info.executable, self.info.name)
 
492
      else:
 
493
        mod = modname
 
494
      self.fp.write('\n.P\n.I %s\n' % mod)
 
495
      for flag in self.info.modules[modname]:
 
496
        help_string = flag.help
 
497
        if flag.default or flag.tips:
 
498
          help_string += '\n.br\n'
 
499
        if flag.default:
 
500
          help_string += '  (default: \'%s\')' % flag.default
 
501
        if flag.tips:
 
502
          help_string += '  (%s)' % flag.tips
 
503
        self.fp.write(
 
504
          '.TP\n%s\n%s\n' % (flag.desc, help_string))
 
505
 
 
506
  def Footer(self):
 
507
    self.fp.write(
 
508
      '.SH COPYRIGHT\nCopyright \(co %s Google.\n'
 
509
      % time.strftime('%Y', self.info.date))
 
510
    self.fp.write('Gflags2man created this page from "%s %s" output.\n'
 
511
                  % (self.info.name, FLAGS.help_flag))
 
512
    self.fp.write('\nGflags2man was written by Dan Christian. '
 
513
                  ' Note that the date on this'
 
514
                  ' page is the modification date of %s.\n' % self.info.name)
 
515
 
 
516
 
 
517
def main(argv):
 
518
  argv = FLAGS(argv)           # handles help as well
 
519
  if len(argv) <= 1:
 
520
    print >>sys.stderr, __doc__
 
521
    print >>sys.stderr, "flags:"
 
522
    print >>sys.stderr, str(FLAGS)
 
523
    return 1
 
524
 
 
525
  for arg in argv[1:]:
 
526
    prog = ProgramInfo(arg)
 
527
    if not prog.Run():
 
528
      continue
 
529
    prog.Parse()
 
530
    prog.Filter()
 
531
    doc = GenerateMan(prog, FLAGS.dest_dir)
 
532
    doc.Output()
 
533
  return 0
 
534
 
 
535
if __name__ == '__main__':
 
536
  main(sys.argv)