def visit_Start(self, node): try: line, column = node.prefix.location except AttributeError: line, column = 0, 0 yield Comment( " %s%s ... (%d:%d)\n" " --------------------------------------------------------" % (node.prefix, node.name, line, column)) if not hasattr(node, "repeatable"): print "ea" if node.repeatable: push = template("STACK.repeat(N)", STACK=self._current_stack, N=ast.Str(s=node.name)) elif node.replayable: push = template("STACK.replay(N)", STACK=self._current_stack, N=ast.Str(s=node.name)) else: push = template("STACK.u(N)", STACK=self._current_stack, N=ast.Str(s=node.name)) for stmt in push: yield stmt if node.attributes: for attribute in node.attributes: for stmt in self.visit(attribute): yield stmt
def visit_Macro(self, node): body = [] # Resolve defaults for name in self.defaults: body += template("NAME = econtext[KEY]", NAME=name, KEY=ast.Str(s="__" + name)) # Visit macro body nodes = itertools.chain(*tuple(map(self.visit, node.body))) # Append visited nodes body += template("__i18n_domain = None") body += nodes function_name = "render" if node.name is None else \ "render_%s" % mangle(node.name) function = ast.FunctionDef(name=function_name, args=ast.arguments( args=[ param(self._current_stack), param(self._defined_models[""]), param("econtext"), param("rcontext"), ], defaults=[], ), body=body) yield function
def visit_Assignment(self, node): for name in node.names: if name in COMPILER_INTERNALS_OR_DISALLOWED: raise TranslationError("Name disallowed by compiler.", name) if name.startswith('__'): raise TranslationError( "Name disallowed by compiler (double underscore).", name) assignment = self._engine(node.expression, store("__value")) if len(node.names) != 1: target = ast.Tuple( elts=[store_econtext(name) for name in node.names], ctx=ast.Store(), ) else: target = store_econtext(node.names[0]) assignment.append(ast.Assign(targets=[target], value=load("__value"))) for name in node.names: if not node.local: assignment += template("rcontext[KEY] = __value", KEY=ast.Str(s=native_string(name))) return assignment
def __call__(self, expression, target): if isinstance(target, string_type): target = store(target) try: stmts = self.translate(expression, target) except ExpressionError: if self.strict: raise exc = sys.exc_info()[1] p = pickle.dumps(exc) stmts = template("__exc = loads(p)", loads=self.loads_symbol, p=ast.Str(s=p)) token = Token(exc.token, exc.offset, filename=exc.filename) stmts += set_error(token, load("__exc")) stmts += [ast.Raise(exc=load("__exc"))] # Apply visitor to each statement for stmt in stmts: self.visitor(stmt) return stmts
def visit_Translate(self, node, target): if node.msgid is not None: msgid = ast.Str(s=node.msgid) else: msgid = target return self.translate(node.node, target) + \ emit_translate(target, msgid, default=target)
def visit_BindChange(self, node): body = [] body.append(Comment("start model-binding")) new_stack = identifier("stack", id(node)) old_stack = self._current_stack body += template("CAPTURED_STACK = STACK.capture()", CAPTURED_STACK=new_stack, STACK=old_stack) self._current_stack = new_stack self._aliases.append(self._aliases[-1].copy()) inner = self.visit(node.node) self._aliases.pop() self._current_stack = old_stack on_change_name = identifier("on_change", id(node)) on_change_func = [ ast.FunctionDef(name=on_change_name, args=ast.arguments( args=[], defaults=(), ), body=inner) ] if node.model_name not in self._defined_models: raise TranslationError( "Cannot find bind model on current context.", node.model_name) body += on_change_func bind_attrs = ast.Tuple( elts=[ast.Str(s=attr) for attr in node.bind_attrs], ctx=ast.Load()) bind_ons = ast.Tuple(elts=[ast.Str(s=attr) for attr in node.bind_ons], ctx=ast.Load()) body += template("BIND_CHANGE(MODEL, BIND_ONS, BIND_ATTRS, ON_CHANGE)", BIND_CHANGE=Symbol(bind_change), MODEL=load(self._defined_models[node.model_name]), BIND_ATTRS=bind_attrs, BIND_ONS=bind_ons, ON_CHANGE=on_change_name) body.append(Comment("end model-binding")) return body
def visit_Module(self, node): body = [] body += template("__marker = object()") body += template("__filename = f", f=ast.Str(s=self.filename)) body += self.visit(node.program) return body
def _leave_assignment(self, names): for name in names: for stmt in template( "deleteitem(econtext, KEY, BACKUP, __marker)", deleteitem=Symbol(deleteitem), BACKUP=identifier("backup_%s" % name, id(names)), KEY=ast.Str(s=native_string(name)), ): yield stmt
def _convert_bool(self, target, s): """Converts value given by ``target`` to a string ``s`` if the target is a true value, otherwise ``None``. """ return emit_bool(target, ast.Str(s=s), default=self._default, default_marker=self._default_marker)
def visit_Attribute(self, node): f = node.space + "%s," + node.quote + "%s" + node.quote # Static attributes are just outputted directly if isinstance(node.expression, ast.Str): s = f % (node.name, node.expression.s) return template("STACK.a(N,S)", STACK=self._current_stack, N=ast.Str(s=node.name), S=ast.Str(s=node.expression.s)) target = identifier("attr", node.name) body = self._engine(node.expression, store(target)) return body + template( "if VALUE is not None: STACK.a(NAME, VALUE)", STACK=self._current_stack, NAME=ast.Str(s=node.name), VALUE=target, )
def set_error(token, exception): try: line, column = token.location filename = token.filename except AttributeError: line, column = 0, 0 filename = "<string>" string = safe_native(token) return template( "rcontext.setdefault('__error__', [])." "append((string, line, col, src, exc))", string=ast.Str(s=string), line=ast.Num(n=line), col=ast.Num(n=column), src=ast.Str(s=filename), sys=Symbol(sys), exc=exception, )
def _convert_text(self, target, body=None): """Converts value given by ``target`` to text.""" if self._char_escape: # This is a cop-out - we really only support a very select # set of escape characters other = set(self._char_escape) - self.supported_char_escape_set if other: for supported in '"', '\'', '': if supported in self._char_escape: quote = supported break else: raise RuntimeError("Unsupported escape set: %s." % repr(self._char_escape)) else: quote = '\0' entity = char2entity(quote or '\0') result = [] if body is not None: result += template("T = F", F=body, T=target) return result + emit_convert_and_escape( target, quote=ast.Str(s=quote), quote_entity=ast.Str(s=entity), default=self._default, default_marker=self._default_marker, ) return emit_convert( target, default=self._default, default_marker=self._default_marker, )
def __call__(self, node): name = node.id # Don't rewrite names that begin with an underscore; they are # internal and can be assumed to be locally defined. This # policy really should be part of the template program, not # defined here in the compiler. if isinstance(node.ctx, ast.Load): if self.validate_model and name.startswith("__model_"): name = node.id[8:] if not name in self.defined_models: err = ParseError( "Cannot find model", Token(name, pos=node.lineno, source=self.source, filename=self.filename)) raise err node.id = self.defined_models[name] return node if name.startswith('__') or name in self.internals: return node if name == "el": return Builtin("None") if isinstance(node.ctx, ast.Store): return store_econtext(name) aliased = self.aliases.get(name) if aliased is not None: return load(aliased) # If the name is a Python global, first try acquiring it from # the dynamic context, then fall back to the global. if name in self.builtins: return template( "getitem(econtext, key, name)", getitem=Symbol(getitem), mode="eval", key=ast.Str(s=name), name=load(name), ) # Otherwise, simply acquire it from the dynamic context. return load_econtext(name)
def visit_BindReplay(self, node): body = [] body.append(Comment("start replay-binding")) new_stack = identifier("stack", id(node)) old_stack = self._current_stack body += template("CAPTURED_STACK = STACK.capture()", CAPTURED_STACK=new_stack, STACK=old_stack) self._current_stack = new_stack self._aliases.append(self._aliases[-1].copy()) inner = self.visit(node.node) self._aliases.pop() self._current_stack = old_stack on_event_name = identifier("on_event", id(node)) on_event_func = [ ast.FunctionDef(name=on_event_name, args=ast.arguments( args=[], defaults=(), ), body=inner) ] body += on_event_func bindable = "__bindable" body += self._engine(node.expression, store(bindable)) events = ast.Tuple(elts=[ast.Str(s=attr) for attr in node.events], ctx=ast.Load()) body += template("BIND_REPLAY(BINDABLE, EVENTS, ON_EVENT)", BIND_REPLAY=Symbol(bind_replay), BINDABLE=bindable, EVENTS=events, ON_EVENT=on_event_name) body.append(Comment("end model-binding")) return body
def visit_OnError(self, node): body = [] fallback = identifier("__fallback") body += template("fallback = len(__stream)", fallback=fallback) self._enter_assignment((node.name, )) fallback_body = self.visit(node.fallback) self._leave_assignment((node.name, )) error_assignment = template( "econtext[key] = cls(__exc, rcontext['__error__'][-1][1:3])", cls=ErrorInfo, key=ast.Str(s=node.name), ) body += self.visit(node.node) return body
def visit_Text(self, node): #remove xml comment text = comment_re.sub("", node.value) return emit_node(ast.Str(s=text), STACK=self._current_stack)
def visit_Domain(self, node): backup = "__previous_i18n_domain_%d" % id(node) return template("BACKUP = __i18n_domain", BACKUP=backup) + \ template("__i18n_domain = NAME", NAME=ast.Str(s=node.name)) + \ self.visit(node.node) + \ template("__i18n_domain = BACKUP", BACKUP=backup)
def load_econtext(name): return template("econtext[KEY]", KEY=ast.Str(s=name), mode="eval")
def __call__(self, name, engine): """The strategy is to find possible expression strings and call the ``validate`` function of the parser to validate. For every possible starting point, the longest possible expression is tried first, then the second longest and so forth. Example 1: ${'expressions use the ${<expression>} format'} The entire expression is attempted first and it is also the only one that validates. Example 2: ${'Hello'} ${'world!'} Validation of the longest possible expression (the entire string) will fail, while the second round of attempts, ``${'Hello'}`` and ``${'world!'}`` respectively, validate. """ body = [] nodes = [] text = self.expression expr_map = {} translate = self.translate while text: matched = text m = self.regex.search(matched) if m is None: nodes.append(ast.Str(s=text)) break part = text[:m.start()] text = text[m.start():] if part: node = ast.Str(s=part) nodes.append(node) if not body: target = name else: target = store("%s_%d" % (name.id, text.pos)) while True: d = groupdict(m, matched) string = d["expression"] or d["variable"] or "" string = decode_htmlentities(string) try: compiler = engine.parse(string) body += compiler.assign_text(target) except ExpressionError: matched = matched[m.start():m.end() - 1] m = self.regex.search(matched) if m is None: raise else: break # If one or more expressions are not simple names, we # disable translation. if RE_NAME.match(string) is None: translate = False # if this is the first expression, use the provided # assignment name; otherwise, generate one (here based # on the string position) node = load(target.id) nodes.append(node) expr_map[node] = safe_native(string) text = text[len(m.group()):] if len(nodes) == 1: target = nodes[0] if translate and isinstance(target, ast.Str): target = template( "translate(msgid, domain=__i18n_domain)", msgid=target, mode="eval", ) else: if translate: formatting_string = "" keys = [] values = [] for node in nodes: if isinstance(node, ast.Str): formatting_string += node.s else: string = expr_map[node] formatting_string += "${%s}" % string keys.append(ast.Str(s=string)) values.append(node) target = template( "translate(msgid, mapping=mapping, domain=__i18n_domain)", msgid=ast.Str(s=formatting_string), mapping=ast.Dict(keys=keys, values=values), mode="eval") else: nodes = [ template("NODE", NODE=node, mode="eval") for node in nodes ] target = ast.BinOp(left=ast.Str(s="%s" * len(nodes)), op=ast.Mod(), right=ast.Tuple(elts=nodes, ctx=ast.Load())) body += [ast.Assign(targets=[name], value=target)] return body
def visit_Translate(self, node): """Translation. Visit items and assign output to a default value. Finally, compile a translation expression and use either result or default. """ body = [] # Track the blocks of this translation self._translations.append(set()) # Prepare new stream append = identifier("append", id(node)) stream = identifier("stream", id(node)) body += template("s = new_list", s=stream, new_list=LIST) + \ template("a = s.append", a=append, s=stream) # Visit body to generate the message body code = self.visit(node.node) swap(ast.Suite(body=code), load(append), "__append") body += code # Reduce white space and assign as message id msgid = identifier("msgid", id(node)) body += template("msgid = __re_whitespace(''.join(stream)).strip()", msgid=msgid, stream=stream) default = msgid # Compute translation block mapping if applicable names = self._translations[-1] if names: keys = [] values = [] for name in names: stream, append = self._get_translation_identifiers(name) keys.append(ast.Str(s=name)) values.append(load(stream)) # Initialize value body.insert( 0, ast.Assign(targets=[store(stream)], value=ast.Str(s=native_string("")))) mapping = ast.Dict(keys=keys, values=values) else: mapping = None # if this translation node has a name, use it as the message id if node.msgid: msgid = ast.Str(s=node.msgid) # emit the translation expression body += template( "STACK.t(translate(" "msgid, mapping=mapping, default=default, domain=__i18n_domain))", stack=self._current_stack, msgid=msgid, default=default, mapping=mapping) # pop away translation block reference self._translations.pop() return body
def visit_Repeat(self, node): # Used for loop variable definition and restore self._scopes.append(set()) # Variable assignment and repeat key for single- and # multi-variable repeat clause if node.local: contexts = "econtext", else: contexts = "econtext", "rcontext" for name in node.names: if name in COMPILER_INTERNALS_OR_DISALLOWED: raise TranslationError("Name disallowed by compiler.", name) if len(node.names) > 1: targets = [ ast.Tuple(elts=[ subscript(native_string(name), load(context), ast.Store()) for name in node.names ], ctx=ast.Store()) for context in contexts ] key = ast.Tuple(elts=[ast.Str(s=name) for name in node.names], ctx=ast.Load()) else: name = node.names[0] targets = [ subscript(native_string(name), load(context), ast.Store()) for context in contexts ] key = ast.Str(s=node.names[0]) repeat = identifier("__repeat", id(node)) index = identifier("__index", id(node)) iterator = identifier("__iterator", id(node)) assignment = [ast.Assign(targets=targets, value=load("__item"))] # Make repeat assignment in outer loop names = node.names local = node.local outer = self._engine(node.expression, store(iterator)) if local: outer[:] = list(self._enter_assignment(names)) + outer outer += template("REPEAT = econtext['repeat'](key, ITERATOR)", key=key, INDEX=index, REPEAT=repeat, ITERATOR=iterator) outer += template("INDEX = REPEAT.length", INDEX=index, REPEAT=repeat) # Set a trivial default value for each name assigned to make # sure we assign a value even if the iteration is empty for name in node.names: outer += [ ast.Assign(targets=[store_econtext(name)], value=load("None")) ] inner = template("REPEAT._next()", REPEAT=repeat) # Compute inner body inner += self.visit(node.node) # After each iteration, decrease the index inner += template("index -= 1", index=index) # For items up to N - 1, emit repeat whitespace inner += template("if INDEX > 0: STACK.t(WHITESPACE)", STACK=self._current_stack, INDEX=index, WHITESPACE=ast.Str(s=node.whitespace)) # Main repeat loop outer += [ ast.For( target=store("__item"), iter=load(iterator), body=assignment + inner, ) ] # Finally, clean up assignment if it's local if outer: outer += self._leave_assignment(names) self._scopes.pop() return outer