--- a/README Fri Aug 07 19:23:13 2009 +0300
+++ b/README Wed Aug 19 12:27:43 2009 +0300
@@ -1,24 +1,22 @@
.. -*- mode: rst -*-
-=============
- Finaloption
-=============
+========
+ Opster
+========
-Finaloption is a command line parser, intended to make writing command line
+Opster is a command line parser, intended to make writing command line
applications easy and painless. It uses built-in Python types (lists,
dictionaries, etc) to define options, which makes configuration clear and
concise. Additionally it contains possibility to handle subcommands (i.e.
``hg commit`` or ``svn update``).
-JFYI: name is derived from Die Krupps' song Final Option.
-
Quick example
-------------
That's an example of an option definition::
import sys
- from finaloption import command
+ from opster import command
@command(usage='%name [-n] MESSAGE')
def main(message,
@@ -48,4 +46,4 @@
line. This is also true for subcommands: read about that and everything else
you'd like to know in `documentation`_.
-.. _documentation: http://hg.piranha.org.ua/finaloption/docs/
+.. _documentation: http://hg.piranha.org.ua/opster/docs/
--- a/docs/Makefile Fri Aug 07 19:23:13 2009 +0300
+++ b/docs/Makefile Wed Aug 19 12:27:43 2009 +0300
@@ -63,9 +63,9 @@
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in _build/qthelp, like this:"
- @echo "# qcollectiongenerator _build/qthelp/Finaloption.qhcp"
+ @echo "# qcollectiongenerator _build/qthelp/Opster.qhcp"
@echo "To view the help file:"
- @echo "# assistant -collectionFile _build/qthelp/Finaloption.qhc"
+ @echo "# assistant -collectionFile _build/qthelp/Opster.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
--- a/docs/api.rst Fri Aug 07 19:23:13 2009 +0300
+++ b/docs/api.rst Wed Aug 19 12:27:43 2009 +0300
@@ -1,8 +1,8 @@
-=================
- Finaloption API
-=================
+============
+ Opster API
+============
-.. module:: finaloption
+.. module:: opster
.. _api-command:
.. autofunction:: command
--- a/docs/conf.py Fri Aug 07 19:23:13 2009 +0300
+++ b/docs/conf.py Wed Aug 19 12:27:43 2009 +0300
@@ -2,7 +2,7 @@
import sys, os
sys.path.append('..')
-import finaloption
+import opster
# -- General configuration -----------------------------------------------------
@@ -10,9 +10,9 @@
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'index'
-project = u'Finaloption'
+project = u'Opster'
copyright = u'2009, Alexander Solovyov'
-version = release = finaloption.__version__
+version = release = opster.__version__
exclude_trees = ['_build']
pygments_style = 'sphinx'
@@ -29,12 +29,12 @@
#html_favicon = None
html_static_path = ['_static']
html_use_smartypants = True
-htmlhelp_basename = 'Finaloptiondoc'
+htmlhelp_basename = 'Opsterdoc'
# -- Options for LaTeX output --------------------------------------------------
latex_documents = [
- ('index', 'Finaloption.tex', u'Finaloption Documentation',
+ ('index', 'Opster.tex', u'Opster Documentation',
u'Alexander Solovyov', 'manual'),
]
--- a/docs/index.rst Fri Aug 07 19:23:13 2009 +0300
+++ b/docs/index.rst Wed Aug 19 12:27:43 2009 +0300
@@ -1,12 +1,6 @@
-=============
- Finaloption
=============
-
-::
-
- If that's the Final Option,
- I'm gonna choose it.
- Die Krupps
+ Opster
+=============
.. toctree::
:maxdepth: 2
@@ -15,19 +9,12 @@
overview
api
-Finaloption is a command line parser, intended to make writing command line
+Opster is a command line parser, intended to make writing command line
applications easy and painless. It uses built-in Python types (lists,
dictionaries, etc) to define options, which makes configuration clear and
concise. Additionally it contains possibility to handle subcommands (i.e.
``hg commit`` or ``svn update``).
-JFYI: name is derived from `Die Krupps'`_ song `Final Option`_, featured in
-epigraph.
-
-.. _Final Option: http://musi.cx/music/Die_Krupps/III_Odyssey_of_the_Mind/The_Final_Option/
-.. _Die Krupps': http://en.wikipedia.org/wiki/Die_Krupps
-
-
Features
--------
--- a/docs/make.bat Fri Aug 07 19:23:13 2009 +0300
+++ b/docs/make.bat Wed Aug 19 12:27:43 2009 +0300
@@ -73,9 +73,9 @@
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in _build/qthelp, like this:
- echo.^> qcollectiongenerator _build\qthelp\Finaloption.qhcp
+ echo.^> qcollectiongenerator _build\qthelp\Opster.qhcp
echo.To view the help file:
- echo.^> assistant -collectionFile _build\qthelp\Finaloption.ghc
+ echo.^> assistant -collectionFile _build\qthelp\Opster.ghc
goto end
)
--- a/docs/overview.rst Fri Aug 07 19:23:13 2009 +0300
+++ b/docs/overview.rst Wed Aug 19 12:27:43 2009 +0300
@@ -1,5 +1,5 @@
===================
- Finaloption usage
+ Opster usage
===================
Options
@@ -31,7 +31,7 @@
Usage is easy like that::
- from finaloption import command
+ from opster import command
@command(options=opts, usage='%name [-l HOST] DIR')
def main(dirname, **opts):
@@ -81,7 +81,7 @@
-----------
It's pretty usual for complex application to have some system of subcommands,
-and finaloption provides facility for handling them. Configuration is simple::
+and opster provides facility for handling them. Configuration is simple::
cmdtable = {
'^simple':
@@ -124,7 +124,7 @@
After definition of all elements you can call command dispatcher (``cmdtable``
is defined earlier)::
- from finaloption import dispatch
+ from opster import dispatch
if __name__ == '__main__':
dispatch(cmdtable=cmdtable)
--- a/finaloption.py Fri Aug 07 19:23:13 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,500 +0,0 @@
-# (c) Alexander Solovyov, 2009, under terms of the new BSD License
-'''Command line arguments parser
-'''
-
-import sys, traceback, getopt, types, textwrap, inspect
-from itertools import imap
-
-__all__ = ['command', 'dispatch']
-__version__ = '0.9.6'
-__author__ = 'Alexander Solovyov'
-__email__ = 'piranha@piranha.org.ua'
-
-write = sys.stdout.write
-err = sys.stderr.write
-
-CMDTABLE = {}
-
-# --------
-# Public interface
-# --------
-
-def command(options=None, usage=None, name=None, shortlist=False, hide=False):
- '''Decorator to mark function to be used for command line processing.
-
- All arguments are optional:
-
- - ``options``: options in format described in docs. If not supplied,
- will be determined from function.
- - ``usage``: usage string for function, replaces ``%name`` with name
- of program or subcommand. In case if it's subcommand and ``%name``
- is not present, usage is prepended by ``name``
- - ``name``: used for multiple subcommands. Defaults to wrapped
- function name
- - ``shortlist``: if command should be included in shortlist. Used
- only with multiple subcommands
- - ``hide``: if command should be hidden from help listing. Used only
- with multiple subcommands, overrides ``shortlist``
- '''
- def wrapper(func):
- # copy option list
- try:
- options_ = list(options or guess_options(func))
- except TypeError:
- # no options supplied and no options present in func
- options_ = []
-
- name_ = name or func.__name__
- usage_ = usage or guess_usage(func, options_)
- prefix = hide and '~' or (shortlist and '^' or '')
- CMDTABLE[prefix + name_] = (func, options_, usage_)
-
- def help_func(name=None):
- return help_cmd(func, replace_name(usage_, sysname()), options_)
-
- @wraps(func)
- def inner(*arguments, **kwarguments):
- # look if we need to add 'help' option
- try:
- (True for option in reversed(options_)
- if option[1] == 'help').next()
- except StopIteration:
- options_.append(('h', 'help', False, 'show help'))
-
- args = kwarguments.pop('args', None)
- if arguments or kwarguments:
- args, opts = arguments, kwarguments
- else:
- args = args or sys.argv[1:]
- try:
- opts, args = catcher(lambda: parse(args, options_),
- help_func)
- except Abort:
- return -1
-
- try:
- if opts.pop('help', False):
- return help_func()
- return catcher(lambda: call_cmd(name_, func, *args, **opts),
- help_func)
- except Abort:
- return -1
-
- return inner
- return wrapper
-
-
-def dispatch(args=None, cmdtable=None, globaloptions=None,
- middleware=lambda x: x):
- '''Dispatch command arguments based on subcommands.
-
- - ``args``: list of arguments, default: ``sys.argv[1:]``
- - ``cmdtable``: dict of commands in format described below.
- If not supplied, will use functions decorated with ``@command``.
- - ``globaloptions``: list of options which are applied to all
- commands, will contain ``--help`` option at least.
- - ``middleware``: global decorator for all commands.
-
- cmdtable format description::
-
- {'name': (function, options, usage)}
-
- - ``name`` is the name used on command-line. Can contain aliases
- (separate them with ``|``), pointer to a fact that this command
- should be displayed in short help (start name with ``^``), or to
- a fact that this command should be hidden (start name with ``~``)
- - ``function`` is the actual callable
- - ``options`` is options list in format described in docs
- - ``usage`` is the short string of usage
- '''
- args = args or sys.argv[1:]
- cmdtable = cmdtable or CMDTABLE
-
- globaloptions = globaloptions or []
- globaloptions.append(('h', 'help', False, 'display help'))
-
- cmdtable['help'] = (help_(cmdtable, globaloptions), [], '[TOPIC]')
- help_func = cmdtable['help'][0]
-
- try:
- name, func, args, kwargs = catcher(
- lambda: _dispatch(args, cmdtable, globaloptions),
- help_func)
- return catcher(
- lambda: call_cmd(name, middleware(func), *args, **kwargs),
- help_func)
- except Abort:
- return -1
-
-# --------
-# Help
-# --------
-
-def help_(cmdtable, globalopts):
- def help_inner(name=None):
- '''Show help for a given help topic or a help overview
-
- With no arguments, print a list of commands with short help messages.
-
- Given a command name, print help for that command.
- '''
- def helplist():
- hlp = {}
- # determine if any command is marked for shortlist
- shortlist = (name == 'shortlist' and
- any(imap(lambda x: x.startswith('^'), cmdtable)))
-
- for cmd, info in cmdtable.items():
- if cmd.startswith('~'):
- continue # do not display hidden commands
- if shortlist and not cmd.startswith('^'):
- continue # short help contains only marked commands
- cmd = cmd.lstrip('^~')
- doc = info[0].__doc__ or '(no help text available)'
- hlp[cmd] = doc.splitlines()[0].rstrip()
-
- hlplist = sorted(hlp)
- maxlen = max(map(len, hlplist))
-
- write('usage: %s <command> [options]\n' % sysname())
- write('\ncommands:\n\n')
- for cmd in hlplist:
- doc = hlp[cmd]
- if False: # verbose?
- write(' %s:\n %s\n' % (cmd.replace('|', ', '), doc))
- else:
- write(' %-*s %s\n' % (maxlen, cmd.split('|', 1)[0],
- doc))
-
- if not cmdtable:
- return err('No commands specified!\n')
-
- if not name or name == 'shortlist':
- return helplist()
-
- aliases, (cmd, options, usage) = findcmd(name, cmdtable)
- return help_cmd(cmd,
- replace_name(usage, sysname() + ' ' + aliases[0]),
- options + globalopts)
- return help_inner
-
-def help_cmd(func, usage, options):
- '''show help for given command
-
- - ``func``: function to generate help for (``func.__doc__`` is taken)
- - ``usage``: usage string
- - ``options``: options in usual format
-
- >>> def test(*args, **opts):
- ... """that's a test command
- ...
- ... you can do nothing with this command"""
- ... pass
- >>> opts = [('l', 'listen', 'localhost',
- ... 'ip to listen on'),
- ... ('p', 'port', 8000,
- ... 'port to listen on'),
- ... ('d', 'daemonize', False,
- ... 'daemonize process'),
- ... ('', 'pid-file', '',
- ... 'name of file to write process ID to')]
- >>> help_cmd(test, 'test [-l HOST] [NAME]', opts)
- test [-l HOST] [NAME]
- <BLANKLINE>
- that's a test command
- <BLANKLINE>
- you can do nothing with this command
- <BLANKLINE>
- options:
- <BLANKLINE>
- -l --listen ip to listen on (default: localhost)
- -p --port port to listen on (default: 8000)
- -d --daemonize daemonize process
- --pid-file name of file to write process ID to
- <BLANKLINE>
- '''
- print '%s\n' % usage
- doc = func.__doc__
- if not doc:
- doc = '(no help text available)'
- print '%s\n' % doc.strip()
- if options:
- print ''.join(help_options(options))
-
-def help_options(options):
- yield 'options:\n\n'
- output = []
- for short, name, default, desc in options:
- default = default and ' (default: %s)' % default or ''
- output.append(('%2s%s' % (short and '-%s' % short,
- name and ' --%s' % name),
- '%s%s' % (desc, default)))
-
- opts_len = max([len(first) for first, second in output if second] or [0])
- for first, second in output:
- if second:
- # wrap description at 78 chars
- second = textwrap.wrap(second, width=(78 - opts_len - 3))
- pad = '\n' + ' ' * (opts_len + 3)
- yield ' %-*s %s\n' % (opts_len, first, pad.join(second))
- else:
- yield '%s\n' % first
-
-
-# --------
-# Options parsing
-# --------
-
-def parse(args, options):
- '''
- >>> opts = [('l', 'listen', 'localhost',
- ... 'ip to listen on'),
- ... ('p', 'port', 8000,
- ... 'port to listen on'),
- ... ('d', 'daemonize', False,
- ... 'daemonize process'),
- ... ('', 'pid-file', '',
- ... 'name of file to write process ID to')]
- >>> print parse(['-l', '0.0.0.0', '--pi', 'test', 'all'], opts)
- ({'pid_file': 'test', 'daemonize': False, 'port': 8000, 'listen': '0.0.0.0'}, ['all'])
-
- '''
- argmap, defmap, state = {}, {}, {}
- shortlist, namelist = '', []
-
- for short, name, default, comment in options:
- if short and len(short) != 1:
- raise FOError('Short option should be only a single'
- ' character: %s' % short)
- if not name:
- raise FOError(
- 'Long name should be defined for every option')
- # change name to match Python styling
- pyname = name.replace('-', '_')
- argmap['-' + short] = argmap['--' + name] = pyname
- defmap[pyname] = default
-
- # copy defaults to state
- if isinstance(default, list):
- state[pyname] = default[:]
- elif callable(default):
- state[pyname] = None
- else:
- state[pyname] = default
-
- # getopt wants indication that it takes a parameter
- if not (default is None or default is True or default is False):
- if short: short += ':'
- if name: name += '='
- if short:
- shortlist += short
- if name:
- namelist.append(name)
-
- opts, args = getopt.gnu_getopt(args, shortlist, namelist)
-
- # transfer result to state
- for opt, val in opts:
- name = argmap[opt]
- t = type(defmap[name])
- if t is types.FunctionType:
- state[name] = defmap[name](val)
- elif t is types.IntType:
- state[name] = int(val)
- elif t is types.StringType:
- state[name] = val
- elif t is types.ListType:
- state[name].append(val)
- elif t in (types.NoneType, types.BooleanType):
- state[name] = not defmap[name]
-
- return state, args
-
-
-# --------
-# Subcommand system
-# --------
-
-def _dispatch(args, cmdtable, globalopts):
- cmd, func, args, options, globaloptions = cmdparse(args, cmdtable,
- globalopts)
-
- if globaloptions['help']:
- return 'help', cmdtable['help'][0], [cmd], {}
- elif not cmd:
- return 'help', cmdtable['help'][0], ['shortlist'], {}
-
- return cmd, func, args, options
-
-def cmdparse(args, cmdtable, globalopts):
- # command is the first non-option
- cmd = None
- for arg in args:
- if not arg.startswith('-'):
- cmd = arg
- break
-
- if cmd:
- args.pop(args.index(cmd))
-
- aliases, info = findcmd(cmd, cmdtable)
- cmd = aliases[0]
- possibleopts = list(info[1])
- else:
- possibleopts = []
-
- possibleopts.extend(globalopts)
-
- try:
- options, args = parse(args, possibleopts)
- except getopt.GetoptError, e:
- raise ParseError(cmd, e)
-
- globaloptions = {}
- for o in globalopts:
- name = o[1]
- globaloptions[name] = options.pop(name)
-
- return (cmd, cmd and info[0] or None, args, options, globaloptions)
-
-def findpossible(cmd, table):
- """
- Return cmd -> (aliases, command table entry)
- for each matching command.
- """
- choice = {}
- for e in table.keys():
- aliases = e.lstrip("^~").split("|")
- found = None
- if cmd in aliases:
- found = cmd
- else:
- for a in aliases:
- if a.startswith(cmd):
- found = a
- break
- if found is not None:
- choice[found] = (aliases, table[e])
-
- return choice
-
-def findcmd(cmd, table):
- """Return (aliases, command table entry) for command string."""
- choice = findpossible(cmd, table)
-
- if cmd in choice:
- return choice[cmd]
-
- if len(choice) > 1:
- clist = choice.keys()
- clist.sort()
- raise AmbiguousCommand(cmd, clist)
-
- if choice:
- return choice.values()[0]
-
- raise UnknownCommand(cmd)
-
-# --------
-# Helpers
-# --------
-
-def guess_options(func):
- args, varargs, varkw, defaults = inspect.getargspec(func)
- for lname, (sname, default, hlp) in zip(args[-len(defaults):], defaults):
- yield (sname, lname.replace('_', '-'), default, hlp)
-
-def guess_usage(func, options):
- usage = '%name '
- if options:
- usage += '[OPTIONS] '
- args, varargs = inspect.getargspec(func)[:2]
- argnum = len(args) - len(options)
- if argnum > 0:
- usage += args[0].upper()
- if argnum > 1:
- usage += 'S'
- elif varargs:
- usage += '[%s]' % varargs.upper()
- return usage
-
-def catcher(target, help_func):
- try:
- return target()
- except UnknownCommand, e:
- err("unknown command: '%s'\n" % e)
- except AmbiguousCommand, e:
- err("command '%s' is ambiguous:\n %s\n" %
- (e.args[0], ' '.join(e.args[1])))
- except ParseError, e:
- err('%s: %s\n' % (e.args[0], e.args[1]))
- help_func(e.args[0])
- except getopt.GetoptError, e:
- err('error: %s\n' % e)
- help_func()
- except FOError, e:
- err('%s\n' % e)
- except KeyboardInterrupt:
- err('interrupted!\n')
- except SystemExit:
- raise
- except:
- err('unknown exception encountered')
- raise
-
- raise Abort
-
-def call_cmd(name, func, *args, **kwargs):
- try:
- return func(*args, **kwargs)
- except TypeError:
- if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
- raise ParseError(name, "invalid arguments")
- raise
-
-def replace_name(usage, name):
- if '%name' in usage:
- return usage.replace('%name', name, 1)
- return name + ' ' + usage
-
-def sysname():
- name = sys.argv[0]
- if name.startswith('./'):
- return name[2:]
- return name
-
-try:
- from functools import wraps
-except ImportError:
- def wraps(wrapped, assigned=('__module__', '__name__', '__doc__'),
- updated=('__dict__',)):
- def inner(wrapper):
- for attr in assigned:
- setattr(wrapper, attr, getattr(wrapped, attr))
- for attr in updated:
- getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
- return wrapper
- return inner
-
-# --------
-# Exceptions
-# --------
-
-# Command exceptions
-class CommandException(Exception):
- 'Base class for command exceptions'
-
-class AmbiguousCommand(CommandException):
- 'Raised if command is ambiguous'
-
-class UnknownCommand(CommandException):
- 'Raised if command is unknown'
-
-class ParseError(CommandException):
- 'Raised on error in command line parsing'
-
-class Abort(CommandException):
- 'Abort execution'
-
-class FOError(CommandException):
- 'Raised on trouble with finaloption configuration'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/opster.py Wed Aug 19 12:27:43 2009 +0300
@@ -0,0 +1,500 @@
+# (c) Alexander Solovyov, 2009, under terms of the new BSD License
+'''Command line arguments parser
+'''
+
+import sys, traceback, getopt, types, textwrap, inspect
+from itertools import imap
+
+__all__ = ['command', 'dispatch']
+__version__ = '0.9.6'
+__author__ = 'Alexander Solovyov'
+__email__ = 'piranha@piranha.org.ua'
+
+write = sys.stdout.write
+err = sys.stderr.write
+
+CMDTABLE = {}
+
+# --------
+# Public interface
+# --------
+
+def command(options=None, usage=None, name=None, shortlist=False, hide=False):
+ '''Decorator to mark function to be used for command line processing.
+
+ All arguments are optional:
+
+ - ``options``: options in format described in docs. If not supplied,
+ will be determined from function.
+ - ``usage``: usage string for function, replaces ``%name`` with name
+ of program or subcommand. In case if it's subcommand and ``%name``
+ is not present, usage is prepended by ``name``
+ - ``name``: used for multiple subcommands. Defaults to wrapped
+ function name
+ - ``shortlist``: if command should be included in shortlist. Used
+ only with multiple subcommands
+ - ``hide``: if command should be hidden from help listing. Used only
+ with multiple subcommands, overrides ``shortlist``
+ '''
+ def wrapper(func):
+ # copy option list
+ try:
+ options_ = list(options or guess_options(func))
+ except TypeError:
+ # no options supplied and no options present in func
+ options_ = []
+
+ name_ = name or func.__name__
+ usage_ = usage or guess_usage(func, options_)
+ prefix = hide and '~' or (shortlist and '^' or '')
+ CMDTABLE[prefix + name_] = (func, options_, usage_)
+
+ def help_func(name=None):
+ return help_cmd(func, replace_name(usage_, sysname()), options_)
+
+ @wraps(func)
+ def inner(*arguments, **kwarguments):
+ # look if we need to add 'help' option
+ try:
+ (True for option in reversed(options_)
+ if option[1] == 'help').next()
+ except StopIteration:
+ options_.append(('h', 'help', False, 'show help'))
+
+ args = kwarguments.pop('args', None)
+ if arguments or kwarguments:
+ args, opts = arguments, kwarguments
+ else:
+ args = args or sys.argv[1:]
+ try:
+ opts, args = catcher(lambda: parse(args, options_),
+ help_func)
+ except Abort:
+ return -1
+
+ try:
+ if opts.pop('help', False):
+ return help_func()
+ return catcher(lambda: call_cmd(name_, func, *args, **opts),
+ help_func)
+ except Abort:
+ return -1
+
+ return inner
+ return wrapper
+
+
+def dispatch(args=None, cmdtable=None, globaloptions=None,
+ middleware=lambda x: x):
+ '''Dispatch command arguments based on subcommands.
+
+ - ``args``: list of arguments, default: ``sys.argv[1:]``
+ - ``cmdtable``: dict of commands in format described below.
+ If not supplied, will use functions decorated with ``@command``.
+ - ``globaloptions``: list of options which are applied to all
+ commands, will contain ``--help`` option at least.
+ - ``middleware``: global decorator for all commands.
+
+ cmdtable format description::
+
+ {'name': (function, options, usage)}
+
+ - ``name`` is the name used on command-line. Can contain aliases
+ (separate them with ``|``), pointer to a fact that this command
+ should be displayed in short help (start name with ``^``), or to
+ a fact that this command should be hidden (start name with ``~``)
+ - ``function`` is the actual callable
+ - ``options`` is options list in format described in docs
+ - ``usage`` is the short string of usage
+ '''
+ args = args or sys.argv[1:]
+ cmdtable = cmdtable or CMDTABLE
+
+ globaloptions = globaloptions or []
+ globaloptions.append(('h', 'help', False, 'display help'))
+
+ cmdtable['help'] = (help_(cmdtable, globaloptions), [], '[TOPIC]')
+ help_func = cmdtable['help'][0]
+
+ try:
+ name, func, args, kwargs = catcher(
+ lambda: _dispatch(args, cmdtable, globaloptions),
+ help_func)
+ return catcher(
+ lambda: call_cmd(name, middleware(func), *args, **kwargs),
+ help_func)
+ except Abort:
+ return -1
+
+# --------
+# Help
+# --------
+
+def help_(cmdtable, globalopts):
+ def help_inner(name=None):
+ '''Show help for a given help topic or a help overview
+
+ With no arguments, print a list of commands with short help messages.
+
+ Given a command name, print help for that command.
+ '''
+ def helplist():
+ hlp = {}
+ # determine if any command is marked for shortlist
+ shortlist = (name == 'shortlist' and
+ any(imap(lambda x: x.startswith('^'), cmdtable)))
+
+ for cmd, info in cmdtable.items():
+ if cmd.startswith('~'):
+ continue # do not display hidden commands
+ if shortlist and not cmd.startswith('^'):
+ continue # short help contains only marked commands
+ cmd = cmd.lstrip('^~')
+ doc = info[0].__doc__ or '(no help text available)'
+ hlp[cmd] = doc.splitlines()[0].rstrip()
+
+ hlplist = sorted(hlp)
+ maxlen = max(map(len, hlplist))
+
+ write('usage: %s <command> [options]\n' % sysname())
+ write('\ncommands:\n\n')
+ for cmd in hlplist:
+ doc = hlp[cmd]
+ if False: # verbose?
+ write(' %s:\n %s\n' % (cmd.replace('|', ', '), doc))
+ else:
+ write(' %-*s %s\n' % (maxlen, cmd.split('|', 1)[0],
+ doc))
+
+ if not cmdtable:
+ return err('No commands specified!\n')
+
+ if not name or name == 'shortlist':
+ return helplist()
+
+ aliases, (cmd, options, usage) = findcmd(name, cmdtable)
+ return help_cmd(cmd,
+ replace_name(usage, sysname() + ' ' + aliases[0]),
+ options + globalopts)
+ return help_inner
+
+def help_cmd(func, usage, options):
+ '''show help for given command
+
+ - ``func``: function to generate help for (``func.__doc__`` is taken)
+ - ``usage``: usage string
+ - ``options``: options in usual format
+
+ >>> def test(*args, **opts):
+ ... """that's a test command
+ ...
+ ... you can do nothing with this command"""
+ ... pass
+ >>> opts = [('l', 'listen', 'localhost',
+ ... 'ip to listen on'),
+ ... ('p', 'port', 8000,
+ ... 'port to listen on'),
+ ... ('d', 'daemonize', False,
+ ... 'daemonize process'),
+ ... ('', 'pid-file', '',
+ ... 'name of file to write process ID to')]
+ >>> help_cmd(test, 'test [-l HOST] [NAME]', opts)
+ test [-l HOST] [NAME]
+ <BLANKLINE>
+ that's a test command
+ <BLANKLINE>
+ you can do nothing with this command
+ <BLANKLINE>
+ options:
+ <BLANKLINE>
+ -l --listen ip to listen on (default: localhost)
+ -p --port port to listen on (default: 8000)
+ -d --daemonize daemonize process
+ --pid-file name of file to write process ID to
+ <BLANKLINE>
+ '''
+ print '%s\n' % usage
+ doc = func.__doc__
+ if not doc:
+ doc = '(no help text available)'
+ print '%s\n' % doc.strip()
+ if options:
+ print ''.join(help_options(options))
+
+def help_options(options):
+ yield 'options:\n\n'
+ output = []
+ for short, name, default, desc in options:
+ default = default and ' (default: %s)' % default or ''
+ output.append(('%2s%s' % (short and '-%s' % short,
+ name and ' --%s' % name),
+ '%s%s' % (desc, default)))
+
+ opts_len = max([len(first) for first, second in output if second] or [0])
+ for first, second in output:
+ if second:
+ # wrap description at 78 chars
+ second = textwrap.wrap(second, width=(78 - opts_len - 3))
+ pad = '\n' + ' ' * (opts_len + 3)
+ yield ' %-*s %s\n' % (opts_len, first, pad.join(second))
+ else:
+ yield '%s\n' % first
+
+
+# --------
+# Options parsing
+# --------
+
+def parse(args, options):
+ '''
+ >>> opts = [('l', 'listen', 'localhost',
+ ... 'ip to listen on'),
+ ... ('p', 'port', 8000,
+ ... 'port to listen on'),
+ ... ('d', 'daemonize', False,
+ ... 'daemonize process'),
+ ... ('', 'pid-file', '',
+ ... 'name of file to write process ID to')]
+ >>> print parse(['-l', '0.0.0.0', '--pi', 'test', 'all'], opts)
+ ({'pid_file': 'test', 'daemonize': False, 'port': 8000, 'listen': '0.0.0.0'}, ['all'])
+
+ '''
+ argmap, defmap, state = {}, {}, {}
+ shortlist, namelist = '', []
+
+ for short, name, default, comment in options:
+ if short and len(short) != 1:
+ raise FOError('Short option should be only a single'
+ ' character: %s' % short)
+ if not name:
+ raise FOError(
+ 'Long name should be defined for every option')
+ # change name to match Python styling
+ pyname = name.replace('-', '_')
+ argmap['-' + short] = argmap['--' + name] = pyname
+ defmap[pyname] = default
+
+ # copy defaults to state
+ if isinstance(default, list):
+ state[pyname] = default[:]
+ elif callable(default):
+ state[pyname] = None
+ else:
+ state[pyname] = default
+
+ # getopt wants indication that it takes a parameter
+ if not (default is None or default is True or default is False):
+ if short: short += ':'
+ if name: name += '='
+ if short:
+ shortlist += short
+ if name:
+ namelist.append(name)
+
+ opts, args = getopt.gnu_getopt(args, shortlist, namelist)
+
+ # transfer result to state
+ for opt, val in opts:
+ name = argmap[opt]
+ t = type(defmap[name])
+ if t is types.FunctionType:
+ state[name] = defmap[name](val)
+ elif t is types.IntType:
+ state[name] = int(val)
+ elif t is types.StringType:
+ state[name] = val
+ elif t is types.ListType:
+ state[name].append(val)
+ elif t in (types.NoneType, types.BooleanType):
+ state[name] = not defmap[name]
+
+ return state, args
+
+
+# --------
+# Subcommand system
+# --------
+
+def _dispatch(args, cmdtable, globalopts):
+ cmd, func, args, options, globaloptions = cmdparse(args, cmdtable,
+ globalopts)
+
+ if globaloptions['help']:
+ return 'help', cmdtable['help'][0], [cmd], {}
+ elif not cmd:
+ return 'help', cmdtable['help'][0], ['shortlist'], {}
+
+ return cmd, func, args, options
+
+def cmdparse(args, cmdtable, globalopts):
+ # command is the first non-option
+ cmd = None
+ for arg in args:
+ if not arg.startswith('-'):
+ cmd = arg
+ break
+
+ if cmd:
+ args.pop(args.index(cmd))
+
+ aliases, info = findcmd(cmd, cmdtable)
+ cmd = aliases[0]
+ possibleopts = list(info[1])
+ else:
+ possibleopts = []
+
+ possibleopts.extend(globalopts)
+
+ try:
+ options, args = parse(args, possibleopts)
+ except getopt.GetoptError, e:
+ raise ParseError(cmd, e)
+
+ globaloptions = {}
+ for o in globalopts:
+ name = o[1]
+ globaloptions[name] = options.pop(name)
+
+ return (cmd, cmd and info[0] or None, args, options, globaloptions)
+
+def findpossible(cmd, table):
+ """
+ Return cmd -> (aliases, command table entry)
+ for each matching command.
+ """
+ choice = {}
+ for e in table.keys():
+ aliases = e.lstrip("^~").split("|")
+ found = None
+ if cmd in aliases:
+ found = cmd
+ else:
+ for a in aliases:
+ if a.startswith(cmd):
+ found = a
+ break
+ if found is not None:
+ choice[found] = (aliases, table[e])
+
+ return choice
+
+def findcmd(cmd, table):
+ """Return (aliases, command table entry) for command string."""
+ choice = findpossible(cmd, table)
+
+ if cmd in choice:
+ return choice[cmd]
+
+ if len(choice) > 1:
+ clist = choice.keys()
+ clist.sort()
+ raise AmbiguousCommand(cmd, clist)
+
+ if choice:
+ return choice.values()[0]
+
+ raise UnknownCommand(cmd)
+
+# --------
+# Helpers
+# --------
+
+def guess_options(func):
+ args, varargs, varkw, defaults = inspect.getargspec(func)
+ for lname, (sname, default, hlp) in zip(args[-len(defaults):], defaults):
+ yield (sname, lname.replace('_', '-'), default, hlp)
+
+def guess_usage(func, options):
+ usage = '%name '
+ if options:
+ usage += '[OPTIONS] '
+ args, varargs = inspect.getargspec(func)[:2]
+ argnum = len(args) - len(options)
+ if argnum > 0:
+ usage += args[0].upper()
+ if argnum > 1:
+ usage += 'S'
+ elif varargs:
+ usage += '[%s]' % varargs.upper()
+ return usage
+
+def catcher(target, help_func):
+ try:
+ return target()
+ except UnknownCommand, e:
+ err("unknown command: '%s'\n" % e)
+ except AmbiguousCommand, e:
+ err("command '%s' is ambiguous:\n %s\n" %
+ (e.args[0], ' '.join(e.args[1])))
+ except ParseError, e:
+ err('%s: %s\n' % (e.args[0], e.args[1]))
+ help_func(e.args[0])
+ except getopt.GetoptError, e:
+ err('error: %s\n' % e)
+ help_func()
+ except FOError, e:
+ err('%s\n' % e)
+ except KeyboardInterrupt:
+ err('interrupted!\n')
+ except SystemExit:
+ raise
+ except:
+ err('unknown exception encountered')
+ raise
+
+ raise Abort
+
+def call_cmd(name, func, *args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except TypeError:
+ if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
+ raise ParseError(name, "invalid arguments")
+ raise
+
+def replace_name(usage, name):
+ if '%name' in usage:
+ return usage.replace('%name', name, 1)
+ return name + ' ' + usage
+
+def sysname():
+ name = sys.argv[0]
+ if name.startswith('./'):
+ return name[2:]
+ return name
+
+try:
+ from functools import wraps
+except ImportError:
+ def wraps(wrapped, assigned=('__module__', '__name__', '__doc__'),
+ updated=('__dict__',)):
+ def inner(wrapper):
+ for attr in assigned:
+ setattr(wrapper, attr, getattr(wrapped, attr))
+ for attr in updated:
+ getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
+ return wrapper
+ return inner
+
+# --------
+# Exceptions
+# --------
+
+# Command exceptions
+class CommandException(Exception):
+ 'Base class for command exceptions'
+
+class AmbiguousCommand(CommandException):
+ 'Raised if command is ambiguous'
+
+class UnknownCommand(CommandException):
+ 'Raised if command is unknown'
+
+class ParseError(CommandException):
+ 'Raised on error in command line parsing'
+
+class Abort(CommandException):
+ 'Abort execution'
+
+class FOError(CommandException):
+ 'Raised on trouble with opster configuration'
--- a/setup.py Fri Aug 07 19:23:13 2009 +0300
+++ b/setup.py Wed Aug 19 12:27:43 2009 +0300
@@ -2,7 +2,7 @@
import os
from distutils.core import setup
-import finaloption
+import opster
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
@@ -16,14 +16,14 @@
return info
setup(
- name = 'finaloption',
- description = 'command line parsing done right',
+ name = 'opster',
+ description = 'command line parsing speedster',
long_description = desc(),
license = 'BSD',
- version = finaloption.__version__,
- author = finaloption.__author__,
- author_email = finaloption.__email__,
- url = 'http://hg.piranha.org.ua/finaloption/',
+ version = opster.__version__,
+ author = opster.__author__,
+ author_email = opster.__email__,
+ url = 'http://hg.piranha.org.ua/opster/',
classifiers = [
'Development Status :: 4 - Beta',
'Environment :: Console',
@@ -33,6 +33,6 @@
'Programming Language :: Python',
'Topic :: Software Development',
],
- py_modules = ['finaloption'],
+ py_modules = ['opster'],
platforms='any',
)
--- a/test.py Fri Aug 07 19:23:13 2009 +0300
+++ b/test.py Wed Aug 19 12:27:43 2009 +0300
@@ -2,7 +2,7 @@
import sys
-from finaloption import dispatch, command
+from opster import dispatch, command
@command(usage='[-t]', shortlist=True)
--- a/test_cmd.py Fri Aug 07 19:23:13 2009 +0300
+++ b/test_cmd.py Wed Aug 19 12:27:43 2009 +0300
@@ -1,17 +1,17 @@
#!/usr/bin/env python
-import finaloption
+import opster
config_opts=[('c', 'config', 'webshops.ini', 'config file to use')]
-@finaloption.command(config_opts)
+@opster.command(config_opts)
def initdb(config):
"""Initialize database"""
pass
-@finaloption.command(options=config_opts + [
+@opster.command(options=config_opts + [
('h', 'host', 'localhost', 'The host for the application.'),
('p', 'port', 5000, 'The port for the server.'),
('', 'nolint', False, 'Do not use LintMiddleware')
@@ -21,4 +21,4 @@
print opts
-finaloption.dispatch()
+opster.dispatch()
--- a/test_opts.py Fri Aug 07 19:23:13 2009 +0300
+++ b/test_opts.py Wed Aug 19 12:27:43 2009 +0300
@@ -2,7 +2,7 @@
import sys
-from finaloption import command
+from opster import command
opts = [('l', 'listen', 'localhost', 'ip to listen on'),
('p', 'port', 8000, 'port to listen on'),