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