Пример #1
0
def __extendContent(node, call, targetBlock, stopCombineAt):
    """
    Builds up a list of selector/mediaqueries to insert after the extend to produce
    the @content sections on the intended selectors.
    """

    for child in reversed(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 = Util.combineSelector(node, stop=stopCombineAt)

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

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

        # Support media queries, too
        if media:
            mediaNode = Node.Node(type="media")
            mediaNode.name = media
            mediaNode.append(selectorNode)
            targetBlock.append(mediaNode)

        else:
            targetBlock.append(selectorNode)
Пример #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)
Пример #3
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)
Пример #4
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
Пример #5
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
Пример #6
0
def includeGenerator(node):
    """A generator which yiels include names and the origin include nodes for every include in the given root node."""

    for child in node:
        if child:
            if child.type == "include":
                valueNode = child[0]
                if valueNode.type in ("string", "identifier"):
                    includeName = valueNode.value
                elif valueNode.type == "dot":
                    includeName = Util.assembleDot(valueNode)
                else:
                    raise Exception("Invalid include: %s" % valueNode)

                yield includeName, child

            else:
                includeGenerator(child)
Пример #7
0
        def resolveIncludesRecurser(node):
            for child in node:
                if child.type == "include":
                    valueNode = child[0]
                    if valueNode.type in ("string", "identifier"):
                        includeName = valueNode.value
                    elif valueNode.type == "dot":
                        includeName = Util.assembleDot(valueNode)
                    else:
                        raise Exception("Invalid include: %s" % valueNode)

                    childStyleItem = session.getStyleByName(includeName)

                    # Use merged tree for children as well...
                    childRoot = childStyleItem.getMergedTree(permutation, session)

                    # and copy it for being free to modify it
                    childRoot = copy.deepcopy(childRoot)

                    node.replace(child, childRoot)

                else:
                    resolveIncludesRecurser(child)
Пример #8
0
def __recurser(node, permutation, inCondition=False):

    # Support block commands
    # These come with their own node.type
    if node.type == "if":

        # Pre-process condition
        # We manually process each child in for if-types
        __recurser(node.condition, permutation, True)

        # Cast condition to Python boolean type
        resultValue = None
        try:
            resultValue = Operation.castToBool(node.condition)
        except Operation.OperationError as ex:
            Console.debug("Walked into unprocessed condition. Waiting for actual execution. Message: %s", ex)

        if not resultValue is None:

            # Process relevant part of the sub tree
            if resultValue is True:
                # Fix missing processing of result node
                __recurser(node.thenPart, permutation)

                # Finally replace if-node with result node
                node.parent.replace(node, node.thenPart)

            elif resultValue is False and hasattr(node, "elsePart"):
                # Fix missing processing of result node
                __recurser(node.elsePart, permutation)

                # Finally replace if-node with result node
                node.parent.replace(node, node.elsePart)

            else:
                # Cleanup original if-node
                node.parent.remove(node)

            # All done including child nodes
            return


    # Process children first (resolve logic is inner-out)
    for child in list(node):
        if child is not None:
            __recurser(child, permutation, inCondition)


    # Inside of conditions replace identifiers with their actual value (from current permutation)
    if inCondition and node.type == "identifier":
        if not permutation.has(node.value):
            raise ResolverError("Could not find field %s" % node.value, node)

        repl = Util.castNativeToNode(permutation.get(node.value))
        node.parent.replace(node, repl)


    # Support inline @field() commands
    elif node.type == "command" and node.name == "field":
        if len(node.params) == 0:
            raise ResolverError("Missing parameter to insert field via @field.", node)

        identifierNode = node.params[0]
        if identifierNode.type != "identifier":
            raise ResolverError("Invalid parameter to @field call: %s" % identifierNode.type, identifierNode)

        identifier = identifierNode.value
        if not permutation.has(identifier):
            raise ResolverError("Could not find field with the name %s" % identifier, identifierNode)

        repl = Util.castNativeToNode(permutation.get(identifier))
        node.parent.replace(node, repl)


    # Support typical operators
    elif node.type in Util.ALL_OPERATORS:
        repl = Operation.compute(node)
        if repl is not None:
            node.parent.replace(node, repl)
Пример #9
0
    def __flatter(node):
        """
        Moves all selectors to the top tree node while keeping media queries intact 
        and/or making them CSS2 compatible (regarding formatting)
        """

        nonlocal insertIndex

        # Process children first
        for child in list(node):
            if child is not None:
                __flatter(child)

        # Increase index position when reaching new child of tree
        if getattr(node, "parent", None) == tree:
            insertIndex += 1

        # Extended mixin
        if node.type in ("selector", "mixin", "media") and len(node.rules) > 0:
            combinedSelector, combinedMedia = Util.combineSelector(node)

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

            if node.type == "selector" or node.type == "mixin":
                if combinedMedia:
                    # Share same media element when possible
                    previousMedia = insertIndex > 0 and tree[insertIndex-1]
                    if previousMedia and previousMedia.name[0] == combinedMedia:
                        mediaBlock = previousMedia.rules
                        mediaBlock.append(node)
                        return

                    else:
                        # 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)

                        # Insert media query node instead of rule node to tree
                        node = mediaNode

            elif node.type == "media":
                # Insert direct properties into new selector:block

                previousMedia = insertIndex > 0 and tree[insertIndex-1]

                selectorNode = Node.Node(None, "selector")
                selectorNode.name = combinedSelector

                selectorBlock = Node.Node(None, "block")
                selectorNode.append(selectorBlock, "rules")
                
                # Move all rules from local block into selector block
                for mediaChild in list(node.rules):
                    if mediaChild:
                        selectorBlock.append(mediaChild)

                # Then insert the newly created and filled selector block into the media node
                node.rules.append(selectorNode)

            # When node is not yet in the root tree, append it there
            # and correct insertIndex for next insertion
            if getattr(node, "parent", None) != tree:
                tree.insert(insertIndex, node)
                insertIndex += 1
Пример #10
0
def __extend(node):
    """
    Finds extend requests for mixins aka.

    - mixins calls without params
    - simple variables in a block

    For all found extend requests it detects the flattened selector and appends
    the selector section of the extendable mixin accordingly. After that it
    removes the original mixin request.

    """

    modified = 0

    for child in reversed(list(node)):
        # Ignore all mixin declarations. Can't operate inside them.
        # For these things to work we have to wait for the include mechanics to resolve them first
        # (which actually just remove these mixin declarations though)
        if child is not None:
            modified += __extend(child)

    if isExtendCall(node):

        name = node.name

        Console.debug("Extend request to mixin %s at: %s", name, node.line)
        Console.indent()

        mixin = __findMixin(node.parent, name)
        if not mixin:
            raise Exception("Could not find mixin %s as required by extend request at line %s" % (node.name, node.line))

        Console.debug("Found matching mixin declaration at line: %s", mixin.line)

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

        # There is no possibility to handle this in a series of CSS selectors. This is why
        # we have to use an include like approach instead of extend to correctly deal
        # with the situation. This should work well, but is not as efficient regarding
        # output file size.
        if media or supports:
            Console.warn("Extending inside a @media/@support structure behaves like including (larger result size): %s %s + %s", media, supports, ", ".join(selector))

            replacements = __resolveMixin(mixin, None)

            Console.debug("Replacing call %s at line %s with mixin from line %s" % (name, node.line, replacements.line))

            # Reverse inject all children of that block
            # at the same position as the original call
            parent = node.parent
            pos = parent.index(node)
            parent.insertAll(pos, replacements)

        elif selector:
            Console.debug("Extending selector of mixin by: %s", ", ".join(selector))

            if hasattr(mixin, "selector"):
                # We iterate from in inverse mode, so add new selectors to the front
                mixin.selector[0:0] = selector

            else:
                mixin.selector = selector

            virtualBlock = Node.Node(type="block")
            __extendContent(mixin.rules, node, virtualBlock, mixin)

            if len(virtualBlock) > 0:
                callSelector, callMedia, callSupports = Util.combineSelector(node)

                if callSelector:
                    virtualSelector = Node.Node(type="selector")
                    virtualSelector.name = callSelector

                if callMedia:
                    virtualMedia = Node.Node(type="media")
                    virtualMedia.name = callMedia

                if callSupports:
                    virtualSupports = Node.Node(type="supports")
                    virtualSupports.name = callSupports

                if callSelector:
                    virtualSelector.append(virtualBlock, "rules")
                elif callMedia:
                    virtualMedia.append(virtualBlock, "rules")
                elif callSupports:
                    virtualSupports.append(virtualBlock, "rules")

                if callSupports:
                    virtualTop = virtualSupports
                elif callMedia:
                    virtualTop = virtualMedia
                elif callSelector:
                    virtualTop = virtualSelector

                pos = mixin.parent.index(mixin)
                mixin.parent.insert(pos + 1, virtualTop)

        node.parent.remove(node)
        Console.outdent()

        modified += 1

    return modified
Пример #11
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)
Пример #12
0
def __extend(node, scanMixins=False):
    """
    Finds extend requests for mixins aka 

    - mixins calls without params
    - simple variables in a block

    For all found extend requests it detects the flattened selector and appends 
    the selector section of the extendable mixin accordingly. After that it 
    removes the original mixin request.
    """

    modified = 0

    for child in reversed(node):
        # Ignore all mixin declarations. Can't operate inside them.
        # For these things to work we have to wait for the include mechanics to resolve them first 
        # (which actually just remove these mixin declarations though)
        if child is not None and (scanMixins or child.type != "mixin"):
            modified += __extend(child)

    if isExtendCall(node):

        name = node.name

        Console.debug("Extend request to mixin %s at: %s", name, node.line)
        Console.indent()

        mixin = __findMixin(node.parent, name)
        if not mixin:
            raise Exception("Could not find mixin %s as required by extend request at line %s" % (node.name, node.line))

        Console.debug("Found matching mixin declaration at line: %s", mixin.line)

        # selector, media = Util.combineSelector(node.parent, stop=node.parent)
        selector, media = Util.combineSelector(node.parent)

        if media:
            Console.warn("Extending inside media query behaves like including (less efficient): %s + %s", media, ", ".join(selector))

            replacements = __resolveMixin(mixin, None)

            Console.debug("Replacing call %s at line %s with mixin from line %s" % (name, node.line, replacements.line))

            # Reverse inject all children of that block
            # at the same position as the original call
            parent = node.parent
            pos = parent.index(node)
            for child in reversed(replacements):
                parent.insert(pos, child)

        else:
            Console.debug("Extending selector of mixin by: %s", ", ".join(selector))

            if hasattr(mixin, "selector"):
                # We iterate from in inverse mode, so add new selectors to the front
                mixin.selector[0:0] = selector

            else:
                mixin.selector = selector

            virtualBlock = Node.Node(type="block")
            __extendContent(mixin.rules, node, virtualBlock, mixin)

            if len(virtualBlock) > 0:
                callSelector, callMedia = Util.combineSelector(node)

                if callSelector:
                    virtualSelector = Node.Node(type="selector")
                    virtualSelector.name = callSelector

                if callMedia:
                    virtualMedia = Node.Node(type="media")
                    virtualMedia.name = callMedia

                if callSelector:
                    virtualSelector.append(virtualBlock, "rules")
                elif callMedia:
                    virtualMedia.append(virtualBlock, "rules")

                if callMedia:
                    virtualTop = virtualMedia
                elif callSelector:
                    virtualTop = virtualSelector

                pos = mixin.parent.index(mixin)
                mixin.parent.insert(pos+1, virtualTop)

        node.parent.remove(node)
        Console.outdent()

        modified += 1

    return modified
Пример #13
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)
Пример #14
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)
Пример #15
0
def __recurser(node, scope, values, profile):
    # Replace variable with actual value
    if node.type == "variable" and not (node.parent.type == "assign"
                                        and node.parent[0] is node):
        name = node.name
        if name not in values:
            raise ExecuterError(
                "Could not resolve variable %s! Missing value!" % name, node)

        value = values[name]
        if value is None:
            raise ExecuterError(
                "Could not resolve variable %s! Value is none!" % name, node)

        Console.debug("Resolving variable: %s at line %s with %s from %s",
                      name, node.line, values[name].type, values[name].line)
        node.parent.replace(node, copy.deepcopy(values[name]))

    # Decide which sub tree of an if-condition is relevant based on current variable situation
    elif node.type == "if":

        Console.debug("Processing if-condition at %s", node.line)

        # Pre-process condition
        # We manually process each child in for if-types
        __recurser(node.condition, scope, values, profile)

        # Cast condition to Python boolean type
        resultValue = Operation.castToBool(node.condition)

        # Process relevant part of the sub tree
        if resultValue is True:
            # Fix missing processing of result node
            __recurser(node.thenPart, scope, values, profile)

            # Finally replace if-node with result node
            node.parent.replace(node, node.thenPart)

        elif resultValue is False and hasattr(node, "elsePart"):
            # Fix missing processing of result node
            __recurser(node.elsePart, scope, values, profile)

            # Finally replace if-node with result node
            node.parent.replace(node, node.elsePart)

        else:
            # Cleanup original if-node
            node.parent.remove(node)

        # Nothing to do here as content is already processed
        return

    # Update scope of new block starts
    if hasattr(node, "scope"):
        relation = getattr(node, "rel", None)

        # Conditional blocks are not exactly blocks in this variable resolution engine
        if not relation in ("thenPart", "elsePart"):
            scope = node.scope
            values = copy.copy(values)
            node.values = values

            # Reset all local variables to None
            # which enforces not to keep values from outer scope
            for name in scope.modified:
                values[name] = None

    # Process children / content
    for child in list(node):
        # Ignore non-children... through possible interactive structure changes
        if child and child.parent is node:
            __recurser(child, scope, values, profile)

    # Update values of variables
    # This happens after processing children to possibly reduce child structure to an easy to assign (aka preprocessed value)
    if (node.type == "declaration"
            and hasattr(node, "initializer")) or node.type == "assign":

        if node.type == "declaration":
            name = node.name
            init = node.initializer
            Console.debug("Found declaration of %s at line %s", name,
                          node.line)

        else:
            name = node[0].name
            init = node[1]
            Console.debug("Found assignment of %s at line %s", name, node.line)

        # Modify value instead of replace when assign operator is set
        if hasattr(node, "assignOp") and node.assignOp is not None:
            if name not in values:
                raise ExecuterError(
                    "Assign operator is not supported as left hand variable is missing: %s"
                    % name, node)

            repl = Operation.compute(node, values[name], init, node.assignOp)
            if repl is not None:
                values[name] = repl

        else:
            # Update internal variable mapping
            # Console.debug("Update value of %s to %s" % (name, init))
            values[name] = init

        # Remove declaration node from tree
        node.parent.remove(node)

    # Support for variables inside property names or selectors
    elif node.type in ("property", "selector") and getattr(
            node, "dynamic", False):

        def replacer(matchObj):
            name = matchObj.group(1)

            if name not in values:
                raise ExecuterError(
                    "Could not resolve variable %s! Missing value!" % name,
                    node)

            value = values[name]
            if value is None:
                raise ExecuterError(
                    "Could not resolve variable %s! Value is none!" % name,
                    node)

            if value.type == "identifier":
                return value.value
            elif value.type == "string":
                return value.value
            elif value.type == "number":
                return "%s%s" % (value.value, getattr(value, "unit", ""))
            else:
                raise ExecuterError(
                    "Could not replace property inline variable with value of type: %s"
                    % value.type, node)

        # Fix all selectors
        if node.type == "selector":
            selectors = node.name
            for pos, selector in enumerate(selectors):
                selectors[pos] = RE_INLINE_VARIABLE.sub(replacer, selector)

        else:
            node.name = RE_INLINE_VARIABLE.sub(replacer, node.name)

    # Execute system commands
    elif node.type == "command":
        repl = Util.executeCommand(node, profile)
        if not repl is node:
            node.parent.replace(node, repl)

    # Support typical operators
    elif node.type in Util.ALL_OPERATORS:
        repl = Operation.compute(node)
        if repl is not None:
            node.parent.replace(node, repl)
Пример #16
0
def __extend(node):
    """
    Finds extend requests for mixins aka.

    - mixins calls without params
    - simple variables in a block

    For all found extend requests it detects the flattened selector and appends
    the selector section of the extendable mixin accordingly. After that it
    removes the original mixin request.

    """

    modified = 0

    for child in reversed(list(node)):
        # Ignore all mixin declarations. Can't operate inside them.
        # For these things to work we have to wait for the include mechanics to resolve them first
        # (which actually just remove these mixin declarations though)
        if child is not None:
            modified += __extend(child)

    if isExtendCall(node):

        name = node.name

        Console.debug("Extend request to mixin %s at: %s", name, node.line)
        Console.indent()

        mixin = __findMixin(node.parent, name)
        if not mixin:
            raise Exception(
                "Could not find mixin %s as required by extend request at line %s"
                % (node.name, node.line))

        Console.debug("Found matching mixin declaration at line: %s",
                      mixin.line)

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

        # There is no possibility to handle this in a series of CSS selectors. This is why
        # we have to use an include like approach instead of extend to correctly deal
        # with the situation. This should work well, but is not as efficient regarding
        # output file size.
        if media or supports:
            Console.warn(
                "Extending inside a @media/@support structure behaves like including (larger result size): %s %s + %s",
                media, supports, ", ".join(selector))

            replacements = __resolveMixin(mixin, None)

            Console.debug(
                "Replacing call %s at line %s with mixin from line %s" %
                (name, node.line, replacements.line))

            # Reverse inject all children of that block
            # at the same position as the original call
            parent = node.parent
            pos = parent.index(node)
            parent.insertAll(pos, replacements)

        elif selector:
            Console.debug("Extending selector of mixin by: %s",
                          ", ".join(selector))

            if hasattr(mixin, "selector"):
                # We iterate from in inverse mode, so add new selectors to the front
                mixin.selector[0:0] = selector

            else:
                mixin.selector = selector

            virtualBlock = Node.Node(type="block")
            __extendContent(mixin.rules, node, virtualBlock, mixin)

            if len(virtualBlock) > 0:
                callSelector, callMedia, callSupports = Util.combineSelector(
                    node)

                if callSelector:
                    virtualSelector = Node.Node(type="selector")
                    virtualSelector.name = callSelector

                if callMedia:
                    virtualMedia = Node.Node(type="media")
                    virtualMedia.name = callMedia

                if callSupports:
                    virtualSupports = Node.Node(type="supports")
                    virtualSupports.name = callSupports

                if callSelector:
                    virtualSelector.append(virtualBlock, "rules")
                elif callMedia:
                    virtualMedia.append(virtualBlock, "rules")
                elif callSupports:
                    virtualSupports.append(virtualBlock, "rules")

                if callSupports:
                    virtualTop = virtualSupports
                elif callMedia:
                    virtualTop = virtualMedia
                elif callSelector:
                    virtualTop = virtualSelector

                pos = mixin.parent.index(mixin)
                mixin.parent.insert(pos + 1, virtualTop)

        node.parent.remove(node)
        Console.outdent()

        modified += 1

    return modified