Package Products :: Package ZenUtils :: Module CmdBase |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenUtils.CmdBase

      1  ##############################################################################
      2  #
      3  # Copyright (C) Zenoss, Inc. 2007, all rights reserved.
      4  #
      5  # This content is made available according to terms specified in
      6  # License.zenoss under the directory where your Zenoss product is installed.
      7  #
      8  ##############################################################################
      9
     10
     11  __doc__="""CmdBase
     12
     13  Provide utility functions for logging and config file parsing
     14  to command-line programs
     15  """
     16
     17  import os
     18  import sys
     19  import datetime
     20  import logging
     21  import re
     22  from copy import copy
     23  import zope.component
     24  from zope.traversing.adapters import DefaultTraversable
     25  from Products.Five import zcml
     26
     27  from optparse import (
     28          OptionParser, OptionGroup, Values, Option,
     29          SUPPRESS_HELP, NO_DEFAULT, OptionValueError, BadOptionError,
     30      )
     31  from urllib import quote
     32
     33  # There is a nasty incompatibility between pkg_resources and twisted.
     34  # This pkg_resources import works around the problem.
     35  # See http://dev.zenoss.org/trac/ticket/3146 for details
     36  from Products.ZenUtils.PkgResources import pkg_resources
     37
     38  from Products.ZenUtils.Utils import unused, load_config_override, zenPath, getAllParserOptionsGen
     39  from Products.ZenUtils.GlobalConfig import _convertConfigLinesToArguments, applyGlobalConfToParser
     40  unused(pkg_resources)
     41
     42 -class DMDError: pass
     43
     44
     45 -def checkLogLevel(option, opt, value):
     46      if re.match(r'^\d+$', value):
     47          value = int(value)
     48      else:
     49          intval = getattr(logging, value.upper(), None)
     50          if intval:
     51              value = intval
     52          else:
     53              raise OptionValueError('"%s" is not a valid log level.' % value)
     54
     55      return value
     56
     57 -def remove_args(argv, remove_args_novals, remove_args_vals):
     58      """
     59      Removes arguments from the argument list. Arguments in
     60      remove_args_novals have no arguments. Arguments in
     61      remove_args_vals have arguments, either in the format
     62      --arg=<val> or --arg <val>.
     63      """
     64      new_args = []
     65      it = iter(argv)
     66      for arg in it:
     67          if arg in remove_args_novals:
     68              continue
     69          add_arg = True
     70          for remove_arg in remove_args_vals:
     71              if remove_arg == arg:
     72                  add_arg = False
     73                  it.next() # Skip the argument value
     74                  break
     75              elif arg.startswith(remove_arg + '='):
     76                  add_arg = False
     77                  break
     78          if add_arg:
     79              new_args.append(arg)
     80      return new_args
     81
     82 -class LogSeverityOption(Option):
     83      TYPES = Option.TYPES + ("loglevel",)
     84      TYPE_CHECKER = copy(Option.TYPE_CHECKER)
     85      TYPE_CHECKER["loglevel"] = checkLogLevel
     86
     87
     88 -class CmdBase(object):
     89      """
     90      Class used for all Zenoss commands
     91      """
     92
     93      doesLogging = True
     94
     95 -    def __init__(self, noopts=0, args=None):
     96          zope.component.provideAdapter(DefaultTraversable, (None,))
     97          # This explicitly loads all of the products - must happen first!
     98          from OFS.Application import import_products
     99          import_products()
    100          #make sure we aren't in debug mode
    101          import Globals
    102          Globals.DevelopmentMode = False
    103          # We must import ZenossStartup at this point so that all Zenoss daemons
    104          # and tools will have any ZenPack monkey-patched methods available.
    105          import Products.ZenossStartup
    106          unused(Products.ZenossStartup)
    107          zcml.load_site()
    108          import Products.ZenWidgets
    109          load_config_override('scriptmessaging.zcml', Products.ZenWidgets)
    110
    111          self.usage = "%prog [options]"
    112          self.noopts = noopts
    113          self.inputArgs = args
    114
    115          # inputArgs was created to allow unit tests to pass in command line
    116          # arguments and get around whatever Zope was doing to sys.argv.
    117          if self.inputArgs is None:
    118              self.inputArgs = sys.argv[1:]
    119
    120          self.parser = None
    121          self.args = []
    122
    123          self.buildParser()
    124          self.buildOptions()
    125
    126          # Get defaults from global.conf. They will be overridden by
    127          # daemon-specific config file or command line arguments.
    128          applyGlobalConfToParser(self.parser)
    129          self.parseOptions()
    130          if self.options.configfile:
    131              self.parser.defaults = self.getConfigFileDefaults(self.options.configfile)
    132              # We've updated the parser with defaults from configs, now we need
    133              # to reparse our command-line to get the correct overrides from
    134              # the command-line
    135              self.parseOptions()
    136
    137          if self.doesLogging:
    138              self.setupLogging()
    139
    140
    141 -    def buildParser(self):
    142          """
    143          Create the options parser
    144          """
    145          if not self.parser:
    146              from Products.ZenModel.ZenossInfo import ZenossInfo
    147              try:
    148                  zinfo= ZenossInfo('')
    149                  version= str(zinfo.getZenossVersion())
    150              except:
    151                  from Products.ZenModel.ZVersion import VERSION
    152                  version= VERSION
    153              self.parser = OptionParser(usage=self.usage,
    154                                         version="%prog " + version,
    155                                         option_class=LogSeverityOption)
    156
    157
    158 -    def buildOptions(self):
    159          """
    160          Basic options setup. Other classes should call this before adding
    161          more options
    162          """
    163          self.buildParser()
    164          if self.doesLogging:
    165              group = OptionGroup(self.parser, "Logging Options")
    166              group.add_option(
    167                  '-v', '--logseverity',
    168                  dest='logseverity', default='INFO', type='loglevel',
    169                  help='Logging severity threshold',
    170              )
    171              group.add_option(
    172                  '--logpath', dest='logpath',
    173                  help='Override the default logging path'
    174              )
    175              group.add_option(
    176                  '--maxlogsize',
    177                  dest='maxLogKiloBytes', default=10240, type='int',
    178                  help='Max size of log file in KB; default 10240',
    179              )
    180              group.add_option(
    181                  '--maxbackuplogs',
    182                  dest='maxBackupLogs', default=3, type='int',
    183                  help='Max number of back up log files; default 3',
    184              )
    185              self.parser.add_option_group(group)
    186
    187          self.parser.add_option("-C", "--configfile",
    188                      dest="configfile",
    189                      help="Use an alternate configuration file" )
    190
    191          self.parser.add_option("--genconf",
    192                                 action="store_true",
    193                                 default=False,
    194                                 help="Generate a template configuration file" )
    195
    196          self.parser.add_option("--genxmltable",
    197                                 action="store_true",
    198                                 default=False,
    199                                 help="Generate a Docbook table showing command-line switches." )
    200
    201          self.parser.add_option("--genxmlconfigs",
    202                                 action="store_true",
    203                                 default=False,
    204                                 help="Generate an XML file containing command-line switches." )
    205
    206
    207 -    def parseOptions(self):
    208          """
    209          Uses the optparse parse previously populated and performs common options.
    210          """
    211
    212          if self.noopts:
    213              args = []
    214          else:
    215              args = self.inputArgs
    216
    217          (self.options, self.args) = self.parser.parse_args(args=args)
    218
    219          if self.options.genconf:
    220              self.generate_configs( self.parser, self.options )
    221
    222          if self.options.genxmltable:
    223              self.generate_xml_table( self.parser, self.options )
    224
    225          if self.options.genxmlconfigs:
    226              self.generate_xml_configs( self.parser, self.options )
    227
    228
    229 -    def getConfigFileDefaults(self, filename):
    230          # TODO: This should be refactored - duplicated code with CmdBase.
    231          """
    232          Parse a config file which has key-value pairs delimited by white space,
    233          and update the parser's option defaults with these values.
    234
    235          @parameter filename: name of configuration file
    236          @type filename: string
    237          """
    238
    239          options = self.parser.get_default_values()
    240          lines = self.loadConfigFile(filename)
    241          if lines:
    242              lines, errors = self.validateConfigFile(filename, lines)
    243
    244              args = self.getParamatersFromConfig(lines)
    245              try:
    246                  self.parser._process_args([], args, options)
    247              except (BadOptionError, OptionValueError) as err:
    248                  print >>sys.stderr, 'WARN: %s in config file %s' % (err, filename)
    249
    250          return options.__dict__
    251
    252
    253 -    def getGlobalConfigFileDefaults(self):
    254          # Deprecated: This method is going away - it is duplicated in GlobalConfig.py
    255          """
    256          Parse a config file which has key-value pairs delimited by white space,
    257          and update the parser's option defaults with these values.
    258          """
    259
    260          filename = zenPath('etc', 'global.conf')
    261          options = self.parser.get_default_values()
    262          lines = self.loadConfigFile(filename)
    263          if lines:
    264              args = self.getParamatersFromConfig(lines)
    265
    266              try:
    267                  self.parser._process_args([], args, options)
    268              except (BadOptionError, OptionValueError) as err:
    269                  # Ignore it, we only care about our own options as defined in the parser
    270                  pass
    271
    272          return options.__dict__
    273
    274
    275 -    def loadConfigFile(self, filename):
    276          # TODO: This should be refactored - duplicated code with CmdBase.
    277          """
    278          Parse a config file which has key-value pairs delimited by white space.
    279
    280          @parameter filename: path to the configuration file
    281          @type filename: string
    282          """
    283          lines = []
    284          try:
    285              with open(filename) as file:
    286                  for line in file:
    287                      if line.lstrip().startswith('#') or line.strip() == '':
    288                          lines.append(dict(type='comment', line=line))
    289                      else:
    290                          try:
    291                              # add default blank string for keys with no default value
    292                              # valid delimiters are space, ':' and/or '=' (see ZenUtils/config.py)
    293                              key, value = (re.split(r'[\s:=]+', line.strip(), 1) + ['',])[:2]
    294                          except ValueError:
    295                              lines.append(dict(type='option', line=line, key=line.strip(), value=None, option=None))
    296                          else:
    297                              option = self.parser.get_option('--%s' % key)
    298                              lines.append(dict(type='option', line=line, key=key, value=value, option=option))
    299          except IOError as e:
    300              errorMessage = 'WARN: unable to read config file {filename} \
    301                  -- skipping. ({exceptionName}: {exception})'.format(
    302                  filename=filename,
    303                  exceptionName=e.__class__.__name__,
    304                  exception=e
    305              )
    306              print >>sys.stderr, errorMessage
    307              return []
    308
    309          return lines
    310
    311
    312 -    def validateConfigFile(self, filename, lines, correctErrors=True, warnErrors=True):
    313          """
    314          Validate config file lines which has key-value pairs delimited by white space,
    315          and validate that the keys exist for this command's option parser. If
    316          the option does not exist or has an empty value it will comment it out
    317          in the config file.
    318
    319          @parameter filename: path to the configuration file
    320          @type filename: string
    321          @parameter lines: lines from config parser
    322          @type lines: list
    323          @parameter correctErrors: Whether or not invalid conf values should be
    324              commented out.
    325          @type correctErrors: boolean
    326          """
    327
    328          output = []
    329          errors = []
    330          validLines = []
    331          date = datetime.datetime.now().isoformat()
    332          errorTemplate = '## Commenting out by config parser on %s: %%s\n' % date
    333
    334          for lineno, line in enumerate(lines):
    335              if line['type'] == 'comment':
    336                  output.append(line['line'])
    337              elif line['type'] == 'option':
    338                  if line['value'] is None:
    339                      errors.append((lineno + 1, 'missing value for "%s"' % line['key']))
    340                      output.append(errorTemplate % 'missing value')
    341                      output.append('## %s' % line['line'])
    342                  elif line['option'] is None:
    343                      errors.append((lineno + 1, 'unknown option "%s"' % line['key']))
    344                      output.append(errorTemplate % 'unknown option')
    345                      output.append('## %s' % line['line'])
    346                  else:
    347                      validLines.append(line)
    348                      output.append(line['line'])
    349              else:
    350                  errors.append((lineno + 1, 'unknown line "%s"' % line['line']))
    351                  output.append(errorTemplate % 'unknown line')
    352                  output.append('## %s' % line['line'])
    353
    354          if errors:
    355              if correctErrors:
    356                  for lineno, message in errors:
    357                      print >>sys.stderr, 'INFO: Commenting out %s on line %d in %s' % (message, lineno, filename)
    358
    359                  with open(filename, 'w') as file:
    360                      file.writelines(output)
    361
    362              if warnErrors:
    363                  for lineno, message in errors:
    364                      print >>sys.stderr, 'WARN: %s on line %d in %s' % (message, lineno, filename)
    365
    366          return validLines, errors
    367
    368
    369 -    def getParamatersFromConfig(self, lines):
    370          # Deprecated: This method is going away
    371          return _convertConfigLinesToArguments(self.parser, lines)
    372
    373
    374 -    def setupLogging(self):
    375          """
    376          Set common logging options
    377          """
    378          rlog = logging.getLogger()
    379          rlog.setLevel(logging.WARN)
    380          mname = self.__class__.__name__
    381          self.log = logging.getLogger("zen."+ mname)
    382          zlog = logging.getLogger("zen")
    383          try:
    384              loglevel = int(self.options.logseverity)
    385          except ValueError:
    386              loglevel = getattr(logging, self.options.logseverity.upper(), logging.INFO)
    387          zlog.setLevel(loglevel)
    388
    389          logdir = self.checkLogpath()
    390          if logdir:
    391              logfile = os.path.join(logdir, mname.lower()+".log")
    392              maxBytes = self.options.maxLogKiloBytes * 1024
    393              backupCount = self.options.maxBackupLogs
    394              h = logging.handlers.RotatingFileHandler(logfile, maxBytes, backupCount)
    395              h.setFormatter(logging.Formatter(
    396                  "%(asctime)s %(levelname)s %(name)s: %(message)s",
    397                  "%Y-%m-%d %H:%M:%S"))
    398              rlog.addHandler(h)
    399          else:
    400              logging.basicConfig()
    401
    402
    403 -    def checkLogpath(self):
    404          """
    405          Validate the logpath is valid
    406          """
    407          if not self.options.logpath:
    408              return None
    409          else:
    410              logdir = self.options.logpath
    411              if not os.path.exists(logdir):
    412                  # try creating the directory hierarchy if it doesn't exist...
    413                  try:
    414                      os.makedirs(logdir)
    415                  except OSError, ex:
    416                      raise SystemExit("logpath:%s doesn't exist and cannot be created" % logdir)
    417              elif not os.path.isdir(logdir):
    418                  raise SystemExit("logpath:%s exists but is not a directory" % logdir)
    419              return logdir
    420
    421
    422 -    def pretty_print_config_comment( self, comment ):
    423          """
    424          Quick and dirty pretty printer for comments that happen to be longer than can comfortably
    425  be seen on the display.
    426          """
    427
    428          max_size= 40
    429          #
    430          # As a heuristic we'll accept strings that are +-  text_window
    431          # size in length.
    432          #
    433          text_window= 5
    434
    435          if len( comment ) <= max_size + text_window:
    436               return comment
    437
    438          #
    439          # First, take care of embedded newlines and expand them out to array entries
    440          #
    441          new_comment= []
    442          all_lines= comment.split( '\n' )
    443          for line in all_lines:
    444             if len(line) <= max_size + text_window:
    445                  new_comment.append( line )
    446                  continue
    447
    448             start_position= max_size - text_window
    449             while len(line) > max_size + text_window:
    450                  index= line.find( ' ', start_position )
    451                  if index > 0:
    452                       new_comment.append( line[ 0:index ] )
    453                       line= line[ index: ]
    454
    455                  else:
    456                       if start_position == 0:
    457                          #
    458                          # If we get here it means that the line is just one big string with no spaces
    459                          # in it.  There's nothing that we can do except print it out.  Doh!
    460                          #
    461                          new_comment.append( line )
    462                          break
    463
    464                       #
    465                       # Okay, haven't found anything to split on -- go back and try again
    466                       #
    467                       start_position= start_position - text_window
    468                       if start_position < 0:
    469                          start_position= 0
    470
    471             else:
    472                  new_comment.append( line )
    473
    474          return "\n# ".join( new_comment )
    475
    476
    477
    478 -    def generate_configs( self, parser, options ):
    479          """
    480          Create a configuration file based on the long-form of the option names
    481
    482          @parameter parser: an optparse parser object which contains defaults, help
    483          @parameter options: parsed options list containing actual values
    484          """
    485
    486          #
    487          # Header for the configuration file
    488          #
    489          unused(options)
    490          daemon_name= os.path.basename( sys.argv[0] )
    491          daemon_name= daemon_name.replace( '.py', '' )
    492
    493          print """#
    494  # Configuration file for %s
    495  #
    496  #  To enable a particular option, uncomment the desired entry.
    497  #
    498  # Parameter     Setting
    499  # ---------     -------""" % ( daemon_name )
    500
    501
    502          options_to_ignore= ( 'help', 'version', '', 'genconf', 'genxmltable' )
    503
    504          #
    505          # Create an entry for each of the command line flags
    506          #
    507          # NB: Ideally, this should print out only the option parser dest
    508          #     entries, rather than the command line options.
    509          #
    510          import re
    511          for opt in getAllParserOptionsGen(parser):
    512                  if opt.help is SUPPRESS_HELP:
    513                          continue
    514
    515                  #
    516                  # Get rid of the short version of the command
    517                  #
    518                  option_name= re.sub( r'.*/--', '', "%s" % opt )
    519
    520                  #
    521                  # And what if there's no short version?
    522                  #
    523                  option_name= re.sub( r'^--', '', "%s" % option_name )
    524
    525                  #
    526                  # Don't display anything we shouldn't be displaying
    527                  #
    528                  if option_name in options_to_ignore:
    529                          continue
    530
    531                  #
    532                  # Find the actual value specified on the command line, if any,
    533                  # and display it
    534                  #
    535
    536                  value= getattr( parser.values,  opt.dest )
    537
    538                  default_value= parser.defaults.get( opt.dest )
    539                  if default_value is NO_DEFAULT or default_value is None:
    540                          default_value= ""
    541                  default_string= ""
    542                  if default_value != "":
    543                          default_string= ", default: " + str( default_value )
    544
    545                  comment=  self.pretty_print_config_comment( opt.help + default_string )
    546
    547                  #
    548                  # NB: I would prefer to use tabs to separate the parameter name
    549                  #     and value, but I don't know that this would work.
    550                  #
    551                  print """#
    552  # %s
    553  #%s %s""" % ( comment, option_name, value )
    554
    555          #
    556          # Pretty print and exit
    557          #
    558          print "#"
    559          sys.exit( 0 )
    560
    561
    562
    563 -    def generate_xml_table( self, parser, options ):
    564          """
    565          Create a Docbook table based on the long-form of the option names
    566
    567          @parameter parser: an optparse parser object which contains defaults, help
    568          @parameter options: parsed options list containing actual values
    569          """
    570
    571          #
    572          # Header for the configuration file
    573          #
    574          unused(options)
    575          daemon_name= os.path.basename( sys.argv[0] )
    576          daemon_name= daemon_name.replace( '.py', '' )
    577
    578          print """<?xml version="1.0" encoding="UTF-8"?>
    579
    580  <section version="4.0" xmlns="http://docbook.org/ns/docbook"
    581     xmlns:xlink="http://www.w3.org/1999/xlink"
    582     xmlns:xi="http://www.w3.org/2001/XInclude"
    583     xmlns:svg="http://www.w3.org/2000/svg"
    584     xmlns:mml="http://www.w3.org/1998/Math/MathML"
    585     xmlns:html="http://www.w3.org/1999/xhtml"
    586     xmlns:db="http://docbook.org/ns/docbook"
    587
    588    xml:id="%s.options"
    589  >
    590
    591  <title>%s Options</title>
    592  <para />
    593  <table frame="all">
    594    <caption>%s <indexterm><primary>Daemons</primary><secondary>%s</secondary></indexterm> options</caption>
    595  <tgroup cols="2">
    596  <colspec colname="option" colwidth="1*" />
    597  <colspec colname="description" colwidth="2*" />
    598  <thead>
    599  <row>
    600  <entry> <para>Option</para> </entry>
    601  <entry> <para>Description</para> </entry>
    602  </row>
    603  </thead>
    604  <tbody>
    605  """ % ( daemon_name, daemon_name, daemon_name, daemon_name )
    606
    607
    608          options_to_ignore= ( 'help', 'version', '', 'genconf', 'genxmltable' )
    609
    610          #
    611          # Create an entry for each of the command line flags
    612          #
    613          # NB: Ideally, this should print out only the option parser dest
    614          #     entries, rather than the command line options.
    615          #
    616          import re
    617          for opt in getAllParserOptionsGen(parser):
    618                  if opt.help is SUPPRESS_HELP:
    619                          continue
    620
    621                  #
    622                  # Create a Docbook-happy version of the option strings
    623                  # Yes, <arg></arg> would be better semantically, but the output
    624                  # just looks goofy in a table.  Use literal instead.
    625                  #
    626                  all_options= '<literal>' + re.sub( r'/', '</literal>,</para> <para><literal>', "%s" % opt ) + '</literal>'
    627
    628                  #
    629                  # Don't display anything we shouldn't be displaying
    630                  #
    631                  option_name= re.sub( r'.*/--', '', "%s" % opt )
    632                  option_name= re.sub( r'^--', '', "%s" % option_name )
    633                  if option_name in options_to_ignore:
    634                          continue
    635
    636                  default_value= parser.defaults.get( opt.dest )
    637                  if default_value is NO_DEFAULT or default_value is None:
    638                          default_value= ""
    639                  default_string= ""
    640                  if default_value != "":
    641                          default_string= "<para> Default: <literal>" + str( default_value ) + "</literal></para>\n"
    642
    643                  comment= self.pretty_print_config_comment( opt.help )
    644
    645  #
    646  # TODO: Determine the variable name used and display the --option_name=variable_name
    647  #
    648                  if opt.action in [ 'store_true', 'store_false' ]:
    649                     print """<row>
    650  <entry> <para>%s</para> </entry>
    651  <entry>
    652  <para>%s</para>
    653  %s</entry>
    654  </row>
    655  """ % ( all_options, comment, default_string )
    656
    657                  else:
    658                     target= '=<replaceable>' +  opt.dest.lower() + '</replaceable>'
    659                     all_options= all_options + target
    660                     all_options= re.sub( r',', target + ',', all_options )
    661                     print """<row>
    662  <entry> <para>%s</para> </entry>
    663  <entry>
    664  <para>%s</para>
    665  %s</entry>
    666  </row>
    667  """ % ( all_options, comment, default_string )
    668
    669
    670
    671          #
    672          # Close the table elements
    673          #
    674          print """</tbody></tgroup>
    675  </table>
    676  <para />
    677  </section>
    678  """
    679          sys.exit( 0 )
    680
    681
    682
    683 -    def generate_xml_configs( self, parser, options ):
    684          """
    685          Create an XML file that can be used to create Docbook files
    686          as well as used as the basis for GUI-based daemon option
    687          configuration.
    688          """
    689
    690          #
    691          # Header for the configuration file
    692          #
    693          unused(options)
    694          daemon_name= os.path.basename( sys.argv[0] )
    695          daemon_name= daemon_name.replace( '.py', '' )
    696
    697          export_date = datetime.datetime.now()
    698
    699          print """<?xml version="1.0" encoding="UTF-8"?>
    700
    701  <!-- Default daemon configuration generated on %s -->
    702  <configuration id="%s" >
    703
    704  """ % ( export_date, daemon_name )
    705
    706          options_to_ignore= (
    707              'help', 'version', '', 'genconf', 'genxmltable',
    708              'genxmlconfigs',
    709          )
    710
    711          #
    712          # Create an entry for each of the command line flags
    713          #
    714          # NB: Ideally, this should print out only the option parser dest
    715          #     entries, rather than the command line options.
    716          #
    717          import re
    718          for opt in getAllParserOptionsGen(parser):
    719                  if opt.help is SUPPRESS_HELP:
    720                          continue
    721
    722                  #
    723                  # Don't display anything we shouldn't be displaying
    724                  #
    725                  option_name= re.sub( r'.*/--', '', "%s" % opt )
    726                  option_name= re.sub( r'^--', '', "%s" % option_name )
    727                  if option_name in options_to_ignore:
    728                          continue
    729
    730                  default_value= parser.defaults.get( opt.dest )
    731                  if default_value is NO_DEFAULT or default_value is None:
    732                          default_string= ""
    733                  else:
    734                          default_string= str( default_value )
    735
    736  #
    737  # TODO: Determine the variable name used and display the --option_name=variable_name
    738  #
    739                  if opt.action in [ 'store_true', 'store_false' ]:
    740                     print """    <option id="%s" type="%s" default="%s" help="%s" />
    741  """ % ( option_name, "boolean", default_string, quote(opt.help),  )
    742
    743                  else:
    744                     target= opt.dest.lower()
    745                     print """    <option id="%s" type="%s" default="%s" target="%s" help="%s" />
    746  """ % ( option_name, opt.type, quote(default_string), target, quote(opt.help), )
    747
    748
    749          #
    750          # Close the table elements
    751          #
    752          print """
    753  </configuration>
    754  """
    755          sys.exit( 0 )
    756