#!/usr/bin/env python """ argparse.py - Another Command Line Options Parser License: GPL 2; share and enjoy! Author: Sean B. Palmer, inamidst.com Documentation: [a] http://inamidst.com/notes/argparse [b] http://swhack.com/logs/2004-02-26#T01-28-50 From IRC, [b]: this is what I'm aiming for: o = Opts('%prog [options] [] ') o.addopt('-c --chmod chmod a file on the server') o.addopt('-u --update update a file, on the server or locally') o.addopt('-g --get download a file from the server') o.addopt('-d --delete delete a file from the server') options = o.parse(argv) """ import sys, re class OptParser(object): def __init__(self): self.pos = 0 self.data = None self.short = None self.long = None self.args = [] self.help = None def whitespace(self): while self.data[self.pos] in ' \t': self.pos += 1 def getword(self): word = '' while self.pos < len(self.data): if self.data[self.pos] in ' \t': break word += self.data[self.pos] self.pos += 1 return word def option(self): self.whitespace() if self.data[self.pos:].startswith('--'): self.long = self.getword() elif self.data[self.pos:].startswith('-'): self.short = self.getword() self.whitespace() if self.data[self.pos:].startswith('--'): self.long = self.getword() else: raise 'Either a long or a short option is required' self.whitespace() while (self.data[self.pos:].startswith('<') or self.data[self.pos:].startswith('[')): word = self.getword() if word.startswith('['): optional = 1 word = word[1:-1] else: optional = 0 word = word[1:-1] if word in ('str', 'int'): self.args.append((optional, eval(word))) self.whitespace() self.help = self.data[self.pos:] def parse(self, s): self.data = s self.option() def parseOpt(opt): parser = OptParser() parser.parse(opt) return parser.short, parser.long, parser.args, parser.help class Opts(object): def __init__(self, usage=None): self.options = {} self.done = [] self.addopt('-h --help print out a help message') if usage is None: usage = '%prog [options]' self.usage(usage) def usage(self, s): assert s.startswith('%prog [options]') args = s[len('%prog [options]'):].strip(' \t') self.summary = sys.argv[0] self.summary += ' [options] ' self.summary += re.sub('[a-z]+ ', '', args) args = re.sub(' [a-z]+', '', args) self.addopt('--args %s remainder args' % args) def help(self): print 'usage: %s' % self.summary print print 'options: ' for key in self.options.keys(): print ' %s\t%s' % (key, self.options[key][2]) def addopt(self, opt): small, large, args, help = parseOpt(opt) if small: if self.options.has_key(small): raise 'Flag already added: %s' % small if large: self.options[small] = ([large], args, help) else: self.options[small] = ([], args, help) if large: if self.options.has_key(large): raise 'Flag already added: %s' % large if small: self.options[large] = ([small], args, help) else: self.options[large] = ([], args, help) option = addopt def paramparse(self, orig): n = orig[:] BEFORE = [] while n: if n[0][0] != 1: break else: BEFORE.append(n[0]) n = n[1:] DURING = [] while n: if n[0][0] != 0: break else: DURING.append(n[0]) n = n[1:] AFTER = [] while n: if n[0][0] != 1: break else: AFTER.append(n[0]) n = n[1:] if n: raise 'Invalid collapsability: %s' % orig print BEFORE, DURING, AFTER if not BEFORE + DURING + AFTER: def command(q): return [] elif BEFORE and not DURING and not AFTER: def command(q): n = BEFORE return [n[i][1](q[i]) for i in range(len(q))] elif BEFORE and DURING and not AFTER: def command(q): n = (BEFORE + DURING)[-len(q):] return [n[i][1](q[i]) for i in range(len(q))] elif not BEFORE and DURING: def command(q): n = (DURING + AFTER)[:len(q)] return [n[i][1](q[i]) for i in range(len(q))] # elif BEFORE and DURING and AFTER: * + * else: raise 'Not implemented: %s' % orig return command def parse(self, argv): if argv is None: argv = sys.argv[1:] class Result: pass result = Result() result.args = [] while 1: if len(argv) < 1: break arg, argv = argv[0], argv[1:] if arg.startswith('-'): if len(arg) > 2 and not arg.startswith('--'): arg, argv = arg[:2], ['-'+flag for flag in arg[2:]] + argv if arg in ('-h', '--help'): self.help() sys.exit(0) elif not self.options.has_key(arg): raise 'No such argument "%s", try --help' % arg elif arg in self.done: if arg is '--args': raise 'Error: an extra unexpected parameter was passed' else: raise 'Error: %s has already been used' % arg others, args, help = self.options[arg] names = [arg.lstrip('-')] for other in others: names.append(other.lstrip('-')) pargs = [] while argv: if argv[0].startswith('-') or (len(pargs) == len(args)): break pargs.append(argv[0]) argv = argv[1:] parser = self.paramparse(args) params = parser(pargs) if len(params) == 1: params = params[0] for name in names: if params is not None: setattr(result, name, params) else: setattr(result, name, True) # prevent repetition of options self.done.extend(names) else: argv = ['--args', arg] + argv return result def test(): o = Opts('%prog [options] [] ') o.addopt('-c --chmod chmod a file on the server') o.addopt('-u --update update a file, on the server or locally') o.addopt('-g --get download a file from the server') o.addopt('-d --delete delete a file from the server') print o.options options = o.parse(['-c', '755', '1', 'path']) assert options.c == 755 assert options.chmod == 755 print options.args print # o = Opts('%prog [options] [] ') # o.addopt('-c --chmod chmod a file on the server') # o.addopt('-u --update update a file, on the server or locally') # o.addopt('-g --get download a file from the server') # o.addopt('-d --delete delete a file from the server') print options = o.parse(['-c', '755', 'path']) assert options.c == 755 assert options.chmod == 755 print options.args print o.summary o.parse(['-help']) def main(argv=None): o = Opts('%prog [options]') o.option('-a --about print the main module documentation') o.option('-t --test run the built-in module tests') options = o.parse(argv) # @@ set others to false # @@ have some kind of rewind mechanism? if hasattr(options, 'a'): print __doc__ if hasattr(options, 't'): test() if __name__=='__main__': main()