# -*- coding: utf8 -*- """ .. module:: lesscpy.plib.identifier :synopsis: Identifier node. Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ import re from .node import Node from lesscpy.lessc import utility from lesscpy.lib import reserved class Identifier(Node): """Identifier node. Represents block identifier. """ def parse(self, scope): """Parse node. Block identifiers are stored as strings with spaces replaced with ? args: scope (Scope): Current scope raises: SyntaxError returns: self """ names = [] name = [] self._subp = ( '@media', '@keyframes', '@-moz-keyframes', '@-webkit-keyframes', '@-ms-keyframes' ) if self.tokens and hasattr(self.tokens, 'parse'): self.tokens = list(utility.flatten([id.split() + [','] for id in self.tokens.parse(scope).split(',')])) self.tokens.pop() if self.tokens and any(hasattr(t, 'parse') for t in self.tokens): tmp_tokens = [] for t in self.tokens: if hasattr(t, 'parse'): tmp_tokens.append(t.parse(scope)) else: tmp_tokens.append(t) self.tokens = list(utility.flatten(tmp_tokens)) if self.tokens and self.tokens[0] in self._subp: name = list(utility.flatten(self.tokens)) self.subparse = True else: self.subparse = False for n in utility.flatten(self.tokens): if n == '*': name.append('* ') elif n in '>+~': if name and name[-1] == ' ': name.pop() name.append('?%s?' % n) elif n == ',': names.append(name) name = [] else: name.append(n) names.append(name) parsed = self.root(scope, names) if scope else names # Interpolated selectors need another step, we have to replace variables. Avoid reserved words though # # Example: '.@{var}' results in [['.', '@{var}']] # But: '@media print' results in [['@media', ' ', 'print']] # def replace_variables(tokens, scope): return [scope.swap(t) if (utility.is_variable(t) and not t in reserved.tokens) else t for t in tokens] parsed = [list(utility.flatten(replace_variables(part, scope))) for part in parsed] self.parsed = [[i for i, j in utility.pairwise(part) if i != ' ' or (j and '?' not in j)] for part in parsed] return self def root(self, scope, names): """Find root of identifier, from scope args: scope (Scope): current scope names (list): identifier name list (, separated identifiers) returns: list """ parent = scope.scopename if parent: parent = parent[-1] if parent.parsed: parsed_names = [] for name in names: ampersand_count = name.count('&') if ampersand_count: filtered_parts = [] for part in parent.parsed: if part and part[0] not in self._subp: filtered_parts.append(part) permutations = list(utility.permutations_with_replacement(filtered_parts, ampersand_count)) for permutation in permutations: parsed = [] for name_part in name: if name_part == "&": parent_part = permutation.pop(0) if parsed and parsed[-1].endswith(']'): parsed.extend(' ') if parent_part[-1] == ' ': parent_part.pop() parsed.extend(parent_part) else: parsed.append(name_part) parsed_names.append(parsed) else: # NOTE(saschpe): Maybe this code can be expressed with permutations too? for part in parent.parsed: if part and part[0] not in self._subp: parsed = [] if name[0] == "@media": parsed.extend(name) else: parsed.extend(part) if part[-1] != ' ': parsed.append(' ') parsed.extend(name) parsed_names.append(parsed) else: parsed_names.append(name) return parsed_names return names def raw(self, clean=False): """Raw identifier. args: clean (bool): clean name returns: str """ if clean: return ''.join(''.join(p) for p in self.parsed).replace('?', ' ') return '%'.join('%'.join(p) for p in self.parsed).strip().strip('%') def copy(self): """ Return copy of self Returns: Identifier object """ tokens = ([t for t in self.tokens] if isinstance(self.tokens, list) else self.tokens) return Identifier(tokens, 0) def fmt(self, fills): """Format identifier args: fills (dict): replacements returns: str (CSS) """ name = ',$$'.join(''.join(p).strip() for p in self.parsed) name = re.sub('\?(.)\?', '%(ws)s\\1%(ws)s', name) % fills return name.replace('$$', fills['nl']).replace(' ', ' ')