diff options
author | Jan Pokorný <jpokorny@redhat.com> | 2014-09-02 21:48:33 +0200 |
---|---|---|
committer | Jan Pokorný <jpokorny@redhat.com> | 2014-09-02 21:51:38 +0200 |
commit | deca55d9d3ac75ddac437d4e6e9957ac56af221f (patch) | |
tree | 3cacd333212cfb23189b21b1bdb8787c7f114a5f | |
parent | a4af62a8b9465f7b9583686dc849b0cdcfb3346e (diff) | |
download | clufter-deca55d9d3ac75ddac437d4e6e9957ac56af221f.tar.gz clufter-deca55d9d3ac75ddac437d4e6e9957ac56af221f.tar.xz clufter-deca55d9d3ac75ddac437d4e6e9957ac56af221f.zip |
command{,_manager}: command responsible for resolving self
... against the passed filters;
The convention is that when the Command{,Alias} cannot be resolved
successfully, None object is returned from constructing the object.
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
-rw-r--r-- | command.py | 76 | ||||
-rw-r--r-- | command_manager.py | 74 | ||||
-rw-r--r-- | tests/command.py | 40 |
3 files changed, 101 insertions, 89 deletions
@@ -31,6 +31,7 @@ from .utils import any2iter, \ from .utils_func import apply_aggregation_preserving_depth, \ apply_intercalate, \ apply_loose_zip_preserving_depth, \ + apply_preserving_depth, \ bifilter, \ tailshake, \ zip_empty @@ -59,13 +60,35 @@ class Command(object): """ __metaclass__ = commands + @classmethod + def _resolve(cls, flts): + res_input = cls._filter_chain + res_output = apply_preserving_depth(flts.get)(res_input) + if apply_aggregation_preserving_depth(all)(res_output): + log.debug("resolve at `{0}' command: `{1}' -> {2}" + .format(cls.name, repr(res_input), repr(res_output))) + return res_output + # drop the command if cannot resolve any of the filters + res_input = apply_intercalate(res_input) + log.debug("cmd_name {0}".format(res_input)) + map(lambda (i, x): log.warning("Resolve at `{0}' command:" + " `{1}' (#{2}) filter fail" + .format(cls.name, res_input[i], i)), + filter(lambda (i, x): not(x), + enumerate(apply_intercalate(res_output)))) + return None + @hybridproperty def filter_chain(this): """Chain of filter identifiers/classes for the command""" return this._filter_chain - def __init__(self, *filter_chain): - self._filter_chain = filter_chain # already resolved + def __new__(cls, flts, *args): + filter_chain = cls._resolve(flts) + if filter_chain is None: + return None + self = super(Command, cls).__new__(cls) + self._filter_chain = filter_chain # following will all be resolved lazily, on-demand; # all of these could be evaluated upon instantiation immediately, # but this is not the right thing to do due to potentially many @@ -73,6 +96,7 @@ class Command(object): # of them will be run later on self._desc_opts = None self._filter_chain_analysis = None # will be dict + return self # # filter chain related @@ -577,30 +601,46 @@ class CommandAlias(object): """Way to define either static or dynamic command alias""" __metaclass__ = commands + def __new__(cls, flts, cmds, *args): + ic, sys, sys_extra = (lambda i={}, s='', e='', *a: (i, s, e))(*args) + # XXX really pass mutable cmds dict? + use_obj = cls + use_obj = use_obj._fnc(cmds, sys.lower(), + tuple(sys_extra.lower().split(','))) + for i in xrange(1, 100): # prevent infloop by force + if isinstance(use_obj, basestring): + use_obj = cmds.get(use_obj, None) + if not isinstance(use_obj, (nonetype, Command)): + assert issubclass(use_obj, CommandAlias) + assert use_obj is not cls, "trivial infloop" + continue + elif use_obj is None: + pass + else: + assert issubclass(use_obj, (Command, CommandAlias)) + if use_obj in ic: + use_obj = cmds[ic[use_obj]] + else: + name = '_' + use_obj.name + assert name not in cmds + ic[use_obj] = name + cmds[name] = use_obj = use_obj(flts, cmds, *args) + assert isinstance(use_obj, (nonetype, Command)), repr(use_obj) + return use_obj + @classmethod def deco(outer_cls, decl): if not hasattr(decl, '__call__'): assert issubclass(decl, Command) - fnc = lambda **kwargs: decl + fnc = lambda *args, **kwargs: decl else: fnc = decl log.debug("CommandAlias: deco for {0}".format(fnc)) - def new(cls, cmds, system='', system_extra=''): - # XXX really pass mutable cmds dict? - use_obj = fnc(cmds, system.lower(), - tuple(system_extra.lower().split(','))) - if isinstance(use_obj, basestring): - use_obj = cmds[use_obj] - assert isinstance(use_obj, Command) - else: - assert issubclass(use_obj, (nonetype, Command, CommandAlias)) - return use_obj - - attrs = { - '__module__': fnc.__module__, - '__new__': new, - } + attrs = dict( + __module__=fnc.__module__, + _fnc=staticmethod(fnc) + ) # optimization: shorten type() -> new() -> probe ret = outer_cls.probe(fnc.__name__, (outer_cls, ), attrs) return ret diff --git a/command_manager.py b/command_manager.py index 013212a..2de9f1a 100644 --- a/command_manager.py +++ b/command_manager.py @@ -8,15 +8,11 @@ __author__ = "Jan Pokorný <jpokorny @at@ Red Hat .dot. com>" import logging from textwrap import wrap -from .command import commands, Command, CommandAlias +from .command import commands, CommandAlias from .error import ClufterError, ClufterPlainError, \ EC from .plugin_registry import PluginManager -from .utils import nonetype -from .utils_func import apply_preserving_depth, \ - apply_aggregation_preserving_depth, \ - apply_intercalate, \ - bifilter +from .utils_func import bifilter from .utils_prog import make_options, set_logging log = logging.getLogger(__name__) @@ -61,52 +57,36 @@ class CommandManager(PluginManager): def _resolve(filters, commands, system='', system_extra=''): # name -> (cmd obj if not alias or resolvable name) aliases = [] + inverse_commands = dict((b, a) for a, b in commands.iteritems()) + + # first, resolve end-use commands for cmd_name, cmd_cls in commands.items(): if issubclass(cmd_cls, CommandAlias): aliases.append(cmd_name) continue - res_input = cmd_cls.filter_chain - res_output = apply_preserving_depth(filters.get)(res_input) - if apply_aggregation_preserving_depth(all)(res_output): - log.debug("resolve at `{0}' command: `{1}' -> {2}" - .format(cmd_name, repr(res_input), repr(res_output))) - commands[cmd_name] = cmd_cls(*res_output) - continue - # drop the command if cannot resolve any of the filters - res_input = apply_intercalate(res_input) - log.debug("cmd_name {0}".format(res_input)) - map(lambda (i, x): log.warning("Resolve at `{0}' command:" - " `{1}' (#{2}) filter fail" - .format(cmd_name, res_input[i], i)), - filter(lambda (i, x): not(x), - enumerate(apply_intercalate(res_output)))) - commands.pop(cmd_name) - - inverse_commands = dict((b, a) for a, b in commands.iteritems()) - for i, cmd_name in enumerate(aliases): - if i >= 100: - log.warn("Signs of infinite loop observed, escaping loop") - break - try: - alias_singleton = commands[cmd_name] - except KeyError: - continue - assert issubclass(alias_singleton, CommandAlias) - resolved = alias_singleton(commands, system, system_extra) - if resolved is None or resolved not in inverse_commands: - if issubclass(resolved, (Command, CommandAlias)): - commands[cmd_name] = resolved - if issubclass(resolved, CommandAlias): - assert resolved is not alias_singleton, "triv. infloop" - # alias to alias recursion -> just repeat the cycle - aliases.append(resolved) - continue - elif resolved: - log.warning("Resolve at `{0}' alias: target unrecognized" - .format(cmd_name)) + ret = cmd_cls(filters) + if ret is not None: + commands[cmd_name] = ret + else: commands.pop(cmd_name) - continue - commands[cmd_name] = inverse_commands[resolved] + + # only then resolve the command aliases, track a string identifying + # end-use command in `commands` + for cmd_name in aliases: + alias = commands[cmd_name] + if not isinstance(alias, basestring): + # not resolved yet + assert issubclass(alias, CommandAlias) + resolved = alias(filters, commands, inverse_commands, + system, system_extra) + resolved_cls = type(resolved) + if resolved_cls not in inverse_commands: + if resolved is not None: + log.warning("Resolve at `{0}' alias: target unknown" + .format(cmd_name)) + commands.pop(cmd_name) + else: + commands[cmd_name] = inverse_commands[resolved_cls] return commands diff --git a/tests/command.py b/tests/command.py index bf3ead4..2019dfa 100644 --- a/tests/command.py +++ b/tests/command.py @@ -22,9 +22,6 @@ from clufter.formats.ccs import ccs, flatccs from clufter.formats.coro import coroxml from clufter.formats.pcs import pcs -from clufter.utils_func import apply_preserving_depth, \ - apply_aggregation_preserving_depth - class ChainResolve(unittest.TestCase): def testShapeAndProtocolMatch(self): @@ -112,27 +109,22 @@ class ChainResolve(unittest.TestCase): ) split = cmd_classes.index(cmd_chain_nonmatch_01) for i, cmd_cls in enumerate(cmd_classes): - res_input = cmd_cls.filter_chain - res_output = apply_preserving_depth(filters.get)(res_input) - if apply_aggregation_preserving_depth(all)(res_output): - #log.debug("resolve at `{0}' command: `{1}' -> {2}" - # .format(cmd_cls.name, repr(res_input), repr(res_output))) - try: - cmd_cls(*res_output)({}, []) # no args/kwargs - except CommandError as e: - print "{0}: {1}".format(cmd_cls.name, e) - self.assertFalse(i < split) - except Exception as e: - print "{0}: {1}".format(cmd_cls.name, e) - self.assertTrue(i < split) - raise - else: - self.assertTrue(i < split) - # also test non-zero-size output whe - self.assertTrue(stat(testoutput).st_size > 0) - remove(testoutput) - continue - self.assertTrue(False) + try: + ret = cmd_cls(filters)({}, []) # no args/kwargs + self.assertTrue(ret is not None) + except CommandError as e: + print "{0}: {1}".format(cmd_cls.name, e) + self.assertFalse(i < split) + except Exception as e: + print "{0}: {1}".format(cmd_cls.name, e) + self.assertTrue(i < split) + raise + else: + self.assertTrue(i < split) + # also test non-zero-size output whe + self.assertTrue(stat(testoutput).st_size > 0) + remove(testoutput) + continue if __name__ == '__main__': |