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 __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 __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_BindRepeat(self, node): body = [] body.append(Comment("start model-repeat-binding")) new_stack = identifier("stack", id(node)) old_stack = self._current_stack body += template("CAPTURED_STACK = STACK.capture_for_repeat()", CAPTURED_STACK=new_stack, STACK=old_stack) new_stack = identifier("stack", id(node)) self._current_stack = new_stack self._aliases.append(self._aliases[-1].copy()) model_name = identifier("model_%s" % node.alias, id(node)) self._defined_models[node.alias] = model_name inner_on_add = [] inner_on_add += self.visit(node.node) inner_on_add += template("return STACK.repeat_el", STACK=self._current_stack) on_add_name = identifier("on_add", id(node)) on_add_func = [ ast.FunctionDef(name=on_add_name, args=ast.arguments( args=[load(model_name)], defaults=(), ), body=inner_on_add) ] body += on_add_func collection = "__collection" initializer = self._engine(node.expression, store(collection)) initializer += template("BIND_REPEAT(COLLECTION, ON_ADD)", BIND_REPEAT=Symbol(bind_repeat), COLLECTION=load(collection), ON_ADD=on_add_name) body += initializer self._aliases.pop() self._current_stack = old_stack body.append(Comment("end model-repeat-binding")) return body
def visit_Name(self, node): """Translation name.""" if not self._translations: raise TranslationError("Not allowed outside of translation.", node.name) if node.name in self._translations[-1]: raise TranslationError("Duplicate translation name: %s." % node.name) self._translations[-1].add(node.name) body = [] # prepare new stream stream, append = self._get_translation_identifiers(node.name) body += template("s = new_list", s=stream, new_list=LIST) + \ template("a = s.append", a=append, s=stream) # generate code code = self.visit(node.node) swap(ast.Suite(body=code), load(append), "__append") body += code # output msgid text = Text('${%s}' % node.name) body += self.visit(text) # Concatenate stream body += template("stream = ''.join(stream)", stream=stream) return body
def visit_Name(self, node): """Translation name.""" if not self._translations: raise TranslationError( "Not allowed outside of translation.", node.name) if node.name in self._translations[-1]: raise TranslationError( "Duplicate translation name: %s." % node.name) self._translations[-1].add(node.name) body = [] # prepare new stream stream, append = self._get_translation_identifiers(node.name) body += template("s = new_list", s=stream, new_list=LIST) + \ template("a = s.append", a=append, s=stream) # generate code code = self.visit(node.node) swap(ast.Suite(body=code), load(append), "__append") body += code # output msgid text = Text('${%s}' % node.name) body += self.visit(text) # Concatenate stream body += template("stream = ''.join(stream)", stream=stream) return body
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_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_BindRepeat(self, node): body = [] body.append(Comment("start model-repeat-binding")) new_stack = identifier("stack", id(node)) old_stack = self._current_stack body += template("CAPTURED_STACK = STACK.capture_for_repeat()", CAPTURED_STACK=new_stack, STACK=old_stack) new_stack = identifier("stack", id(node)) self._current_stack = new_stack self._aliases.append(self._aliases[-1].copy()) model_name = identifier("model_%s" % node.alias, id(node)) self._defined_models[node.alias] = model_name inner_on_add = [] inner_on_add += self.visit(node.node) inner_on_add += template("return STACK.repeat_el", STACK=self._current_stack) on_add_name = identifier("on_add", id(node)) on_add_func = [ast.FunctionDef( name=on_add_name, args=ast.arguments( args=[load(model_name)], defaults=(), ), body=inner_on_add )] body += on_add_func collection = "__collection" initializer = self._engine(node.expression, store(collection)) initializer += template("BIND_REPEAT(COLLECTION, ON_ADD)", BIND_REPEAT=Symbol(bind_repeat), COLLECTION=load(collection), ON_ADD=on_add_name) body += initializer self._aliases.pop() self._current_stack = old_stack body.append(Comment("end model-repeat-binding")) return body
def translate(self, string, target): """ >>> from chameleon.tales import test >>> test(PathExpr('None')) is None True """ string = string.strip() if not string: return template("target = None", target=target) m = self.path_regex.match(string) if m is None: raise ExpressionError("Not a valid path-expression.", string) nocall, path = m.groups() # note that unicode paths are not allowed parts = str(path).split("/") components = self._find_translation_components(parts) base = parts[0] if not components: if len(parts) == 1 and (nocall or base == "None"): return template("target = base", base=base, target=target) else: components = () call = template( "traverse(base, econtext, call, path_items)", traverse=self.traverser, base=load(base), call=load(str(not nocall)), path_items=ast.Tuple(elts=components), mode="eval", ) return template("target = value", target=target, value=call)
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_Condition(self, node): target = "__condition" assignment = self._engine(node.expression, target) assert assignment for stmt in assignment: yield stmt body = self.visit(node.node) or [ast.Pass()] orelse = getattr(node, "orelse", None) if orelse is not None: orelse = self.visit(orelse) test = load(target) yield ast.If(test=test, body=body, orelse=orelse)
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 translate(self, string, target): """ >>> from chameleon.tales import test >>> test(PathExpr('None')) is None True """ string = string.strip() if not string: return template("target = None", target=target) m = self.path_regex.match(string) if m is None: raise ExpressionError("Not a valid path-expression.", string) nocall, path = m.groups() # note that unicode paths are not allowed parts = str(path).split('/') components = [] for part in parts[1:]: interpolation_args = [] def replace(match): start, end = match.span() interpolation_args.append( part[start + 1:end]) return "%s" while True: part, count = self.interpolation_regex.subn(replace, part) if count == 0: break if len(interpolation_args): component = template( "format % args", format=ast.Str(part), args=ast.Tuple( list(map(load, interpolation_args)), ast.Load() ), mode="eval") else: component = ast.Str(part) components.append(component) base = parts[0] if not components: if len(parts) == 1 and (nocall or base == 'None'): return template("target = base", base=base, target=target) else: components = () call = template( "traverse(base, econtext, call, path_items)", traverse=self.traverser, base=load(base), call=load(str(not nocall)), path_items=ast.Tuple(elts=components), mode="eval", ) return template("target = value", target=target, value=call)
def translate(self, string, target): """ >>> from chameleon.tales import test >>> test(PathExpr('None')) is None True """ string = string.strip() if not string: return template("target = None", target=target) m = self.path_regex.match(string) if m is None: raise ExpressionError("Not a valid path-expression.", string) nocall, path = m.groups() # note that unicode paths are not allowed parts = str(path).split('/') components = [] for part in parts[1:]: interpolation_args = [] def replace(match): start, end = match.span() interpolation_args.append(part[start + 1:end]) return "%s" while True: part, count = self.interpolation_regex.subn(replace, part) if count == 0: break if len(interpolation_args): component = template("format % args", format=ast.Str(part), args=ast.Tuple( list(map(load, interpolation_args)), ast.Load()), mode="eval") else: component = ast.Str(part) components.append(component) base = parts[0] if not components: if len(parts) == 1 and (nocall or base == 'None'): return template("target = base", base=base, target=target) else: components = () call = template( "traverse(base, econtext, call, path_items)", traverse=self.traverser, base=load(base), call=load(str(not nocall)), path_items=ast.Tuple(elts=components), mode="eval", ) return template("target = value", target=target, value=call)
def store_rcontext(name): name = native_string(name) return subscript(name, load("rcontext"), ast.Store())
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
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
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