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
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. "
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()))