Esempio n. 1
0
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]"
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
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]
Esempio n. 9
0
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
Esempio n. 10
0
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
Esempio n. 11
0
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
Esempio n. 12
0
    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
Esempio n. 13
0
    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
Esempio n. 14
0
    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
Esempio n. 15
0
    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)
Esempio n. 16
0
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
Esempio n. 17
0
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
Esempio n. 18
0
    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