mirror of https://github.com/kivy/kivy.git
kvlang: finish template implementation, unit test are working now.
This commit is contained in:
parent
16bd02cfda
commit
356e124d94
130
kivy/lang.py
130
kivy/lang.py
|
@ -563,7 +563,7 @@ class ParserRule(object):
|
|||
#: Id given to the rule
|
||||
self.id = None
|
||||
#: Properties associated to the rule
|
||||
self.properties = {}
|
||||
self.properties = OrderedDict()
|
||||
#: Canvas normal
|
||||
self.canvas_root = None
|
||||
#: Canvas before
|
||||
|
@ -576,6 +576,8 @@ class ParserRule(object):
|
|||
def precompile(self):
|
||||
for x in self.properties.itervalues():
|
||||
x.precompile()
|
||||
for x in self.handlers:
|
||||
x.precompile()
|
||||
for x in self.children:
|
||||
x.precompile()
|
||||
if self.canvas_before:
|
||||
|
@ -626,11 +628,6 @@ class ParserRule(object):
|
|||
'Invalid template name (missing @)')
|
||||
template_name, template_root_cls = item_content.split('@')
|
||||
self.ctx.templates.append((template_name, template_root_cls, self))
|
||||
'''
|
||||
Factory.register(template_name,
|
||||
cls=partial(self.template, template_name),
|
||||
is_template=True)
|
||||
'''
|
||||
|
||||
def __repr__(self):
|
||||
return '<ParserRule name=%r>' % (self.name, )
|
||||
|
@ -790,6 +787,7 @@ class Parser(object):
|
|||
|
||||
current_object = None
|
||||
current_property = None
|
||||
current_propobject = None
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
@ -821,6 +819,13 @@ class Parser(object):
|
|||
if len(x) == 2 and len(x[1]):
|
||||
raise ParserError(self, ln,
|
||||
'Invalid data after declaration')
|
||||
name = x[0]
|
||||
# if it's not a root rule, then we got some restriction
|
||||
# aka, a valid name, without point or everything else
|
||||
if count != 0:
|
||||
if False in [ord(z) in Parser.PROP_RANGE for z in name]:
|
||||
raise ParserError(self, ln, 'Invalid class name')
|
||||
|
||||
current_object = ParserRule(self, ln, x[0])
|
||||
current_property = None
|
||||
objects.append(current_object)
|
||||
|
@ -856,10 +861,14 @@ class Parser(object):
|
|||
'Invalid id, cannot be "self" or "root"')
|
||||
current_object.id = value
|
||||
elif len(value):
|
||||
current_object.properties[name] = \
|
||||
ParserRuleProperty(self, ln, name, value)
|
||||
rule = ParserRuleProperty(self, ln, name, value)
|
||||
if name[:3] == 'on_':
|
||||
current_object.handlers.append(rule)
|
||||
else:
|
||||
current_object.properties[name] = rule
|
||||
else:
|
||||
current_property = name
|
||||
current_propobject = None
|
||||
|
||||
# Two more levels?
|
||||
elif count == indent + 8:
|
||||
|
@ -878,12 +887,15 @@ class Parser(object):
|
|||
lines = _lines
|
||||
i = 0
|
||||
else:
|
||||
if current_property in current_object:
|
||||
prop = current_object[current_property]
|
||||
prop.value += '\n' + content
|
||||
if current_propobject is None:
|
||||
current_propobject = ParserRuleProperty(self, ln, current_property, content)
|
||||
if current_property[:3] == 'on_':
|
||||
current_object.handlers.append(current_propobject)
|
||||
else:
|
||||
current_object.properties[current_property] = \
|
||||
current_propobject
|
||||
else:
|
||||
current_object.properties[current_property] = \
|
||||
ParserRuleProperty(self, ln, current_property, content)
|
||||
current_propobject.value += '\n' + content
|
||||
|
||||
# Too much indentation, invalid
|
||||
else:
|
||||
|
@ -910,12 +922,12 @@ _eval_globals['center'] = _eval_center
|
|||
|
||||
|
||||
def custom_callback(*largs, **kwargs):
|
||||
element, key, value, idmap = largs[0]
|
||||
__kvlang__ = value
|
||||
self, rule, idmap = largs[0]
|
||||
__kvlang__ = rule
|
||||
locals().update(idmap)
|
||||
args = largs[1:]
|
||||
try:
|
||||
exec value[1]
|
||||
exec rule.co_value
|
||||
except:
|
||||
exc_info = sys.exc_info()
|
||||
traceback = make_traceback(exc_info)
|
||||
|
@ -1059,8 +1071,13 @@ class BuilderBase(object):
|
|||
parser = Parser(content=string, filename=kwargs.get(
|
||||
'filename', None))
|
||||
self.rules.extend(parser.rules)
|
||||
|
||||
# add the template found by the parser into ours
|
||||
for name, cls, template in parser.templates:
|
||||
self.templates[name] = (cls, template)
|
||||
Factory.register(name,
|
||||
cls=partial(self.template, name),
|
||||
is_template=True)
|
||||
'''
|
||||
if kwargs['rulesonly'] and root:
|
||||
filename = kwargs.get('rulesonly', '<string>')
|
||||
|
@ -1071,6 +1088,33 @@ class BuilderBase(object):
|
|||
self._current_filename = None
|
||||
#return root
|
||||
|
||||
def template(self, *args, **ctx):
|
||||
'''Create a specialized template using a specific context.
|
||||
.. versionadded:: 1.0.5
|
||||
|
||||
With template, you can construct custom widget from a kv lang definition
|
||||
by giving them a context. Check :ref:`Template usage <template_usage>`.
|
||||
'''
|
||||
# Prevent naming clash with whatever the user might be putting into the
|
||||
# ctx as key.
|
||||
name = args[0]
|
||||
if name not in self.templates:
|
||||
raise Exception('Unknown <%s> template name' % name)
|
||||
baseclasses, rule = self.templates[name]
|
||||
key = '%s|%s' % (name, baseclasses)
|
||||
cls = Cache.get('kv.lang', key)
|
||||
if cls is None:
|
||||
rootwidgets = []
|
||||
for basecls in baseclasses.split('+'):
|
||||
rootwidgets.append(Factory.get(basecls))
|
||||
cls = ClassType(name, tuple(rootwidgets), {})
|
||||
Cache.append('kv.lang', key, cls)
|
||||
print 'create template', args, rule
|
||||
widget = cls()
|
||||
self._apply_rule(widget, rule, rule, template_ctx=ctx)
|
||||
#self.build_item(widget, rule, is_rule=True, from_template=True)
|
||||
return widget
|
||||
|
||||
def apply(self, widget):
|
||||
rules = self.match(widget)
|
||||
if __debug__:
|
||||
|
@ -1081,7 +1125,7 @@ class BuilderBase(object):
|
|||
for rule in rules:
|
||||
self._apply_rule(widget, rule, rule)
|
||||
|
||||
def _apply_rule(self, widget, rule, rootrule):
|
||||
def _apply_rule(self, widget, rule, rootrule, template_ctx=None):
|
||||
# widget: the current instanciated widget
|
||||
# rule: the current rule
|
||||
# rootrule: the current root rule (for children of a rule)
|
||||
|
@ -1092,6 +1136,9 @@ class BuilderBase(object):
|
|||
'ids': {'root': widget},
|
||||
'set': [], 'hdl': []}
|
||||
|
||||
if template_ctx is not None:
|
||||
self.rulectx[rootrule]['ids']['ctx'] = QueryDict(template_ctx)
|
||||
|
||||
# if we got an id, put it in the root rule for a later global usage
|
||||
if rule.id:
|
||||
self.rulectx[rootrule]['ids'][rule.id] = widget
|
||||
|
@ -1116,17 +1163,35 @@ class BuilderBase(object):
|
|||
rule.canvas_after, rootrule)
|
||||
|
||||
# create children tree
|
||||
for childrule in rule.children:
|
||||
cls = Factory.get(childrule.name)
|
||||
child = cls()
|
||||
self.apply(child)
|
||||
self._apply_rule(child, childrule, rootrule)
|
||||
widget.add_widget(child)
|
||||
for crule in rule.children:
|
||||
cname = crule.name
|
||||
cls = Factory.get(cname)
|
||||
if Factory.is_template(cname):
|
||||
# contruct the context, and pass it.
|
||||
ctx = {}
|
||||
idmap = copy(global_idmap)
|
||||
idmap.update({'root': self.rulectx[rootrule]['ids']['root']})
|
||||
for prule in crule.properties.itervalues():
|
||||
print type(prule.co_value), repr(prule.co_value)
|
||||
value = prule.co_value
|
||||
if type(value) is CodeType:
|
||||
value = eval(value, _eval_globals, idmap)
|
||||
ctx[prule.name] = value
|
||||
child = cls(**ctx)
|
||||
widget.add_widget(child)
|
||||
if crule.id:
|
||||
self.rulectx[rootrule]['ids'][crule.id] = child
|
||||
else:
|
||||
child = cls()
|
||||
self.apply(child)
|
||||
self._apply_rule(child, crule, rootrule)
|
||||
widget.add_widget(child)
|
||||
|
||||
# create properties
|
||||
assert(rootrule in self.rulectx)
|
||||
self.rulectx[rootrule]['set'].append((widget, rule.properties.values()))
|
||||
self.rulectx[rootrule]['hdl'].append((widget, rule.handlers))
|
||||
rctx = self.rulectx[rootrule]
|
||||
rctx['set'].append((widget, rule.properties.values()))
|
||||
rctx['hdl'].append((widget, rule.handlers))
|
||||
|
||||
# if we are applying another rule that the root one, then it's done for
|
||||
# us!
|
||||
|
@ -1135,7 +1200,6 @@ class BuilderBase(object):
|
|||
return
|
||||
|
||||
# normally, we can apply a list of properties with a proper context
|
||||
rctx = self.rulectx[rootrule]
|
||||
for widget_set, rules in rctx['set']:
|
||||
for rule in rules:
|
||||
assert(isinstance(rule, ParserRuleProperty))
|
||||
|
@ -1144,16 +1208,20 @@ class BuilderBase(object):
|
|||
if type(value) is CodeType:
|
||||
value = create_handler(widget, widget, key,
|
||||
value, rule, rctx['ids'])
|
||||
print 'setattr', widget_set, key, (rule.value, value)
|
||||
setattr(widget_set, key, value)
|
||||
|
||||
# build handlers
|
||||
for widget_set, rules in rctx['hdl']:
|
||||
for rule in rules:
|
||||
assert(isinstance(rule, ParserRuleProperty))
|
||||
assert(rule.name.startswith('on_'))
|
||||
#print '# create handler for', widget_set, rule.name, rule.value
|
||||
for crule in rules:
|
||||
assert(isinstance(crule, ParserRuleProperty))
|
||||
assert(crule.name.startswith('on_'))
|
||||
print '# create handler for', widget_set, crule.name, crule.value
|
||||
key = crule.name
|
||||
if not widget.is_event_type(key):
|
||||
key = crule.name[3:]
|
||||
widget.bind(**{ key: partial(custom_callback, (
|
||||
widget, rule.name, rule.co_value, rctx['ids']))})
|
||||
widget, crule, rctx['ids']))})
|
||||
|
||||
#print '\n--------- end', rule, 'for', widget, '\n'
|
||||
|
||||
|
|
|
@ -161,8 +161,8 @@ def translate_exception(exc_info, initial_skip=0):
|
|||
# fake template exceptions
|
||||
kvlang = tb.tb_frame.f_locals.get('__kvlang__')
|
||||
if kvlang is not None:
|
||||
parser = kvlang[4]
|
||||
line = kvlang[3]
|
||||
parser = kvlang.ctx
|
||||
line = kvlang.line
|
||||
filename = parser.filename or '<string:%s>' % id(parser)
|
||||
tb = fake_exc_info(exc_info[:2] + (tb,), filename, line)[2]
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import unittest
|
|||
class InvalidLangTestCase(unittest.TestCase):
|
||||
|
||||
def test_invalid_childname(self):
|
||||
from kivy.lang import Builder
|
||||
from kivy.lang import Builder, ParserError
|
||||
from kivy.factory import FactoryException
|
||||
try:
|
||||
Builder.load_string('''
|
||||
|
@ -19,12 +19,13 @@ Widget:
|
|||
thecursor.Cursor:
|
||||
''')
|
||||
self.fail('Invalid children name')
|
||||
except ParserError:
|
||||
pass
|
||||
except FactoryException:
|
||||
pass
|
||||
|
||||
def test_invalid_childname_before(self):
|
||||
from kivy.lang import Builder
|
||||
from kivy.lang import ParserError
|
||||
from kivy.lang import Builder, ParserError
|
||||
try:
|
||||
Builder.load_string('''
|
||||
Widget:
|
||||
|
|
Loading…
Reference in New Issue