import sys from decorator import decorator from collections import OrderedDict import re from . import formatting, cache, db from .dependencies import Dependencies PARENT_DOT_THIS = '<>' # TODO(xavid): normalize things that match PARENT_DOT_THIS_PAT? PARENT_DOT_THIS_PAT = re.compile('^\s*<>\s*$') def is_parent_dot_this(s): #return PARENT_DOT_THIS_PAT.search(s) is not None return PARENT_DOT_THIS == s class Element(object): __slots__ = ('_ename') @property def ename(self): return self._ename def __init__(self, ename): assert isinstance(ename, unicode), repr(ename) self._ename = ename def set_ename(self, ename): oldename = self._ename self._ename = ename db.esetattr(self, 'ename', oldename, ename) # TODO(xavid): change to has_prop() def has_propval(self, propname): return propname in self.get_value_map() def get_propval(self, propname): value_map = self.get_value_map() try: assert isinstance(value_map[propname], Propval), (value_map, propname) return value_map[propname] except KeyError: return None def get_parent(self): return get_parent(self.ename, self) def get_parent_ename(self): p = get_parent(self.ename, self) if p is None: return None else: return p.ename def set_prop(self, propname, value, format='creole'): db.setprop(self, propname, value, format) def get_prop(self, propname): try: return self.get_value_map()[propname].value except KeyError: return None def remove_prop(self, propname): db.delete(self, propname) def get_orgmode(self): raise NotImplementedError def set_orgmode(self, orgmode): db.esetattr(self, 'orgmode', self.get_orgmode(), orgmode) def get_descendants(self): raise NotImplementedError def is_ancestor_of(self, other): raise NotImplementedError def search_descendants(self, restr): return search_elements(restr, elements=self.get_descendants()) def get_ancestors(self): raise NotImplementedError def has_children(self): return len(get_children(self.ename, self)) > 0 def get_children(self): return get_children(self.ename, self) def list_props(self): return self.get_value_map().keys() def create_child(self, ename): raise NotImplementedError @cache.metadata_cached('nice_value_map') def get_nice_value_map(self, deps=None): ret = {} for propname in self.list_props(): flav = get_flavor(propname) # TODO(xavid): dependency on the flavor of the prop? if not flav.quick and not flav.binary: pv = self.get_propval(propname) assert pv is not None, (self, propname) nv, ds = formatting.nice_value(pv) ret[propname] = nv deps.update(ds) return ret def _get_value_map_impl(self, od): raise NotImplementedError @cache.metadata_cached('value_map') def get_value_map(self, deps=None): ret = OrderedDict() # Can't check without looking at this, especially since this is # now how we check the list of props in an element. # This is still useful because of the svn-revision # shortcutting. These deps are only used for the metadata # itself; callers from, say, wiki will cache based on # addElementPropvalsDep(). # i.e., can't do deps.addDep(Propval(self, pv.prop.name, pv.value)) deps.addFragileDep(self) self._get_value_map_impl(ret) return ret @cache.metadata_cached('final_map') def get_final_map(self, deps=None): """Returns a map of propname to ename mapping the props that are defaulting to a parent element's final propval to that parent element's ename. It does not include other propnames at all. A propval is 'final' if: * It doesn't start with '< 0 and not value.isspace()): ret[pname] = e.ename else: pass return ret @cache.metadata_cached('flavor_map') def get_flavor_map(self, deps=None): ret = {} # In theory we could avoid getting propvals at all, like the # following, but we don't have prop-only dependencies. # TODO(xavid): Add prop dependencies/versioning? #for prop in (Prop.query.join((Propval,Prop.id == Propval.prop_id)) # .filter(Propval.element_id == self.id).all()): vm = self.get_value_map() deps.addElementPropvalsDep(self) for pname in vm: # TODO(xavid): we don't actually care about the value deps.addDep(vm[pname]) ret[pname] = get_prop(pname).flavor return ret def set_parent(self, parent): db.esetattr(self, 'parent', self.get_parent(), parent) def __unicode__(self): return self.ename def __repr__(self): return "<$Element: %s>"%self.ename.encode('utf-8') def __eq__(self, other): return hasattr(other, 'ename') and self.ename == other.ename def __hash__(self): return hash(self.ename) def get_root_element(): return impl.get_root_element() def create_root_element(ename): return impl.create_root_element(ename) def list_all_elements(): root = get_root_element() return [root] + root.get_descendents() TXT = u'txt' class Propval(object): __slots__ = ('_element', '_propname', '_value', '_format') @property def element(self): return self._element @property def propname(self): return self._propname @property def value(self): return self._value @property def format(self): return self._format def __init__(self, element, propname, value, format): self._element = element self._propname = propname self._value = value self._format = format def __setstate__(self, state): ename, propname, value, format = state self.__init__(get_element(ename), propname, value, format) def __getstate__(self): return (self.element.ename, self.propname, self.value, self.format) # TODO(xavid): replace this with a method in conversion. def render(self, format=TXT, filters={}, method='render', offset=0): from . import conversion im = conversion.convert_any(self.element.ename, self.propname, [format], filters, method=method, offset=offset + 1) return im.asData() def set_value(self, value): self._value = value self.element.set_prop(self.propname, value) def __repr__(self): return "<$Propval: %s.%s>" % (self.element.ename.encode('utf-8'), self.propname.encode('utf-8')) class Prop(object): __slots__ = ('_name', '_flavor', '_default', '_visible', '_comment') @property def name(self): return self._name @property def flavor(self): return self._flavor @property def default(self): return self._default @property def visible(self): return self._visible @property def comment(self): return self._comment def __init__(self, name, flavor, default, visible, comment): self._name = name self._flavor = flavor self._default = default self._visible = visible self._comment = comment def set_name(self, name): db.psetattr(self, 'pname', name) self._name = name def set_flavor(self, flavor): db.psetattr(self, 'flavor', flavor) self._flavor = flavor def set_default(self, default): db.psetattr(self, 'default', default) self._default = default def set_visible(self, visible): db.psetattr(self, 'visible', visible) self._visible = visible def set_comment(self, comment): db.psetattr(self, 'comment', comment) self._comment = comment # Should be ordered such that each element is followed by a single # region consisting of any included descendants. Because of prop # inheritance, there shouldn't be holes. def containing_elements(self): raise NotImplementedError def list_all_props(): return impl.list_all_props() def list_all_elements(): return impl.list_all_elements() class Symbol(object): pass # Singleton because None and False can be valid values. NOT_FOUND = Symbol() def str_memoed(key): def helper(func, arg): k = '%s:%s' % (key, arg) val = cache.get_memo(k, NOT_FOUND) if val is NOT_FOUND: val = func(arg) cache.set_memo(k, val) return val return decorator(helper) def replace_memo(key, arg, val): k = '%s:%s' % (key, arg) cache.set_memo(k, val) @str_memoed('element') def get_element(ename): return impl.get_element(ename) # Special restriction values for # search_elements()/get_element_with()/get_element_raw(). # (Be sure to add to implementations' _get_element_raw_simple() if you add # more.) NOT_BLANK = Symbol() @cache.args_cached('search_elements', lambda es: [e.ename for e in es], lambda es: [get_element(e) for e in es]) def search_elements(restrictions, raw=False, elements=None): """search_elements({u'category':'mon',u'name':'sakura') returns a list of elements with those propvals, using inheritance and such. restrictions must have unicode keys.""" if elements is None: elements = list_all_elements() for pname in restrictions: target = restrictions[pname] newelms = [] flav = get_flavor(pname) assert flav.indexed or target is NOT_BLANK, (flav, target) if raw: assert flav.raw or target is NOT_BLANK else: assert not flav.raw, pname if hasattr(target, 'ename'): from bazbase.flavors import FLAVORS target = FLAVORS['reference'].element_to_string(target) for e in elements: if e.has_propval(pname): if raw: rendered = e.get_prop(pname) else: from . import conversion from .wiki import NoContentException try: rendered = conversion.render(e, pname) except NoContentException: continue if target is NOT_BLANK: if rendered != '': newelms.append(e) else: if rendered == target: newelms.append(e) elements = newelms return elements def _one_of_list(l): if len(l) == 1: return l[0] elif len(l) == 0: return None else: raise LookupError("Multiple results!") def get_element_with(restrs): return _one_of_list(search_elements(restrs)) def get_element_raw(restrs): # TODO(xavid): Should we do something more deterministic/optimized than # using an arbitrary key for the initial filtering? if hasattr(impl, '_get_element_raw_simple'): k, v = restrs.items()[0] elms = impl._get_element_raw_simple(k, v) if len(restrs) == 1: return _one_of_list(elms) else: restrs.pop(k) return _one_of_list(search_elements(restrs, raw=True, elements=elms)) else: return _one_of_list(search_elements(restrs, raw=True)) @str_memoed('prop') def get_prop(pname): return impl.get_prop(pname) def create_prop(pname, flavor=None): from bazbase.flavors import FLAVORS prop = impl.create_prop(pname, flavor) replace_memo('prop', pname, prop) replace_memo('flavor', pname, FLAVORS[prop.flavor]) return prop @str_memoed('flavor') def get_flavor(pname): from bazbase.flavors import FLAVORS prop = get_prop(pname) if prop is not None: return FLAVORS[prop.flavor] else: return None def get_propval(ename, pname): e = get_element(ename) if e is not None: return e.get_propval(pname) else: return None def has_propval(ename, pname): return get_element(ename).has_propval(pname) def e_memoed(key): def helper(func, ename, e=None): k = '%s:%s' % (key, ename) val = cache.get_memo(k, NOT_FOUND) if val is NOT_FOUND: if e is None: e = get_element(ename) val = func(ename, e) cache.set_memo(k, val) return val return decorator(helper) @e_memoed('parent') def get_parent(ename, e=None): return impl.get_parent(ename, e) def element_get_parent(e): return get_parent(e.ename, e) @e_memoed('children') def get_children(ename, e=None): return impl.get_children(ename, e) # Overall operations def clear_database(): impl.clear_database() def flush_database_for_test(): impl.flush_database_for_test() def verify_tree_for_test(): impl.verify_tree_for_test() def set_impl(i): global impl impl = i