|
|
a4e9bd |
Index: Paste-1.7.4/paste/util/looper/__init__.py
|
|
|
a4e9bd |
===================================================================
|
|
|
a4e9bd |
--- /dev/null
|
|
|
a4e9bd |
+++ Paste-1.7.4/paste/util/looper/__init__.py
|
|
|
a4e9bd |
@@ -0,0 +1,4 @@
|
|
|
a4e9bd |
+try:
|
|
|
a4e9bd |
+ from tempita._looper import *
|
|
|
a4e9bd |
+except ImportError:
|
|
|
a4e9bd |
+ from _looper import *
|
|
|
a4e9bd |
Index: Paste-1.7.4/paste/util/looper/_looper.py
|
|
|
a4e9bd |
===================================================================
|
|
|
a4e9bd |
--- /dev/null
|
|
|
a4e9bd |
+++ Paste-1.7.4/paste/util/looper/_looper.py
|
|
|
a4e9bd |
@@ -0,0 +1,152 @@
|
|
|
a4e9bd |
+"""
|
|
|
a4e9bd |
+Helper for looping over sequences, particular in templates.
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+Often in a loop in a template it's handy to know what's next up,
|
|
|
a4e9bd |
+previously up, if this is the first or last item in the sequence, etc.
|
|
|
a4e9bd |
+These can be awkward to manage in a normal Python loop, but using the
|
|
|
a4e9bd |
+looper you can get a better sense of the context. Use like::
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ >>> for loop, item in looper(['a', 'b', 'c']):
|
|
|
a4e9bd |
+ ... print loop.number, item
|
|
|
a4e9bd |
+ ... if not loop.last:
|
|
|
a4e9bd |
+ ... print '---'
|
|
|
a4e9bd |
+ 1 a
|
|
|
a4e9bd |
+ ---
|
|
|
a4e9bd |
+ 2 b
|
|
|
a4e9bd |
+ ---
|
|
|
a4e9bd |
+ 3 c
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+"""
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+__all__ = ['looper']
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class looper(object):
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+ Helper for looping (particularly in templates)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ Use this like::
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ for loop, item in looper(seq):
|
|
|
a4e9bd |
+ if loop.first:
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __init__(self, seq):
|
|
|
a4e9bd |
+ self.seq = seq
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __iter__(self):
|
|
|
a4e9bd |
+ return looper_iter(self.seq)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __repr__(self):
|
|
|
a4e9bd |
+ return '<%s for %r>' % (
|
|
|
a4e9bd |
+ self.__class__.__name__, self.seq)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class looper_iter(object):
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __init__(self, seq):
|
|
|
a4e9bd |
+ self.seq = list(seq)
|
|
|
a4e9bd |
+ self.pos = 0
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __iter__(self):
|
|
|
a4e9bd |
+ return self
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def next(self):
|
|
|
a4e9bd |
+ if self.pos >= len(self.seq):
|
|
|
a4e9bd |
+ raise StopIteration
|
|
|
a4e9bd |
+ result = loop_pos(self.seq, self.pos), self.seq[self.pos]
|
|
|
a4e9bd |
+ self.pos += 1
|
|
|
a4e9bd |
+ return result
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class loop_pos(object):
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __init__(self, seq, pos):
|
|
|
a4e9bd |
+ self.seq = seq
|
|
|
a4e9bd |
+ self.pos = pos
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __repr__(self):
|
|
|
a4e9bd |
+ return '<loop pos=%r at %r>' % (
|
|
|
a4e9bd |
+ self.seq[pos], pos)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def index(self):
|
|
|
a4e9bd |
+ return self.pos
|
|
|
a4e9bd |
+ index = property(index)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def number(self):
|
|
|
a4e9bd |
+ return self.pos + 1
|
|
|
a4e9bd |
+ number = property(number)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def item(self):
|
|
|
a4e9bd |
+ return self.seq[self.pos]
|
|
|
a4e9bd |
+ item = property(item)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def next(self):
|
|
|
a4e9bd |
+ try:
|
|
|
a4e9bd |
+ return self.seq[self.pos+1]
|
|
|
a4e9bd |
+ except IndexError:
|
|
|
a4e9bd |
+ return None
|
|
|
a4e9bd |
+ next = property(next)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def previous(self):
|
|
|
a4e9bd |
+ if self.pos == 0:
|
|
|
a4e9bd |
+ return None
|
|
|
a4e9bd |
+ return self.seq[self.pos-1]
|
|
|
a4e9bd |
+ previous = property(previous)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def odd(self):
|
|
|
a4e9bd |
+ return not self.pos % 2
|
|
|
a4e9bd |
+ odd = property(odd)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def even(self):
|
|
|
a4e9bd |
+ return self.pos % 2
|
|
|
a4e9bd |
+ even = property(even)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def first(self):
|
|
|
a4e9bd |
+ return self.pos == 0
|
|
|
a4e9bd |
+ first = property(first)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def last(self):
|
|
|
a4e9bd |
+ return self.pos == len(self.seq)-1
|
|
|
a4e9bd |
+ last = property(last)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def length(self):
|
|
|
a4e9bd |
+ return len(self.seq)
|
|
|
a4e9bd |
+ length = property(length)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def first_group(self, getter=None):
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+ Returns true if this item is the start of a new group,
|
|
|
a4e9bd |
+ where groups mean that some attribute has changed. The getter
|
|
|
a4e9bd |
+ can be None (the item itself changes), an attribute name like
|
|
|
a4e9bd |
+ ``'.attr'``, a function, or a dict key or list index.
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+ if self.first:
|
|
|
a4e9bd |
+ return True
|
|
|
a4e9bd |
+ return self._compare_group(self.item, self.previous, getter)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def last_group(self, getter=None):
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+ Returns true if this item is the end of a new group,
|
|
|
a4e9bd |
+ where groups mean that some attribute has changed. The getter
|
|
|
a4e9bd |
+ can be None (the item itself changes), an attribute name like
|
|
|
a4e9bd |
+ ``'.attr'``, a function, or a dict key or list index.
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+ if self.last:
|
|
|
a4e9bd |
+ return True
|
|
|
a4e9bd |
+ return self._compare_group(self.item, self.next, getter)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _compare_group(self, item, other, getter):
|
|
|
a4e9bd |
+ if getter is None:
|
|
|
a4e9bd |
+ return item != other
|
|
|
a4e9bd |
+ elif (isinstance(getter, basestring)
|
|
|
a4e9bd |
+ and getter.startswith('.')):
|
|
|
a4e9bd |
+ getter = getter[1:]
|
|
|
a4e9bd |
+ if getter.endswith('()'):
|
|
|
a4e9bd |
+ getter = getter[:-2]
|
|
|
a4e9bd |
+ return getattr(item, getter)() != getattr(other, getter)()
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ return getattr(item, getter) != getattr(other, getter)
|
|
|
a4e9bd |
+ elif callable(getter):
|
|
|
a4e9bd |
+ return getter(item) != getter(other)
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ return item[getter] != other[getter]
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
Index: Paste-1.7.4/paste/util/looper.py
|
|
|
a4e9bd |
===================================================================
|
|
|
a4e9bd |
--- Paste-1.7.4.orig/paste/util/looper.py
|
|
|
a4e9bd |
+++ /dev/null
|
|
|
a4e9bd |
@@ -1,152 +0,0 @@
|
|
|
a4e9bd |
-"""
|
|
|
a4e9bd |
-Helper for looping over sequences, particular in templates.
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-Often in a loop in a template it's handy to know what's next up,
|
|
|
a4e9bd |
-previously up, if this is the first or last item in the sequence, etc.
|
|
|
a4e9bd |
-These can be awkward to manage in a normal Python loop, but using the
|
|
|
a4e9bd |
-looper you can get a better sense of the context. Use like::
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- >>> for loop, item in looper(['a', 'b', 'c']):
|
|
|
a4e9bd |
- ... print loop.number, item
|
|
|
a4e9bd |
- ... if not loop.last:
|
|
|
a4e9bd |
- ... print '---'
|
|
|
a4e9bd |
- 1 a
|
|
|
a4e9bd |
- ---
|
|
|
a4e9bd |
- 2 b
|
|
|
a4e9bd |
- ---
|
|
|
a4e9bd |
- 3 c
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-"""
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-__all__ = ['looper']
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class looper(object):
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
- Helper for looping (particularly in templates)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- Use this like::
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- for loop, item in looper(seq):
|
|
|
a4e9bd |
- if loop.first:
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __init__(self, seq):
|
|
|
a4e9bd |
- self.seq = seq
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __iter__(self):
|
|
|
a4e9bd |
- return looper_iter(self.seq)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __repr__(self):
|
|
|
a4e9bd |
- return '<%s for %r>' % (
|
|
|
a4e9bd |
- self.__class__.__name__, self.seq)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class looper_iter(object):
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __init__(self, seq):
|
|
|
a4e9bd |
- self.seq = list(seq)
|
|
|
a4e9bd |
- self.pos = 0
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __iter__(self):
|
|
|
a4e9bd |
- return self
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def next(self):
|
|
|
a4e9bd |
- if self.pos >= len(self.seq):
|
|
|
a4e9bd |
- raise StopIteration
|
|
|
a4e9bd |
- result = loop_pos(self.seq, self.pos), self.seq[self.pos]
|
|
|
a4e9bd |
- self.pos += 1
|
|
|
a4e9bd |
- return result
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class loop_pos(object):
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __init__(self, seq, pos):
|
|
|
a4e9bd |
- self.seq = seq
|
|
|
a4e9bd |
- self.pos = pos
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __repr__(self):
|
|
|
a4e9bd |
- return '<loop pos=%r at %r>' % (
|
|
|
a4e9bd |
- self.seq[pos], pos)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def index(self):
|
|
|
a4e9bd |
- return self.pos
|
|
|
a4e9bd |
- index = property(index)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def number(self):
|
|
|
a4e9bd |
- return self.pos + 1
|
|
|
a4e9bd |
- number = property(number)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def item(self):
|
|
|
a4e9bd |
- return self.seq[self.pos]
|
|
|
a4e9bd |
- item = property(item)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def next(self):
|
|
|
a4e9bd |
- try:
|
|
|
a4e9bd |
- return self.seq[self.pos+1]
|
|
|
a4e9bd |
- except IndexError:
|
|
|
a4e9bd |
- return None
|
|
|
a4e9bd |
- next = property(next)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def previous(self):
|
|
|
a4e9bd |
- if self.pos == 0:
|
|
|
a4e9bd |
- return None
|
|
|
a4e9bd |
- return self.seq[self.pos-1]
|
|
|
a4e9bd |
- previous = property(previous)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def odd(self):
|
|
|
a4e9bd |
- return not self.pos % 2
|
|
|
a4e9bd |
- odd = property(odd)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def even(self):
|
|
|
a4e9bd |
- return self.pos % 2
|
|
|
a4e9bd |
- even = property(even)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def first(self):
|
|
|
a4e9bd |
- return self.pos == 0
|
|
|
a4e9bd |
- first = property(first)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def last(self):
|
|
|
a4e9bd |
- return self.pos == len(self.seq)-1
|
|
|
a4e9bd |
- last = property(last)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def length(self):
|
|
|
a4e9bd |
- return len(self.seq)
|
|
|
a4e9bd |
- length = property(length)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def first_group(self, getter=None):
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
- Returns true if this item is the start of a new group,
|
|
|
a4e9bd |
- where groups mean that some attribute has changed. The getter
|
|
|
a4e9bd |
- can be None (the item itself changes), an attribute name like
|
|
|
a4e9bd |
- ``'.attr'``, a function, or a dict key or list index.
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
- if self.first:
|
|
|
a4e9bd |
- return True
|
|
|
a4e9bd |
- return self._compare_group(self.item, self.previous, getter)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def last_group(self, getter=None):
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
- Returns true if this item is the end of a new group,
|
|
|
a4e9bd |
- where groups mean that some attribute has changed. The getter
|
|
|
a4e9bd |
- can be None (the item itself changes), an attribute name like
|
|
|
a4e9bd |
- ``'.attr'``, a function, or a dict key or list index.
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
- if self.last:
|
|
|
a4e9bd |
- return True
|
|
|
a4e9bd |
- return self._compare_group(self.item, self.next, getter)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _compare_group(self, item, other, getter):
|
|
|
a4e9bd |
- if getter is None:
|
|
|
a4e9bd |
- return item != other
|
|
|
a4e9bd |
- elif (isinstance(getter, basestring)
|
|
|
a4e9bd |
- and getter.startswith('.')):
|
|
|
a4e9bd |
- getter = getter[1:]
|
|
|
a4e9bd |
- if getter.endswith('()'):
|
|
|
a4e9bd |
- getter = getter[:-2]
|
|
|
a4e9bd |
- return getattr(item, getter)() != getattr(other, getter)()
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- return getattr(item, getter) != getattr(other, getter)
|
|
|
a4e9bd |
- elif callable(getter):
|
|
|
a4e9bd |
- return getter(item) != getter(other)
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- return item[getter] != other[getter]
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
Index: Paste-1.7.4/paste/util/template/__init__.py
|
|
|
a4e9bd |
===================================================================
|
|
|
a4e9bd |
--- /dev/null
|
|
|
a4e9bd |
+++ Paste-1.7.4/paste/util/template/__init__.py
|
|
|
a4e9bd |
@@ -0,0 +1,6 @@
|
|
|
a4e9bd |
+try:
|
|
|
a4e9bd |
+ from tempita import *
|
|
|
a4e9bd |
+ from tempita import paste_script_template_renderer
|
|
|
a4e9bd |
+except ImportError:
|
|
|
a4e9bd |
+ from _template import *
|
|
|
a4e9bd |
+ from _template import paste_script_template_renderer
|
|
|
a4e9bd |
Index: Paste-1.7.4/paste/util/template/_template.py
|
|
|
a4e9bd |
===================================================================
|
|
|
a4e9bd |
--- /dev/null
|
|
|
a4e9bd |
+++ Paste-1.7.4/paste/util/template/_template.py
|
|
|
a4e9bd |
@@ -0,0 +1,758 @@
|
|
|
a4e9bd |
+"""
|
|
|
a4e9bd |
+A small templating language
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+This implements a small templating language for use internally in
|
|
|
a4e9bd |
+Paste and Paste Script. This language implements if/elif/else,
|
|
|
a4e9bd |
+for/continue/break, expressions, and blocks of Python code. The
|
|
|
a4e9bd |
+syntax is::
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ {{any expression (function calls etc)}}
|
|
|
a4e9bd |
+ {{any expression | filter}}
|
|
|
a4e9bd |
+ {{for x in y}}...{{endfor}}
|
|
|
a4e9bd |
+ {{if x}}x{{elif y}}y{{else}}z{{endif}}
|
|
|
a4e9bd |
+ {{py:x=1}}
|
|
|
a4e9bd |
+ {{py:
|
|
|
a4e9bd |
+ def foo(bar):
|
|
|
a4e9bd |
+ return 'baz'
|
|
|
a4e9bd |
+ }}
|
|
|
a4e9bd |
+ {{default var = default_value}}
|
|
|
a4e9bd |
+ {{# comment}}
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+You use this with the ``Template`` class or the ``sub`` shortcut.
|
|
|
a4e9bd |
+The ``Template`` class takes the template string and the name of
|
|
|
a4e9bd |
+the template (for errors) and a default namespace. Then (like
|
|
|
a4e9bd |
+``string.Template``) you can call the ``tmpl.substitute(**kw)``
|
|
|
a4e9bd |
+method to make a substitution (or ``tmpl.substitute(a_dict)``).
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+``sub(content, **kw)`` substitutes the template immediately. You
|
|
|
a4e9bd |
+can use ``__name='tmpl.html'`` to set the name of the template.
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+If there are syntax errors ``TemplateError`` will be raised.
|
|
|
a4e9bd |
+"""
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+import re
|
|
|
a4e9bd |
+import sys
|
|
|
a4e9bd |
+import cgi
|
|
|
a4e9bd |
+import urllib
|
|
|
a4e9bd |
+from paste.util.looper import looper
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+__all__ = ['TemplateError', 'Template', 'sub', 'HTMLTemplate',
|
|
|
a4e9bd |
+ 'sub_html', 'html', 'bunch']
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+token_re = re.compile(r'\{\{|\}\}')
|
|
|
a4e9bd |
+in_re = re.compile(r'\s+in\s+')
|
|
|
a4e9bd |
+var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class TemplateError(Exception):
|
|
|
a4e9bd |
+ """Exception raised while parsing a template
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __init__(self, message, position, name=None):
|
|
|
a4e9bd |
+ self.message = message
|
|
|
a4e9bd |
+ self.position = position
|
|
|
a4e9bd |
+ self.name = name
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __str__(self):
|
|
|
a4e9bd |
+ msg = '%s at line %s column %s' % (
|
|
|
a4e9bd |
+ self.message, self.position[0], self.position[1])
|
|
|
a4e9bd |
+ if self.name:
|
|
|
a4e9bd |
+ msg += ' in %s' % self.name
|
|
|
a4e9bd |
+ return msg
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class _TemplateContinue(Exception):
|
|
|
a4e9bd |
+ pass
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class _TemplateBreak(Exception):
|
|
|
a4e9bd |
+ pass
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class Template(object):
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ default_namespace = {
|
|
|
a4e9bd |
+ 'start_braces': '{{',
|
|
|
a4e9bd |
+ 'end_braces': '}}',
|
|
|
a4e9bd |
+ 'looper': looper,
|
|
|
a4e9bd |
+ }
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ default_encoding = 'utf8'
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __init__(self, content, name=None, namespace=None):
|
|
|
a4e9bd |
+ self.content = content
|
|
|
a4e9bd |
+ self._unicode = isinstance(content, unicode)
|
|
|
a4e9bd |
+ self.name = name
|
|
|
a4e9bd |
+ self._parsed = parse(content, name=name)
|
|
|
a4e9bd |
+ if namespace is None:
|
|
|
a4e9bd |
+ namespace = {}
|
|
|
a4e9bd |
+ self.namespace = namespace
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def from_filename(cls, filename, namespace=None, encoding=None):
|
|
|
a4e9bd |
+ f = open(filename, 'rb')
|
|
|
a4e9bd |
+ c = f.read()
|
|
|
a4e9bd |
+ f.close()
|
|
|
a4e9bd |
+ if encoding:
|
|
|
a4e9bd |
+ c = c.decode(encoding)
|
|
|
a4e9bd |
+ return cls(content=c, name=filename, namespace=namespace)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ from_filename = classmethod(from_filename)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __repr__(self):
|
|
|
a4e9bd |
+ return '<%s %s name=%r>' % (
|
|
|
a4e9bd |
+ self.__class__.__name__,
|
|
|
a4e9bd |
+ hex(id(self))[2:], self.name)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def substitute(self, *args, **kw):
|
|
|
a4e9bd |
+ if args:
|
|
|
a4e9bd |
+ if kw:
|
|
|
a4e9bd |
+ raise TypeError(
|
|
|
a4e9bd |
+ "You can only give positional *or* keyword arguments")
|
|
|
a4e9bd |
+ if len(args) > 1:
|
|
|
a4e9bd |
+ raise TypeError(
|
|
|
a4e9bd |
+ "You can only give on positional argument")
|
|
|
a4e9bd |
+ kw = args[0]
|
|
|
a4e9bd |
+ ns = self.default_namespace.copy()
|
|
|
a4e9bd |
+ ns.update(self.namespace)
|
|
|
a4e9bd |
+ ns.update(kw)
|
|
|
a4e9bd |
+ result = self._interpret(ns)
|
|
|
a4e9bd |
+ return result
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _interpret(self, ns):
|
|
|
a4e9bd |
+ __traceback_hide__ = True
|
|
|
a4e9bd |
+ parts = []
|
|
|
a4e9bd |
+ self._interpret_codes(self._parsed, ns, out=parts)
|
|
|
a4e9bd |
+ return ''.join(parts)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _interpret_codes(self, codes, ns, out):
|
|
|
a4e9bd |
+ __traceback_hide__ = True
|
|
|
a4e9bd |
+ for item in codes:
|
|
|
a4e9bd |
+ if isinstance(item, basestring):
|
|
|
a4e9bd |
+ out.append(item)
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ self._interpret_code(item, ns, out)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _interpret_code(self, code, ns, out):
|
|
|
a4e9bd |
+ __traceback_hide__ = True
|
|
|
a4e9bd |
+ name, pos = code[0], code[1]
|
|
|
a4e9bd |
+ if name == 'py':
|
|
|
a4e9bd |
+ self._exec(code[2], ns, pos)
|
|
|
a4e9bd |
+ elif name == 'continue':
|
|
|
a4e9bd |
+ raise _TemplateContinue()
|
|
|
a4e9bd |
+ elif name == 'break':
|
|
|
a4e9bd |
+ raise _TemplateBreak()
|
|
|
a4e9bd |
+ elif name == 'for':
|
|
|
a4e9bd |
+ vars, expr, content = code[2], code[3], code[4]
|
|
|
a4e9bd |
+ expr = self._eval(expr, ns, pos)
|
|
|
a4e9bd |
+ self._interpret_for(vars, expr, content, ns, out)
|
|
|
a4e9bd |
+ elif name == 'cond':
|
|
|
a4e9bd |
+ parts = code[2:]
|
|
|
a4e9bd |
+ self._interpret_if(parts, ns, out)
|
|
|
a4e9bd |
+ elif name == 'expr':
|
|
|
a4e9bd |
+ parts = code[2].split('|')
|
|
|
a4e9bd |
+ base = self._eval(parts[0], ns, pos)
|
|
|
a4e9bd |
+ for part in parts[1:]:
|
|
|
a4e9bd |
+ func = self._eval(part, ns, pos)
|
|
|
a4e9bd |
+ base = func(base)
|
|
|
a4e9bd |
+ out.append(self._repr(base, pos))
|
|
|
a4e9bd |
+ elif name == 'default':
|
|
|
a4e9bd |
+ var, expr = code[2], code[3]
|
|
|
a4e9bd |
+ if var not in ns:
|
|
|
a4e9bd |
+ result = self._eval(expr, ns, pos)
|
|
|
a4e9bd |
+ ns[var] = result
|
|
|
a4e9bd |
+ elif name == 'comment':
|
|
|
a4e9bd |
+ return
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ assert 0, "Unknown code: %r" % name
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _interpret_for(self, vars, expr, content, ns, out):
|
|
|
a4e9bd |
+ __traceback_hide__ = True
|
|
|
a4e9bd |
+ for item in expr:
|
|
|
a4e9bd |
+ if len(vars) == 1:
|
|
|
a4e9bd |
+ ns[vars[0]] = item
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ if len(vars) != len(item):
|
|
|
a4e9bd |
+ raise ValueError(
|
|
|
a4e9bd |
+ 'Need %i items to unpack (got %i items)'
|
|
|
a4e9bd |
+ % (len(vars), len(item)))
|
|
|
a4e9bd |
+ for name, value in zip(vars, item):
|
|
|
a4e9bd |
+ ns[name] = value
|
|
|
a4e9bd |
+ try:
|
|
|
a4e9bd |
+ self._interpret_codes(content, ns, out)
|
|
|
a4e9bd |
+ except _TemplateContinue:
|
|
|
a4e9bd |
+ continue
|
|
|
a4e9bd |
+ except _TemplateBreak:
|
|
|
a4e9bd |
+ break
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _interpret_if(self, parts, ns, out):
|
|
|
a4e9bd |
+ __traceback_hide__ = True
|
|
|
a4e9bd |
+ # @@: if/else/else gets through
|
|
|
a4e9bd |
+ for part in parts:
|
|
|
a4e9bd |
+ assert not isinstance(part, basestring)
|
|
|
a4e9bd |
+ name, pos = part[0], part[1]
|
|
|
a4e9bd |
+ if name == 'else':
|
|
|
a4e9bd |
+ result = True
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ result = self._eval(part[2], ns, pos)
|
|
|
a4e9bd |
+ if result:
|
|
|
a4e9bd |
+ self._interpret_codes(part[3], ns, out)
|
|
|
a4e9bd |
+ break
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _eval(self, code, ns, pos):
|
|
|
a4e9bd |
+ __traceback_hide__ = True
|
|
|
a4e9bd |
+ try:
|
|
|
a4e9bd |
+ value = eval(code, ns)
|
|
|
a4e9bd |
+ return value
|
|
|
a4e9bd |
+ except:
|
|
|
a4e9bd |
+ exc_info = sys.exc_info()
|
|
|
a4e9bd |
+ e = exc_info[1]
|
|
|
a4e9bd |
+ if getattr(e, 'args'):
|
|
|
a4e9bd |
+ arg0 = e.args[0]
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ arg0 = str(e)
|
|
|
a4e9bd |
+ e.args = (self._add_line_info(arg0, pos),)
|
|
|
a4e9bd |
+ raise exc_info[0], e, exc_info[2]
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _exec(self, code, ns, pos):
|
|
|
a4e9bd |
+ __traceback_hide__ = True
|
|
|
a4e9bd |
+ try:
|
|
|
a4e9bd |
+ exec code in ns
|
|
|
a4e9bd |
+ except:
|
|
|
a4e9bd |
+ exc_info = sys.exc_info()
|
|
|
a4e9bd |
+ e = exc_info[1]
|
|
|
a4e9bd |
+ e.args = (self._add_line_info(e.args[0], pos),)
|
|
|
a4e9bd |
+ raise exc_info[0], e, exc_info[2]
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _repr(self, value, pos):
|
|
|
a4e9bd |
+ __traceback_hide__ = True
|
|
|
a4e9bd |
+ try:
|
|
|
a4e9bd |
+ if value is None:
|
|
|
a4e9bd |
+ return ''
|
|
|
a4e9bd |
+ if self._unicode:
|
|
|
a4e9bd |
+ try:
|
|
|
a4e9bd |
+ value = unicode(value)
|
|
|
a4e9bd |
+ except UnicodeDecodeError:
|
|
|
a4e9bd |
+ value = str(value)
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ value = str(value)
|
|
|
a4e9bd |
+ except:
|
|
|
a4e9bd |
+ exc_info = sys.exc_info()
|
|
|
a4e9bd |
+ e = exc_info[1]
|
|
|
a4e9bd |
+ e.args = (self._add_line_info(e.args[0], pos),)
|
|
|
a4e9bd |
+ raise exc_info[0], e, exc_info[2]
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ if self._unicode and isinstance(value, str):
|
|
|
a4e9bd |
+ if not self.decode_encoding:
|
|
|
a4e9bd |
+ raise UnicodeDecodeError(
|
|
|
a4e9bd |
+ 'Cannot decode str value %r into unicode '
|
|
|
a4e9bd |
+ '(no default_encoding provided)' % value)
|
|
|
a4e9bd |
+ value = value.decode(self.default_encoding)
|
|
|
a4e9bd |
+ elif not self._unicode and isinstance(value, unicode):
|
|
|
a4e9bd |
+ if not self.decode_encoding:
|
|
|
a4e9bd |
+ raise UnicodeEncodeError(
|
|
|
a4e9bd |
+ 'Cannot encode unicode value %r into str '
|
|
|
a4e9bd |
+ '(no default_encoding provided)' % value)
|
|
|
a4e9bd |
+ value = value.encode(self.default_encoding)
|
|
|
a4e9bd |
+ return value
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _add_line_info(self, msg, pos):
|
|
|
a4e9bd |
+ msg = "%s at line %s column %s" % (
|
|
|
a4e9bd |
+ msg, pos[0], pos[1])
|
|
|
a4e9bd |
+ if self.name:
|
|
|
a4e9bd |
+ msg += " in file %s" % self.name
|
|
|
a4e9bd |
+ return msg
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def sub(content, **kw):
|
|
|
a4e9bd |
+ name = kw.get('__name')
|
|
|
a4e9bd |
+ tmpl = Template(content, name=name)
|
|
|
a4e9bd |
+ return tmpl.substitute(kw)
|
|
|
a4e9bd |
+ return result
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def paste_script_template_renderer(content, vars, filename=None):
|
|
|
a4e9bd |
+ tmpl = Template(content, name=filename)
|
|
|
a4e9bd |
+ return tmpl.substitute(vars)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class bunch(dict):
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __init__(self, **kw):
|
|
|
a4e9bd |
+ for name, value in kw.items():
|
|
|
a4e9bd |
+ setattr(self, name, value)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __setattr__(self, name, value):
|
|
|
a4e9bd |
+ self[name] = value
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __getattr__(self, name):
|
|
|
a4e9bd |
+ try:
|
|
|
a4e9bd |
+ return self[name]
|
|
|
a4e9bd |
+ except KeyError:
|
|
|
a4e9bd |
+ raise AttributeError(name)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __getitem__(self, key):
|
|
|
a4e9bd |
+ if 'default' in self:
|
|
|
a4e9bd |
+ try:
|
|
|
a4e9bd |
+ return dict.__getitem__(self, key)
|
|
|
a4e9bd |
+ except KeyError:
|
|
|
a4e9bd |
+ return dict.__getitem__(self, 'default')
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ return dict.__getitem__(self, key)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def __repr__(self):
|
|
|
a4e9bd |
+ items = [
|
|
|
a4e9bd |
+ (k, v) for k, v in self.items()]
|
|
|
a4e9bd |
+ items.sort()
|
|
|
a4e9bd |
+ return '<%s %s>' % (
|
|
|
a4e9bd |
+ self.__class__.__name__,
|
|
|
a4e9bd |
+ ' '.join(['%s=%r' % (k, v) for k, v in items]))
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+############################################################
|
|
|
a4e9bd |
+## HTML Templating
|
|
|
a4e9bd |
+############################################################
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class html(object):
|
|
|
a4e9bd |
+ def __init__(self, value):
|
|
|
a4e9bd |
+ self.value = value
|
|
|
a4e9bd |
+ def __str__(self):
|
|
|
a4e9bd |
+ return self.value
|
|
|
a4e9bd |
+ def __repr__(self):
|
|
|
a4e9bd |
+ return '<%s %r>' % (
|
|
|
a4e9bd |
+ self.__class__.__name__, self.value)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def html_quote(value):
|
|
|
a4e9bd |
+ if value is None:
|
|
|
a4e9bd |
+ return ''
|
|
|
a4e9bd |
+ if not isinstance(value, basestring):
|
|
|
a4e9bd |
+ if hasattr(value, '__unicode__'):
|
|
|
a4e9bd |
+ value = unicode(value)
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ value = str(value)
|
|
|
a4e9bd |
+ value = cgi.escape(value, 1)
|
|
|
a4e9bd |
+ if isinstance(value, unicode):
|
|
|
a4e9bd |
+ value = value.encode('ascii', 'xmlcharrefreplace')
|
|
|
a4e9bd |
+ return value
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def url(v):
|
|
|
a4e9bd |
+ if not isinstance(v, basestring):
|
|
|
a4e9bd |
+ if hasattr(v, '__unicode__'):
|
|
|
a4e9bd |
+ v = unicode(v)
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ v = str(v)
|
|
|
a4e9bd |
+ if isinstance(v, unicode):
|
|
|
a4e9bd |
+ v = v.encode('utf8')
|
|
|
a4e9bd |
+ return urllib.quote(v)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def attr(**kw):
|
|
|
a4e9bd |
+ kw = kw.items()
|
|
|
a4e9bd |
+ kw.sort()
|
|
|
a4e9bd |
+ parts = []
|
|
|
a4e9bd |
+ for name, value in kw:
|
|
|
a4e9bd |
+ if value is None:
|
|
|
a4e9bd |
+ continue
|
|
|
a4e9bd |
+ if name.endswith('_'):
|
|
|
a4e9bd |
+ name = name[:-1]
|
|
|
a4e9bd |
+ parts.append('%s="%s"' % (html_quote(name), html_quote(value)))
|
|
|
a4e9bd |
+ return html(' '.join(parts))
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+class HTMLTemplate(Template):
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ default_namespace = Template.default_namespace.copy()
|
|
|
a4e9bd |
+ default_namespace.update(dict(
|
|
|
a4e9bd |
+ html=html,
|
|
|
a4e9bd |
+ attr=attr,
|
|
|
a4e9bd |
+ url=url,
|
|
|
a4e9bd |
+ ))
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ def _repr(self, value, pos):
|
|
|
a4e9bd |
+ plain = Template._repr(self, value, pos)
|
|
|
a4e9bd |
+ if isinstance(value, html):
|
|
|
a4e9bd |
+ return plain
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ return html_quote(plain)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def sub_html(content, **kw):
|
|
|
a4e9bd |
+ name = kw.get('__name')
|
|
|
a4e9bd |
+ tmpl = HTMLTemplate(content, name=name)
|
|
|
a4e9bd |
+ return tmpl.substitute(kw)
|
|
|
a4e9bd |
+ return result
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+############################################################
|
|
|
a4e9bd |
+## Lexing and Parsing
|
|
|
a4e9bd |
+############################################################
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def lex(s, name=None, trim_whitespace=True):
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+ Lex a string into chunks:
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ >>> lex('hey')
|
|
|
a4e9bd |
+ ['hey']
|
|
|
a4e9bd |
+ >>> lex('hey {{you}}')
|
|
|
a4e9bd |
+ ['hey ', ('you', (1, 7))]
|
|
|
a4e9bd |
+ >>> lex('hey {{')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: No }} to finish last expression at line 1 column 7
|
|
|
a4e9bd |
+ >>> lex('hey }}')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: }} outside expression at line 1 column 7
|
|
|
a4e9bd |
+ >>> lex('hey {{ {{')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: {{ inside expression at line 1 column 10
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+ in_expr = False
|
|
|
a4e9bd |
+ chunks = []
|
|
|
a4e9bd |
+ last = 0
|
|
|
a4e9bd |
+ last_pos = (1, 1)
|
|
|
a4e9bd |
+ for match in token_re.finditer(s):
|
|
|
a4e9bd |
+ expr = match.group(0)
|
|
|
a4e9bd |
+ pos = find_position(s, match.end())
|
|
|
a4e9bd |
+ if expr == '{{' and in_expr:
|
|
|
a4e9bd |
+ raise TemplateError('{{ inside expression', position=pos,
|
|
|
a4e9bd |
+ name=name)
|
|
|
a4e9bd |
+ elif expr == '}}' and not in_expr:
|
|
|
a4e9bd |
+ raise TemplateError('}} outside expression', position=pos,
|
|
|
a4e9bd |
+ name=name)
|
|
|
a4e9bd |
+ if expr == '{{':
|
|
|
a4e9bd |
+ part = s[last:match.start()]
|
|
|
a4e9bd |
+ if part:
|
|
|
a4e9bd |
+ chunks.append(part)
|
|
|
a4e9bd |
+ in_expr = True
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ chunks.append((s[last:match.start()], last_pos))
|
|
|
a4e9bd |
+ in_expr = False
|
|
|
a4e9bd |
+ last = match.end()
|
|
|
a4e9bd |
+ last_pos = pos
|
|
|
a4e9bd |
+ if in_expr:
|
|
|
a4e9bd |
+ raise TemplateError('No }} to finish last expression',
|
|
|
a4e9bd |
+ name=name, position=last_pos)
|
|
|
a4e9bd |
+ part = s[last:]
|
|
|
a4e9bd |
+ if part:
|
|
|
a4e9bd |
+ chunks.append(part)
|
|
|
a4e9bd |
+ if trim_whitespace:
|
|
|
a4e9bd |
+ chunks = trim_lex(chunks)
|
|
|
a4e9bd |
+ return chunks
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+statement_re = re.compile(r'^(?:if |elif |else |for |py:)')
|
|
|
a4e9bd |
+single_statements = ['endif', 'endfor', 'continue', 'break']
|
|
|
a4e9bd |
+trail_whitespace_re = re.compile(r'\n[\t ]*$')
|
|
|
a4e9bd |
+lead_whitespace_re = re.compile(r'^[\t ]*\n')
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def trim_lex(tokens):
|
|
|
a4e9bd |
+ r"""
|
|
|
a4e9bd |
+ Takes a lexed set of tokens, and removes whitespace when there is
|
|
|
a4e9bd |
+ a directive on a line by itself:
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
|
|
|
a4e9bd |
+ >>> tokens
|
|
|
a4e9bd |
+ [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
|
|
|
a4e9bd |
+ >>> trim_lex(tokens)
|
|
|
a4e9bd |
+ [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+ for i in range(len(tokens)):
|
|
|
a4e9bd |
+ current = tokens[i]
|
|
|
a4e9bd |
+ if isinstance(tokens[i], basestring):
|
|
|
a4e9bd |
+ # we don't trim this
|
|
|
a4e9bd |
+ continue
|
|
|
a4e9bd |
+ item = current[0]
|
|
|
a4e9bd |
+ if not statement_re.search(item) and item not in single_statements:
|
|
|
a4e9bd |
+ continue
|
|
|
a4e9bd |
+ if not i:
|
|
|
a4e9bd |
+ prev = ''
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ prev = tokens[i-1]
|
|
|
a4e9bd |
+ if i+1 >= len(tokens):
|
|
|
a4e9bd |
+ next = ''
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ next = tokens[i+1]
|
|
|
a4e9bd |
+ if (not isinstance(next, basestring)
|
|
|
a4e9bd |
+ or not isinstance(prev, basestring)):
|
|
|
a4e9bd |
+ continue
|
|
|
a4e9bd |
+ if ((not prev or trail_whitespace_re.search(prev))
|
|
|
a4e9bd |
+ and (not next or lead_whitespace_re.search(next))):
|
|
|
a4e9bd |
+ if prev:
|
|
|
a4e9bd |
+ m = trail_whitespace_re.search(prev)
|
|
|
a4e9bd |
+ # +1 to leave the leading \n on:
|
|
|
a4e9bd |
+ prev = prev[:m.start()+1]
|
|
|
a4e9bd |
+ tokens[i-1] = prev
|
|
|
a4e9bd |
+ if next:
|
|
|
a4e9bd |
+ m = lead_whitespace_re.search(next)
|
|
|
a4e9bd |
+ next = next[m.end():]
|
|
|
a4e9bd |
+ tokens[i+1] = next
|
|
|
a4e9bd |
+ return tokens
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def find_position(string, index):
|
|
|
a4e9bd |
+ """Given a string and index, return (line, column)"""
|
|
|
a4e9bd |
+ leading = string[:index].splitlines()
|
|
|
a4e9bd |
+ return (len(leading), len(leading[-1])+1)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def parse(s, name=None):
|
|
|
a4e9bd |
+ r"""
|
|
|
a4e9bd |
+ Parses a string into a kind of AST
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ >>> parse('{{x}}')
|
|
|
a4e9bd |
+ [('expr', (1, 3), 'x')]
|
|
|
a4e9bd |
+ >>> parse('foo')
|
|
|
a4e9bd |
+ ['foo']
|
|
|
a4e9bd |
+ >>> parse('{{if x}}test{{endif}}')
|
|
|
a4e9bd |
+ [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
|
|
|
a4e9bd |
+ >>> parse('series->{{for x in y}}x={{x}}{{endfor}}')
|
|
|
a4e9bd |
+ ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
|
|
|
a4e9bd |
+ >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
|
|
|
a4e9bd |
+ [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
|
|
|
a4e9bd |
+ >>> parse('{{py:x=1}}')
|
|
|
a4e9bd |
+ [('py', (1, 3), 'x=1')]
|
|
|
a4e9bd |
+ >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}')
|
|
|
a4e9bd |
+ [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ Some exceptions::
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+ >>> parse('{{continue}}')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: continue outside of for loop at line 1 column 3
|
|
|
a4e9bd |
+ >>> parse('{{if x}}foo')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: No {{endif}} at line 1 column 3
|
|
|
a4e9bd |
+ >>> parse('{{else}}')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: else outside of an if block at line 1 column 3
|
|
|
a4e9bd |
+ >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: Unexpected endif at line 1 column 25
|
|
|
a4e9bd |
+ >>> parse('{{if}}{{endif}}')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: if with no expression at line 1 column 3
|
|
|
a4e9bd |
+ >>> parse('{{for x y}}{{endfor}}')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
|
|
|
a4e9bd |
+ >>> parse('{{py:x=1\ny=2}}')
|
|
|
a4e9bd |
+ Traceback (most recent call last):
|
|
|
a4e9bd |
+ ...
|
|
|
a4e9bd |
+ TemplateError: Multi-line py blocks must start with a newline at line 1 column 3
|
|
|
a4e9bd |
+ """
|
|
|
a4e9bd |
+ tokens = lex(s, name=name)
|
|
|
a4e9bd |
+ result = []
|
|
|
a4e9bd |
+ while tokens:
|
|
|
a4e9bd |
+ next, tokens = parse_expr(tokens, name)
|
|
|
a4e9bd |
+ result.append(next)
|
|
|
a4e9bd |
+ return result
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def parse_expr(tokens, name, context=()):
|
|
|
a4e9bd |
+ if isinstance(tokens[0], basestring):
|
|
|
a4e9bd |
+ return tokens[0], tokens[1:]
|
|
|
a4e9bd |
+ expr, pos = tokens[0]
|
|
|
a4e9bd |
+ expr = expr.strip()
|
|
|
a4e9bd |
+ if expr.startswith('py:'):
|
|
|
a4e9bd |
+ expr = expr[3:].lstrip(' \t')
|
|
|
a4e9bd |
+ if expr.startswith('\n'):
|
|
|
a4e9bd |
+ expr = expr[1:]
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ if '\n' in expr:
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ 'Multi-line py blocks must start with a newline',
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ return ('py', pos, expr), tokens[1:]
|
|
|
a4e9bd |
+ elif expr in ('continue', 'break'):
|
|
|
a4e9bd |
+ if 'for' not in context:
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ 'continue outside of for loop',
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ return (expr, pos), tokens[1:]
|
|
|
a4e9bd |
+ elif expr.startswith('if '):
|
|
|
a4e9bd |
+ return parse_cond(tokens, name, context)
|
|
|
a4e9bd |
+ elif (expr.startswith('elif ')
|
|
|
a4e9bd |
+ or expr == 'else'):
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ '%s outside of an if block' % expr.split()[0],
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ elif expr in ('if', 'elif', 'for'):
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ '%s with no expression' % expr,
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ elif expr in ('endif', 'endfor'):
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ 'Unexpected %s' % expr,
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ elif expr.startswith('for '):
|
|
|
a4e9bd |
+ return parse_for(tokens, name, context)
|
|
|
a4e9bd |
+ elif expr.startswith('default '):
|
|
|
a4e9bd |
+ return parse_default(tokens, name, context)
|
|
|
a4e9bd |
+ elif expr.startswith('#'):
|
|
|
a4e9bd |
+ return ('comment', pos, tokens[0][0]), tokens[1:]
|
|
|
a4e9bd |
+ return ('expr', pos, tokens[0][0]), tokens[1:]
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def parse_cond(tokens, name, context):
|
|
|
a4e9bd |
+ start = tokens[0][1]
|
|
|
a4e9bd |
+ pieces = []
|
|
|
a4e9bd |
+ context = context + ('if',)
|
|
|
a4e9bd |
+ while 1:
|
|
|
a4e9bd |
+ if not tokens:
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ 'Missing {{endif}}',
|
|
|
a4e9bd |
+ position=start, name=name)
|
|
|
a4e9bd |
+ if (isinstance(tokens[0], tuple)
|
|
|
a4e9bd |
+ and tokens[0][0] == 'endif'):
|
|
|
a4e9bd |
+ return ('cond', start) + tuple(pieces), tokens[1:]
|
|
|
a4e9bd |
+ next, tokens = parse_one_cond(tokens, name, context)
|
|
|
a4e9bd |
+ pieces.append(next)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def parse_one_cond(tokens, name, context):
|
|
|
a4e9bd |
+ (first, pos), tokens = tokens[0], tokens[1:]
|
|
|
a4e9bd |
+ content = []
|
|
|
a4e9bd |
+ if first.endswith(':'):
|
|
|
a4e9bd |
+ first = first[:-1]
|
|
|
a4e9bd |
+ if first.startswith('if '):
|
|
|
a4e9bd |
+ part = ('if', pos, first[3:].lstrip(), content)
|
|
|
a4e9bd |
+ elif first.startswith('elif '):
|
|
|
a4e9bd |
+ part = ('elif', pos, first[5:].lstrip(), content)
|
|
|
a4e9bd |
+ elif first == 'else':
|
|
|
a4e9bd |
+ part = ('else', pos, None, content)
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ assert 0, "Unexpected token %r at %s" % (first, pos)
|
|
|
a4e9bd |
+ while 1:
|
|
|
a4e9bd |
+ if not tokens:
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ 'No {{endif}}',
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ if (isinstance(tokens[0], tuple)
|
|
|
a4e9bd |
+ and (tokens[0][0] == 'endif'
|
|
|
a4e9bd |
+ or tokens[0][0].startswith('elif ')
|
|
|
a4e9bd |
+ or tokens[0][0] == 'else')):
|
|
|
a4e9bd |
+ return part, tokens
|
|
|
a4e9bd |
+ next, tokens = parse_expr(tokens, name, context)
|
|
|
a4e9bd |
+ content.append(next)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def parse_for(tokens, name, context):
|
|
|
a4e9bd |
+ first, pos = tokens[0]
|
|
|
a4e9bd |
+ tokens = tokens[1:]
|
|
|
a4e9bd |
+ context = ('for',) + context
|
|
|
a4e9bd |
+ content = []
|
|
|
a4e9bd |
+ assert first.startswith('for ')
|
|
|
a4e9bd |
+ if first.endswith(':'):
|
|
|
a4e9bd |
+ first = first[:-1]
|
|
|
a4e9bd |
+ first = first[3:].strip()
|
|
|
a4e9bd |
+ match = in_re.search(first)
|
|
|
a4e9bd |
+ if not match:
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ 'Bad for (no "in") in %r' % first,
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ vars = first[:match.start()]
|
|
|
a4e9bd |
+ if '(' in vars:
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ 'You cannot have () in the variable section of a for loop (%r)'
|
|
|
a4e9bd |
+ % vars, position=pos, name=name)
|
|
|
a4e9bd |
+ vars = tuple([
|
|
|
a4e9bd |
+ v.strip() for v in first[:match.start()].split(',')
|
|
|
a4e9bd |
+ if v.strip()])
|
|
|
a4e9bd |
+ expr = first[match.end():]
|
|
|
a4e9bd |
+ while 1:
|
|
|
a4e9bd |
+ if not tokens:
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ 'No {{endfor}}',
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ if (isinstance(tokens[0], tuple)
|
|
|
a4e9bd |
+ and tokens[0][0] == 'endfor'):
|
|
|
a4e9bd |
+ return ('for', pos, vars, expr, content), tokens[1:]
|
|
|
a4e9bd |
+ next, tokens = parse_expr(tokens, name, context)
|
|
|
a4e9bd |
+ content.append(next)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def parse_default(tokens, name, context):
|
|
|
a4e9bd |
+ first, pos = tokens[0]
|
|
|
a4e9bd |
+ assert first.startswith('default ')
|
|
|
a4e9bd |
+ first = first.split(None, 1)[1]
|
|
|
a4e9bd |
+ parts = first.split('=', 1)
|
|
|
a4e9bd |
+ if len(parts) == 1:
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ "Expression must be {{default var=value}}; no = found in %r" % first,
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ var = parts[0].strip()
|
|
|
a4e9bd |
+ if ',' in var:
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ "{{default x, y = ...}} is not supported",
|
|
|
a4e9bd |
+ position=pos, name=name)
|
|
|
a4e9bd |
+ if not var_re.search(var):
|
|
|
a4e9bd |
+ raise TemplateError(
|
|
|
a4e9bd |
+ "Not a valid variable name for {{default}}: %r"
|
|
|
a4e9bd |
+ % var, position=pos, name=name)
|
|
|
a4e9bd |
+ expr = parts[1].strip()
|
|
|
a4e9bd |
+ return ('default', pos, var, expr), tokens[1:]
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+_fill_command_usage = """\
|
|
|
a4e9bd |
+%prog [OPTIONS] TEMPLATE arg=value
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+Use py:arg=value to set a Python value; otherwise all values are
|
|
|
a4e9bd |
+strings.
|
|
|
a4e9bd |
+"""
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+def fill_command(args=None):
|
|
|
a4e9bd |
+ import sys, optparse, pkg_resources, os
|
|
|
a4e9bd |
+ if args is None:
|
|
|
a4e9bd |
+ args = sys.argv[1:]
|
|
|
a4e9bd |
+ dist = pkg_resources.get_distribution('Paste')
|
|
|
a4e9bd |
+ parser = optparse.OptionParser(
|
|
|
a4e9bd |
+ version=str(dist),
|
|
|
a4e9bd |
+ usage=_fill_command_usage)
|
|
|
a4e9bd |
+ parser.add_option(
|
|
|
a4e9bd |
+ '-o', '--output',
|
|
|
a4e9bd |
+ dest='output',
|
|
|
a4e9bd |
+ metavar="FILENAME",
|
|
|
a4e9bd |
+ help="File to write output to (default stdout)")
|
|
|
a4e9bd |
+ parser.add_option(
|
|
|
a4e9bd |
+ '--html',
|
|
|
a4e9bd |
+ dest='use_html',
|
|
|
a4e9bd |
+ action='store_true',
|
|
|
a4e9bd |
+ help="Use HTML style filling (including automatic HTML quoting)")
|
|
|
a4e9bd |
+ parser.add_option(
|
|
|
a4e9bd |
+ '--env',
|
|
|
a4e9bd |
+ dest='use_env',
|
|
|
a4e9bd |
+ action='store_true',
|
|
|
a4e9bd |
+ help="Put the environment in as top-level variables")
|
|
|
a4e9bd |
+ options, args = parser.parse_args(args)
|
|
|
a4e9bd |
+ if len(args) < 1:
|
|
|
a4e9bd |
+ print 'You must give a template filename'
|
|
|
a4e9bd |
+ print dir(parser)
|
|
|
a4e9bd |
+ assert 0
|
|
|
a4e9bd |
+ template_name = args[0]
|
|
|
a4e9bd |
+ args = args[1:]
|
|
|
a4e9bd |
+ vars = {}
|
|
|
a4e9bd |
+ if options.use_env:
|
|
|
a4e9bd |
+ vars.update(os.environ)
|
|
|
a4e9bd |
+ for value in args:
|
|
|
a4e9bd |
+ if '=' not in value:
|
|
|
a4e9bd |
+ print 'Bad argument: %r' % value
|
|
|
a4e9bd |
+ sys.exit(2)
|
|
|
a4e9bd |
+ name, value = value.split('=', 1)
|
|
|
a4e9bd |
+ if name.startswith('py:'):
|
|
|
a4e9bd |
+ name = name[:3]
|
|
|
a4e9bd |
+ value = eval(value)
|
|
|
a4e9bd |
+ vars[name] = value
|
|
|
a4e9bd |
+ if template_name == '-':
|
|
|
a4e9bd |
+ template_content = sys.stdin.read()
|
|
|
a4e9bd |
+ template_name = '<stdin>'
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ f = open(template_name, 'rb')
|
|
|
a4e9bd |
+ template_content = f.read()
|
|
|
a4e9bd |
+ f.close()
|
|
|
a4e9bd |
+ if options.use_html:
|
|
|
a4e9bd |
+ TemplateClass = HTMLTemplate
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ TemplateClass = Template
|
|
|
a4e9bd |
+ template = TemplateClass(template_content, name=template_name)
|
|
|
a4e9bd |
+ result = template.substitute(vars)
|
|
|
a4e9bd |
+ if options.output:
|
|
|
a4e9bd |
+ f = open(options.output, 'wb')
|
|
|
a4e9bd |
+ f.write(result)
|
|
|
a4e9bd |
+ f.close()
|
|
|
a4e9bd |
+ else:
|
|
|
a4e9bd |
+ sys.stdout.write(result)
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+if __name__ == '__main__':
|
|
|
a4e9bd |
+ from paste.util.template import fill_command
|
|
|
a4e9bd |
+ fill_command()
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
+
|
|
|
a4e9bd |
Index: Paste-1.7.4/paste/util/template.py
|
|
|
a4e9bd |
===================================================================
|
|
|
a4e9bd |
--- Paste-1.7.4.orig/paste/util/template.py
|
|
|
a4e9bd |
+++ /dev/null
|
|
|
a4e9bd |
@@ -1,758 +0,0 @@
|
|
|
a4e9bd |
-"""
|
|
|
a4e9bd |
-A small templating language
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-This implements a small templating language for use internally in
|
|
|
a4e9bd |
-Paste and Paste Script. This language implements if/elif/else,
|
|
|
a4e9bd |
-for/continue/break, expressions, and blocks of Python code. The
|
|
|
a4e9bd |
-syntax is::
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- {{any expression (function calls etc)}}
|
|
|
a4e9bd |
- {{any expression | filter}}
|
|
|
a4e9bd |
- {{for x in y}}...{{endfor}}
|
|
|
a4e9bd |
- {{if x}}x{{elif y}}y{{else}}z{{endif}}
|
|
|
a4e9bd |
- {{py:x=1}}
|
|
|
a4e9bd |
- {{py:
|
|
|
a4e9bd |
- def foo(bar):
|
|
|
a4e9bd |
- return 'baz'
|
|
|
a4e9bd |
- }}
|
|
|
a4e9bd |
- {{default var = default_value}}
|
|
|
a4e9bd |
- {{# comment}}
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-You use this with the ``Template`` class or the ``sub`` shortcut.
|
|
|
a4e9bd |
-The ``Template`` class takes the template string and the name of
|
|
|
a4e9bd |
-the template (for errors) and a default namespace. Then (like
|
|
|
a4e9bd |
-``string.Template``) you can call the ``tmpl.substitute(**kw)``
|
|
|
a4e9bd |
-method to make a substitution (or ``tmpl.substitute(a_dict)``).
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-``sub(content, **kw)`` substitutes the template immediately. You
|
|
|
a4e9bd |
-can use ``__name='tmpl.html'`` to set the name of the template.
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-If there are syntax errors ``TemplateError`` will be raised.
|
|
|
a4e9bd |
-"""
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-import re
|
|
|
a4e9bd |
-import sys
|
|
|
a4e9bd |
-import cgi
|
|
|
a4e9bd |
-import urllib
|
|
|
a4e9bd |
-from paste.util.looper import looper
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-__all__ = ['TemplateError', 'Template', 'sub', 'HTMLTemplate',
|
|
|
a4e9bd |
- 'sub_html', 'html', 'bunch']
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-token_re = re.compile(r'\{\{|\}\}')
|
|
|
a4e9bd |
-in_re = re.compile(r'\s+in\s+')
|
|
|
a4e9bd |
-var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class TemplateError(Exception):
|
|
|
a4e9bd |
- """Exception raised while parsing a template
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __init__(self, message, position, name=None):
|
|
|
a4e9bd |
- self.message = message
|
|
|
a4e9bd |
- self.position = position
|
|
|
a4e9bd |
- self.name = name
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __str__(self):
|
|
|
a4e9bd |
- msg = '%s at line %s column %s' % (
|
|
|
a4e9bd |
- self.message, self.position[0], self.position[1])
|
|
|
a4e9bd |
- if self.name:
|
|
|
a4e9bd |
- msg += ' in %s' % self.name
|
|
|
a4e9bd |
- return msg
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class _TemplateContinue(Exception):
|
|
|
a4e9bd |
- pass
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class _TemplateBreak(Exception):
|
|
|
a4e9bd |
- pass
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class Template(object):
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- default_namespace = {
|
|
|
a4e9bd |
- 'start_braces': '{{',
|
|
|
a4e9bd |
- 'end_braces': '}}',
|
|
|
a4e9bd |
- 'looper': looper,
|
|
|
a4e9bd |
- }
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- default_encoding = 'utf8'
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __init__(self, content, name=None, namespace=None):
|
|
|
a4e9bd |
- self.content = content
|
|
|
a4e9bd |
- self._unicode = isinstance(content, unicode)
|
|
|
a4e9bd |
- self.name = name
|
|
|
a4e9bd |
- self._parsed = parse(content, name=name)
|
|
|
a4e9bd |
- if namespace is None:
|
|
|
a4e9bd |
- namespace = {}
|
|
|
a4e9bd |
- self.namespace = namespace
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def from_filename(cls, filename, namespace=None, encoding=None):
|
|
|
a4e9bd |
- f = open(filename, 'rb')
|
|
|
a4e9bd |
- c = f.read()
|
|
|
a4e9bd |
- f.close()
|
|
|
a4e9bd |
- if encoding:
|
|
|
a4e9bd |
- c = c.decode(encoding)
|
|
|
a4e9bd |
- return cls(content=c, name=filename, namespace=namespace)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- from_filename = classmethod(from_filename)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __repr__(self):
|
|
|
a4e9bd |
- return '<%s %s name=%r>' % (
|
|
|
a4e9bd |
- self.__class__.__name__,
|
|
|
a4e9bd |
- hex(id(self))[2:], self.name)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def substitute(self, *args, **kw):
|
|
|
a4e9bd |
- if args:
|
|
|
a4e9bd |
- if kw:
|
|
|
a4e9bd |
- raise TypeError(
|
|
|
a4e9bd |
- "You can only give positional *or* keyword arguments")
|
|
|
a4e9bd |
- if len(args) > 1:
|
|
|
a4e9bd |
- raise TypeError(
|
|
|
a4e9bd |
- "You can only give on positional argument")
|
|
|
a4e9bd |
- kw = args[0]
|
|
|
a4e9bd |
- ns = self.default_namespace.copy()
|
|
|
a4e9bd |
- ns.update(self.namespace)
|
|
|
a4e9bd |
- ns.update(kw)
|
|
|
a4e9bd |
- result = self._interpret(ns)
|
|
|
a4e9bd |
- return result
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _interpret(self, ns):
|
|
|
a4e9bd |
- __traceback_hide__ = True
|
|
|
a4e9bd |
- parts = []
|
|
|
a4e9bd |
- self._interpret_codes(self._parsed, ns, out=parts)
|
|
|
a4e9bd |
- return ''.join(parts)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _interpret_codes(self, codes, ns, out):
|
|
|
a4e9bd |
- __traceback_hide__ = True
|
|
|
a4e9bd |
- for item in codes:
|
|
|
a4e9bd |
- if isinstance(item, basestring):
|
|
|
a4e9bd |
- out.append(item)
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- self._interpret_code(item, ns, out)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _interpret_code(self, code, ns, out):
|
|
|
a4e9bd |
- __traceback_hide__ = True
|
|
|
a4e9bd |
- name, pos = code[0], code[1]
|
|
|
a4e9bd |
- if name == 'py':
|
|
|
a4e9bd |
- self._exec(code[2], ns, pos)
|
|
|
a4e9bd |
- elif name == 'continue':
|
|
|
a4e9bd |
- raise _TemplateContinue()
|
|
|
a4e9bd |
- elif name == 'break':
|
|
|
a4e9bd |
- raise _TemplateBreak()
|
|
|
a4e9bd |
- elif name == 'for':
|
|
|
a4e9bd |
- vars, expr, content = code[2], code[3], code[4]
|
|
|
a4e9bd |
- expr = self._eval(expr, ns, pos)
|
|
|
a4e9bd |
- self._interpret_for(vars, expr, content, ns, out)
|
|
|
a4e9bd |
- elif name == 'cond':
|
|
|
a4e9bd |
- parts = code[2:]
|
|
|
a4e9bd |
- self._interpret_if(parts, ns, out)
|
|
|
a4e9bd |
- elif name == 'expr':
|
|
|
a4e9bd |
- parts = code[2].split('|')
|
|
|
a4e9bd |
- base = self._eval(parts[0], ns, pos)
|
|
|
a4e9bd |
- for part in parts[1:]:
|
|
|
a4e9bd |
- func = self._eval(part, ns, pos)
|
|
|
a4e9bd |
- base = func(base)
|
|
|
a4e9bd |
- out.append(self._repr(base, pos))
|
|
|
a4e9bd |
- elif name == 'default':
|
|
|
a4e9bd |
- var, expr = code[2], code[3]
|
|
|
a4e9bd |
- if var not in ns:
|
|
|
a4e9bd |
- result = self._eval(expr, ns, pos)
|
|
|
a4e9bd |
- ns[var] = result
|
|
|
a4e9bd |
- elif name == 'comment':
|
|
|
a4e9bd |
- return
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- assert 0, "Unknown code: %r" % name
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _interpret_for(self, vars, expr, content, ns, out):
|
|
|
a4e9bd |
- __traceback_hide__ = True
|
|
|
a4e9bd |
- for item in expr:
|
|
|
a4e9bd |
- if len(vars) == 1:
|
|
|
a4e9bd |
- ns[vars[0]] = item
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- if len(vars) != len(item):
|
|
|
a4e9bd |
- raise ValueError(
|
|
|
a4e9bd |
- 'Need %i items to unpack (got %i items)'
|
|
|
a4e9bd |
- % (len(vars), len(item)))
|
|
|
a4e9bd |
- for name, value in zip(vars, item):
|
|
|
a4e9bd |
- ns[name] = value
|
|
|
a4e9bd |
- try:
|
|
|
a4e9bd |
- self._interpret_codes(content, ns, out)
|
|
|
a4e9bd |
- except _TemplateContinue:
|
|
|
a4e9bd |
- continue
|
|
|
a4e9bd |
- except _TemplateBreak:
|
|
|
a4e9bd |
- break
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _interpret_if(self, parts, ns, out):
|
|
|
a4e9bd |
- __traceback_hide__ = True
|
|
|
a4e9bd |
- # @@: if/else/else gets through
|
|
|
a4e9bd |
- for part in parts:
|
|
|
a4e9bd |
- assert not isinstance(part, basestring)
|
|
|
a4e9bd |
- name, pos = part[0], part[1]
|
|
|
a4e9bd |
- if name == 'else':
|
|
|
a4e9bd |
- result = True
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- result = self._eval(part[2], ns, pos)
|
|
|
a4e9bd |
- if result:
|
|
|
a4e9bd |
- self._interpret_codes(part[3], ns, out)
|
|
|
a4e9bd |
- break
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _eval(self, code, ns, pos):
|
|
|
a4e9bd |
- __traceback_hide__ = True
|
|
|
a4e9bd |
- try:
|
|
|
a4e9bd |
- value = eval(code, ns)
|
|
|
a4e9bd |
- return value
|
|
|
a4e9bd |
- except:
|
|
|
a4e9bd |
- exc_info = sys.exc_info()
|
|
|
a4e9bd |
- e = exc_info[1]
|
|
|
a4e9bd |
- if getattr(e, 'args'):
|
|
|
a4e9bd |
- arg0 = e.args[0]
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- arg0 = str(e)
|
|
|
a4e9bd |
- e.args = (self._add_line_info(arg0, pos),)
|
|
|
a4e9bd |
- raise exc_info[0], e, exc_info[2]
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _exec(self, code, ns, pos):
|
|
|
a4e9bd |
- __traceback_hide__ = True
|
|
|
a4e9bd |
- try:
|
|
|
a4e9bd |
- exec code in ns
|
|
|
a4e9bd |
- except:
|
|
|
a4e9bd |
- exc_info = sys.exc_info()
|
|
|
a4e9bd |
- e = exc_info[1]
|
|
|
a4e9bd |
- e.args = (self._add_line_info(e.args[0], pos),)
|
|
|
a4e9bd |
- raise exc_info[0], e, exc_info[2]
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _repr(self, value, pos):
|
|
|
a4e9bd |
- __traceback_hide__ = True
|
|
|
a4e9bd |
- try:
|
|
|
a4e9bd |
- if value is None:
|
|
|
a4e9bd |
- return ''
|
|
|
a4e9bd |
- if self._unicode:
|
|
|
a4e9bd |
- try:
|
|
|
a4e9bd |
- value = unicode(value)
|
|
|
a4e9bd |
- except UnicodeDecodeError:
|
|
|
a4e9bd |
- value = str(value)
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- value = str(value)
|
|
|
a4e9bd |
- except:
|
|
|
a4e9bd |
- exc_info = sys.exc_info()
|
|
|
a4e9bd |
- e = exc_info[1]
|
|
|
a4e9bd |
- e.args = (self._add_line_info(e.args[0], pos),)
|
|
|
a4e9bd |
- raise exc_info[0], e, exc_info[2]
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- if self._unicode and isinstance(value, str):
|
|
|
a4e9bd |
- if not self.decode_encoding:
|
|
|
a4e9bd |
- raise UnicodeDecodeError(
|
|
|
a4e9bd |
- 'Cannot decode str value %r into unicode '
|
|
|
a4e9bd |
- '(no default_encoding provided)' % value)
|
|
|
a4e9bd |
- value = value.decode(self.default_encoding)
|
|
|
a4e9bd |
- elif not self._unicode and isinstance(value, unicode):
|
|
|
a4e9bd |
- if not self.decode_encoding:
|
|
|
a4e9bd |
- raise UnicodeEncodeError(
|
|
|
a4e9bd |
- 'Cannot encode unicode value %r into str '
|
|
|
a4e9bd |
- '(no default_encoding provided)' % value)
|
|
|
a4e9bd |
- value = value.encode(self.default_encoding)
|
|
|
a4e9bd |
- return value
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _add_line_info(self, msg, pos):
|
|
|
a4e9bd |
- msg = "%s at line %s column %s" % (
|
|
|
a4e9bd |
- msg, pos[0], pos[1])
|
|
|
a4e9bd |
- if self.name:
|
|
|
a4e9bd |
- msg += " in file %s" % self.name
|
|
|
a4e9bd |
- return msg
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def sub(content, **kw):
|
|
|
a4e9bd |
- name = kw.get('__name')
|
|
|
a4e9bd |
- tmpl = Template(content, name=name)
|
|
|
a4e9bd |
- return tmpl.substitute(kw)
|
|
|
a4e9bd |
- return result
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def paste_script_template_renderer(content, vars, filename=None):
|
|
|
a4e9bd |
- tmpl = Template(content, name=filename)
|
|
|
a4e9bd |
- return tmpl.substitute(vars)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class bunch(dict):
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __init__(self, **kw):
|
|
|
a4e9bd |
- for name, value in kw.items():
|
|
|
a4e9bd |
- setattr(self, name, value)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __setattr__(self, name, value):
|
|
|
a4e9bd |
- self[name] = value
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __getattr__(self, name):
|
|
|
a4e9bd |
- try:
|
|
|
a4e9bd |
- return self[name]
|
|
|
a4e9bd |
- except KeyError:
|
|
|
a4e9bd |
- raise AttributeError(name)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __getitem__(self, key):
|
|
|
a4e9bd |
- if 'default' in self:
|
|
|
a4e9bd |
- try:
|
|
|
a4e9bd |
- return dict.__getitem__(self, key)
|
|
|
a4e9bd |
- except KeyError:
|
|
|
a4e9bd |
- return dict.__getitem__(self, 'default')
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- return dict.__getitem__(self, key)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def __repr__(self):
|
|
|
a4e9bd |
- items = [
|
|
|
a4e9bd |
- (k, v) for k, v in self.items()]
|
|
|
a4e9bd |
- items.sort()
|
|
|
a4e9bd |
- return '<%s %s>' % (
|
|
|
a4e9bd |
- self.__class__.__name__,
|
|
|
a4e9bd |
- ' '.join(['%s=%r' % (k, v) for k, v in items]))
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-############################################################
|
|
|
a4e9bd |
-## HTML Templating
|
|
|
a4e9bd |
-############################################################
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class html(object):
|
|
|
a4e9bd |
- def __init__(self, value):
|
|
|
a4e9bd |
- self.value = value
|
|
|
a4e9bd |
- def __str__(self):
|
|
|
a4e9bd |
- return self.value
|
|
|
a4e9bd |
- def __repr__(self):
|
|
|
a4e9bd |
- return '<%s %r>' % (
|
|
|
a4e9bd |
- self.__class__.__name__, self.value)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def html_quote(value):
|
|
|
a4e9bd |
- if value is None:
|
|
|
a4e9bd |
- return ''
|
|
|
a4e9bd |
- if not isinstance(value, basestring):
|
|
|
a4e9bd |
- if hasattr(value, '__unicode__'):
|
|
|
a4e9bd |
- value = unicode(value)
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- value = str(value)
|
|
|
a4e9bd |
- value = cgi.escape(value, 1)
|
|
|
a4e9bd |
- if isinstance(value, unicode):
|
|
|
a4e9bd |
- value = value.encode('ascii', 'xmlcharrefreplace')
|
|
|
a4e9bd |
- return value
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def url(v):
|
|
|
a4e9bd |
- if not isinstance(v, basestring):
|
|
|
a4e9bd |
- if hasattr(v, '__unicode__'):
|
|
|
a4e9bd |
- v = unicode(v)
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- v = str(v)
|
|
|
a4e9bd |
- if isinstance(v, unicode):
|
|
|
a4e9bd |
- v = v.encode('utf8')
|
|
|
a4e9bd |
- return urllib.quote(v)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def attr(**kw):
|
|
|
a4e9bd |
- kw = kw.items()
|
|
|
a4e9bd |
- kw.sort()
|
|
|
a4e9bd |
- parts = []
|
|
|
a4e9bd |
- for name, value in kw:
|
|
|
a4e9bd |
- if value is None:
|
|
|
a4e9bd |
- continue
|
|
|
a4e9bd |
- if name.endswith('_'):
|
|
|
a4e9bd |
- name = name[:-1]
|
|
|
a4e9bd |
- parts.append('%s="%s"' % (html_quote(name), html_quote(value)))
|
|
|
a4e9bd |
- return html(' '.join(parts))
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-class HTMLTemplate(Template):
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- default_namespace = Template.default_namespace.copy()
|
|
|
a4e9bd |
- default_namespace.update(dict(
|
|
|
a4e9bd |
- html=html,
|
|
|
a4e9bd |
- attr=attr,
|
|
|
a4e9bd |
- url=url,
|
|
|
a4e9bd |
- ))
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- def _repr(self, value, pos):
|
|
|
a4e9bd |
- plain = Template._repr(self, value, pos)
|
|
|
a4e9bd |
- if isinstance(value, html):
|
|
|
a4e9bd |
- return plain
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- return html_quote(plain)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def sub_html(content, **kw):
|
|
|
a4e9bd |
- name = kw.get('__name')
|
|
|
a4e9bd |
- tmpl = HTMLTemplate(content, name=name)
|
|
|
a4e9bd |
- return tmpl.substitute(kw)
|
|
|
a4e9bd |
- return result
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-############################################################
|
|
|
a4e9bd |
-## Lexing and Parsing
|
|
|
a4e9bd |
-############################################################
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def lex(s, name=None, trim_whitespace=True):
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
- Lex a string into chunks:
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- >>> lex('hey')
|
|
|
a4e9bd |
- ['hey']
|
|
|
a4e9bd |
- >>> lex('hey {{you}}')
|
|
|
a4e9bd |
- ['hey ', ('you', (1, 7))]
|
|
|
a4e9bd |
- >>> lex('hey {{')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: No }} to finish last expression at line 1 column 7
|
|
|
a4e9bd |
- >>> lex('hey }}')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: }} outside expression at line 1 column 7
|
|
|
a4e9bd |
- >>> lex('hey {{ {{')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: {{ inside expression at line 1 column 10
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
- in_expr = False
|
|
|
a4e9bd |
- chunks = []
|
|
|
a4e9bd |
- last = 0
|
|
|
a4e9bd |
- last_pos = (1, 1)
|
|
|
a4e9bd |
- for match in token_re.finditer(s):
|
|
|
a4e9bd |
- expr = match.group(0)
|
|
|
a4e9bd |
- pos = find_position(s, match.end())
|
|
|
a4e9bd |
- if expr == '{{' and in_expr:
|
|
|
a4e9bd |
- raise TemplateError('{{ inside expression', position=pos,
|
|
|
a4e9bd |
- name=name)
|
|
|
a4e9bd |
- elif expr == '}}' and not in_expr:
|
|
|
a4e9bd |
- raise TemplateError('}} outside expression', position=pos,
|
|
|
a4e9bd |
- name=name)
|
|
|
a4e9bd |
- if expr == '{{':
|
|
|
a4e9bd |
- part = s[last:match.start()]
|
|
|
a4e9bd |
- if part:
|
|
|
a4e9bd |
- chunks.append(part)
|
|
|
a4e9bd |
- in_expr = True
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- chunks.append((s[last:match.start()], last_pos))
|
|
|
a4e9bd |
- in_expr = False
|
|
|
a4e9bd |
- last = match.end()
|
|
|
a4e9bd |
- last_pos = pos
|
|
|
a4e9bd |
- if in_expr:
|
|
|
a4e9bd |
- raise TemplateError('No }} to finish last expression',
|
|
|
a4e9bd |
- name=name, position=last_pos)
|
|
|
a4e9bd |
- part = s[last:]
|
|
|
a4e9bd |
- if part:
|
|
|
a4e9bd |
- chunks.append(part)
|
|
|
a4e9bd |
- if trim_whitespace:
|
|
|
a4e9bd |
- chunks = trim_lex(chunks)
|
|
|
a4e9bd |
- return chunks
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-statement_re = re.compile(r'^(?:if |elif |else |for |py:)')
|
|
|
a4e9bd |
-single_statements = ['endif', 'endfor', 'continue', 'break']
|
|
|
a4e9bd |
-trail_whitespace_re = re.compile(r'\n[\t ]*$')
|
|
|
a4e9bd |
-lead_whitespace_re = re.compile(r'^[\t ]*\n')
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def trim_lex(tokens):
|
|
|
a4e9bd |
- r"""
|
|
|
a4e9bd |
- Takes a lexed set of tokens, and removes whitespace when there is
|
|
|
a4e9bd |
- a directive on a line by itself:
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
|
|
|
a4e9bd |
- >>> tokens
|
|
|
a4e9bd |
- [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
|
|
|
a4e9bd |
- >>> trim_lex(tokens)
|
|
|
a4e9bd |
- [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
- for i in range(len(tokens)):
|
|
|
a4e9bd |
- current = tokens[i]
|
|
|
a4e9bd |
- if isinstance(tokens[i], basestring):
|
|
|
a4e9bd |
- # we don't trim this
|
|
|
a4e9bd |
- continue
|
|
|
a4e9bd |
- item = current[0]
|
|
|
a4e9bd |
- if not statement_re.search(item) and item not in single_statements:
|
|
|
a4e9bd |
- continue
|
|
|
a4e9bd |
- if not i:
|
|
|
a4e9bd |
- prev = ''
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- prev = tokens[i-1]
|
|
|
a4e9bd |
- if i+1 >= len(tokens):
|
|
|
a4e9bd |
- next = ''
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- next = tokens[i+1]
|
|
|
a4e9bd |
- if (not isinstance(next, basestring)
|
|
|
a4e9bd |
- or not isinstance(prev, basestring)):
|
|
|
a4e9bd |
- continue
|
|
|
a4e9bd |
- if ((not prev or trail_whitespace_re.search(prev))
|
|
|
a4e9bd |
- and (not next or lead_whitespace_re.search(next))):
|
|
|
a4e9bd |
- if prev:
|
|
|
a4e9bd |
- m = trail_whitespace_re.search(prev)
|
|
|
a4e9bd |
- # +1 to leave the leading \n on:
|
|
|
a4e9bd |
- prev = prev[:m.start()+1]
|
|
|
a4e9bd |
- tokens[i-1] = prev
|
|
|
a4e9bd |
- if next:
|
|
|
a4e9bd |
- m = lead_whitespace_re.search(next)
|
|
|
a4e9bd |
- next = next[m.end():]
|
|
|
a4e9bd |
- tokens[i+1] = next
|
|
|
a4e9bd |
- return tokens
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def find_position(string, index):
|
|
|
a4e9bd |
- """Given a string and index, return (line, column)"""
|
|
|
a4e9bd |
- leading = string[:index].splitlines()
|
|
|
a4e9bd |
- return (len(leading), len(leading[-1])+1)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def parse(s, name=None):
|
|
|
a4e9bd |
- r"""
|
|
|
a4e9bd |
- Parses a string into a kind of AST
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- >>> parse('{{x}}')
|
|
|
a4e9bd |
- [('expr', (1, 3), 'x')]
|
|
|
a4e9bd |
- >>> parse('foo')
|
|
|
a4e9bd |
- ['foo']
|
|
|
a4e9bd |
- >>> parse('{{if x}}test{{endif}}')
|
|
|
a4e9bd |
- [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
|
|
|
a4e9bd |
- >>> parse('series->{{for x in y}}x={{x}}{{endfor}}')
|
|
|
a4e9bd |
- ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
|
|
|
a4e9bd |
- >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
|
|
|
a4e9bd |
- [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
|
|
|
a4e9bd |
- >>> parse('{{py:x=1}}')
|
|
|
a4e9bd |
- [('py', (1, 3), 'x=1')]
|
|
|
a4e9bd |
- >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}')
|
|
|
a4e9bd |
- [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- Some exceptions::
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
- >>> parse('{{continue}}')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: continue outside of for loop at line 1 column 3
|
|
|
a4e9bd |
- >>> parse('{{if x}}foo')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: No {{endif}} at line 1 column 3
|
|
|
a4e9bd |
- >>> parse('{{else}}')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: else outside of an if block at line 1 column 3
|
|
|
a4e9bd |
- >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: Unexpected endif at line 1 column 25
|
|
|
a4e9bd |
- >>> parse('{{if}}{{endif}}')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: if with no expression at line 1 column 3
|
|
|
a4e9bd |
- >>> parse('{{for x y}}{{endfor}}')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
|
|
|
a4e9bd |
- >>> parse('{{py:x=1\ny=2}}')
|
|
|
a4e9bd |
- Traceback (most recent call last):
|
|
|
a4e9bd |
- ...
|
|
|
a4e9bd |
- TemplateError: Multi-line py blocks must start with a newline at line 1 column 3
|
|
|
a4e9bd |
- """
|
|
|
a4e9bd |
- tokens = lex(s, name=name)
|
|
|
a4e9bd |
- result = []
|
|
|
a4e9bd |
- while tokens:
|
|
|
a4e9bd |
- next, tokens = parse_expr(tokens, name)
|
|
|
a4e9bd |
- result.append(next)
|
|
|
a4e9bd |
- return result
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def parse_expr(tokens, name, context=()):
|
|
|
a4e9bd |
- if isinstance(tokens[0], basestring):
|
|
|
a4e9bd |
- return tokens[0], tokens[1:]
|
|
|
a4e9bd |
- expr, pos = tokens[0]
|
|
|
a4e9bd |
- expr = expr.strip()
|
|
|
a4e9bd |
- if expr.startswith('py:'):
|
|
|
a4e9bd |
- expr = expr[3:].lstrip(' \t')
|
|
|
a4e9bd |
- if expr.startswith('\n'):
|
|
|
a4e9bd |
- expr = expr[1:]
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- if '\n' in expr:
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- 'Multi-line py blocks must start with a newline',
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- return ('py', pos, expr), tokens[1:]
|
|
|
a4e9bd |
- elif expr in ('continue', 'break'):
|
|
|
a4e9bd |
- if 'for' not in context:
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- 'continue outside of for loop',
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- return (expr, pos), tokens[1:]
|
|
|
a4e9bd |
- elif expr.startswith('if '):
|
|
|
a4e9bd |
- return parse_cond(tokens, name, context)
|
|
|
a4e9bd |
- elif (expr.startswith('elif ')
|
|
|
a4e9bd |
- or expr == 'else'):
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- '%s outside of an if block' % expr.split()[0],
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- elif expr in ('if', 'elif', 'for'):
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- '%s with no expression' % expr,
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- elif expr in ('endif', 'endfor'):
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- 'Unexpected %s' % expr,
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- elif expr.startswith('for '):
|
|
|
a4e9bd |
- return parse_for(tokens, name, context)
|
|
|
a4e9bd |
- elif expr.startswith('default '):
|
|
|
a4e9bd |
- return parse_default(tokens, name, context)
|
|
|
a4e9bd |
- elif expr.startswith('#'):
|
|
|
a4e9bd |
- return ('comment', pos, tokens[0][0]), tokens[1:]
|
|
|
a4e9bd |
- return ('expr', pos, tokens[0][0]), tokens[1:]
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def parse_cond(tokens, name, context):
|
|
|
a4e9bd |
- start = tokens[0][1]
|
|
|
a4e9bd |
- pieces = []
|
|
|
a4e9bd |
- context = context + ('if',)
|
|
|
a4e9bd |
- while 1:
|
|
|
a4e9bd |
- if not tokens:
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- 'Missing {{endif}}',
|
|
|
a4e9bd |
- position=start, name=name)
|
|
|
a4e9bd |
- if (isinstance(tokens[0], tuple)
|
|
|
a4e9bd |
- and tokens[0][0] == 'endif'):
|
|
|
a4e9bd |
- return ('cond', start) + tuple(pieces), tokens[1:]
|
|
|
a4e9bd |
- next, tokens = parse_one_cond(tokens, name, context)
|
|
|
a4e9bd |
- pieces.append(next)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def parse_one_cond(tokens, name, context):
|
|
|
a4e9bd |
- (first, pos), tokens = tokens[0], tokens[1:]
|
|
|
a4e9bd |
- content = []
|
|
|
a4e9bd |
- if first.endswith(':'):
|
|
|
a4e9bd |
- first = first[:-1]
|
|
|
a4e9bd |
- if first.startswith('if '):
|
|
|
a4e9bd |
- part = ('if', pos, first[3:].lstrip(), content)
|
|
|
a4e9bd |
- elif first.startswith('elif '):
|
|
|
a4e9bd |
- part = ('elif', pos, first[5:].lstrip(), content)
|
|
|
a4e9bd |
- elif first == 'else':
|
|
|
a4e9bd |
- part = ('else', pos, None, content)
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- assert 0, "Unexpected token %r at %s" % (first, pos)
|
|
|
a4e9bd |
- while 1:
|
|
|
a4e9bd |
- if not tokens:
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- 'No {{endif}}',
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- if (isinstance(tokens[0], tuple)
|
|
|
a4e9bd |
- and (tokens[0][0] == 'endif'
|
|
|
a4e9bd |
- or tokens[0][0].startswith('elif ')
|
|
|
a4e9bd |
- or tokens[0][0] == 'else')):
|
|
|
a4e9bd |
- return part, tokens
|
|
|
a4e9bd |
- next, tokens = parse_expr(tokens, name, context)
|
|
|
a4e9bd |
- content.append(next)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def parse_for(tokens, name, context):
|
|
|
a4e9bd |
- first, pos = tokens[0]
|
|
|
a4e9bd |
- tokens = tokens[1:]
|
|
|
a4e9bd |
- context = ('for',) + context
|
|
|
a4e9bd |
- content = []
|
|
|
a4e9bd |
- assert first.startswith('for ')
|
|
|
a4e9bd |
- if first.endswith(':'):
|
|
|
a4e9bd |
- first = first[:-1]
|
|
|
a4e9bd |
- first = first[3:].strip()
|
|
|
a4e9bd |
- match = in_re.search(first)
|
|
|
a4e9bd |
- if not match:
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- 'Bad for (no "in") in %r' % first,
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- vars = first[:match.start()]
|
|
|
a4e9bd |
- if '(' in vars:
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- 'You cannot have () in the variable section of a for loop (%r)'
|
|
|
a4e9bd |
- % vars, position=pos, name=name)
|
|
|
a4e9bd |
- vars = tuple([
|
|
|
a4e9bd |
- v.strip() for v in first[:match.start()].split(',')
|
|
|
a4e9bd |
- if v.strip()])
|
|
|
a4e9bd |
- expr = first[match.end():]
|
|
|
a4e9bd |
- while 1:
|
|
|
a4e9bd |
- if not tokens:
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- 'No {{endfor}}',
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- if (isinstance(tokens[0], tuple)
|
|
|
a4e9bd |
- and tokens[0][0] == 'endfor'):
|
|
|
a4e9bd |
- return ('for', pos, vars, expr, content), tokens[1:]
|
|
|
a4e9bd |
- next, tokens = parse_expr(tokens, name, context)
|
|
|
a4e9bd |
- content.append(next)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def parse_default(tokens, name, context):
|
|
|
a4e9bd |
- first, pos = tokens[0]
|
|
|
a4e9bd |
- assert first.startswith('default ')
|
|
|
a4e9bd |
- first = first.split(None, 1)[1]
|
|
|
a4e9bd |
- parts = first.split('=', 1)
|
|
|
a4e9bd |
- if len(parts) == 1:
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- "Expression must be {{default var=value}}; no = found in %r" % first,
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- var = parts[0].strip()
|
|
|
a4e9bd |
- if ',' in var:
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- "{{default x, y = ...}} is not supported",
|
|
|
a4e9bd |
- position=pos, name=name)
|
|
|
a4e9bd |
- if not var_re.search(var):
|
|
|
a4e9bd |
- raise TemplateError(
|
|
|
a4e9bd |
- "Not a valid variable name for {{default}}: %r"
|
|
|
a4e9bd |
- % var, position=pos, name=name)
|
|
|
a4e9bd |
- expr = parts[1].strip()
|
|
|
a4e9bd |
- return ('default', pos, var, expr), tokens[1:]
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-_fill_command_usage = """\
|
|
|
a4e9bd |
-%prog [OPTIONS] TEMPLATE arg=value
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-Use py:arg=value to set a Python value; otherwise all values are
|
|
|
a4e9bd |
-strings.
|
|
|
a4e9bd |
-"""
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-def fill_command(args=None):
|
|
|
a4e9bd |
- import sys, optparse, pkg_resources, os
|
|
|
a4e9bd |
- if args is None:
|
|
|
a4e9bd |
- args = sys.argv[1:]
|
|
|
a4e9bd |
- dist = pkg_resources.get_distribution('Paste')
|
|
|
a4e9bd |
- parser = optparse.OptionParser(
|
|
|
a4e9bd |
- version=str(dist),
|
|
|
a4e9bd |
- usage=_fill_command_usage)
|
|
|
a4e9bd |
- parser.add_option(
|
|
|
a4e9bd |
- '-o', '--output',
|
|
|
a4e9bd |
- dest='output',
|
|
|
a4e9bd |
- metavar="FILENAME",
|
|
|
a4e9bd |
- help="File to write output to (default stdout)")
|
|
|
a4e9bd |
- parser.add_option(
|
|
|
a4e9bd |
- '--html',
|
|
|
a4e9bd |
- dest='use_html',
|
|
|
a4e9bd |
- action='store_true',
|
|
|
a4e9bd |
- help="Use HTML style filling (including automatic HTML quoting)")
|
|
|
a4e9bd |
- parser.add_option(
|
|
|
a4e9bd |
- '--env',
|
|
|
a4e9bd |
- dest='use_env',
|
|
|
a4e9bd |
- action='store_true',
|
|
|
a4e9bd |
- help="Put the environment in as top-level variables")
|
|
|
a4e9bd |
- options, args = parser.parse_args(args)
|
|
|
a4e9bd |
- if len(args) < 1:
|
|
|
a4e9bd |
- print 'You must give a template filename'
|
|
|
a4e9bd |
- print dir(parser)
|
|
|
a4e9bd |
- assert 0
|
|
|
a4e9bd |
- template_name = args[0]
|
|
|
a4e9bd |
- args = args[1:]
|
|
|
a4e9bd |
- vars = {}
|
|
|
a4e9bd |
- if options.use_env:
|
|
|
a4e9bd |
- vars.update(os.environ)
|
|
|
a4e9bd |
- for value in args:
|
|
|
a4e9bd |
- if '=' not in value:
|
|
|
a4e9bd |
- print 'Bad argument: %r' % value
|
|
|
a4e9bd |
- sys.exit(2)
|
|
|
a4e9bd |
- name, value = value.split('=', 1)
|
|
|
a4e9bd |
- if name.startswith('py:'):
|
|
|
a4e9bd |
- name = name[:3]
|
|
|
a4e9bd |
- value = eval(value)
|
|
|
a4e9bd |
- vars[name] = value
|
|
|
a4e9bd |
- if template_name == '-':
|
|
|
a4e9bd |
- template_content = sys.stdin.read()
|
|
|
a4e9bd |
- template_name = '<stdin>'
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- f = open(template_name, 'rb')
|
|
|
a4e9bd |
- template_content = f.read()
|
|
|
a4e9bd |
- f.close()
|
|
|
a4e9bd |
- if options.use_html:
|
|
|
a4e9bd |
- TemplateClass = HTMLTemplate
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- TemplateClass = Template
|
|
|
a4e9bd |
- template = TemplateClass(template_content, name=template_name)
|
|
|
a4e9bd |
- result = template.substitute(vars)
|
|
|
a4e9bd |
- if options.output:
|
|
|
a4e9bd |
- f = open(options.output, 'wb')
|
|
|
a4e9bd |
- f.write(result)
|
|
|
a4e9bd |
- f.close()
|
|
|
a4e9bd |
- else:
|
|
|
a4e9bd |
- sys.stdout.write(result)
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-if __name__ == '__main__':
|
|
|
a4e9bd |
- from paste.util.template import fill_command
|
|
|
a4e9bd |
- fill_command()
|
|
|
a4e9bd |
-
|
|
|
a4e9bd |
-
|