# A visitor for converting a Sass tree into a source string.classSass::Tree::Visitors::Convert<Sass::Tree::Visitors::Base# Runs the visitor on a tree.## @param root [Tree::Node] The root node of the Sass tree.# @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).# @param format [Symbol] `:sass` or `:scss`.# @return [String] The Sass or SCSS source for the tree.defself.visit(root,options,format)new(options,format).send(:visit,root)endprotecteddefinitialize(options,format)@options=options@format=format@tabs=0# 2 spaces by default@tab_chars=@options[:indent]||" "enddefvisit_children(parent)@tabs+=1return@format==:sass?"\n":" {}\n"ifparent.children.empty?if@format==:sass"\n"+super.join.rstrip+"\n"else" {\n"+super.join.rstrip+"\n#{@tab_chars*(@tabs-1)}}\n"endensure@tabs-=1end# Ensures proper spacing between top-level nodes.defvisit_root(node)Sass::Util.enum_cons(node.children+[nil],2).mapdo|child,nxt|visit(child)+ifnxt&&(child.is_a?(Sass::Tree::CommentNode)&&child.line+child.lines+1==nxt.line)||(child.is_a?(Sass::Tree::ImportNode)&&nxt.is_a?(Sass::Tree::ImportNode)&&child.line+1==nxt.line)||(child.is_a?(Sass::Tree::VariableNode)&&nxt.is_a?(Sass::Tree::VariableNode)&&child.line+1==nxt.line)""else"\n"endend.join.rstrip+"\n"enddefvisit_charset(node)"#{tab_str}@charset \"#{node.name}\"#{semi}\n"enddefvisit_comment(node)value=interp_to_src(node.value)if@format==:sasscontent=value.gsub(/\*\/$/,'').rstripifcontent=~/\A[ \t]/# Re-indent SCSS comments like this:# /* foo# bar# baz */content.gsub!(/^/,' ')content.sub!(/\A([ \t]*)\/\*/,'/*\1')endifcontent.include?("\n")content.gsub!(/\n \*/,"\n ")spaces=content.scan(/\n( *)/).map{|s|s.first.size}.minsep=node.type==:silent?"\n//":"\n *"ifspaces>=2content.gsub!(/\n /,sep)elsecontent.gsub!(/\n#{' '*spaces}/,sep)endendcontent.gsub!(/\A\/\*/,'//')ifnode.type==:silentcontent.gsub!(/^/,tab_str)content=content.rstrip+"\n"elsespaces=(@tab_chars*[@tabs-value[/^ */].size,0].max)content=ifnode.type==:silentvalue.gsub(/^[\/ ]\*/,'//').gsub(/ *\*\/$/,'')elsevalueend.gsub(/^/,spaces)+"\n"endcontentenddefvisit_debug(node)"#{tab_str}@debug #{node.expr.to_sass(@options)}#{semi}\n"enddefvisit_directive(node)res="#{tab_str}#{interp_to_src(node.value)}"res.gsub!(/^@import \#\{(.*)\}([^}]*)$/,'@import \1\2')returnres+"#{semi}\n"unlessnode.has_childrenres+yield+"\n"enddefvisit_each(node)vars=node.vars.map{|var|"$#{dasherize(var)}"}.join(", ")"#{tab_str}@each #{vars} in #{node.list.to_sass(@options)}#{yield}"enddefvisit_extend(node)"#{tab_str}@extend #{selector_to_src(node.selector).lstrip}#{semi}"+"#{" !optional"ifnode.optional?}\n"enddefvisit_for(node)"#{tab_str}@for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} "+"#{node.exclusive?"to":"through"}#{node.to.to_sass(@options)}#{yield}"enddefvisit_function(node)args=node.args.mapdo|v,d|d?"#{v.to_sass(@options)}: #{d.to_sass(@options)}":v.to_sass(@options)end.join(", ")ifnode.splatargs<<", "unlessnode.args.empty?args<<node.splat.to_sass(@options)<<"..."end"#{tab_str}@function #{dasherize(node.name)}(#{args})#{yield}"enddefvisit_if(node)name=if!@is_else"if"elsifnode.expr"else if"else"else"end@is_else=falsestr="#{tab_str}@#{name}"str<<" #{node.expr.to_sass(@options)}"ifnode.exprstr<<yield@is_else=truestr<<visit(node.else)ifnode.elsestrensure@is_else=falseenddefvisit_import(node)quote=@format==:scss?'"':''"#{tab_str}@import #{quote}#{node.imported_filename}#{quote}#{semi}\n"enddefvisit_media(node)"#{tab_str}@media #{query_interp_to_src(node.query)}#{yield}"enddefvisit_supports(node)"#{tab_str}@#{node.name}#{node.condition.to_src(@options)}#{yield}"enddefvisit_cssimport(node)ifnode.uri.is_a?(Sass::Script::Tree::Node)str="#{tab_str}@import #{node.uri.to_sass(@options)}"elsestr="#{tab_str}@import #{node.uri}"endstr<<" #{interp_to_src(node.query)}"unlessnode.query.empty?"#{str}#{semi}\n"enddefvisit_mixindef(node)args=ifnode.args.empty?&&node.splat.nil?""elsestr='('str<<node.args.mapdo|v,d|ifd"#{v.to_sass(@options)}: #{d.to_sass(@options)}"elsev.to_sass(@options)endend.join(", ")ifnode.splatstr<<", "unlessnode.args.empty?str<<node.splat.to_sass(@options)<<'...'endstr<<')'end"#{tab_str}#{@format==:sass?'=':'@mixin '}#{dasherize(node.name)}#{args}#{yield}"enddefvisit_mixin(node)arg_to_sass=lambdado|arg|sass=arg.to_sass(@options)sass="(#{sass})"ifarg.is_a?(Sass::Script::Tree::ListLiteral)&&arg.separator==:commasassendunlessnode.args.empty?&&node.keywords.empty?&&node.splat.nil?args=node.args.map(&arg_to_sass)keywords=Sass::Util.hash_to_a(node.keywords.as_stored).map{|k,v|"$#{dasherize(k)}: #{arg_to_sass[v]}"}ifnode.splatsplat="#{arg_to_sass[node.splat]}..."kwarg_splat="#{arg_to_sass[node.kwarg_splat]}..."ifnode.kwarg_splatendarglist="(#{[args,splat,keywords,kwarg_splat].flatten.compact.join(', ')})"end"#{tab_str}#{@format==:sass?'+':'@include '}"+"#{dasherize(node.name)}#{arglist}#{node.has_children?yield:semi}\n"enddefvisit_content(node)"#{tab_str}@content#{semi}\n"enddefvisit_prop(node)res=tab_str+node.declaration(@options,@format)returnres+semi+"\n"ifnode.children.empty?res+yield.rstrip+semi+"\n"enddefvisit_return(node)"#{tab_str}@return #{node.expr.to_sass(@options)}#{semi}\n"enddefvisit_rule(node)rule=node.parsed_rules?node.parsed_rules.to_a:node.ruleif@format==:sassname=selector_to_sass(rule)name="\\"+nameifname[0]==?:name.gsub(/^/,tab_str)+yieldelsif@format==:scssname=selector_to_scss(rule)res=name+yieldifnode.children.last.is_a?(Sass::Tree::CommentNode)&&node.children.last.type==:silentres.slice!(-3..-1)res<<"\n"<<tab_str<<"}\n"endresendenddefvisit_variable(node)"#{tab_str}$#{dasherize(node.name)}: #{node.expr.to_sass(@options)}"+"#{' !global'ifnode.global}#{' !default'ifnode.guarded}#{semi}\n"enddefvisit_warn(node)"#{tab_str}@warn #{node.expr.to_sass(@options)}#{semi}\n"enddefvisit_while(node)"#{tab_str}@while #{node.expr.to_sass(@options)}#{yield}"enddefvisit_atroot(node)ifnode.query"#{tab_str}@at-root #{query_interp_to_src(node.query)}#{yield}"elsifnode.children.length==1&&node.children.first.is_a?(Sass::Tree::RuleNode)rule=node.children.first"#{tab_str}@at-root #{selector_to_src(rule.rule)}#{visit_children(rule)}"else"#{tab_str}@at-root#{yield}"endendprivatedefinterp_to_src(interp)interp.mapdo|r|nextrifr.is_a?(String)"\#{#{r.to_sass(@options)}}"end.joinend# Like interp_to_src, but removes the unnecessary `#{}` around the keys and# values in query expressions.defquery_interp_to_src(interp)interp=interp.mapdo|e|nexteunlesse.is_a?(Sass::Script::Tree::Literal)nexteunlesse.value.is_a?(Sass::Script::Value::String)e.value.valueendSass::Util.enum_with_index(interp).mapdo|r,i|nextrifr.is_a?(String)before,after=interp[i-1],interp[i+1]ifbefore.is_a?(String)&&after.is_a?(String)&&((before[-1]==?(&&after[0]==?:)||(before=~/:\s*/&&after[0]==?)))r.to_sass(@options)else"\#{#{r.to_sass(@options)}}"endend.joinenddefselector_to_src(sel)@format==:sass?selector_to_sass(sel):selector_to_scss(sel)enddefselector_to_sass(sel)sel.mapdo|r|ifr.is_a?(String)r.gsub(/(,)?([ \t]*)\n\s*/){$1?"#{$1}#{$2}\n":" "}else"\#{#{r.to_sass(@options)}}"endend.joinenddefselector_to_scss(sel)interp_to_src(sel).gsub(/^[ \t]*/,tab_str).gsub(/[ \t]*$/,'')enddefsemi@format==:sass?"":";"enddeftab_str@tab_chars*@tabsenddefdasherize(s)if@options[:dasherize]s.gsub('_','-')elsesendendend