Example #1
0
def test_literal():
    literal = Literal("value", True, "prefixed")
    assert literal.value == "value"
    assert literal.string == "value"
    assert literal.css is True
    assert literal.prefixed == "prefixed"
    assert literal.node_name == "literal"
    clone = literal.clone()
    assert clone == literal
Example #2
0
 def handle(node):
     # returns `block` literal if mixin's block
     # is used as part of a property value
     if node.node_name == "block":
         literal = Literal("block")
         literal.lineno = node.lineno
         literal.column = node.column
         return literal
     else:
         return node
Example #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
Example #4
0
def test_lexer_boolean_unicode():
    lexer = Lexer("if true:\n  return U+abcdef;\n", {})
    tokens = [token for token in lexer]
    assert tokens[1] == Token("boolean", true, "", lineno=1, column=4)
    assert tokens[4] == Token(
        "literal", Literal("U+abcdef"), lineno=2, column=17
    )
Example #5
0
 def _mixin(self, items, dest, block):
     for item in items:
         checked = False
         media_passed = False
         if item.node_name == "returnnode":
             return
         elif item.node_name == "block":
             checked = True
             self._mixin(item.nodes, dest, block)
         elif item.node_name == "media":
             # fix link to the parent block
             parent_node = item.block.parent.node
             if parent_node and parent_node.node_name != "call":
                 item.block.parent = block
             media_passed = True
         if media_passed or item.node_name == "property":
             value = None
             if hasattr(item, "expr"):
                 value = item.expr
             # prevent `block` mixin recursion
             if (hasattr(item, "literal") and item.literal
                     and hasattr(value, "first")
                     and value.first().node_name == "block"):
                 value = unwrap(value)
                 value.nodes[0] = Literal(
                     "block",
                     lineno=self.parser.lineno,
                     column=self.parser.column,
                 )
         if not checked:
             dest.append(item)
Example #6
0
def contrast(top: Color = None, bottom: Color = None, evaluator=None):
    if not isinstance(top, Color) and not isinstance(bottom, Color):
        c = "" if isinstance(top, (Null, type(None))) else f"{top}"
        return Literal(f"contrast({c})")
    result = ObjectNode()
    if not bottom:
        bottom = RGBA(255, 255, 255, 1)
    assert_color(bottom)
    bottom = bottom.rgba()

    def contrast_function(top, bottom):
        if 1 > top.a:
            top = blend(top, bottom)
        l1 = luminosity(bottom).value + 0.05
        l2 = luminosity(top).value + 0.05
        ratio = l1 / l2

        if l2 > l1:
            ratio = 1 / ratio

        return round(ratio * 10) / 10

    if 1 <= bottom.a:
        result_ratio = Unit(contrast_function(top, bottom))
        result.set("ratio", result_ratio)
        result.set("error", Unit(0))
        result.set("min", result_ratio)
        result.set("max", result_ratio)
    else:
        on_black = contrast_function(top, blend(bottom, RGBA(0, 0, 0, 1)))
        on_white = contrast_function(top, blend(bottom, RGBA(255, 255, 255,
                                                             1)))
        the_max = max(on_black, on_white)

        def process_channel(top_channel, bottm_channel):
            return min(
                max(
                    0,
                    (top_channel - bottm_channel * bottom.a) / (1 - bottom.a),
                ),
                255,
            )

        closest = RGBA(
            process_channel(top.r, bottom.r),
            process_channel(top.g, bottom.g),
            process_channel(top.b, bottom.b),
            1,
        )

        the_min = contrast_function(top, blend(bottom, closest))

        result.set("ratio", Unit(stilus_round((the_min + the_max) * 50) / 100))
        result.set("error", Unit(stilus_round((the_max - the_min) * 50) / 100))
        result.set("min", Unit(the_min))
        result.set("max", Unit(the_max))

    return result
Example #7
0
def test_lexer_urlchars_important():
    lexer = Lexer('url("/images/foo.png")\n' "!important foo", {})
    tokens = [token for token in lexer]
    assert tokens[1] == Token(
        "string", String("/images/foo.png", '"'), lineno=1, column=5
    )
    assert tokens[4] == Token(
        "ident", Literal("!important"), lineno=2, column=1
    )
Example #8
0
def opposite_position(positions, evaluator=None):
    expr = []
    nodes = utils.unwrap(positions)
    for i, node in enumerate(nodes):
        utils.assert_string(node, f"position {i}")
        if node.string == "top":
            expr.append(Literal("bottom"))
        elif node.string == "bottom":
            expr.append(Literal("top"))
        elif node.string == "left":
            expr.append(Literal("right"))
        elif node.string == "right":
            expr.append(Literal("left"))
        elif node.string == "center":
            expr.append(Literal("center"))
        else:
            raise StilusError(f"invalid position {i}")
    return expr
Example #9
0
    def fn(url, enc=None):
        compiler = Compiler(url, {})
        url = compiler.visit(url)

        # parse the url
        url = urlparse(url[1:-1])
        ext = Path(url.path).suffix
        mime = None
        if ext and ext in mimes:
            mime = mimes[ext]
        hash = ""
        if url.fragment:
            hash = f"#{url.fragment}"
        literal = Literal(f'url("{url.geturl()}")')

        # not mime or absolute
        if not mime or url.scheme:
            return literal

        # lookup
        found = utils.lookup(url.path, _paths)
        if not found:
            # todo: add event management
            logging.warning(f"File not found; File {literal} could not be "
                            f"found, literal url retained!")
            return literal

        # read the url as a binary
        buf = open(found, "rb").read()

        # too large?
        if size_limit and len(buf) > size_limit:
            return literal

        if enc and enc.first().value.lower() == "utf8":
            encoding = "charset=utf-8"
            buf = re.sub(r"\s+", " ", buf.decode("utf-8"))
            result = urllib.parse.quote(buf, safe=" ?=:/").strip()
        else:
            encoding = "base64"
            result = f'{base64.b64encode(buf).decode("utf-8")}{hash}'

        return Literal(f'url("data:{mime};{encoding},{result}")')
Example #10
0
 def replace(node):
     if (node.node_name == "call" and hasattr(node, "function_name")
             and name == node.function_name):
         return Literal(
             "__CALL__",
             lineno=self.parser.lineno,
             column=self.parser.column,
         )
     if hasattr(node, "nodes") and node.nodes:
         node.nodes = [replace(n) for n in node.nodes]
     return node
Example #11
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
Example #12
0
def test_parser_parse_basic():
    parser = Parser("abc\n  color: red\n", {})
    root = parser.parse()
    assert root.node_name == "root"
    assert root.nodes[0].node_name == "group"
    selector = root.nodes[0].nodes[0]
    assert selector.segments[0] == Literal("abc")
    assert selector.block.parent == root
    assert selector.block.node.node_name == "group"
    property = selector.block.nodes[0]
    assert property.node_name == "property"
    assert len(property.segments) == 1
    assert property.segments[0] == Ident("color", null, lineno=2, column=3)
    assert len(property.expr.nodes) == 1
    assert property.expr.nodes[0] == Ident("red", null, lineno=2, column=10)
Example #13
0
File: s.py Project: jw/stilus
def s(fmt, *args, evaluator=None):
    options = evaluator.options if evaluator else {}
    fmt = unwrap(fmt).nodes[0]
    assert_string(fmt, "string")
    result = fmt.string
    results = []
    for arg in args:
        from stilus.visitor.evaluator import Evaluator

        if not isinstance(arg, Evaluator):
            results.append(Compiler(arg, options).compile())
    for r in results:
        result = result.replace("%s", r, 1)
    # add nulls for missed %s elements
    c = Compiler(null, options).compile()
    result = result.replace("%s", c)
    return Literal(result)
Example #14
0
def test_parser_selector():
    parser = Parser("abc\n  color: red\n", {})
    selector = parser.stmt_selector()
    assert selector.node_name == "group"
    assert type(selector) == Group
    assert len(selector.nodes) == 1
    assert selector.nodes[0].node_name == "selector"
    assert len(selector.nodes[0].segments) == 1
    assert selector.nodes[0].segments[0] == Literal("abc")
    block = selector.nodes[0].block
    assert block.node_name == "block"
    assert len(block.nodes) == 1
    property = block.nodes[0]
    assert property.node_name == "property"
    assert len(property.segments) == 1
    assert property.segments[0] == Ident("color", null, lineno=2, column=3)
    assert len(property.expr.nodes) == 1
    assert property.expr.nodes[0] == Ident("red", null, lineno=2, column=10)
Example #15
0
def base_convert(num, base, width=None, evaluator=None):
    if isinstance(num, Expression):
        num = unwrap(num).nodes[0]
    if isinstance(base, Expression):
        base = unwrap(base).nodes[0]
    if width and isinstance(width, Expression):
        width = unwrap(width).nodes[0]
    assert_type(num, "unit")
    assert_type(base, "unit")
    if width:
        assert_type(width, "unit")
    if width:
        width = width.value
    else:
        width = 2
    num = int(num.value)
    base = int(base.value)
    result = _convert(num, base)
    while len(result) < width:
        result = "0" + result
    return Literal(result)
Example #16
0
    def visit_group(self, group):
        stack = self.stack
        normalized = []
        # normalize interpolated selectors with a comma
        for selector in group.nodes:
            if selector.value:
                # do nothing
                if "," not in selector.value:
                    normalized = group.nodes
                    break
                # replace '\,' with ','
                if "\\" in selector.value:
                    selector.value = re.sub(r"\\,", ",", selector.value)
                    normalized = group.nodes
                    break
                parts = selector.value.split(",")
                root = True if selector.value[0] == "/" else False
                for i, part in enumerate(parts):
                    part = part.strip()
                    if root and "&" not in part:
                        part = "/" + part
                    s = Selector([Literal(part)])
                    s.value = part
                    s.block = group.block
                    normalized.append(s)
        group.nodes = normalized
        stack.append(normalized)

        selectors = utils.compile_selectors(stack, True)

        # add selectors for lookup
        for selector in selectors:
            self.selector_map[selector].append(group)

        # extensions
        self.extend(group, selectors)

        stack.pop()
        return group
Example #17
0
    def visit_call(self, call):
        fn = self.lookup(call.function_name)
        log.debug(f"Visiting {fn}; {call}...")
        if hasattr(fn, "param") and fn.param:
            log.debug(fn.params)

        # url()
        self.ignore_colors = "url" == call.function_name

        # variable function
        if fn and "expression" == fn.node_name:
            fn = fn.nodes[0]

        # not a function? try user-defined or built-ins
        if fn and "function" != fn.node_name:
            fn = self.lookup_function(call.function_name)

        # undefined function? render literal css
        if fn is None or fn.node_name != "function":
            ret = None
            if "calc" == self.unvendorize(call.function_name):
                if call.args.nodes:
                    if call.args.nodes[0]:
                        ret = Literal(
                            call.function_name + str(call.args.nodes[0]),
                            lineno=self.parser.lineno,
                            column=self.parser.column,
                        )
            else:
                ret = self.literal_call(call)
            self.ignore_colors = False
            return ret

        self.calling.append(call.function_name)

        # massive stack
        if len(self.calling) > 200:
            raise ParseError("Maximum stylus call stack size exceeded")

        # first node in expression
        if fn and "expression" == fn.function_name:
            fn = fn.first()

        # evaluate arguments
        self.result += 1
        args = self.visit(call.args)
        if hasattr(args, "map"):
            for key in args.map:
                args.map[key] = self.visit(args.map[key].clone())
        self.result -= 1

        log.debug(f"Going to invoke function: {fn} "
                  f"(builtin: {fn.builtin}).")
        if fn.builtin or (fn.function_name == "url" and hasattr(fn, "params")
                          and fn.params.__name__ == "resolver"):
            log.debug(f"{fn} is a built-in method ({fn.params}).")
            ret = self.invoke_builtin(fn.params, args)
        elif "function" == fn.node_name:  # user-defined
            # evaluate mixin block
            log.debug(f"{fn} is a regular method.")
            if call.block:
                log.debug(f"{fn} is a mixin.")
                call.block = self.visit(call.block)
            ret = self.invoke_function(fn, args, call.block)

        self.calling.pop()
        self.ignore_colors = False

        return ret
Example #18
0
def test_parser_selector_parts():
    parser = Parser("abc\n  color: red\n", {})
    assert parser.selector_parts() == deque([Literal("abc")])
    parser = Parser("abc def efg\n  color: red\n", {})
    assert parser.selector_parts() == deque(
        [
            Literal("abc"),
            Literal(" "),
            Literal("def"),
            Literal(" "),
            Literal("efg"),
        ]
    )
    parser = Parser("abc:\n  color: red\n", {})
    assert parser.selector_parts() == deque(
        [
            Literal("abc"),
            Literal(":"),
            Literal("color"),
            Literal(":"),
            Literal(" "),
            Literal("red"),
        ]
    )
Example #19
0
def test_operate():
    literal_1 = Literal("foo")
    literal_2 = Literal("bar")
    assert literal_1.operate("+", literal_2).string == "foobar"
Example #20
0
def test_lexer_escaped():
    lexer = Lexer("bar: 1 \\+ 2\n", {})
    tokens = [token for token in lexer]
    assert tokens[3] == Token("ident", Literal("+"), lineno=1, column=8)
Example #21
0
def test_hash():
    literal = Literal("foo")
    assert literal.hash() == "foo"
Example #22
0
File: unquote.py Project: jw/stilus
def unquote(string, evaluator=None):
    assert_string(string, "string")
    return Literal(string.string)
Example #23
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)
Example #24
0
def resolver(url, options=None, evaluator=None):
    frame = inspect.stack()[1]
    module = inspect.getmodule(frame[0])
    log.debug(f"Resolver called by {module.__file__} "
              f"({frame[0].f_lineno}).")
    log.debug(f'Functions: {evaluator.options["functions"]}.')
    if not options:
        options = {}

    original = url  # super ugly

    # compile the urls nodes and create a url from the result
    compiler = Compiler(url, options)
    filename = url.filename
    compiler.is_url = True
    url = urlparse(compiler.visit(url))

    # fixme: dirty hack
    if url.geturl() == "" and f"{original}" in ["'#'", "('#')"]:
        literal = Literal('url("#")')
    else:
        # regular call
        literal = Literal(f'url("{url.geturl()}")')
    path = url.path
    dest = options.get("dest", "")
    tail = ""

    # absolute or hash
    if url.scheme or not path or "/" == path[0]:
        return literal

    # check that a file exists
    if options.get("nocheck", True):
        _paths = options.get("paths", [])
        _paths.extend(evaluator.paths)
        path = utils.lookup(path, _paths)
        if not path:
            return literal
        else:
            path = Path(path)

    if evaluator.include_css and path.suffix == ".css":
        return Literal(url.geturl())

    if url.query or url.fragment:
        # fixme: extend url with a sep!
        if "#" in f"{original}":
            tail += "#"
        else:
            tail += "?"
    if url.query:
        tail += url.query
    if url.fragment:
        tail += url.fragment

    if dest and dest.suffix == ".css":
        dest = dest.parent

    if dest:
        first = dest.parents[1]
    else:
        first = Path(evaluator.filename).parent
    if options.get("nocheck", False):
        other = Path(filename).parent
    else:
        other = path

    res = other.relative_to(first.resolve())

    # use the first path of the options['paths'] list as cwd
    cwd = Path(evaluator.options.get("paths", ["."])[0])
    try:
        res = f"{res.resolve().relative_to(cwd)}{tail}"
    except ValueError:
        res = f"{res}{tail}"

    # todo: handle windows separators?

    return Literal(f'url("{res}")')
Example #25
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
Example #26
0
def test_coerce():
    literal_1 = Literal("first")
    literal_2 = Literal("second")
    assert literal_1.coerce(literal_2).string == "second"