"""Parser======This parser has been done quickly standing only on cases involved by CSSmanifest syntax so it is very naive and may breaks on everything else.We assume CSS source only contains supported syntax for manifest, everythingelse could break process.This flaw is tempered by the behavior of parser that ignores rules thatdon't start with the manifest prefix, so CSS manifest could contains someother syntax for non styleguide rules."""fromcollectionsimportOrderedDictfromtinycss2importparse_stylesheetfromtinycss2.astimportParseErrorasTinyCSS2ParseErrorfrom.nomenclatureimportRULE_BASE_PREFIXfrom.exceptionsimportParserErrors
[docs]classTinycssSourceParser(object):""" CSS parser using tinycss2 Since tinycss2 only return tokens, this parser is in charge to turn them to usable datas: a dict of properties for each selector. """
[docs]defdigest_prelude(self,rule):""" Walk on rule prelude (aka CSS selector) tokens to return a string of the value name (from css selector). Actually only simple selector and selector with descendant combinator are supported. Using any other selector kind may leads to unexpected issues. Arguments: rule (tinycss2.ast.QualifiedRule): Qualified rule object as returned by tinycss2. Returns: string: Selector name. If it's a descendant combinator, items are joined with ``__``. """name=[]fortokeninrule.prelude:iftoken.type=="ident":name.append(token.value)return"__".join(name)
[docs]defdigest_content(self,rule):""" Walk on rule content tokens to return a dict of properties. This is pretty naive and will choke/fail on everything that is more evolved than simple ``ident(string):value(string)`` Arguments: rule (tinycss2.ast.QualifiedRule): Qualified rule object as returned by tinycss2. Returns: dict: Dictionnary of retrieved variables and properties. """data=OrderedDict()current_key=Nonefortokeninrule.content:# Assume first identity token is the property nameiftoken.type=="ident":# Ignore starting dashes from CSS variablesname=token.valueifname.startswith("--"):name=name[2:]elifname.startswith("-"):name=name[1:]current_key=namedata[current_key]=None# Assume first following string or number token is the property value.iftoken.type=="string":data[current_key]=token.valueeliftoken.type=="number":# Number must be stringified again to ensure proper conversiondata[current_key]=str(token.int_valueortoken.value)returndata
[docs]defconsume(self,source):""" Parse source and consume tokens from tinycss2. Arguments: source (string): Source content to parse. Returns: dict: Retrieved rules. """manifest=OrderedDict()rules=parse_stylesheet(source,skip_comments=True,skip_whitespace=True)errors=[itemforiteminrulesifisinstance(item,TinyCSS2ParseError)]iferrors:error_payload=["Line {line} - Column {col} : [{kind}] {msg}".format(line=item.source_line,col=item.source_column,kind=item.kind,msg=item.message,)foriteminerrors]raiseParserErrors("Unable to parse CSS due to {} parsing error(s)".format(len(errors)),error_payload=error_payload,)forruleinrules:# Gather rule selector+propertiesname=self.digest_prelude(rule)# Ignore everything out of styleguide namespaceifnotname.startswith(RULE_BASE_PREFIX):continueproperties=self.digest_content(rule)manifest[name]=propertiesreturnmanifest
[docs]defparse(self,source):""" Read and parse CSS source and return dict of rules. Arguments: source (string): Source content to parse. Returns: dict: Selectors with their properties. """returnself.consume(source)