Beispiel #1
0
def UnaryExpression(tokenizer, staticContext):
    tokenType = tokenizer.get(True)

    if tokenType == "not":
        node = Node.Node(tokenizer, tokenType)
        child = UnaryExpression(tokenizer, staticContext)

        # Support for prefixes with "not" e.g. "!important"
        if child.type == "identifier":
            child.value = "!%s" % child.value
            return child
        else:
            node.append(child)

    elif tokenType in ("plus", "minus"):
        if tokenType == "plus":
            tokenType = "unary_plus"
        elif tokenType == "minus":
            tokenType = "unary_minus"

        node = Node.Node(tokenizer, tokenType)
        node.append(UnaryExpression(tokenizer, staticContext))

    else:
        tokenizer.unget()
        node = MemberExpression(tokenizer, staticContext)

    return node
Beispiel #2
0
def __extendContent(node, call, targetBlock, stopCombineAt):
    """
    Builds up a list of selector/@media/@support to insert after
    the extend to produce the @content sections on the intended selectors.
    """

    for child in reversed(list(node)):
        if child:
            __extendContent(child, call, targetBlock, stopCombineAt)

    if node.type == "content" and hasattr(call, "rules"):
        # Extends support @content as well. In this case we produce a new selector
        # which matches the position of the content section and append it after
        # the original extended mixin on return

        Console.debug("Inserting content section into new virtual selector")

        selector, media, supports = Util.combineSelector(node,
                                                         stop=stopCombineAt)

        selectorNode = Node.Node(type="selector")
        selectorNode.name = selector

        selectorNode.append(copy.deepcopy(call.rules), "rules")

        # Support @supports
        if supports:
            supportsNode = Node.Node(type="supports")
            supportsNode.name = supports

            supportsBlock = Node.Node(type="block")
            supportsBlock.append(selectorNode)
            supportsNode.append(supportsBlock, "rules")

            # Update reference
            selectorNode = supportsNode

        # Support @media
        if media:
            mediaNode = Node.Node(type="media")
            mediaNode.name = media

            mediaBlock = Node.Node(type="block")
            mediaBlock.append(selectorNode)
            mediaNode.append(mediaBlock, "rules")

            # Update reference
            selectorNode = mediaNode

        # Insert selectorNode (or media node or supports node when updated)
        # If all kinds are used we should have the following structure:
        # @media->@supports->selector
        targetBlock.append(selectorNode)
Beispiel #3
0
def KeyFrames(tokenizer, staticContext):
    """
    Supports e.g.:

    @keyframes fade{
      from, 10%{
        background-color: #000000;
      }

      100%{
        background-color: #FFFFFF;
      }
    }
    """

    node = Node.Node(tokenizer, "keyframes")
    node.vendor = Util.extractVendor(tokenizer.token.value)

    # Use param as name on keyframes
    tokenizer.get()
    node.name = tokenizer.token.value

    tokenizer.mustMatch("left_curly")

    while tokenizer.get() != "right_curly":

        # Parse frame as block
        frameNode = Node.Node(tokenizer, "frame")
        token = tokenizer.token
        frameNode.value = "%s%s" % (token.value, getattr(token, "unit", ""))
        node.append(frameNode)

        # Process comma separated values for
        while True:
            if tokenizer.peek() != "comma":
                break
            else:
                tokenizer.mustMatch("comma")

                # Next one is our next value
                tokenizer.get()
                token = tokenizer.token
                frameNode.value += ",%s%s" % (token.value,
                                              getattr(token, "unit", ""))

        # Next process content of selector
        blockNode = Block(tokenizer, staticContext)
        frameNode.append(blockNode, "rules")

    return node
Beispiel #4
0
def AddExpression(tokenizer, staticContext):
    node = MultiplyExpression(tokenizer, staticContext)
    if node.type == "identifier":
        return node

    while tokenizer.match("plus") or tokenizer.match("minus"):
        # Whether there was skipped spaces before
        skippedA = tokenizer.skippedSpaces or tokenizer.skippedLineBreaks

        # ...but not after the plus/minus token
        peek = tokenizer.peek()
        skippedB = tokenizer.skippedSpaces or tokenizer.skippedLineBreaks

        # ... then do not interpret as plus/minus expression but as unary prefix
        if skippedA and not skippedB:
            tokenizer.unget()
            return node

        # Build real plus/minus node
        childNode = Node.Node(tokenizer)
        childNode.append(node)

        express = MultiplyExpression(tokenizer, staticContext)
        if express.type == "identifier":
            raise ParseError("Invalid expression", tokenizer)

        childNode.append(express)
        node = childNode

    return node
Beispiel #5
0
def MemberExpression(tokenizer, staticContext):
    node = PrimaryExpression(tokenizer, staticContext)

    while True:
        tokenType = tokenizer.get()

        if tokenType == "end":
            break

        # system calls
        elif tokenType == "left_paren":

            if node.type == "identifier":
                childNode = Node.Node(tokenizer, "function")
                childNode.name = node.value

                # Special processing of URL commands
                if node.value == "url":
                    childNode.append(UrlArgumentList(tokenizer, staticContext),
                                     "params")
                else:
                    childNode.append(CssArgumentList(tokenizer, staticContext),
                                     "params")

            elif node.type == "command":
                if node.name == "raw":
                    childNode = RawArgument(tokenizer, staticContext)
                elif node.name == "expr":
                    childNode = ExpressionArgument(tokenizer, staticContext)
                else:
                    childNode = Node.Node(tokenizer, "command")
                    childNode.name = node.name
                    childNode.append(ArgumentList(tokenizer, staticContext),
                                     "params")

            else:
                raise ParseError(
                    "Unsupported mixin include in expression statement",
                    tokenizer)

        else:
            tokenizer.unget()
            return node

        node = childNode

    return node
Beispiel #6
0
def FontFace(tokenizer, staticContext):

    # Like a selector but store as a different type

    node = node = Node.Node(tokenizer, "fontface")
    childNode = Block(tokenizer, staticContext)
    node.append(childNode, "rules")

    return node
Beispiel #7
0
def castNativeToNode(value):
    if value is True:
        node = Node.Node(type="true")
    elif value is False:
        node = Node.Node(type="false")
    elif isinstance(value, str):
        node = Node.Node(type="string")
        node.value = value
    elif isinstance(value, (float, int)):
        node = Node.Node(type="number")
        node.value = value
    elif value is None:
        node = Node.Node(type="null")
    else:
        raise ResolverError("Could not transform field %s=%s to style value" %
                            (name, value))

    return node
Beispiel #8
0
def UrlArgumentList(tokenizer, staticContext):
    node = Node.Node(tokenizer, "list")

    if tokenizer.match("right_paren", True):
        return node

    url = ""
    while True:
        tokenType = tokenizer.get()

        if tokenType == "right_paren":
            break
        elif tokenType == "string":
            token = tokenizer.token
            url += token.value
        elif tokenType == "number":
            token = tokenizer.token
            url += "%s%s" % (token.value, getattr(token, "unit", ""))
        elif tokenType == "div":
            url += "/"
        elif tokenType == "dot":
            url += "."
        elif tokenType == "identifier":
            token = tokenizer.token
            url += token.value
        elif tokenType == "variable":
            # Fast path when variable present
            token = tokenizer.token
            var = Node.Node(tokenizer)
            var.name = token.value
            node.append(var)
            tokenizer.mustMatch("right_paren")
            return node
        else:
            token = tokenizer.token
            raise ParseError(
                "Invalid token in URL parameter: Type = %s ; Value = %s" %
                (token.type, getattr(token, "value", None)), tokenizer)

    urlParam = Node.Node(tokenizer, "identifier")
    urlParam.value = url
    node.append(urlParam)

    return node
Beispiel #9
0
def OrExpression(tokenizer, staticContext):
    node = AndExpression(tokenizer, staticContext)

    while tokenizer.match("or"):
        childNode = Node.Node(tokenizer, "or")
        childNode.append(node)
        childNode.append(AndExpression(tokenizer, staticContext))
        node = childNode

    return node
Beispiel #10
0
def ExpressionArgument(tokenizer, staticContext):
    if tokenizer.match("right_paren", True):
        raise ParseError("Expected expression", tokenizer)

    node = Node.Node(tokenizer, "expr")
    node.append(AddExpression(tokenizer, staticContext))

    tokenizer.mustMatch("right_paren")

    return node
Beispiel #11
0
def AndExpression(tokenizer, staticContext):
    node = EqualityExpression(tokenizer, staticContext)

    while tokenizer.match("and"):
        childNode = Node.Node(tokenizer, "and")
        childNode.append(node)
        childNode.append(EqualityExpression(tokenizer, staticContext))
        node = childNode

    return node
Beispiel #12
0
def executeCommand(node, profile):
    command = node.name

    params = []
    for param in node.params:
        # Variable not yet processed (possible e.g. during permutation apply)
        if param.type == "variable":
            return node
        elif param.type == "unary_minus":
            value = -param[0].value
        elif hasattr(param, "value"):
            value = param.value
        else:
            raise Exception(
                "Invalid value for command execution: Type is %s in %s!" %
                (param.type, param.line))

        params.append(value)

    # Catch simple casting requests
    if command in ("identifier", "string", "number"):
        if len(params) != 1:
            raise Exception("Invalid number of arguments for type casting!")

        repl = Node.Node(type=command)
        repl.value = params[0]

        return repl

    # print("Looking for command: %s(%s)" % (command, ", ".join([str(param) for param in params])))
    result, restype = profile.executeCommand(command, params)

    if restype == "px":
        repl = Node.Node(type="number")
        repl.value = result
        repl.unit = restype

    elif restype == "url":
        repl = Node.Node(type="function")
        repl.name = "url"
        listChild = Node.Node(type="list")
        repl.append(listChild, "params")
        valueChild = Node.Node(type="identifier")
        valueChild.value = result
        listChild.append(valueChild)

    elif restype == "number":
        repl = Node.Node(type="number")
        repl.value = result

    else:
        repl = Node.Node(type="identifier")
        repl.value = result

    return repl
Beispiel #13
0
def PrimaryExpression(tokenizer, staticContext):
    tokenType = tokenizer.get(True)

    if tokenType == "function":
        node = FunctionDefinition(tokenizer, staticContext, False,
                                  "expressed_form")

    elif tokenType == "left_paren":
        # ParenExpression does its own matching on parentheses, so we need to unget.
        tokenizer.unget()
        node = ParenExpression(tokenizer, staticContext)
        node.parenthesized = True

    elif tokenType == "variable":
        node = Node.Node(tokenizer, tokenType)
        node.name = tokenizer.token.value

    elif tokenType == "command":
        node = Node.Node(tokenizer, tokenType)
        node.name = tokenizer.token.value

    elif tokenType in [
            "null", "true", "false", "identifier", "number", "string", "div"
    ]:
        node = Node.Node(tokenizer, tokenType)
        if tokenType in ("identifier", "string", "number"):
            node.value = tokenizer.token.value
        if tokenType == "number" and hasattr(tokenizer.token, "unit"):
            node.unit = tokenizer.token.unit
        if tokenType == "string":
            node.quote = tokenizer.token.quote
        if tokenType == "div":
            node.type = "slash"

    else:
        raise ParseError("Missing operand. Found type: %s" % tokenType,
                         tokenizer)

    return node
Beispiel #14
0
def Supports(tokenizer, staticContext):
    node = Node.Node(tokenizer, "supports")

    tokenType = tokenizer.get()
    test = ""
    requiresSpace = False

    while tokenType != "left_curly":
        token = tokenizer.token

        if tokenType == "identifier":
            if requiresSpace:
                test += " "
            test += token.value
            requiresSpace = True
        elif tokenType == "colon":
            test += ":"
            requiresSpace = False
        elif tokenType == "left_paren":
            if requiresSpace:
                test += " "
            test += "("
            requiresSpace = False
        elif tokenType == "right_paren":
            test += ")"
            requiresSpace = True
        elif tokenType == "string":
            if requiresSpace:
                test += " "
            test += ascii_encoder.encode(token.value)
            requiresSpace = True
        elif tokenType == "number":
            if requiresSpace:
                test += " "
            test += "%s%s" % (token.value, getattr(token, "unit", ""))
            requiresSpace = True
        else:
            raise ParseError("Unsupported supports token %s" % tokenType,
                             tokenizer)

        tokenType = tokenizer.get()

    # Split at commas, but ignore any white spaces (trim single selectors)
    node.name = test

    # Next process content of selector
    tokenizer.unget()
    childNode = Block(tokenizer, staticContext)
    node.append(childNode, "rules")

    return node
Beispiel #15
0
def EqualityExpression(tokenizer, staticContext):
    node = RelationalExpression(tokenizer, staticContext)

    while tokenizer.match("eq") or tokenizer.match("ne"):
        childNode = Node.Node(tokenizer)
        childNode.append(node)

        express = RelationalExpression(tokenizer, staticContext)
        if express.type == "identifier":
            raise ParseError("Invalid expression", tokenizer)

        childNode.append(express)
        node = childNode

    return node
Beispiel #16
0
def CssArgumentList(tokenizer, staticContext):
    node = Node.Node(tokenizer, "list")

    if tokenizer.match("right_paren", True):
        return node

    while True:
        collection = Node.Node(tokenizer, "list")

        while True:
            childNode = AssignExpression(tokenizer, staticContext)
            collection.append(childNode)

            # Comma ends the collection + Right paren ends the list
            if tokenizer.peek() in ("comma", "right_paren"):
                break

        node.append(collection)
        if not tokenizer.match("comma"):
            break

    tokenizer.mustMatch("right_paren")

    return node
Beispiel #17
0
def ArgumentList(tokenizer, staticContext):
    node = Node.Node(tokenizer, "list")

    if tokenizer.match("right_paren", True):
        return node

    while True:
        childNode = AssignExpression(tokenizer, staticContext)
        node.append(childNode)

        if not tokenizer.match("comma"):
            break

    tokenizer.mustMatch("right_paren")

    return node
Beispiel #18
0
def Page(tokenizer, staticContext):
    """
    Supports e.g.:

    @page{
      margin: 1cm;
    }

    @page :first{
      margin: 5cm 2cm;
    }
    """

    node = Node.Node(tokenizer, "page")

    tokenType = tokenizer.get()
    selector = ""
    requiresSpace = False

    while tokenType != "left_curly":
        token = tokenizer.token

        if tokenType == "identifier":
            if requiresSpace:
                selector += " "
            selector += token.value
            requiresSpace = True
        elif tokenType == "colon":
            selector += ":"
            requiresSpace = False
        else:
            raise ParseError("Unsupported page selector token %s" % tokenType,
                             tokenizer)

        tokenType = tokenizer.get()

    # Set page selector as name
    node.name = selector

    # Next process content of selector
    tokenizer.unget()
    childNode = Block(tokenizer, staticContext)
    node.append(childNode, "rules")

    return node
Beispiel #19
0
def MultiplyExpression(tokenizer, staticContext):
    node = UnaryExpression(tokenizer, staticContext)
    if node.type == "identifier":
        return node

    while tokenizer.match("mul") or tokenizer.match("div") or tokenizer.match(
            "mod"):
        childNode = Node.Node(tokenizer)
        childNode.append(node)

        express = UnaryExpression(tokenizer, staticContext)
        if express.type == "identifier":
            raise ParseError("Invalid expression", tokenizer)

        childNode.append(express)
        node = childNode

    return node
Beispiel #20
0
def AssignExpression(tokenizer, staticContext):
    comments = tokenizer.getComments()
    node = Node.Node(tokenizer, "assign")
    lhs = OrExpression(tokenizer, staticContext)
    addComments(lhs, None, comments)

    if not tokenizer.match("assign"):
        return lhs

    if lhs.type == "variable":
        pass
    else:
        raise ParseError("Bad left-hand side of assignment", tokenizer)

    node.assignOp = tokenizer.token.assignOp
    node.append(lhs)
    node.append(AssignExpression(tokenizer, staticContext))

    return node
Beispiel #21
0
def Charset(tokenizer, staticContext):
    tokenType = tokenizer.get()

    Console.warn("CSS @charset %s ", tokenType)

    if tokenType != "string":
        raise ParseError(
            "Invalid @charset declaration. Requires the encoding being a string!",
            tokenizer)

    encoding = tokenizer.token.value

    if encoding.lower() != "utf-8":
        raise ParseError("Jasy is not able to process non UTF-8 stylesheets!",
                         tokenizer)

    Console.warn("Found unnecessary @charset definition for encoding %s",
                 encoding)

    return Node.Node(tokenizer, "block")
Beispiel #22
0
def RelationalExpression(tokenizer, staticContext):
    # Uses of the in operator in shiftExprs are always unambiguous,
    # so unset the flag that prohibits recognizing it.
    node = AddExpression(tokenizer, staticContext)
    if node.type == "identifier":
        return node

    while tokenizer.match("lt") or tokenizer.match("le") or tokenizer.match(
            "ge") or tokenizer.match("gt"):
        childNode = Node.Node(tokenizer)
        childNode.append(node)

        express = AddExpression(tokenizer, staticContext)
        if express.type == "identifier":
            raise ParseError("Invalid expression", tokenizer)

        childNode.append(express)
        node = childNode

    return node
Beispiel #23
0
def Expression(tokenizer, staticContext):
    """
    Top-down expression parser for stylestyles.
    """

    node = AssignExpression(tokenizer, staticContext)

    if tokenizer.match("comma"):
        childNode = Node.Node(tokenizer, "comma")
        childNode.append(node)
        node = childNode

        while True:
            childNode = node[len(node) - 1]
            node.append(AssignExpression(tokenizer, staticContext))

            if not tokenizer.match("comma"):
                break

    return node
Beispiel #24
0
def Statements(tokenizer, staticContext):
    """Parses a list of Statements."""

    node = Node.Node(tokenizer, "block")
    staticContext.blockId += 1
    staticContext.statementStack.append(node)

    prevNode = None
    while not tokenizer.done() and tokenizer.peek(True) != "right_curly":
        comments = tokenizer.getComments()
        childNode = Statement(tokenizer, staticContext)

        # Ignore semicolons in AST
        if childNode.type != "semicolon":
            node.append(childNode)

        prevNode = childNode

    staticContext.statementStack.pop()

    return node
Beispiel #25
0
def __resolveMixin(mixin, params):
    """Returns a clone of the given mixin and applies optional parameters to it."""

    # Generate random prefix for variables and parameters
    chars = string.ascii_letters + string.digits
    prefix = ''.join(random.sample(chars * 6, 6))

    # Data base of all local variable and parameter name mappings
    variables = {}

    # Generate full recursive clone of mixin rules
    clone = copy.deepcopy(mixin.rules)

    if hasattr(mixin, "params"):
        for pos, param in enumerate(mixin.params):
            # We have to copy over the parameter value as a local variable declaration
            paramAsDeclaration = Node.Node(type="declaration")

            if param.type == "variable":
                paramAsDeclaration.name = param.name
            elif param.type == "assign" and param[0].type == "variable":
                paramAsDeclaration.name = param[0].name
            else:
                raise Exception(
                    "Unsupported param structure for mixin resolver at line %s! Expected type variable or assignment and got: %s!"
                    % (mixin.line, param.type))

            # Copy over actual param value
            if len(params) > pos:
                paramAsDeclaration.append(copy.deepcopy(params[pos]),
                                          "initializer")
            elif param.type == "assign" and param[0].type == "variable":
                paramAsDeclaration.append(copy.deepcopy(param[1]),
                                          "initializer")

            clone.insert(0, paramAsDeclaration)

    __renameRecurser(clone, variables, prefix)

    return clone
Beispiel #26
0
def Property(tokenizer, staticContext):
    """
    Parses all CSS properties e.g.

    - background: red
    - font: 12px bold Arial;

    """

    node = Node.Node(tokenizer, "property")
    node.name = ""

    # Start from the beginning to support mixed identifiers/variables easily
    tokenizer.unget()

    while tokenizer.match("variable") or tokenizer.match("identifier"):
        token = tokenizer.token
        if token.type == "variable":
            node.name += "${%s}" % token.value

            if hasattr(node, "dynamic"):
                node.dynamic.add(token.value)
            else:
                node.dynamic = set([token.value])

        else:
            node.name += token.value

    if not tokenizer.mustMatch("colon"):
        raise ParseError("Invalid property definition", tokenizer)

    # Add all values until we find a semicolon or right curly
    while tokenizer.peek() not in ("semicolon", "right_curly"):
        childNode = ValueExpression(tokenizer, staticContext)

        node.append(childNode)

    return node
Beispiel #27
0
def Root(tokenizer, staticContext):
    """
    Supports e.g.:

    h1{
      font-size: 20px;

      @root html.desktop &{
        font-size: 30px;
      }
    }

    .date{
      color: black;
      background: white;

      @root{
        &__dialog{
          position: absolute;
        }
      }
    }
    """

    node = Node.Node(tokenizer, "root")

    tokenType = tokenizer.get()

    if tokenType != "left_curly":
        node.append(Selector(tokenizer, staticContext))
    else:
        tokenizer.unget()
        childNode = Block(tokenizer, staticContext)
        node.append(childNode, "rules")

    return node
Beispiel #28
0
    def __flatter(node, dest):
        """Moves all selectors to the top tree node while keeping media queries intact and/or making them CSS2
        compatible (regarding structure)"""

        process = node.type in ("selector", "mixin", "media", "supports")

        # Insert all children of top-level nodes into a helper element first
        # This is required to place mixins first, before the current node and append
        # all remaining nodes afterwards
        if process:
            chdest = Node.Node(None, "helper")
        else:
            chdest = dest

        # Process children first
        if len(node) > 0:
            for child in list(node):
                if child is not None:
                    __flatter(child, chdest)

        # Filter out empty nodes from processing
        if process and hasattr(node, "rules") and len(node.rules) > 0:

            # Combine selector and/or media query
            combinedSelector, combinedMedia, combinedSupports = Util.combineSelector(
                node)

            if node.type == "selector":
                node.name = combinedSelector
            elif node.type == "mixin":
                node.selector = combinedSelector
            elif node.type == "media":
                pass
            elif node.type == "supports":
                pass

            if (combinedMedia or
                    combinedSupports) and node.type in ("selector", "mixin"):

                if combinedSupports:
                    # Dynamically create matching media query
                    supportsNode = Node.Node(None, "supports")
                    supportsNode.name = combinedSupports

                    supportsBlock = Node.Node(None, "block")
                    supportsNode.append(supportsBlock, "rules")

                    supportsBlock.append(node)
                    node = supportsNode

                if combinedMedia:
                    # Dynamically create matching media query
                    mediaNode = Node.Node(None, "media")
                    mediaNode.name = combinedMedia

                    mediaBlock = Node.Node(None, "block")
                    mediaNode.append(mediaBlock, "rules")

                    mediaBlock.append(node)
                    node = mediaNode

            elif node.type == "media" or node.type == "supports":
                # Insert direct properties into new selector block
                # Goal is to place in this structure: @media->@supports->selector

                # Update media query of found media queries as it might
                # contain more than the local one (e.g. queries in parent nodes)
                if node.type == "media":
                    node.name = combinedMedia

                # Update support query of found supports query as it might
                # contain more than the local one (e.g. queries in parent nodes)
                elif node.type == "supports":
                    node.name = combinedSupports

                # Create new selector node where we move all rules into
                selectorNode = Node.Node(None, "selector")
                selectorNode.name = combinedSelector

                selectorBlock = Node.Node(None, "block")
                selectorNode.append(selectorBlock, "rules")

                # Move all rules from local media/supports block into new selector block
                for nonSelectorChild in list(node.rules):
                    if nonSelectorChild:
                        selectorBlock.append(nonSelectorChild)

                if node.type == "supports" and combinedMedia:
                    # Dynamically create matching mediaquery node
                    mediaNode = Node.Node(None, "media")
                    mediaNode.name = combinedMedia

                    mediaBlock = Node.Node(None, "block")
                    mediaNode.append(mediaBlock, "rules")

                    # Replace current node with media node
                    node.parent.replace(node, mediaNode)

                    # Then append this node to the media node
                    mediaBlock.append(node)

                    # Selector should be placed inside this node
                    node.rules.append(selectorNode)

                    # Update node reference to new outer node for further processing
                    node = mediaNode

                elif node.type == "media" and combinedSupports:
                    # Dynamically create matching supports node
                    supportsNode = Node.Node(None, "supports")
                    supportsNode.name = combinedSupports

                    supportsBlock = Node.Node(None, "block")
                    supportsNode.append(supportsBlock, "rules")

                    # Move supports node into this node
                    node.rules.append(supportsNode)

                    # The supports block is the parent of the selector
                    supportsBlock.append(selectorNode)

                else:
                    node.rules.append(selectorNode)

        if process:

            # Place any mixins before the current node
            for child in list(chdest):
                if child.type == "mixin":
                    dest.append(child)

            # The append self
            dest.append(node)

            # Afterwards append any children
            for child in list(chdest):
                dest.append(child)
Beispiel #29
0
def process(tree):
    """Flattens selectors to that `h1{ span{ ...` is merged into `h1 span{ ...`"""

    Console.info("Flattening selectors...")
    Console.indent()

    def __flatter(node, dest):
        """Moves all selectors to the top tree node while keeping media queries intact and/or making them CSS2
        compatible (regarding structure)"""

        process = node.type in ("selector", "mixin", "media", "supports")

        # Insert all children of top-level nodes into a helper element first
        # This is required to place mixins first, before the current node and append
        # all remaining nodes afterwards
        if process:
            chdest = Node.Node(None, "helper")
        else:
            chdest = dest

        # Process children first
        if len(node) > 0:
            for child in list(node):
                if child is not None:
                    __flatter(child, chdest)

        # Filter out empty nodes from processing
        if process and hasattr(node, "rules") and len(node.rules) > 0:

            # Combine selector and/or media query
            combinedSelector, combinedMedia, combinedSupports = Util.combineSelector(
                node)

            if node.type == "selector":
                node.name = combinedSelector
            elif node.type == "mixin":
                node.selector = combinedSelector
            elif node.type == "media":
                pass
            elif node.type == "supports":
                pass

            if (combinedMedia or
                    combinedSupports) and node.type in ("selector", "mixin"):

                if combinedSupports:
                    # Dynamically create matching media query
                    supportsNode = Node.Node(None, "supports")
                    supportsNode.name = combinedSupports

                    supportsBlock = Node.Node(None, "block")
                    supportsNode.append(supportsBlock, "rules")

                    supportsBlock.append(node)
                    node = supportsNode

                if combinedMedia:
                    # Dynamically create matching media query
                    mediaNode = Node.Node(None, "media")
                    mediaNode.name = combinedMedia

                    mediaBlock = Node.Node(None, "block")
                    mediaNode.append(mediaBlock, "rules")

                    mediaBlock.append(node)
                    node = mediaNode

            elif node.type == "media" or node.type == "supports":
                # Insert direct properties into new selector block
                # Goal is to place in this structure: @media->@supports->selector

                # Update media query of found media queries as it might
                # contain more than the local one (e.g. queries in parent nodes)
                if node.type == "media":
                    node.name = combinedMedia

                # Update support query of found supports query as it might
                # contain more than the local one (e.g. queries in parent nodes)
                elif node.type == "supports":
                    node.name = combinedSupports

                # Create new selector node where we move all rules into
                selectorNode = Node.Node(None, "selector")
                selectorNode.name = combinedSelector

                selectorBlock = Node.Node(None, "block")
                selectorNode.append(selectorBlock, "rules")

                # Move all rules from local media/supports block into new selector block
                for nonSelectorChild in list(node.rules):
                    if nonSelectorChild:
                        selectorBlock.append(nonSelectorChild)

                if node.type == "supports" and combinedMedia:
                    # Dynamically create matching mediaquery node
                    mediaNode = Node.Node(None, "media")
                    mediaNode.name = combinedMedia

                    mediaBlock = Node.Node(None, "block")
                    mediaNode.append(mediaBlock, "rules")

                    # Replace current node with media node
                    node.parent.replace(node, mediaNode)

                    # Then append this node to the media node
                    mediaBlock.append(node)

                    # Selector should be placed inside this node
                    node.rules.append(selectorNode)

                    # Update node reference to new outer node for further processing
                    node = mediaNode

                elif node.type == "media" and combinedSupports:
                    # Dynamically create matching supports node
                    supportsNode = Node.Node(None, "supports")
                    supportsNode.name = combinedSupports

                    supportsBlock = Node.Node(None, "block")
                    supportsNode.append(supportsBlock, "rules")

                    # Move supports node into this node
                    node.rules.append(supportsNode)

                    # The supports block is the parent of the selector
                    supportsBlock.append(selectorNode)

                else:
                    node.rules.append(selectorNode)

        if process:

            # Place any mixins before the current node
            for child in list(chdest):
                if child.type == "mixin":
                    dest.append(child)

            # The append self
            dest.append(node)

            # Afterwards append any children
            for child in list(chdest):
                dest.append(child)

    def __clean(node):
        """
        Removes all empty rules.

        Starting from inside out for a deep cleanup. This is a required step for the next one where we combine media
        queries and selectors and need to have an easy reference point to the previous node.

        """

        # Process children first
        for child in reversed(node):
            if child is not None:
                __clean(child)

        if hasattr(node, "rules") and len(node.rules) == 0:
            Console.debug(
                "Cleaning up empty selector/mixin/@media/@supports at line %s"
                % node.line)
            node.parent.remove(node)

        elif node.type == "content":
            Console.debug("Cleaning up left over @content at line %s" %
                          node.line)
            node.parent.remove(node)

        elif node.type == "meta":
            Console.debug("Cleaning up left over @meta at line %s" % node.line)
            node.parent.remove(node)

        elif node.type == "block" and node.parent.type in ("sheet", "block"):
            Console.debug(
                "Inlining content of unnecessary block node at line %s" %
                node.line)
            node.parent.insertAllReplace(node, node)

        elif node.type == "root" and len(node) == 0:
            Console.debug("Cleaning up left over @root at line %s" % node.line)
            node.parent.remove(node)

    def __combine(tree, top=True):
        """Combines follow up selector/media/supports nodes with the same name."""

        previousSelector = None
        previousMedia = None
        previousSupports = None

        # Work on a copy to be safe for remove situations during merges
        previousChild = None
        for child in list(tree):
            if not child:
                continue

            if child.type == "selector" or child.type == "mixin":
                if child.type == "selector":
                    thisSelector = child.name
                elif child.type == "mixin":
                    thisSelector = child.selector

                if thisSelector == previousSelector:
                    previousChild.rules.insertAll(None, child.rules)
                    tree.remove(child)
                    Console.debug("Combined selector of line %s into %s" %
                                  (child.line, previousChild.line))
                else:
                    previousChild = child

                previousSelector = thisSelector
                previousMedia = None
                previousSupports = None

            elif child.type == "media":
                if child.name == previousMedia:
                    previousChild.rules.insertAll(None, child.rules)
                    tree.remove(child)
                    Console.debug("Combined @media of line %s into %s" %
                                  (child.line, previousChild.line))
                else:
                    previousChild = child

                previousMedia = child.name
                previousSelector = None
                previousSupports = None

            elif child.type == "supports":
                if child.name == previousSupports:
                    previousChild.rules.insertAll(None, child.rules)
                    tree.remove(child)
                    Console.debug("Combined @supports of line %s into %s" %
                                  (child.line, previousChild.line))
                else:
                    previousChild = child

                previousSupports = child.name
                previousSelector = None
                previousMedia = None

            else:
                previousChild = None
                previousSelector = None
                previousSupports = None
                previousMedia = None

        # Re-run combiner inside all media queries.
        # Selectors in there are allowed and could be combined, too
        if top:
            for child in tree:
                if child and (child.type == "media"
                              or child.type == "supports"):
                    __combine(child.rules, False)

    # Execute the different features in order
    dest = Node.Node(None, "sheet")
    __flatter(tree, dest)
    tree.insertAll(0, dest)

    __clean(tree)
    __combine(tree)

    Console.outdent()

    return
Beispiel #30
0
def compute(node, first=None, second=None, operator=None, session=None):
    """
    Recursively processes given operation node.

    Consumes optional hints for the first/second child of an operation as well as the operator itself (in cases where it
    could not be figured out automatically). The session is useful for supporting commands inside of operations.

    """

    # Fill gaps in empty arguments
    if operator is None:
        operator = node.type

    # Fill missing first/second param
    if first is None and len(node) >= 1:
        first = node[0]

    if second is None and len(node) >= 2:
        second = node[1]

    # Error handling
    if node is None or operator is None:
        raise OperationError("Missing arguments for operation compute()", node)

    # Solve inner operations first
    if first is not None:
        if first.type in Util.ALL_OPERATORS:
            first = compute(first, session=session)
        elif first.type == "command":
            first = Util.executeCommand(first, session)

    if second is not None:
        if second.type in Util.ALL_OPERATORS:
            second = compute(second, session=session)
        elif second.type == "command":
            second = Util.executeCommand(second, session)

    # Support for not-/and-/or-operator
    if operator == "not":
        return Util.castNativeToNode(not castToBool(first))
    elif operator == "and":
        return Util.castNativeToNode(castToBool(first) and castToBool(second))
    elif operator == "or":
        return Util.castNativeToNode(castToBool(first) or castToBool(second))

    # Support for default set operator "?=" when variable was not defined before
    elif operator == "questionmark" and first is None:
        return second


    # Ignore when not yet processed
    if first.type in ("command", "variable") or second.type in ("command", "variable"):
        return

    # Compare operation types
    elif first.type == second.type:
        if first.type in ("true", "false", "null"):
            if operator in ("eq", "ge", "le"):
                return Util.castNativeToNode(True)
            else:
                return Util.castNativeToNode(False)

        elif first.type == "number":
            firstUnit = getattr(first, "unit", None)
            secondUnit = getattr(second, "unit", None)

            if operator in Util.COMPARE_OPERATORS:
                if firstUnit == secondUnit or firstUnit is None or secondUnit is None:
                    if operator == "eq":
                        return Util.castNativeToNode(first.value == second.value)
                    elif operator == "ne":
                        return Util.castNativeToNode(first.value != second.value)
                    elif operator == "gt":
                        return Util.castNativeToNode(first.value > second.value)
                    elif operator == "lt":
                        return Util.castNativeToNode(first.value < second.value)
                    elif operator == "ge":
                        return Util.castNativeToNode(first.value >= second.value)
                    elif operator == "le":
                        return Util.castNativeToNode(first.value <= second.value)

                else:
                    raise OperationError("Unsupported unit combination for number comparison", node)


            elif firstUnit == secondUnit or firstUnit is None or secondUnit is None:
                if operator in Util.MATH_OPERATORS:
                    repl = Node.Node(type="number")

                    if firstUnit is not None:
                        repl.unit = firstUnit
                    elif secondUnit is not None:
                        repl.unit = secondUnit

                    if operator == "plus":
                        repl.value = first.value + second.value
                    elif operator == "minus":
                        repl.value = first.value - second.value
                    elif operator == "mul":
                        repl.value = first.value * second.value
                    elif operator == "div":
                        repl.value = first.value / second.value
                    elif operator == "mod":
                        repl.value = first.value % second.value

                    return repl

                elif operator == "questionmark":
                    return first

                else:
                    raise OperationError("Unsupported number operation", node)


            elif firstUnit == "%" or secondUnit == "%":

                if operator in ("mul", "div"):
                    repl = Node.Node(type="number")

                    if operator == "mul":
                        repl.value = first.value * second.value / 100
                    elif operator == "mul":
                        repl.value = first.value / second.value / 100

                    if firstUnit == "%":
                        repl.unit = secondUnit
                    else:
                        repl.unit = firstUnit

                    return repl

                else:
                    raise OperationError("Could not compute mixed percent operations for operators other than \"*\" and \"/\"", node)

            else:
                raise OperationError("Could not compute result from numbers of different units: %s vs %s" % (first.unit, second.unit), node)


        elif first.type == "string":
            if operator == "plus":
                repl = Node.Node(type="string")
                repl.value = first.value + second.value
                return repl

            elif operator == "eq":
                return Util.castNativeToNode(first.value == second.value)
            elif operator == "ne":
                return Util.castNativeToNode(first.value != second.value)
            else:
                raise OperationError("Unsupported string operation", node)


        elif first.type == "list":
            if len(first) == len(second):
                repl = Node.Node(type="list")
                for pos, child in enumerate(first):
                    childRepl = compute(node, first=child, second=second[pos], session=session)
                    if childRepl is not None:
                        repl.append(childRepl)

                return repl

            else:
                raise OperationError("For list operations both lists have to have the same length!", node)


        else:
            raise OperationError("Unsupported operation on %s" % first.type, node)


    elif first.type == "true" and second.type == "false":
        return Util.castNativeToNode(False)


    elif first.type == "false" and second.type == "true":
        return Util.castNativeToNode(False)


    elif first.type == "list" and second.type != "list":
        repl = Node.Node(type="list")
        for child in first:
            childRepl = compute(node, first=child, second=second, session=session)
            if childRepl is not None:
                repl.append(childRepl)

        return repl


    elif first.type != "list" and second.type == "list":
        repl = Node.Node(type="list")
        for child in second:
            childRepl = compute(node, first=first, second=child, session=session)
            if childRepl is not None:
                repl.append(childRepl)

        return repl


    elif first.type == "string" or second.type == "string":
        repl = Node.Node(type="string")

        if first.type == "identifier" or second.type == "identifier":
            if operator == "plus":
                repl.value = first.value + second.value
                return repl
            elif operator == "eq":
                return Util.castNativeToNode(first.value == second.value)
            elif operator == "ne":
                return Util.castNativeToNode(first.value != second.value)

            else:
                raise OperationError("Unsupported string/identifier operation", node)

        else:

            if operator == "plus":
                repl.value = str(first.value) + str(second.value)
                return repl
            else:
                raise OperationError("Unsupported string operation", node)


    # Just handle when not both are null - equal condition is already done before
    elif first.type == "null" or second.type == "null":
        if operator == "eq":
            return Util.castNativeToNode(False)
        elif operator == "ne":
            return Util.castNativeToNode(True)
        elif operator in Util.MATH_OPERATORS:
            return Util.castNativeToNode(None)
        else:
            raise OperationError("Unsupported operation on null type", node)


    else:
        raise OperationError("Different types in operation: %s vs %s" % (first.type, second.type), node)