Viewing file: Options.py (12.16 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
######################################################################## # $Header: /var/local/cvsroot/4Suite/Ft/Lib/CommandLine/Options.py,v 1.13 2005/04/13 23:41:04 jkloth Exp $ """ Classes that support advanced option processing for command-line scripts
Copyright 2004 Fourthought, Inc. (USA). Detailed license and copyright information: http://4suite.org/COPYRIGHT Project home, documentation, distributions: http://4suite.org/ """
from distutils.fancy_getopt import wrap_text from Ft.Lib.CommandLine import CommandLineUtil, CONSOLE_WIDTH
class Options(list): """ A set of options that are available to be used in an invocation of a command-line script, plus related functions. """ def __init__(self, options=None): if options is None: options = () list.__init__(self, options)
# Sanity check the supplied options shorts = [] longs = [] for opt in options: if not isinstance(opt, BaseOption): raise TypeError("Option %s is not of BaseOption" % opt) (short, long) = opt.getForGetOpt({}, {}) for s in short: if s != ':' and s in shorts: raise Exception('Duplicate short option in %s' % str(self)) shorts.append(s) for l in long: if l in longs: raise Exception('Duplicate long option in %s' % str(self)) longs.append(l)
def findMaxOption(self, level=0): max_opt = 0 for option in self: l = option.displayLength()
# add two spaces for each level of sub-options l = l + 2*level
if hasattr(option, 'subOptions'): sublen = option.subOptions.findMaxOption(level+1) if sublen > l: l = sublen
if l > max_opt: max_opt = l
return max_opt
def generate_help(self, level=1, max_opt=0): """Generate help text (a list of strings, one per suggested line of output) from the option table for this FancyGetopt object. """ # If max_opt is > 0, this is help for a sub-option # maximum width has already been determined. if max_opt > 0: # The indent for sub-options is included in option length opt_width = max_opt - 2*(level-1) else: opt_width = max_opt = self.findMaxOption()
# room for indent + short option + dashes + longest option + gutter + add'l indent col_width = 2*level + 4 + 2 + max_opt + 2 + 2
# Typical help block looks like this: # --foo controls foonabulation # Help block for longest option looks like this: # --flimflam set the flim-flam level # and with wrapped text: # --flimflam set the flim-flam level (must be between # 0 and 100, except on Tuesdays) # Options with short names will have the short name shown (but # it doesn't contribute to max_opt): # -f, --foo controls foonabulation # If adding the short option would make the left column too wide, # we push the explanation off to the next line # -l, --flimflam # set the flim-flam level # Important parameters: # - 2 spaces before option block start lines # - 2 dashes for each long option name # - min. 2 spaces between option and explanation (gutter)
# Now generate lines of help text. line_width = CONSOLE_WIDTH text_width = line_width - col_width indent = ' ' * level big_indent = ' ' * (col_width) lines = []
for option in self: if isinstance(option, ExclusiveOptions): lines.extend(option.choices.generate_help(level, max_opt)) continue
text = wrap_text(option.description, text_width)
short_opt = option.shortName if option.takesArg: long_opt = '%s=<%s>' % (option.longName, option.argName) else: long_opt = option.longName if option.shortName: short_part = '-%s' % (short_opt) else: short_part = ' ' if option.shortName and option.longName: short_part += ', ' else: short_part += ' ' long_part = "--%-*s" % (opt_width, long_opt) if text: lines.append('%s%s%s %s' % (indent, short_part, long_part, text[0])) else: lines.append('%s%s%s' % (indent, short_part, long_part))
# Add any description that didn't fit on the first line for line in text[1:]: lines.append(big_indent + line)
if isinstance(option, TypedOption): for (val, desc) in option.allowed: text = wrap_text(desc, text_width) lines.append('%s %-*s%s' % (indent, opt_width, val, text[0])) for line in text[1:]: lines.append(big_indent + line)
if hasattr(option, 'subOptions'): lines.extend(option.subOptions.generate_help(level + 1, max_opt))
return lines
class BaseOption: """ An option that is available to be used in an invocation of a command-line script, plus related functions. """ multiple = False
def validate(self): return
def getForGetOpt(self, short2long, takes_arg): raise NotImplementedError('subclass must override')
def displayLength(self): raise NotImplementedError('subclass %s must override' % self.__class__)
def gen_command_line(self): raise NotImplementedError('subclass %s must override' % self.__class__)
def gen_description(self): raise NotImplementedError('subclass %s must override' % self.__class__)
def apply_options(self, options): raise NotImplementedError('subclass %s must override' % self.__class__)
def isApplied(self): raise NotImplementedError('subclass %s must override' % self.__class__)
def getName(self): raise NotImplementedError('subclass %s must override' % self.__class__)
def __str__(self): return self.gen_command_line() __repr__ = __str__
class Option(BaseOption):
def __init__(self, shortName, longName, description, subOptions=None, multiple=False): # Type- and value-check the option names if len(longName) < 2: raise SyntaxError('invalid long option: ' + longName) if shortName is not None and len(shortName) != 1: raise SyntaxError('invalid short option: ' + shortName)
self.shortName = shortName or '' i = longName.find('=') if i > 0: self.takesArg = 1 argName = longName[i+1:] longName = longName[:i] self.argName = argName or longName else: self.takesArg = 0 self.longName = longName self.description = description
if not isinstance(subOptions, Options): subOptions = Options(subOptions) self.subOptions = subOptions self.multiple = multiple
def getForGetOpt(self, short2long, takes_arg): short_opts = self.shortName if self.takesArg: if short_opts: short_opts = short_opts + ':' long_opts = [self.longName + '='] else: long_opts = [self.longName]
takes_arg[self.longName] = self.takesArg if self.shortName: short2long[self.shortName] = self.longName
# get getopt options for any sub-options for option in self.subOptions: (short, long) = option.getForGetOpt(short2long, takes_arg) short_opts = short_opts + short long_opts.extend(long)
return (short_opts, long_opts)
def displayLength(self): l = len(self.longName) if self.takesArg: # add the "=argName" length l = l + 1 + len(self.argName) return l
def validate(self): for option in self.subOptions: option.validate()
for option in self.subOptions: if option.isApplied() and not self.applied: raise CommandLineUtil.ArgumentError("%s specified without %s" % (option.getName(),self.longName)) return
def apply_options(self, options): self.applied = options.has_key(self.longName) for option in self.subOptions: option.apply_options(options) return
def isApplied(self): return self.applied
def getName(self): return self.longName
def gen_command_line(self): cl = '[--%s' % self.longName if self.takesArg: cl = cl + '=<%s>' % self.argName if self.subOptions: sub = map(lambda s: s.gen_command_line(), self.subOptions) if len(sub) > 1: cl = cl + ' [%s]' % ' '.join(sub) else: cl = cl + ' ' + sub[0] return cl + ']'
class TypedOption(Option): def __init__(self, shortName, longName, description, allowed, subOptions=None): Option.__init__(self, shortName, longName, description, subOptions) self.allowedValues = map(lambda (value, desc): value, allowed) self.allowed = allowed
def apply_options(self, options): self.applied = options.get(self.longName) for option in self.subOptions: option.apply_options(options) return
def validate(self): if self.applied and self.applied not in self.allowedValues: expected = ', '.join(self.allowedValues) raise SyntaxError('option %s: expected %s, got %s' % (self.longName, expected, self.applied))
Option.validate(self) return
def gen_command_line(self): sub = '' for option in self.subOptions: sub = sub + '%s ' % option.gen_command_line() av = '[' for a in self.allowedValues: av = av + a if a != self.allowedValues[-1]: av = av + '|' av =av + ']' if sub: return '[--%s=%s [%s]]' % (self.longName,av,sub) else: return '[--%s=%s]' % (self.longName,av)
class ExclusiveOptions(BaseOption): def __init__(self, choices): if not isinstance(choices, Options): choices = Options(choices) self.choices = choices
def getForGetOpt(self, short2long, takes_arg): short_opts = '' long_opts = [] # get getopt options for all choices for option in self.choices: (short, long) = option.getForGetOpt(short2long, takes_arg) short_opts = short_opts + short long_opts.extend(long)
return (short_opts, long_opts)
def displayLength(self): return self.choices.findMaxOption()
def validate(self): # make sure only one of our choices is in the options
applied = 0 for opt in self.choices: if opt.isApplied(): if applied: opts = ', '.join(map(lambda x: '--%s' % x.getName(), self.choices)) raise CommandLineUtil.ArgumentError("Only one of %s allowed" % opts) applied = opt if applied: #NOTE, this allows some sub commands to sneak in applied.validate() else: #Validate them all for option in self.choices: option.validate()
def apply_options(self, options): for option in self.choices: option.apply_options(options) return
def isApplied(self): for option in self.choices: if option.isApplied(): return 1 return 0
def getName(self): return '(%s)' % ', '.join(map(lambda x: '--%s' % x.getName(), self.choices))
def gen_command_line(self): cl = '[' first = 1 for c in self.choices: if not first: cl = cl + ' | ' else: first = 0 cl = cl + c.gen_command_line() return cl + ']'
|