def translate(self, expression, target): if isinstance(target, string_type): target = store(target) cached = self.cache.get(expression) if cached is not None: stmts = [ast.Assign(targets=[target], value=cached)] elif isinstance(expression, ast.expr): stmts = [ast.Assign(targets=[target], value=expression)] else: # The engine interface supports simple strings, which # default to expression nodes if isinstance(expression, string_type): expression = Value(expression, True) kind = type(expression).__name__ visitor = getattr(self, "visit_%s" % kind) stmts = visitor(expression, target) # Add comment target_id = getattr(target, "id", target) comment = Comment(" %r -> %s" % (expression, target_id)) stmts.insert(0, comment) return stmts
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 visit_Builtin(self, node, target): value = annotated(node) return [ast.Assign(targets=[target], value=value)]
def visit_Default(self, node, target): value = annotated(node.marker) return [ast.Assign(targets=[target], value=value)]
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_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
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