[Subversion] / PEAK / src / peak / running / options.py  

View of /PEAK/src/peak/running/options.py

Parent Directory | Revision Log
Revision: 2107 - (download) (as text)
Mon Nov 21 04:21:13 2005 UTC (18 years, 6 months ago) by pje
File size: 12067 byte(s)
The 'peak' script is now an .exe on Windows, using setuptools' "entry point"
system.  Also, cleaned up the setup script a bit so I won't have to use
PyPI's web interface to keep the PEAK listing(s) up-to-date.  The download
URL now points to the snapshots directory, and there's a link for the
subversion release as well.  So "easy_install PEAK" will install the latest
snapshot, and "easy_install PEAK==dev" will build PEAK from source.
from peak.api import binding, NOT_GIVEN
from dispatch import combiners, NoApplicableMethods
from protocols.advice import addClassAdvisor, add_assignment_advisor

__all__ = [
    'parse', 'make_parser', 'get_help', 'Group',
    'Set', 'Add', 'Append', 'Handler', 'reject_inheritance', 'option_handler',
    'AbstractOption',
]

class OptionDispatcher(combiners.MapDispatcher):

    """Method combiner for options"""

    def getItems(self,signature,method):
        return method       # The methods are key-val pairs already

    def shouldStop(self,signature,method):
        return not method   # () means stop, as per 'reject_inheritance' below

OptionRegistry = OptionDispatcher(['ob'])

_optcount = 0

def _gen_key():
    global _optcount
    _optcount +=1
    return _optcount













def reject_inheritance(*names):
    """Reject inheritance of the named options, or all options if none named

    Call this function in a class body to reject inheritance of options
    registered for any of the class' base classes, e.g.::

        class Foo(Bar):
            options.reject_inheritance('-v','--verbose')

    Note that you must list all the variations that you wish to exclude from
    inheritance.  You can also specify no option names, in which case *all*
    inherited options are ignored, and your class will have no options except
    those you explicitly declare.
    """
    def callback(klass):
        # if no names are spec'd, 'method' will equal '()',
        #    which is the sentinel for "don't inherit anything"
        method = tuple([(name,(None, None)) for name in names])
        OptionRegistry[(klass,)] = method
        return klass

    addClassAdvisor(callback)



















class Group:
    """Designate a group of options to be displayed under a common heading

    Example usage::

        class MyClass(commands.AbstractCommand):

            db_options = options.Group("Database Options")

            dbURL = binding.Obtain(
                PropertyName('myapp.dburl'),
                [options.Set(
                    '--db', type=str, metavar="URL", help="Database URL",
                    group = db_options)
                ]
            )
    When help is displayed for the above class, it will list the '--db' option
    under a heading with the title "Database Options", along with any other
    options that have their 'group' set to the 'db_options' object.

    In addition to a title, you may also specify a 'description' (explanatory
    text that will appear under the group's heading and before the options),
    and a 'sortKey'.  If specified, the 'sortKey' will determine the relative
    order of groups' display (groups with lower keys appear first).  Groups
    with the same sort key appear in the same order that they were created in.
    """

    def __init__(self,title,description=None,sortKey=0):
        self.title, self.description, self.sortKey = title,description,sortKey
        self.sort_stable = _gen_key()

    def __repr__(self):
        return "Group"+`(self.title,self.description,self.sortKey)`

    def makeGroup(self,parser):
        from peak.util.optparse import OptionGroup
        return ((self.sortKey,self.sort_stable),
            OptionGroup(parser,self.title,self.description)
        )


class AbstractOption:
    """Base class for option metadata objects"""

    repeatable = True; sortKey = 0
    metavar = help = group = None
    value = type = option_names = NOT_GIVEN

    def __init__(self,*option_names,**kw):
        kw['option_names'] = option_names
        binding.initAttrs(self,kw.iteritems())
        if not option_names:
            raise TypeError(
                "%s must have at least one option name"
                % self.__class__.__name__
            )
        for option in option_names:
            if not option.startswith('-') or option.startswith('---'):
                raise ValueError(
                    "Invalid option name %r:"
                    " option names must begin with '-' or '--'" % (option,)
                )
        if (self.type is NOT_GIVEN) == (self.value is NOT_GIVEN):
            raise TypeError(
                "%s options must have a value or a type, not both or neither"
                % self.__class__.__name__
            )

        if self.type is NOT_GIVEN and self.metavar is not None:
            raise TypeError(
                "'metavar' is meaningless for options without a type"
            )
        if self.type is not NOT_GIVEN:
            self.nargs = 1
            if self.metavar is None:
                self.metavar = self.type.__name__.upper()
        else:
            self.nargs = 0

        self.sort_stable = _gen_key()


    def makeOption(self, attrname,optmap=None):
        options = self.option_names

        if optmap is not None:
            options = [opt for opt in options if optmap.get(opt) is self]

        from peak.util.optparse import make_option
        popt = make_option(
            action="callback", nargs=self.nargs, callback_args=(attrname,),
            callback = self.callback, metavar=self.metavar, help=self.help,
            type=(self.type is not NOT_GIVEN and "string" or None),*options
        )
        return (self.sortKey, self.sort_stable), popt

    def convert(self,option,value):
        if self.value is NOT_GIVEN:
            try:
                return self.type(value)
            except ValueError:
                from commands import InvocationError
                raise InvocationError(
                    "%s: %r is not a valid %s" % (option,value,self.metavar)
                )
        else:
            return self.value


    def check_repeat(self,option,parser):
        if not self.repeatable:

            if not hasattr(parser,'use_counts'):
                parser.use_counts = {}

            count = parser.use_counts.setdefault(self,0) + 1
            parser.use_counts[self] = count

            if count>1:
                from commands import InvocationError
                raise InvocationError("%s can only be used once" % option)


class Set(AbstractOption):
    """Set the attribute to the argument value or a constant"""

    repeatable = False

    def callback(self, option, opt, value, parser, attrname):
        self.check_repeat(option,parser)
        setattr(parser.values, attrname, self.convert(option,value))


class Add(AbstractOption):
    """Add the argument value or a constant to the attribute"""

    def callback(self, option, opt, value, parser, attrname):
        self.check_repeat(option,parser)
        value = getattr(parser.values, attrname) + self.convert(option,value)
        setattr(parser.values, attrname, value)


class Append(AbstractOption):
    """Append the argument value or a constant to the attribute"""

    def callback(self, option, opt, value, parser, attrname):
        self.check_repeat(option,parser)
        getattr(parser.values, attrname, value).append(
            self.convert(option,value)
        )


class Handler(AbstractOption):
    """Invoke a handler method when the option appears on the command line"""

    repeatable = False

    def callback(self, option, opt, value, parser, attrname):
        self.check_repeat(option,parser)
        self.function(
            parser.values,parser,opt,self.convert(option,value),parser.rargs
        )


def parse(ob,args,**kw):
    """Parse 'args' into 'ob', returning non-option arguments

    'ob' can be any object whose class has options registered.  'args' must
    be a list of arguments, such as one might find in 'sys.argv[1:]'.  A
    list of all the non-option arguments is returned, and 'ob' will have
    its attributes set or modified according to the defined behavior for
    any options found in 'args'.

    You can also supply any keyword arguments that are accepted by
    'optparse.OptionParser', to configure things like the usage string,
    program name and description, etc.
    """
    opts, args = make_parser(ob,**kw).parse_args(args, ob)
    return args


def get_help(ob,**kw):
    """Return a nicely-formatted help message for 'ob'

    The return value is a formatted help message for any options that are
    registered for the class of 'ob'.

    You can also supply any keyword arguments that are accepted by
    'optparse.OptionParser', to configure things like the usage string,
    program name and description, etc., that are used to format the help.
    """
    return make_parser(ob,**kw).format_help().strip()













def make_parser(ob,**kw):
    """Make an 'optparse.OptionParser' for 'ob'

    The parser will be populated with the options registered for the
    object's class.  Any keyword arguments supplied will be passed
    directly to 'optparse.OptionParser', so see its docs for details.
    By default, this routine sets 'usage' to an empty string,
    'add_help_option' to 'False', and 'allow_interspersed_args' to
    'False', unless you override them.
    """

    kw.setdefault('usage','')
    intersperse = kw.setdefault('allow_interspersed_args',False)
    del kw['allow_interspersed_args']
    prog = kw.setdefault('prog','')+':'
    kw.setdefault('add_help_option',False)

    from peak.util.optparse import OptionParser
    parser = OptionParser(**kw)
    if not intersperse:
        parser.disable_interspersed_args()

    def _exit_parser(status=0, msg=None):
        if msg:
            from commands import InvocationError
            if msg.startswith(prog):
                msg = msg[len(prog):]
            raise InvocationError(msg.strip())
        if status:
            raise SystemExit(status)

    parser.exit = _exit_parser
    try:
        optinfo = OptionRegistry[ob,].items()
    except NoApplicableMethods:
        optinfo = []





    optmap = dict([(k,opt)for k,(a,opt) in optinfo if opt is not None])
    optsused = {}
    optlists = {}
    for optname,(attrname,option) in optinfo:
        if option in optsused or option is None:
            continue
        optlists.setdefault(option.group,[]).append(
            option.makeOption(attrname,optmap)
        )
        optsused[option] = True

    groups = []
    for group,optlist in optlists.items():
        if group is None:
            container = parser
        else:
            key,container = group.makeGroup(parser)
            groups.append((key,container))

        optlist.sort()
        for key,popt in optlist:
            container.add_option(popt)

    groups.sort()
    for key,group in groups:
        parser.add_option_group(group)

    return parser













def option_handler(*option_names, **kw):
    """Decorate a method to be called when option is encountered

    Usage::

        class Bar:
            [options.option_handler('-z', type=int, help="Zapify!")]
            def zapify(self, parser, optname, optval, remaining_args):
                print "Zap!", optval

    The 'zapify' function above will be called on a 'Bar' instance if it
    parses a '-z' option.  'parser' is the 'optparse.OptionParser' being used
    to do the parsing, 'optname' is the option name (e.g. '-z') that was
    encountered, 'optval' is either the option's argument or the 'value'
    keyword given to 'option_handler', and 'remaining_args' is the list of
    arguments that are not yet parsed.  The handler function is free to modify
    the list in-place in order to manipulate the handling of subsequent
    options.  It may also manipulate other attributes of 'parser', if desired.
    """

    option = Handler(*option_names,**kw)

    def class_callback(klass):
        binding.declareAttribute(klass,None,option)
        return klass

    def decorator(frame,name,func,old_locals):
        option.function = func
        addClassAdvisor(class_callback,frame=frame)
        return func

    return add_assignment_advisor(decorator)


[binding.declareAttribute.when(AbstractOption)]
def _declare_option(classobj,attrname,option):
    OptionRegistry[(classobj,)] = tuple(
        [(optname,(attrname,option)) for optname in option.option_names]
    )



cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help