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)
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)
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
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
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 __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
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 __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