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 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 __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_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_DefineModel(self, node): #todo delete defined_models body = [] for alias, expression in node.models: name = identifier("model_%s" % alias, id(node)) self._defined_models[alias] = name body += self._engine(expression, store(name)) body += self.visit(node.node) return body
def visit_Content(self, node): name = "__content" body = self._engine(node.expression, store(name)) if node.translate: body += emit_translate(name, name) if node.is_structure: body += template("STACK.n(NAME)", STACK=self._current_stack, NAME=name) else: body += emit_convert(name) body += template("STACK.t(NAME)", STACK=self._current_stack, NAME=name) return body
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_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 visit_Cache(self, node): body = [] for expression in node.expressions: name = identifier("cache", id(expression)) target = store(name) # Skip re-evaluation if self._expression_cache.get(expression): continue body += self._engine(expression, target) self._expression_cache[expression] = target body += self.visit(node.node) return body
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_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_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_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
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
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