end_of_filter = lambda p, t, d: t == 'name' and d == 'endfilter'
end_of_macro = lambda p, t, d: t == 'name' and d == 'endmacro'
end_of_block_tag = lambda p, t, d: t == 'name' and d == 'endblock'
-end_of_raw = lambda p, t, d: t == 'name' and d == 'endraw'
+end_of_trans = lambda p, t, d: t == 'name' and d == 'endtrans'
+
+
+string_inc_re = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+')
+
+
+def inc_string(s):
+ """
+ Increment a string
+ """
+ m = string_inc_re.search(s)
+ if m:
+ next = str(int(m.group(1)) + 1)
+ start, end = m.span(1)
+ s = s[:max(end - len(next), start)] + next + s[end:]
+ else:
+ name, ext = s.rsplit('.', 1)
+ return '%s2.%s' % (name, ext)
+ return s
class Parser(object):
'macro': self.handle_macro_directive,
'block': self.handle_block_directive,
'extends': self.handle_extends_directive,
- 'include': self.handle_include_directive
+ 'include': self.handle_include_directive,
+ 'trans': self.handle_trans_directive
}
def handle_for_directive(self, lineno, gen):
raise TemplateSyntaxError('include requires a string', lineno)
return nodes.Include(lineno, tokens[0][2][1:-1])
+ def handle_trans_directive(self, lineno, gen):
+ """
+ Handle translatable sections.
+ """
+ # save the initial line number for the resulting node
+ flineno = lineno
+ try:
+ # check for string translations
+ lineno, token, data = gen.next()
+ if token == 'string':
+ # check that there are not any more elements
+ try:
+ gen.next()
+ except StopIteration:
+ #XXX: what about escapes?
+ return nodes.Trans(lineno, data[1:-1], None, None, None)
+ raise TemplateSyntaxError('string based translations '
+ 'require at most one argument.',
+ lineno)
+
+ # create a new generator with the popped item as first one
+ def wrapgen(oldgen):
+ yield lineno, token, data
+ for item in oldgen:
+ yield item
+ gen = wrapgen(gen)
+
+ # block based translations
+ first_var = None
+ replacements = {}
+ for arg in self.parse_python(lineno, gen, '_trans(%s)').expr.args:
+ if arg.__class__ is not ast.Keyword:
+ raise TemplateSyntaxError('translation tags need explicit '
+ 'names for values.', lineno)
+ if first_var is None:
+ first_var = arg.name
+ replacements[arg.name] = arg.expr
+
+ # look for endtrans/pluralize
+ buf = singular = []
+ plural = indicator = None
+
+ while True:
+ lineno, token, data = self.tokenstream.next()
+ # nested variables
+ if token == 'variable_begin':
+ _, variable_token, variable_name = self.tokenstream.next()
+ if variable_token != 'name' or variable_name not in replacements:
+ raise TemplateSyntaxError('unregistered translation '
+ 'variable %r.' % variable_name,
+ lineno)
+ if self.tokenstream.next()[1] != 'variable_end':
+ raise TemplateSyntaxError('invalid syntax for variable '
+ 'expression.', lineno)
+ buf.append('%%(%s)s' % variable_name)
+ # nested blocks are not supported, just look for end blocks
+ elif token == 'block_begin':
+ _, block_token, block_name = self.tokenstream.next()
+ if block_token != 'name' or \
+ block_name not in ('pluralize', 'endtrans'):
+ raise TemplateSyntaxError('blocks in translatable sections '
+ 'are not supported', lineno)
+ # pluralize
+ if block_name == 'pluralize':
+ if plural is not None:
+ raise TemplateSyntaxError('translation blocks support '
+ 'at most one plural block',
+ lineno)
+ _, plural_token, plural_name = self.tokenstream.next()
+ if plural_token == 'block_end':
+ indicator = first_var
+ elif plural_token == 'name':
+ if plural_name not in replacements:
+ raise TemplateSyntaxError('unknown tranlsation '
+ 'variable %r' %
+ plural_name, lineno)
+ elif self.tokenstream.next()[1] != 'block_end':
+ raise TemplateSyntaxError('pluralize takes '
+ 'at most one '
+ 'argument', lineno)
+ indicator = plural_name
+ else:
+ raise TemplateSyntaxError('pluralize requires no '
+ 'argument or a variable '
+ 'name.')
+ plural = buf = []
+ # end translation
+ elif block_name == 'endtrans':
+ self.close_remaining_block()
+ break
+ # normal data
+ else:
+ if replacements:
+ data = data.replace('%', '%%')
+ buf.append(data)
+
+ except StopIteration:
+ raise TemplateSyntaxError('unexpected end of translation section',
+ self.tokenstream.last[0])
+
+ singular = u''.join(singular)
+ if plural is not None:
+ plural = u''.join(plural)
+ return nodes.Trans(flineno, singular, plural, indicator, replacements or None)
+
+
def parse_python(self, lineno, gen, template):
"""
Convert the passed generator into a flat string representing
from compiler import ast
from jinja import nodes
from jinja.parser import Parser
-from jinja.datastructure import Context
from jinja.exceptions import TemplateSyntaxError
from jinja.translators import Translator
exec self.code in ns
self.generate_func = ns['generate']
result = []
- ctx = Context(self.environment, *args, **kwargs)
+ ctx = self.environment.context_class(self.environment, *args, **kwargs)
self.generate_func(ctx, result.append)
return u''.join(result)
nodes.Filter: self.handle_filter,
nodes.Block: self.handle_block,
nodes.Include: self.handle_include,
+ nodes.Trans: self.handle_trans,
# used python nodes
ast.Name: self.handle_name,
ast.AssName: self.handle_name,
ast.GenExpr: 'generator expressions'
})
+ self.require_translations = False
+
# -- public methods
def process(environment, node):
'from __future__ import division\n'
'from jinja.datastructure import Undefined, LoopContext, CycleContext\n\n'
'def generate(context, write):\n'
- ' # BOOTSTRAPPING CODE\n'
' environment = context.environment\n'
' get_attribute = environment.get_attribute\n'
' perform_test = environment.perform_test\n'
' apply_filters = environment.apply_filters\n'
' call_function = environment.call_function\n'
' call_function_simple = environment.call_function_simple\n'
- ' finish_var = environment.finish_var\n\n'
- ' # TEMPLATE CODE'
+ ' finish_var = environment.finish_var\n'
]
- self.indention += 1
- lines.append(self.handle_node_list(node))
- self.indention -= 1
+ self.indention = 1
+ rv = self.handle_node_list(node)
+
+ if self.require_translations:
+ lines.append(' translate = context.get_translator()')
+ lines.append(rv)
return '\n'.join(lines)
buf.append(self.indent('# END OF INCLUSION'))
return '\n'.join(buf)
+ def handle_trans(self, node):
+ """
+ Handle translations.
+ """
+ self.require_translations = True
+ if node.replacements:
+ replacements = []
+ for name, n in node.replacements.iteritems():
+ replacements.append('%r: %s' % (
+ name,
+ self.handle_node(n)
+ ))
+ replacements = '{%s}' % ', '.join(replacements)
+ else:
+ replacements = 'None'
+ if node.indicator is not None:
+ indicator = 'context[\'%s\']' % node.indicator
+ else:
+ indicator = 'None'
+ return self.indent('write(translate(%r, %r, %s) %% %s)' % (
+ node.singular,
+ node.plural,
+ indicator,
+ replacements
+ ))
+
# -- python nodes
def handle_name(self, node):
"""
if node.name in self.constants:
return self.constants[node.name]
+ elif node.name == '_':
+ self.require_translations = True
+ return 'translate'
return 'context[%r]' % node.name
def handle_compare(self, node):