require'pathname'moduleSass::Tree# A static node reprenting a CSS rule.## @see Sass::TreeclassRuleNode<Node# The character used to include the parent selector# @privatePARENT='&'# The CSS selectors for this rule.# Each string is a selector line, and the lines are meant to be separated by commas.# For example,## foo, bar, baz,# bip, bop, bup## would be## ["foo, bar, baz",# "bip, bop, bup"]## @return [Array<String>]attr_accessor:rules# The CSS selectors for this rule,# parsed for commas and parent-references.# It's only set once {Tree::Node#perform} has been called.## It's an array of arrays of arrays.# The first level of arrays represents distinct lines in the Sass file;# the second level represents comma-separated selectors;# the third represents structure within those selectors,# currently only parent-refs (represented by `:parent`).# For example,## &.foo, bar, baz,# bip, &.bop, bup## would be## [[[:parent, "foo"], ["bar"], ["baz"]],# [["bip"], [:parent, "bop"], ["bup"]]]## @return [Array<Array<Array<String|Symbol>>>]attr_accessor:parsed_rules# @param rule [String] The first CSS rule. See \{#rules}definitialize(rule)@rules=[rule]super()end# Compares the contents of two rules.## @param other [Object] The object to compare with# @return [Boolean] Whether or not this node and the other object# are the samedef==(other)self.class==other.class&&rules==other.rules&&superend# Adds another {RuleNode}'s rules to this one's.## @param node [RuleNode] The other nodedefadd_rules(node)@rules+=node.rulesend# @return [Boolean] Whether or not this rule is continued on the next linedefcontinued?@rules.last[-1]==?,end# Computes the CSS for the rule.## @param tabs [Fixnum] The level of indentation for the CSS# @param super_rules [Array<Array<String>>] The rules for the parent node# (see \{#rules}), or `nil` if there are no parents# @return [String] The resulting CSS# @raise [Sass::SyntaxError] if the rule has no parents but uses `&`defto_s(tabs,super_rules=nil)resolved_rules=resolve_parent_refs(super_rules)properties=[]sub_rules=[]rule_separator=style==:compressed?',':', 'line_separator=[:nested,:expanded].include?(style)?",\n":rule_separatorrule_indent=' '*(tabs-1)per_rule_indent,total_indent=[:nested,:expanded].include?(style)?[rule_indent,'']:['',rule_indent]total_rule=total_indent+resolved_rules.mapdo|line|per_rule_indent+line.join(rule_separator)end.join(line_separator)children.eachdo|child|nextifchild.invisible?ifchild.is_a?RuleNodesub_rules<<childelseproperties<<childendendto_return=''if!properties.empty?old_spaces=' '*(tabs-1)spaces=' '*tabsif@options[:line_comments]&&style!=:compressedto_return<<"#{old_spaces}/* line #{line}"iffilenamerelative_filename=if@options[:css_filename]beginPathname.new(filename).relative_path_from(Pathname.new(File.dirname(@options[:css_filename]))).to_srescueArgumentErrornilendendrelative_filename||=filenameto_return<<", #{relative_filename}"endto_return<<" */\n"endifstyle==:compactproperties=properties.map{|a|a.to_s(1)}.select{|a|a&&a.length>0}.join(' ')to_return<<"#{total_rule} { #{properties} }\n"elsifstyle==:compressedproperties=properties.map{|a|a.to_s(1)}.select{|a|a&&a.length>0}.join(';')to_return<<"#{total_rule}{#{properties}}"elseproperties=properties.map{|a|a.to_s(tabs+1)}.select{|a|a&&a.length>0}.join("\n")end_props=(style==:expanded?"\n"+old_spaces:' ')to_return<<"#{total_rule} {\n#{properties}#{end_props}}\n"endendtabs+=1unlessproperties.empty?||style!=:nestedsub_rules.eachdo|sub|to_return<<sub.to_s(tabs,resolved_rules)endto_returnendprotected# Runs any SassScript that may be embedded in the rule,# and parses the selectors for commas.## @param environment [Sass::Environment] The lexical environment containing# variable and mixin valuesdefperform!(environment)@parsed_rules=@rules.map{|r|parse_selector(interpolate(r,environment))}superendprivatedefresolve_parent_refs(super_rules)ifsuper_rules.nil?return@parsed_rules.mapdo|line|line.mapdo|rule|ifrule.include?(:parent)raiseSass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'.",self.line)endrule.joinend.compactendendnew_rules=[]super_rules.eachdo|super_line|@parsed_rules.eachdo|line|new_rules<<[]super_line.eachdo|super_rule|line.eachdo|rule|rule=[:parent," ",*rule]unlessrule.include?(:parent)new_rules.last<<rule.mapdo|segment|nextsegmentunlesssegment==:parentsuper_ruleend.joinendendendendnew_rulesenddefparse_selector(text)scanner=StringScanner.new(text)rules=[[]]whilescanner.rest?rules.last<<scanner.scan(/[^",&]*/)casescanner.scan(/./)when'&';rules.last<<:parentwhen','scanner.scan(/\s*/)rules<<[]ifscanner.rest?when'"'rules.last<<'"'<<scanner.scan(/([^"\\]|\\.)*/)# We don't want to enforce that strings are closed,# but we do want to consume quotes or trailing backslashes.rules.last<<scanner.scan(/./)ifscanner.rest?endendrules.map!do|l|Haml::Util.merge_adjacent_strings(l).reject{|r|r.is_a?(String)&&r.empty?}endrulesendendend