| 1 | # Copyright: 2005-2006 Brian Harring <ferringb@gmail.com> |
|---|
| 2 | # License: GPL2 |
|---|
| 3 | |
|---|
| 4 | """Metaclass to inject dependencies into method calls. |
|---|
| 5 | |
|---|
| 6 | Essentially, method a must be run prior to method b, invoking method a |
|---|
| 7 | if b is called first. |
|---|
| 8 | """ |
|---|
| 9 | |
|---|
| 10 | from snakeoil.lists import iflatten_instance |
|---|
| 11 | from snakeoil.currying import partial |
|---|
| 12 | |
|---|
| 13 | __all__ = ["ForcedDepends"] |
|---|
| 14 | |
|---|
| 15 | def ensure_deps(self, name, *a, **kw): |
|---|
| 16 | ignore_deps = "ignore_deps" in kw |
|---|
| 17 | if ignore_deps: |
|---|
| 18 | del kw["ignore_deps"] |
|---|
| 19 | s = [name] |
|---|
| 20 | else: |
|---|
| 21 | s = yield_deps(self, self.stage_depends, name) |
|---|
| 22 | |
|---|
| 23 | r = True |
|---|
| 24 | for dep in s: |
|---|
| 25 | if dep not in self._stage_state: |
|---|
| 26 | r = getattr(self, dep).raw_func(*a, **kw) |
|---|
| 27 | if r: |
|---|
| 28 | self._stage_state.add(dep) |
|---|
| 29 | else: |
|---|
| 30 | return r |
|---|
| 31 | return r |
|---|
| 32 | |
|---|
| 33 | def yield_deps(inst, d, k): |
|---|
| 34 | # While at first glance this looks like should use expandable_chain, |
|---|
| 35 | # it shouldn't. --charlie |
|---|
| 36 | if k not in d: |
|---|
| 37 | yield k |
|---|
| 38 | return |
|---|
| 39 | s = [k, iflatten_instance(d.get(k, ()))] |
|---|
| 40 | while s: |
|---|
| 41 | if isinstance(s[-1], basestring): |
|---|
| 42 | yield s.pop(-1) |
|---|
| 43 | continue |
|---|
| 44 | exhausted = True |
|---|
| 45 | for x in s[-1]: |
|---|
| 46 | v = d.get(x) |
|---|
| 47 | if v: |
|---|
| 48 | s.append(x) |
|---|
| 49 | s.append(iflatten_instance(v)) |
|---|
| 50 | exhausted = False |
|---|
| 51 | break |
|---|
| 52 | yield x |
|---|
| 53 | if exhausted: |
|---|
| 54 | s.pop(-1) |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | class ForcedDepends(type): |
|---|
| 58 | """ |
|---|
| 59 | Metaclass forcing methods to run in a certain order. |
|---|
| 60 | |
|---|
| 61 | Dependencies are controlled by the existance of a stage_depends |
|---|
| 62 | dict in the class namespace. Its keys are method names, values are |
|---|
| 63 | either a string (name of preceeding method), or list/tuple |
|---|
| 64 | (proceeding methods). |
|---|
| 65 | |
|---|
| 66 | U{pkgcore projects pkgcore.intefaces.format.build_base is an example consumer<http://pkgcore.org>} |
|---|
| 67 | to look at for usage. |
|---|
| 68 | """ |
|---|
| 69 | def __call__(cls, *a, **kw): |
|---|
| 70 | o = super(ForcedDepends, cls).__call__(*a, **kw) |
|---|
| 71 | if not getattr(cls, "stage_depends"): |
|---|
| 72 | return o |
|---|
| 73 | |
|---|
| 74 | if not hasattr(o, "_stage_state"): |
|---|
| 75 | o._stage_state = set() |
|---|
| 76 | |
|---|
| 77 | # wrap the funcs |
|---|
| 78 | |
|---|
| 79 | for x in set(x for x in iflatten_instance(o.stage_depends.iteritems()) |
|---|
| 80 | if x): |
|---|
| 81 | f = getattr(o, x) |
|---|
| 82 | f2 = partial(ensure_deps, o, x) |
|---|
| 83 | f2.raw_func = f |
|---|
| 84 | setattr(o, x, f2) |
|---|
| 85 | |
|---|
| 86 | return o |
|---|