def test_frame_str(): block = Block("hello", "there") frame = Frame(block) assert str(frame) == "[Frame [Empty scope]]" block.scope = False frame = Frame(block) assert str(frame) == "[Frame scope-less]"
def test_block(): block = Block(null, true) assert block.nodes == [] assert block.scope is True assert block.node == true assert block.parent == null assert block.node_name == "block" assert block.is_empty() is True
def test_block_append(): block = Block( Comment("comment", False, False), Block(String("hello"), String("There!")), ) block.append(Literal("Literal")) block.append(true) assert block.nodes == [Literal("Literal"), true] assert block.has_properties() is False assert block.has_media() is False assert block.is_empty() is False
def test_frame_lookup(): frame = Frame(Block("hello", "there")) scope = Scope() scope.add(Ident("foo", "bar")) frame._scope = scope assert frame.lookup("foo") == "bar" assert frame.lookup("unknown") is None
def test_group_block(): group = Group() selector = Selector(["abc", "def"]) selector.value = "$foo" group.append(selector) assert group.block is None block = Block("hello", "there") group.block = block assert group.block == block
def test_stack(): stack = Stack() block = Block(Ident("hello", "there"), Ident("foo", "bar")) assert stack.get_block_frame(block) is None frame = Frame(block) stack.append(frame) assert len(stack) == 1 assert stack.get_block_frame(block) == frame assert stack.current_frame() == frame
def test_property(): expression = Expression() block = Block("parent", Node) each = Each("foo", "fizz", expression, block) assert each.node_name == "each" assert each.value == "foo" assert each.key == "fizz" assert each.expr == expression assert each.block == block
def test_atblock(): atblock = Atblock() assert atblock.node_name == "atblock" assert atblock.block is None assert atblock.nodes == [] block = Block(Node(), Node()) block.append(null) block.append(Node()) block.append(true) atblock.block = block assert atblock.nodes == [null, Node(), true]
def test_block_properties(): block = Block( Comment("comment", False, False), Block(String("hello"), String("There!")), ) block.append(Property(["foo", "bar"], Expression())) assert block.has_properties() is True assert block.has_media() is False
def test_block_media(): block = Block( Comment("comment", False, False), Block(String("hello"), String("There!")), ) block.append(Media("fizz")) assert block.has_properties() is False assert block.has_media() is True
def test_frame_scopes(): # regular block (scope is True) and frame has no parent -> scope == Scope() block = Block(Ident("hello"), Ident("there")) frame = Frame(block) assert frame.scope() == Scope() assert frame.block == block assert frame.parent is None # irregular block (scope is False) frame has no parent... block = Block(Ident("hello"), Ident("there"), scope=False) frame = Frame(block) # ...raises TypeError since no parent with raises(TypeError): frame.scope() assert frame.block == block assert frame.parent is None # regular block (scope is True) and frame has a parent -> scope == Scope() block = Block(Ident("hello"), Ident("there")) parent = Block(Ident("fizz"), Ident("fuzz")) frame = Frame(block, parent) assert frame.scope() == Scope() assert frame.block == block assert frame.parent is parent
def bubble(self, node: Node): props = [] others = [] def filter_props(block): for node in block.nodes: node = self.visit(node) if node.node_name == "property": props.append(node) elif node.node_name == "block": filter_props(node) else: others.append(node) filter_props(node.block) if props: selector = Selector([Literal("&")]) selector.lineno = node.lineno selector.column = node.column selector.filename = node.filename selector.value = "&" group = Group(lineno=self.parser.lineno, column=self.parser.column) group.lineno = node.lineno group.column = node.column group.filename = node.filename block = Block(node.block, group) block.lineno = node.lineno block.column = node.column block.filename = node.filename for prop in props: block.append(prop) group.append(selector) group.block = block node.block.nodes = [] node.block.append(group) for other in others: node.block.append(other) group = self.closest_group(node.block) if group: node.group = group.clone() node.bubbled = True
def visit_import(self, imported): literal = False self.result += 1 path = self.visit(imported.path).first() node_name = "require" if imported.once else "import" self.result -= 1 log.debug(f"import {path}") # url() passed if hasattr(path, "function_name") and path.function_name == "url": if hasattr(imported, "once") and imported.once: raise StilusError("You cannot @require a url") return imported # ensure string if not hasattr(path, "string"): raise StilusError(f"@{node_name} string expected") name = path = path.string # absolute URL or hash m = re.match(r"(?:url\s*\(\s*)?[\'\"]?(?:#|(?:https?:)?\/\/)", path, re.I) if m: if imported.once: raise StilusError("You cannot @require a url") return imported # literal if path.endswith(".css") or '.css"' in path: literal = True if not imported.once and not self.include_css: return imported # support optional .styl if not literal and not path.endswith(".styl"): path += ".styl" # lookup found = utils.find(path, self.paths, self.filename) if not found: found = utils.lookup_index(name, self.paths, self.filename, parser=self.parser) # throw if import failed if not found: raise TypeError(f"failed to locate @{node_name} file in {path}" f" {self.paths}") block = Block(None, None, lineno=self.parser.lineno, column=self.parser.column) for f in found: block.append( self.import_file( imported, f, literal, lineno=self.parser.lineno, column=self.parser.column, )) return block
def import_file(self, node: Import, file, literal, lineno=1, column=1): log.debug(f"importing {file}; {self.import_stack}") # handling 'require' if node.once: if self.require_history.get(file, False): return null self.require_history[file] = True if literal and not self.include_css: return node # avoid overflows from reimporting the same file if file in self.import_stack: raise ImportError("import loop has been found") with open(file, "r") as f: source = f.read() # shortcut for empty files if not source.strip(): return null # expose imports node.path = file node.dirname = Path(file).parent # store modified time node.mtime = os.stat(file).st_mtime self.paths.append(str(node.dirname)) if "_imports" in self.options: self.options["_imports"].append(node.clone()) # parse the file self.import_stack.append(file) filename_node.current_filename = file if literal: re.sub("\n\n?", "\n", source) literal = Literal(source, lineno=self.parser.lineno, column=self.parser.column) literal.lineno = 1 literal.column = 1 if not self.resolve_url: return literal # create block block = Block(None, None, lineno=lineno, column=column) block.filename = file # parse merged = {} merged.update(self.options) merged.update({"root": block}) parser = Parser(source, merged) try: block = parser.parse() except Exception: line = parser.lexer.lineno column = parser.lexer.column if literal and self.include_css and self.resolve_url: self.warn(f"ParseError: {file}:{line}:{column}. " f"This file is included as-is") return literal else: raise ParseError( "Issue when parsing an imported file", filename=file, lineno=line, column=column, input=source, ) # evaluate imported 'root' block = block.clone(self.get_current_block()) block.parent = self.get_current_block() block.scope = False ret = self.visit(block) self.import_stack.pop() if not self.resolve_url: # or not self.resolve_url no_check: self.paths.pop() return ret
def invoke_function(self, fn, args, content): block = Block( fn.block.parent, None, lineno=self.parser.lineno, column=self.parser.column, ) # clone the function body to prevent mutation of subsequent calls body = fn.block.clone(block) mixin_block = self.stack.current_frame().block # new block scope self.stack.append(Frame(block)) scope = self.get_current_scope() # normalize arguments if args.node_name != "arguments": expr = Expression() expr.append(args) args = Arguments.from_expression(expr) # arguments scope.add(Ident("arguments", args)) # mixin scope introspection bn = mixin_block.node_name if self.result: scope.add(Ident("mixin", false)) else: scope.add( Ident( "mixin", String( bn, lineno=self.parser.lineno, column=self.parser.column, ), )) # current property if self.property: prop = self.property_expression(self.property, fn.function_name) scope.add( Ident( "current-property", prop, lineno=self.parser.lineno, column=self.parser.column, )) else: scope.add( Ident( "current-property", null, lineno=self.parser.lineno, column=self.parser.column, )) # current call stack expr = Expression(lineno=self.parser.lineno, column=self.parser.column) for call in reversed(self.calling[:-1]): expr.append( Literal(call, lineno=self.parser.lineno, column=self.parser.column)) scope.add( Ident( "called-from", expr, lineno=self.parser.lineno, column=self.parser.column, )) # inject arguments as locals # todo: rewrite this! i = 0 for index, node in enumerate(fn.params.nodes): # rest param support if node.rest: node.value = Expression(lineno=self.parser.lineno, column=self.parser.column) for n in args.nodes[i:]: node.value.append(n) node.value.preserve = True node.value.is_list = args.is_list else: # argument default support # in dict? arg = args.map.get(node.name) # next node? if not arg: if hasattr(args, "nodes") and i < len(args.nodes): arg = args.nodes[i] i += 1 node = node.clone() if arg is not None: if hasattr(arg, "is_empty") and arg.is_empty(): args.nodes[i - 1] = self.visit(node) else: node.value = arg else: args.append(node.value) # required argument not satisfied if not node.value: raise TypeError(f'argument "{node}" required for {fn}') scope.add(node) # mixin block if content: scope.add(Ident("block", content, True)) # invoke return self.invoke(body, True, fn.filename)
def test_empty_stack(): stack = Stack() assert len(stack) == 0 assert stack.current_frame() is None block = Block(Ident("hello", "there"), Ident("foo", "bar")) assert stack.get_block_frame(block) is None
def test_frame_creation(): frame = Frame(Block(Ident("hello"), Ident("there"))) assert frame.scope() == Scope() # assert frame.block == Block(Ident('hello'), Ident('there')) assert frame.parent is None
def visit_block(self, block: Block): # noqa: C901 if self.compress: separator = "" else: separator = "\n" if block.has_properties() and not block.lacks_rendered_selectors: last_property_index = 0 if self.compress: for i, node in reversed(list(enumerate(block.nodes))): if node.node_name == "property": last_property_index = i break needs_brackets = self.need_brackets(block.node) if needs_brackets: self.buf += self.out("{" if self.compress else " {\n") self.indents += 1 for i, node in enumerate(block.nodes): self.last = last_property_index == i if node.node_name in [ "null", "expression", "function", "group", "block", "unit", "media", "keyframes", "atrule", "supports", ]: continue elif ( node.node_name == "comment" and node.inline and not self.compress ): self.buf = self.buf[0:-1] self.buf += self.out(f" {self.visit(node)}\n", node) elif node.node_name == "property": ret = self.visit(node) + separator self.buf += ret if self.compress else self.out(ret, node) else: self.buf += self.out(self.visit(node) + separator, node) if needs_brackets: self.indents -= 1 self.buf += self.out(self.indent() + "}" + separator) # nesting index = 0 while index < len(block.nodes): node = block.nodes[index] if node.node_name in ["group", "block", "keyframes"]: if self.linenos: self.debug_info(node) self.visit(node) elif node.node_name in ["media", "import", "atrule", "supports"]: self.visit(node) elif node.node_name == "comment": if not node.suppress: self.buf += self.out( self.indent() + self.visit(node) + "\n", node ) elif node.node_name in ["charset", "literal", "namespace"]: self.buf += self.out(self.visit(node) + "\n", node) index += 1