Exemple #1
0
def extract_indices(model_meta):
    indices = Values(dict(
        normal= {}, # XXX normal index?
        unique= {},
        primary= None,
    ))
    model_indices = default('indices', model_meta, [])
    named_indices = extract_listed_named(model_indices, force=False)
    for index in named_indices:
        index_fields = list(extract_listed_names(index['fields']))
        assert isinstance(index_fields, list), index_fields
        index_name = default('name', index)
        index_type = default('type', index, 'normal')
        assert index_type in "normal primary unique"
        if not index_name:
            if index_type == 'primary':
                index_name = model_meta['table']+'_pk'
            else:
                index_name = '_'.join(pluck('name', index_fields))+(dict(
                    unique = '_unique',
                    normal = '_idx'
                )[index_type])
        if index_type == 'primary':
            assert not indices.primary, (index, indices.primary)
            indices.primary = list(pluck('name', index_fields))
        elif index_type == 'unique':
            for named in index_fields:
                name = named['name']
                assert name not in indices.unique, name
                indices.unique[name] = index_name
        elif index_type == 'normal':
            for named in index_fields:
                name = named['name']
                assert name not in indices.normal, name
                indices.normal[name] = index_name
    return indices
Exemple #2
0
    def main(Klass, argv=None, optionparser=None, result_adapter=None, default_reporter=None):

        self = Klass()
        self.globaldict = Values(dict(
            prog=Values(),
            opts=Values(),
            args=[] ))

        self.globaldict.prog.handlers = self.BOOTSTRAP
        for handler_name in self.resolve_handlers():
            target = handler_name.replace('_', ':', 1)
            log.debug("%s.main deferring to %s", lib.cn(self), target)
            self.execute( handler_name )
            log.info("%s.main returned from %s", lib.cn(self), target)

        return self
Exemple #3
0
    def __init__(self):
        super(SimpleCommand, self).__init__()

        self.settings = Values()
        "Global settings, set to Values loaded from config_file. "
Exemple #4
0
class SimpleCommand(object):

    """
    Helper base-class for command-line functions.
    XXX Perhaps generalize to use optionspecs without command-line-style
    parsing but for specification and validation only.
    XXX also, looking for more generic way to invoke subcommands, without
    resorting to cmddict.
    """
    """
    currently
        static_args: prog.name => prog.{pwdspec, configspec, optspec}
        load_config:  prog.{pwdspec, configspec} => settings, rc, prog.{config}
        cmd_options:  prog.optspec => args, opts + globaldict

        --save-user-config
    """

    # optparse vars: prog name, version and short usage descr.
    NAME = 'libcmd'
    PROG_NAME = os.path.splitext(os.path.basename(sys.modules['__main__'].__file__))[0]
    VERSION = "0.1"
    USAGE = """Usage: %prog [options] paths """

    OPTS_INHERIT = ( '-v', )
    COMMAND_FLAG = ('-C', '--command')

    BOOTSTRAP = [ 'static_args', 'parse_options', 'load_config', 'prepare_output', 'set_commands' ]
    DEFAULT = [ 'stat', ]

    DEFAULT_RC = 'libcmdrc'
    DEFAULT_CONFIG_KEY = NAME
    INIT_RC = None

    @classmethod
    def get_optspec(Klass, inheritor):
        """
        Return tuples with optparse command-line argument specification.

        XXX: cannot stuff away options at StackedCommand, need to solve some
          issues at SimpleCommand.
        StackedCommand will prefix flags from the higher classes, keeping the
        entire name-space free for the subclass to fill. --cmd-list vs. --list
        FIXME: what todo upon conflicts. better solve this explicitly i think?
            so the inheritor needs to override local behaviour
            perhaps inheritor.get_optspec_override can return its options
            and locally these are prefixed

        StackedCommand defines a flag-prefixer that should be used in get_optspec
        implementations when the current Klass is not the same as the inheritor.
        The SimpleCommand.get_optspec does the same and it ensures the entire
        flag namespace is free for the subclass to use.

        SimpleCommand defines a dummy flag-prefixer.

        The inheritor can redefine or inherit get_prefixer, inheritor.get_prefixer
        should be used to get it. And so it goes with all static properties to
        allow for overrides. There is no option to leave out an option.
        """
        p = inheritor.get_prefixer(Klass)
        return (
            p(inheritor.COMMAND_FLAG, { 'metavar':'ID',
                'help': "Action (default: %default). ",
                'dest': 'commands',
                'action': 'callback',
                'callback': optparse_set_handler_list,
                'default': inheritor.DEFAULT
            }),
            # XXX: is this reserved for names to be used with confparse path
            # scan, or can it have full paths too.. currently it is just a name
            p(('-c', '--config',),{ 'metavar':'NAME',
                'dest': "config_file",
                'default': inheritor.DEFAULT_RC,
                'help': "Run time configuration. This is loaded after parsing command "
                    "line options, non-default option values wil override persisted "
                    "values (see --update-config) (default: %default). " }),

            p(('-U', '--update-config',),{ 'action':'store_true', 'help': "Write back "
                "configuration after updating the settings with non-default option "
                "values.  This will lose any formatting and comments in the "
                "serialized configuration. ",
                'default': False }),

            p(('-K', '--config-key',),{ 'metavar':'ID',
                'dest': 'config_key',
                'default': inheritor.DEFAULT_CONFIG_KEY,
                'help': "Key to current program settings in config-file. Set "
                    "if only part of settings in config file are used. "
                    " (default: %default). " }),

#            p(('--init-config',),cmddict(help="runtime-configuration with default values. "
#                'dest': 'command',
#                'callback': optparse_override_handler }),
#
#            p(('--print-config',),{ 'action':'callback', 'help': "",
#                'dest': 'command',
#                'callback': optparse_override_handler }),

            p(('-i', '--interactive',),{ 'help': "Allows commands to run extra heuristics, e.g. for "
                "selection and entry that needs user supervision. Normally all options should "
                "be explicitly given or the command fails. This allows instead to use a readline"
                "UI during execution. ",
                'default': False,
                'action': 'store_true' }),

            p(('--continue','--non-interactive',),{
                'help': "Never prompt user, solve and continue or raise error. ",
                'dest': 'interactive',
                'default': False,
                'action': 'store_false' }),

            # FIXME see what happes with this later
            p(('-L', '--message-level',),{ 'metavar':'level',
                'dest': 'message_level',
                'help': "Increase chatter by lowering "
                    "message threshold. Overriden by --quiet or --verbose. "
                    "Levels are 0--7 (debug--emergency) with default of 2 (notice). "
                    "Others 1:info, 3:warning, 4:error, 5:alert, and 6:critical.",
                'default': 2,
            }),

            p(('-v', '--verbose',),{ 'help': "Increase chatter by lowering message "
                "threshold. Overriden by --quiet or --message-level.",
                'action': 'callback',
                'callback': optparse_increase_verbosity}),

            p(('-q', '--quiet',),{ 'help': "Turn off informal message (level<4) "
                "and prompts (--interactive). ",
                'dest': 'quiet',
                'default': False,
                'action': 'callback',
                'callback': optparse_override_quiet }),

                )

    @classmethod
    def check_helpstring(Klass, longopt, attrs):
        if not 'help' in attrs or not attrs['help']:
            cmd = longopt[2:].replace('-', '_')
            if hasattr( Klass, cmd ):
                attrs['help'] = getattr( Klass, cmd ).__doc__

    @classmethod
    def get_prefixer(Klass, context):
        "Return dummy optparse flag prefixer. "
        def add_option_prefix(optnames, attrs):
            if 'dest' in attrs and attrs['dest'] == 'commands':
                if len(optnames[0]) == 2:
                    longopt = optnames[1]
                else:
                    longopt = optnames[0]
                Klass.check_helpstring(longopt, attrs)
            return optnames, attrs
        return add_option_prefix

    @classmethod
    def main(Klass, argv=None, optionparser=None, result_adapter=None, default_reporter=None):

        self = Klass()
        self.globaldict = Values(dict(
            prog=Values(),
            opts=Values(),
            args=[] ))

        self.globaldict.prog.handlers = self.BOOTSTRAP
        for handler_name in self.resolve_handlers():
            target = handler_name.replace('_', ':', 1)
            log.debug("%s.main deferring to %s", lib.cn(self), target)
            self.execute( handler_name )
            log.info("%s.main returned from %s", lib.cn(self), target)

        return self

    def __init__(self):
        super(SimpleCommand, self).__init__()

        self.settings = Values()
        "Global settings, set to Values loaded from config_file. "

    def get_optspecs(self):
        """
        Collect all options for the current class if used as Main command.
        Should be implemented by each subclass independently.

        XXX: doing this at instance time allows it to further pre-configure the
        options before returning them, but nothing much is passed along right
        now.
        """
        # get bottom up inheritance list
        mro = list(self.__class__.mro())
        # reorder to yield options top-down
        mro.reverse()
        for k in mro:
            if hasattr(k, 'get_optspec'):
                # that MRO Class actually defines get_optspec without inheriting it
                assert 'get_optspec' in k.__dict__, \
                        "SimpleCommand subclass must override get_optspec"
                yield k, k.get_optspec(self.__class__)

    def parse_argv(self, options, argv, usage, version):
        """
        Given the option spec and argument vector,
        parse it into a dictionary and a list of arguments.
        Uses Python standard library (OptionParser).
        Returns a tuple of the parser and option-values instances,
        and a list left-over arguments.
        """
        # TODO: rewrite to cllct.oslibcmd_docopt once that is packaged
        parser = optparse.OptionParser(usage, version=version)

        optnames = []
        nullable = []
        classdict = {}
        for klass, optspec in options:
            if hasattr(klass, 'get_opt_prefix'):
                prefix = klass.get_opt_prefix()
            else:
                prefix = 'cmd'
            classdict[ prefix ] = klass, optspec
            for optnames, optattr in optspec:
                try:
                    opt = parser.add_option(*optnames, **optattr)
                except Exception as e:
                    print("Error adding optspec %r to parser from %r: %s" % (
                            (optnames,optattr), klass, e))
                    traceback.print_exc()

        optsv, args = parser.parse_args(argv)

        #return parser, optsv, args

        # superficially move options from their confparse.Values object
        optsd = {}
        for name in dir(optsv):
            v = getattr(optsv, name)
            if not name.startswith('_') and not isinstance(v, collections.Callable):
                optsd[name] = v

        return parser, optsd, args

    def resolve_handlers( self ):
        """
        XXX
        """
        while self.globaldict.prog.handlers:
            o = self.globaldict.prog.handlers.pop(0)
            if hasattr(self, 'get_opt_prefix'):
                p = '%s_' % self.get_opt_prefix(self)
            else:
                p = self.DEFAULT_CONFIG_KEY or self.NAME
            yield o.startswith(p) and o.replace( p, '' ) or o

    def execute( self, handler_name, update={}, return_mode=None ):
        """
        During program execution this will call the individual handlers.
        It is called from execute program for every target and dependency,
        and can be called by handlers themselves.

        For each handler, the implements resolving variable names from the
        function signature to runtime values XXX IProgramHandler,
        and processing of the returned
        arguments with the help of IProgramHandlerResultProc.

        The return is always integreated with the current XXX IProgram

        return_mode specifies how the handler return value is processed
        by the result adapter.

        Currently the 'first:' prefix determines that the first named
        keywords is to be `return`\ 'ed. XXX: It should offer various
        methods of filter and either generate, return or be silent.

        """
        log.debug("SimpleCommand.execute %s %s", handler_name, update)
        if update:
            self.globaldict.update(update)
        handler = getattr( self, handler_name )
        args, kwds = self.select_kwds(handler, self.globaldict)
        log.debug("SimpleCommand.execute %s, %r, %r", handler.__name__,
                repr(args), repr(kwds))
        try:
            ret = handler(*args, **kwds)
        except Exception as e:
            log.crit("Exception in handler %s: %s", handler_name, e)
            traceback.print_exc()
            raise e
        # XXX:
        result_adapter = HandlerReturnAdapter( self.globaldict )
        #if isinstance( result_adapter, basestring ):
        #    result_adapter = getUtility(IResultAdapter, name=result_adapter)
        if return_mode:
            result_adapter.set_return_mode( return_mode )
        g = result_adapter.start( ret )
        if result_adapter.generates:
            return g
        for res in g:
            # XXX extracted.append(res)
            for reporter in self.globaldict.prog.output:
                reporter.append(res)
        if result_adapter.returns:
            return result_adapter.generated

    def select_kwds(self, handler, globaldict):
        """
        select values to feed a handler from the opts and args passed from the
        command line, and given a global dictionary to look up names from.

        see pyfuncsig.py for some practical info.
        """
        func_arg_vars, func_args_var, func_kwds_var, func_defaults = \
                inspect.getargspec(handler)
        assert func_arg_vars, \
                "Command handler %s is missing 'self' argument. " % handler
        assert func_arg_vars.pop(0) == 'self', \
                "Expected a method %s" % handler
        #  initialize the two return values
        ret_args, ret_kwds = [], {}
        if not ( func_arg_vars or func_args_var or func_kwds_var or func_defaults):
            return ret_args, ret_kwds
        if func_defaults:
            func_defaults = list(func_defaults)
        # remember which args we have in ret_args
        pos_args = []
        #log.debug(pformat(dict(handler=handler, inspect=dict(
        #    func_arg_vars = func_arg_vars,
        #    func_args_var = func_args_var,
        #    func_kwds_var = func_kwds_var,
        #    func_defaults = func_defaults
        #))))
        # gobble first positions if present from args
        while func_arg_vars \
                and len(func_arg_vars) > ( func_defaults and len(func_defaults) or 0 ):
            arg_name = func_arg_vars.pop(0)
            if arg_name in globaldict:
                value = globaldict[arg_name]
            elif globaldict.args:
                value = globaldict.args.pop(0)
            else:
                value = None
            pos_args.append(arg_name)
            ret_args.append(value)
        # add all positions with a default
        while func_defaults:
            arg_name = func_arg_vars.pop(0)
            value = func_defaults.pop(0)
            if hasattr(globaldict.opts, arg_name):
                value = getattr(globaldict.opts, arg_name)
            #if hasattr(self.settings, arg_name):
            #    value = getattr(self.settings, arg_name)
            elif arg_name in globaldict:
                value = globaldict[arg_name]
            #ret_kwds[arg_name] = value
            #print 'default to position', arg_name, value
            pos_args.append(arg_name)
            ret_args.append(value)
        # feed rest of args to arg pass-through if present
        if globaldict.args and func_args_var:
            ret_args.extend(globaldict.args)
            pos_args.extend('*'+func_args_var)
#        else:
#            print 'hiding args from %s' % handler, args
        # ret_kwds gets argnames missed, if there is kwds pass-through
        if func_kwds_var:
            for kwd, val in list(globaldict.items()):
                if kwd in pos_args:
                    continue
                ret_kwds[kwd] = value
        return ret_args, ret_kwds

    # Handlers

    def static_args( self ):
        argv = list(sys.argv)
        yield dict( prog = dict(
            pwd = lib.cmd('pwd').strip(), # because os.getcwd resolves links
            home = os.getenv('HOME'),
            name = argv.pop(0),
            argv = argv ) )
        #name=os.path.splitext(os.path.basename(__file__))[0],
        #version="0.1",
        init.configure_components()

    def parse_options( self, prog ):
        # XXX
        #if optionparser and isinstance( optionparser, basestring ):
        #    parser = getUtility(IOptionParser, name=optionparser)
        #elif optionparser:
        #    #assert provides IOptionParser
        #    parser = optionparser
        #else:
        #   parser.set_defaults( values )

        optspecs = self.get_optspecs()
        prog.optparser, opts, args = \
                self.parse_argv( optspecs, prog.argv, self.USAGE, self.VERSION )

        yield dict( opts=opts, args=args )

        # XXX iface.gsm.registerUtility(iface.IResultAdapter, HandlerReturnAdapter, 'default')
        iface.registerAdapter(ResultFormatter)

    def load_config(self, prog, opts):
        """
        Optionally find prog.config_file from opts.config_file,
        and load returning its dict.
        If set but path is non-existant, call self.INIT_RC if exists.
        """
        if self.INIT_RC and hasattr(self, self.INIT_RC):
            self.default_rc = getattr(self, self.INIT_RC)(prog, opts)
        else:
            self.default_rc = dict()

        if 'config_file' not in opts or not opts.config_file:
            self.rc = self.default_rc
            log.err( "Nothing to load configuration from")

        else:
            # FIXME: init default config
                #print self.DEFAULT_RC, self.DEFAULT_CONFIG_KEY, self.INIT_RC

            prog.config_file = self.find_config_file(opts.config_file)
            self.load_config_( prog.config_file, opts )
            yield dict(settings=self.settings)

    def find_config_file(self, rc):
        rcfile = list(confparse.expand_config_path(rc))
        config_file = None
        if rcfile:
            config_file = rcfile.pop()
        # FIXME :if not config_file:
        assert config_file, \
                "Missing config-file for %s, perhaps use init_config_file" %( rc, )
        assert isinstance(config_file, str), config_file
        assert os.path.exists(config_file), \
                "Missing %s, perhaps use init_config_file"%config_file
        return config_file

    def load_config_(self, config_file, opts=None ):
        settings = confparse.load_path(config_file)

        config_key = opts.config_key
        if not config_key:
            self.rc = 'global'
            self.settings.update(settings)
            return

        if hasattr(settings, config_key):
            self.rc = self.default_rc
            if getattr(settings, config_key):
                self.rc.update(getattr(settings, config_key))
            self.rc.update({ k: v for k, v in opts.items() if v })
        else:
            log.warn("Config key %s does not exist in %s" % (config_key,
                config_file))

        settings.set_source_key('config_file')
        settings.config_file = config_file
        self.config_key = config_key
        self.settings.update(settings)

    def prepare_output( self, prog, opts ):
# XXX
        default_reporter = ResultFormatter()
        #if isinstance( default_reporter, basestring ):
        #    self.globaldict.prog.default_reporter_name = default_reporter
        #    default_reporter = getUtility(IReporter)
        #elif not default_reporter:
        #    default_reporter = self

        prog.output = [ default_reporter ]
        log.category = 7-opts.message_level
        #print 'log level', log.category

        import taxus.core
        import taxus.net
        import taxus.model
        prog.module = (
                ( iface.INode, taxus.core.Node ),
                #( iface.IGroupNode, taxus.core.GroupNode ),
                ( iface.ILocator, taxus.net.Locator ),
                ( iface.IBookmark, taxus.model.Bookmark ),
            )
        #zope.interface.classImplements(prog, iface.IProgram)

    def path_args(self, prog, opts):
        """
        XXX this yields an args=[path] for each path arg,
        can this filter combined with parse_options..
        """
        for a in prog.argv[1:]:
            if os.path.exists(a):
                yield dict( args = [ a ] )
            else:
                log.warn("Ignored non-path argument %s", a)

    def set_commands(self, prog, opts):
        " Copy opts.commands to prog.handlers. "
        if opts and opts.commands:
            prog.handlers += opts.commands
        else:
            prog.handlers += self.DEFAULT
        log.debug("Initial commands are %s", repr(prog.handlers))

# TODO: post-deps
    def flush_reporters(self):
        for reporter in self.globaldict.prog.output:
            reporter.flush()

    def help(self, parser, opts, args):
        print("""
        libcmd.Cmd.help
        """)

    def stat(self, opts=None, args=None):
        if not self.rc:
            log.err("Missing run-com for %s", self.NAME)
        elif not self.rc['version']:
            log.err("Missing version for run-com")
        elif self.VERSION != self.rc['version']:
            if self.VERSION > self.rc['version']:
                log.err("Run com requires upgrade")
            else:
                log.err("Run com version mismatch: %s vs %s", self.rc['version'],
                        self.VERSION)
        print('args:', args)
        print('opts:', pformat(opts.todict()))