summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Pokorný <jpokorny@redhat.com>2014-09-02 21:48:33 +0200
committerJan Pokorný <jpokorny@redhat.com>2014-09-02 21:51:38 +0200
commitdeca55d9d3ac75ddac437d4e6e9957ac56af221f (patch)
tree3cacd333212cfb23189b21b1bdb8787c7f114a5f
parenta4af62a8b9465f7b9583686dc849b0cdcfb3346e (diff)
downloadclufter-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.py76
-rw-r--r--command_manager.py74
-rw-r--r--tests/command.py40
3 files changed, 101 insertions, 89 deletions
diff --git a/command.py b/command.py
index 84f7cce..73e175c 100644
--- a/command.py
+++ b/command.py
@@ -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__':