sass 3.1.0.alpha.48 → 3.1.0.alpha.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ # Visitors are used to traverse the Sass parse tree.
2
+ # Visitors should extend {Visitors::Base},
3
+ # which provides a small amount of scaffolding for traversal.
4
+ module Sass::Tree::Visitors
5
+ # The abstract base class for Sass visitors.
6
+ # Visitors should extend this class,
7
+ # then implement `visit_*` methods for each node they care about
8
+ # (e.g. `visit_rule` for {RuleNode} or `visit_for` for {FileNode}).
9
+ # These methods take the node in question as argument.
10
+ # They may `yield` to visit the child nodes of the current node.
11
+ #
12
+ # *Note*: due to the unusual nature of {Sass::Tree::IfNode},
13
+ # special care must be taken to ensure that it is properly handled.
14
+ # In particular, there is no built-in scaffolding
15
+ # for dealing with the return value of `@else` nodes.
16
+ #
17
+ # @abstract
18
+ class Base
19
+ # Runs the visitor on a tree.
20
+ #
21
+ # @param root [Tree::Node] The root node of the Sass tree.
22
+ # @return [Object] The return value of \{#visit} for the root node.
23
+ def self.visit(root)
24
+ new.send(:visit, root)
25
+ end
26
+
27
+ protected
28
+
29
+ # Runs the visitor on the given node.
30
+ # This can be overridden by subclasses that need to do something for each node.
31
+ #
32
+ # @param node [Tree::Node] The node to visit.
33
+ # @return [Object] The return value of the `visit_*` method for this node.
34
+ def visit(node)
35
+ method = "visit_#{node.class.name.gsub(/.*::(.*?)Node$/, '\\1').downcase}"
36
+ if self.respond_to?(method)
37
+ self.send(method, node) {visit_children(node)}
38
+ else
39
+ visit_children(node)
40
+ end
41
+ end
42
+
43
+ # Visit the child nodes for a given node.
44
+ # This can be overridden by subclasses that need to do something
45
+ # with the child nodes' return values.
46
+ #
47
+ # This method is run when `visit_*` methods `yield`,
48
+ # and its return value is returned from the `yield`.
49
+ #
50
+ # @param parent [Tree::Node] The parent node of the children to visit.
51
+ # @return [Array<Object>] The return values of the `visit_*` methods for the children.
52
+ def visit_children(parent)
53
+ parent.children.map {|c| visit(c)}
54
+ end
55
+
56
+ # `yield`s, then runs the visitor on the `@else` clause if the node has one.
57
+ # This exists to ensure that the contents of the `@else` clause get visited.
58
+ def visit_if(node)
59
+ yield
60
+ visit(node.else) if node.else
61
+ node
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,234 @@
1
+ # A visitor for converting a Sass tree into a source string.
2
+ class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base
3
+ # Runs the visitor on a tree.
4
+ #
5
+ # @param root [Tree::Node] The root node of the Sass tree.
6
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
7
+ # @param format [Symbol] `:sass` or `:scss`.
8
+ # @return [String] The Sass or SCSS source for the tree.
9
+ def self.visit(root, options, format)
10
+ new(options, format).send(:visit, root)
11
+ end
12
+
13
+ protected
14
+
15
+ def initialize(options, format)
16
+ @options = options
17
+ @format = format
18
+ @tabs = 0
19
+ end
20
+
21
+ def visit_children(parent)
22
+ @tabs += 1
23
+ return @format == :sass ? "\n" : " {}\n" if parent.children.empty?
24
+ (@format == :sass ? "\n" : " {\n") + super.join.rstrip + (@format == :sass ? "\n" : " }\n")
25
+ ensure
26
+ @tabs -= 1
27
+ end
28
+
29
+ # Ensures proper spacing between top-level nodes.
30
+ def visit_root(node)
31
+ Sass::Util.enum_cons(node.children + [nil], 2).map do |child, nxt|
32
+ visit(child) +
33
+ if nxt &&
34
+ (child.is_a?(Sass::Tree::CommentNode) &&
35
+ child.line + child.value.count("\n") + 1 == nxt.line) ||
36
+ (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) &&
37
+ child.line + 1 == nxt.line) ||
38
+ (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) &&
39
+ child.line + 1 == nxt.line)
40
+ ""
41
+ else
42
+ "\n"
43
+ end
44
+ end.join.rstrip + "\n"
45
+ end
46
+
47
+ def visit_charset(node)
48
+ "#{tab_str}@charset \"#{node.name}\"#{semi}\n"
49
+ end
50
+
51
+ def visit_comment(node)
52
+ if @format == :sass
53
+ content = node.value.gsub(/\*\/$/, '').rstrip
54
+ if content =~ /\A[ \t]/
55
+ # Re-indent SCSS comments like this:
56
+ # /* foo
57
+ # bar
58
+ # baz */
59
+ content.gsub!(/^/, ' ')
60
+ content.sub!(/\A([ \t]*)\/\*/, '/*\1')
61
+ end
62
+
63
+ content =
64
+ unless content.include?("\n")
65
+ content
66
+ else
67
+ content.gsub!(/\n( \*|\/\/)/, "\n ")
68
+ spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min
69
+ sep = node.silent ? "\n//" : "\n *"
70
+ if spaces >= 2
71
+ content.gsub(/\n /, sep)
72
+ else
73
+ content.gsub(/\n#{' ' * spaces}/, sep)
74
+ end
75
+ end
76
+
77
+ content.gsub!(/\A\/\*/, '//') if node.silent
78
+ content.gsub!(/^/, tab_str)
79
+ content.rstrip + "\n"
80
+ else
81
+ spaces = (' ' * [@tabs - node.value[/^ */].size, 0].max)
82
+ if node.silent
83
+ node.value.gsub(/^[\/ ]\*/, '//').gsub(/ *\*\/$/, '')
84
+ else
85
+ node.value
86
+ end.gsub(/^/, spaces) + "\n"
87
+ end
88
+ end
89
+
90
+ def visit_debug(node)
91
+ "#{tab_str}@debug #{node.expr.to_sass(@options)}#{semi}\n"
92
+ end
93
+
94
+ def visit_directive(node)
95
+ res = "#{tab_str}#{node.value}"
96
+ return res + "#{semi}\n" unless node.has_children
97
+ res + yield + "\n"
98
+ end
99
+
100
+ def visit_each(node)
101
+ "#{tab_str}@each $#{dasherize(node.var)} in #{node.list.to_sass(@options)}#{yield}"
102
+ end
103
+
104
+ def visit_extend(node)
105
+ "#{tab_str}@extend #{selector_to_src(node.selector).lstrip}#{semi}\n"
106
+ end
107
+
108
+ def visit_for(node)
109
+ "#{tab_str}@for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} " +
110
+ "#{node.exclusive ? "to" : "through"} #{node.to.to_sass(@options)}#{yield}"
111
+ end
112
+
113
+ def visit_if(node)
114
+ name =
115
+ if !@is_else; "if"
116
+ elsif node.expr; "else if"
117
+ else; "else"
118
+ end
119
+ str = "#{tab_str}@#{name}"
120
+ str << " #{node.expr.to_sass(@options)}" if node.expr
121
+ str << yield
122
+ @is_else = true
123
+ str << visit(node.else) if node.else
124
+ str
125
+ ensure
126
+ @is_else = false
127
+ end
128
+
129
+ def visit_import(node)
130
+ quote = @format == :scss ? '"' : ''
131
+ "#{tab_str}@import #{quote}#{node.imported_filename}#{quote}#{semi}\n"
132
+ end
133
+
134
+ def visit_media(node)
135
+ "#{tab_str}@media #{node.query}#{yield}"
136
+ end
137
+
138
+ def visit_mixindef(node)
139
+ args =
140
+ if node.args.empty?
141
+ ""
142
+ else
143
+ '(' + node.args.map do |v, d|
144
+ if d
145
+ "#{v.to_sass(@options)}: #{d.to_sass(@options)}"
146
+ else
147
+ v.to_sass(@options)
148
+ end
149
+ end.join(", ") + ')'
150
+ end
151
+
152
+ "#{tab_str}#{@format == :sass ? '=' : '@mixin '}#{dasherize(node.name)}#{args}#{yield}"
153
+ end
154
+
155
+ def visit_mixin(node)
156
+ unless node.args.empty? && node.keywords.empty?
157
+ args = node.args.map {|a| a.to_sass(@options)}.join(", ")
158
+ keywords = node.keywords.map {|k, v| "$#{dasherize(k)}: #{v.to_sass(@options)}"}.join(', ')
159
+ arglist = "(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
160
+ end
161
+ "#{tab_str}#{@format == :sass ? '+' : '@include '}#{dasherize(node.name)}#{arglist}#{semi}\n"
162
+ end
163
+
164
+ def visit_prop(node)
165
+ res = tab_str + node.declaration(@options, @format)
166
+ return res + semi + "\n" if node.children.empty?
167
+ res + yield.rstrip + semi + "\n"
168
+ end
169
+
170
+ def visit_rule(node)
171
+ if @format == :sass
172
+ name = selector_to_sass(node.rule)
173
+ name = "\\" + name if name[0] == ?:
174
+ name.gsub(/^/, tab_str) + yield
175
+ elsif @format == :scss
176
+ name = selector_to_scss(node.rule)
177
+ res = name + yield
178
+ if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.silent
179
+ res.slice!(-3..-1)
180
+ res << "\n" << tab_str << "}\n"
181
+ end
182
+ res
183
+ end
184
+ end
185
+
186
+ def visit_variable(node)
187
+ "#{tab_str}$#{dasherize(node.name)}: #{node.expr.to_sass(@options)}#{' !default' if node.guarded}#{semi}\n"
188
+ end
189
+
190
+ def visit_warn(node)
191
+ "#{tab_str}@warn #{node.expr.to_sass(@options)}#{semi}\n"
192
+ end
193
+
194
+ def visit_while(node)
195
+ "#{tab_str}@while #{node.expr.to_sass(@options)}#{yield}"
196
+ end
197
+
198
+ private
199
+
200
+ def selector_to_src(sel)
201
+ @format == :sass ? selector_to_sass(sel) : selector_to_scss(sel)
202
+ end
203
+
204
+ def selector_to_sass(sel)
205
+ sel.map do |r|
206
+ if r.is_a?(String)
207
+ r.gsub(/(,[ \t]*)?\n\s*/) {$1 ? $1 + "\n" : " "}
208
+ else
209
+ "\#{#{r.to_sass(@options)}}"
210
+ end
211
+ end.join
212
+ end
213
+
214
+ def selector_to_scss(sel)
215
+ sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(@options)}}"}.
216
+ join.gsub(/^[ \t]*/, tab_str)
217
+ end
218
+
219
+ def semi
220
+ @format == :sass ? "" : ";"
221
+ end
222
+
223
+ def tab_str
224
+ ' ' * @tabs
225
+ end
226
+
227
+ def dasherize(s)
228
+ if @options[:dasherize]
229
+ s.gsub('_', '-')
230
+ else
231
+ s
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,177 @@
1
+ # A visitor for converting a static Sass tree into a static CSS tree.
2
+ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
3
+ # @param root [Tree::Node] The root node of the tree to visit.
4
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
5
+ # *and* the extensions defined for this tree
6
+ def self.visit(root); super; end
7
+
8
+ protected
9
+
10
+ # Returns the immediate parent of the current node.
11
+ # @return [Tree::Node]
12
+ attr_reader :parent
13
+
14
+ def initialize
15
+ @extends = Sass::Util::SubsetMap.new
16
+ end
17
+
18
+ # If an exception is raised, this adds proper metadata to the backtrace.
19
+ def visit(node)
20
+ super(node.dup)
21
+ rescue Sass::SyntaxError => e
22
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
23
+ raise e
24
+ end
25
+
26
+ # Keeps track of the current parent node.
27
+ def visit_children(parent)
28
+ with_parent parent do
29
+ parent.children = super.flatten
30
+ parent
31
+ end
32
+ end
33
+
34
+ # Runs a block of code with the current parent node
35
+ # replaced with the given node.
36
+ #
37
+ # @param parent [Tree::Node] The new parent for the duration of the block.
38
+ # @yield A block in which the parent is set to `parent`.
39
+ # @return [Object] The return value of the block.
40
+ def with_parent(parent)
41
+ old_parent, @parent = @parent, parent
42
+ yield
43
+ ensure
44
+ @parent = old_parent
45
+ end
46
+
47
+ # In Ruby 1.8, ensures that there's only one `@charset` directive
48
+ # and that it's at the top of the document.
49
+ #
50
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
51
+ # *and* the extensions defined for this tree
52
+ def visit_root(node)
53
+ yield
54
+
55
+ # In Ruby 1.9 we can make all @charset nodes invisible
56
+ # and infer the final @charset from the encoding of the final string.
57
+ if Sass::Util.ruby1_8? && parent.nil?
58
+ charset = node.children.find {|c| c.is_a?(Sass::Tree::CharsetNode)}
59
+ node.children.reject! {|c| c.is_a?(Sass::Tree::CharsetNode)}
60
+ node.children.unshift charset if charset
61
+ end
62
+
63
+ return node, @extends
64
+ rescue Sass::SyntaxError => e
65
+ e.sass_template ||= node.template
66
+ raise e
67
+ end
68
+
69
+ # Registers an extension in the `@extends` subset map.
70
+ def visit_extend(node)
71
+ node.resolved_selector.members.each do |seq|
72
+ if seq.members.size > 1
73
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend nested selectors")
74
+ end
75
+
76
+ sseq = seq.members.first
77
+ if !sseq.is_a?(Sass::Selector::SimpleSequence)
78
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
79
+ end
80
+
81
+ sel = sseq.members
82
+ parent.resolved_rules.members.each do |seq|
83
+ if !seq.members.last.is_a?(Sass::Selector::SimpleSequence)
84
+ raise Sass::SyntaxError.new("#{seq} can't extend: invalid selector")
85
+ end
86
+
87
+ @extends[sel] = seq
88
+ end
89
+ end
90
+
91
+ []
92
+ end
93
+
94
+ # Modifies exception backtraces to include the imported file.
95
+ def visit_import(node)
96
+ yield
97
+ node.children
98
+ rescue Sass::SyntaxError => e
99
+ e.modify_backtrace(:filename => node.children.first.filename)
100
+ e.add_backtrace(:filename => node.filename, :line => node.line)
101
+ raise e
102
+ end
103
+
104
+ # Bubbles the `@media` directive up through RuleNodes
105
+ # and merges it with other `@media` directives.
106
+ def visit_media(node)
107
+ if parent.is_a?(Sass::Tree::RuleNode)
108
+ new_rule = parent.dup
109
+ new_rule.children = node.children
110
+ node.children = with_parent(node) {Array(visit(new_rule))}
111
+ # If the last child is actually the end of the group,
112
+ # the parent's cssize will set it properly
113
+ node.children.last.group_end = false unless node.children.empty?
114
+ else
115
+ yield
116
+ end
117
+
118
+ media = node.children.select {|c| c.is_a?(Sass::Tree::MediaNode)}
119
+ node.children.reject! {|c| c.is_a?(Sass::Tree::MediaNode)}
120
+ media.each {|n| n.query = "#{node.query} and #{n.query}"}
121
+ (node.children.empty? ? [] : [node]) + media
122
+ end
123
+
124
+ # Asserts that all the mixin's children are valid in their new location.
125
+ def visit_mixin(node)
126
+ node.children.map do |c|
127
+ parent.check_child! c
128
+ # Don't use #visit_children to avoid adding the mixin node to the list of parents.
129
+ visit(c)
130
+ end.flatten
131
+ rescue Sass::SyntaxError => e
132
+ e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
133
+ e.add_backtrace(:filename => node.filename, :line => node.line)
134
+ raise e
135
+ end
136
+
137
+ # Converts nested properties into flat properties
138
+ # and updates the indentation of the prop node based on the nesting level.
139
+ def visit_prop(node)
140
+ if parent.is_a?(Sass::Tree::PropNode)
141
+ node.resolved_name = "#{parent.resolved_name}-#{node.resolved_name}"
142
+ node.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if node.style == :nested
143
+ end
144
+
145
+ yield
146
+
147
+ if !node.resolved_value.empty? || node.children.empty?
148
+ node.send(:check!)
149
+ node.children.unshift(node)
150
+ end
151
+
152
+ node.children
153
+ end
154
+
155
+ # Resolves parent references and nested selectors,
156
+ # and updates the indentation of the rule node based on the nesting level.
157
+ def visit_rule(node)
158
+ parent_resolved_rules = parent.is_a?(Sass::Tree::RuleNode) ? parent.resolved_rules : nil
159
+ # It's possible for resolved_rules to be set if we've duplicated this node during @media bubbling
160
+ node.resolved_rules ||= node.parsed_rules.resolve_parent_refs(parent_resolved_rules)
161
+
162
+ yield
163
+
164
+ rules = node.children.select {|c| c.is_a?(Sass::Tree::RuleNode) || c.is_a?(Sass::Tree::MediaNode)}
165
+ props = node.children.reject {|c| c.is_a?(Sass::Tree::RuleNode) || c.is_a?(Sass::Tree::MediaNode) || c.invisible?}
166
+
167
+ unless props.empty?
168
+ node.children = props
169
+ rules.each {|r| r.tabs += 1} if node.style == :nested
170
+ rules.unshift(node)
171
+ end
172
+
173
+ rules.last.group_end = true unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty?
174
+
175
+ rules
176
+ end
177
+ end