PageRenderTime 46ms CodeModel.GetById 14ms app.highlight 27ms RepoModel.GetById 2ms app.codeStats 0ms

/scalate-jruby/src/main/resources/haml-3.0.25/lib/sass/tree/node.rb

http://github.com/scalate/scalate
Ruby | 464 lines | 164 code | 39 blank | 261 comment | 21 complexity | 78cfa7d33567a52086ffc0e8aabc0d6b MD5 | raw file
  1module Sass
  2  # A namespace for nodes in the Sass parse tree.
  3  #
  4  # The Sass parse tree has three states: dynamic, static Sass, and static CSS.
  5  #
  6  # When it's first parsed, a Sass document is in the dynamic state.
  7  # It has nodes for mixin definitions and `@for` loops and so forth,
  8  # in addition to nodes for CSS rules and properties.
  9  # Nodes that only appear in this state are called **dynamic nodes**.
 10  #
 11  # {Tree::Node#perform} returns a static Sass tree, which is different.
 12  # It still has nodes for CSS rules and properties
 13  # but it doesn't have any dynamic-generation-related nodes.
 14  # The nodes in this state are in the same structure as the Sass document:
 15  # rules and properties are nested beneath one another.
 16  # Nodes that can be in this state or in the dynamic state
 17  # are called **static nodes**.
 18  #
 19  # {Tree::Node#cssize} then returns a static CSS tree.
 20  # This is like a static Sass tree,
 21  # but the structure exactly mirrors that of the generated CSS.
 22  # Rules and properties can't be nested beneath one another in this state.
 23  #
 24  # Finally, {Tree::Node#to_s} can be called on a static CSS tree
 25  # to get the actual CSS code as a string.
 26  module Tree
 27    # The abstract superclass of all parse-tree nodes.
 28    class Node
 29      include Enumerable
 30
 31      # The child nodes of this node.
 32      #
 33      # @return [Array<Tree::Node>]
 34      attr_accessor :children
 35
 36      # Whether or not this node has child nodes.
 37      # This may be true even when \{#children} is empty,
 38      # in which case this node has an empty block (e.g. `{}`).
 39      #
 40      # @return [Boolean]
 41      attr_accessor :has_children
 42
 43      # The line of the document on which this node appeared.
 44      #
 45      # @return [Fixnum]
 46      attr_accessor :line
 47
 48      # The name of the document on which this node appeared.
 49      #
 50      # @return [String]
 51      attr_writer :filename
 52
 53      # The options hash for the node.
 54      # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
 55      #
 56      # @return [{Symbol => Object}]
 57      attr_reader :options
 58
 59      def initialize
 60        @children = []
 61      end
 62
 63      # Sets the options hash for the node and all its children.
 64      #
 65      # @param options [{Symbol => Object}] The options
 66      # @see #options
 67      def options=(options)
 68        children.each {|c| c.options = options}
 69        @options = options
 70      end
 71
 72      # @private
 73      def children=(children)
 74        self.has_children ||= !children.empty?
 75        @children = children
 76      end
 77
 78      # The name of the document on which this node appeared.
 79      #
 80      # @return [String]
 81      def filename
 82        @filename || (@options && @options[:filename])
 83      end
 84
 85      # Appends a child to the node.
 86      #
 87      # @param child [Tree::Node, Array<Tree::Node>] The child node or nodes
 88      # @raise [Sass::SyntaxError] if `child` is invalid
 89      # @see #invalid_child?
 90      def <<(child)
 91        return if child.nil?
 92        if child.is_a?(Array)
 93          child.each {|c| self << c}
 94        else
 95          check_child! child
 96          self.has_children = true
 97          @children << child
 98        end
 99      end
100
101      # Raises an error if the given child node is invalid.
102      #
103      # @param child [Tree::Node] The child node
104      # @raise [Sass::SyntaxError] if `child` is invalid
105      # @see #invalid_child?
106      def check_child!(child)
107        if msg = invalid_child?(child)
108          raise Sass::SyntaxError.new(msg, :line => child.line)
109        end
110      end
111
112      # Compares this node and another object (only other {Tree::Node}s will be equal).
113      # This does a structural comparison;
114      # if the contents of the nodes and all the child nodes are equivalent,
115      # then the nodes are as well.
116      #
117      # Only static nodes need to override this.
118      #
119      # @param other [Object] The object to compare with
120      # @return [Boolean] Whether or not this node and the other object
121      #   are the same
122      # @see Sass::Tree
123      def ==(other)
124        self.class == other.class && other.children == children
125      end
126
127      # True if \{#to\_s} will return `nil`;
128      # that is, if the node shouldn't be rendered.
129      # Should only be called in a static tree.
130      #
131      # @return [Boolean]
132      def invisible?; false; end
133
134      # The output style. See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
135      #
136      # @return [Symbol]
137      def style
138        @options[:style]
139      end
140
141      # Computes the CSS corresponding to this static CSS tree.
142      #
143      # \{#to_s} shouldn't be overridden directly; instead, override \{#\_to\_s}.
144      # Only static-node subclasses need to implement \{#to\_s}.
145      #
146      # This may return `nil`, but it will only do so if \{#invisible?} is true.
147      #
148      # @param args [Array] Passed on to \{#\_to\_s}
149      # @return [String, nil] The resulting CSS
150      # @see Sass::Tree
151      def to_s(*args)
152        _to_s(*args)
153      rescue Sass::SyntaxError => e
154        e.modify_backtrace(:filename => filename, :line => line)
155        raise e
156      end
157
158      # Converts a static CSS tree (e.g. the output of \{#cssize})
159      # into another static CSS tree,
160      # with the given extensions applied to all relevant {RuleNode}s.
161      #
162      # @todo Link this to the reference documentation on `@extend`
163      #   when such a thing exists.
164      #
165      # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
166      #   The extensions to perform on this tree
167      # @return [Tree::Node] The resulting tree of static CSS nodes.
168      # @raise [Sass::SyntaxError] Only if there's a programmer error
169      #   and this is not a static CSS tree
170      def do_extend(extends)
171        node = dup
172        node.children = children.map {|c| c.do_extend(extends)}
173        node
174      rescue Sass::SyntaxError => e
175        e.modify_backtrace(:filename => filename, :line => line)
176        raise e
177      end
178
179      # Converts a static Sass tree (e.g. the output of \{#perform})
180      # into a static CSS tree.
181      #
182      # \{#cssize} shouldn't be overridden directly;
183      # instead, override \{#\_cssize} or \{#cssize!}.
184      #
185      # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
186      #   The extensions defined for this tree
187      # @param parent [Node, nil] The parent node of this node.
188      #   This should only be non-nil if the parent is the same class as this node
189      # @return [Tree::Node] The resulting tree of static nodes
190      # @raise [Sass::SyntaxError] if some element of the tree is invalid
191      # @see Sass::Tree
192      def cssize(extends, parent = nil)
193        _cssize(extends, (parent if parent.class == self.class))
194      rescue Sass::SyntaxError => e
195        e.modify_backtrace(:filename => filename, :line => line)
196        raise e
197      end
198
199      # Converts a dynamic tree into a static Sass tree.
200      # That is, runs the dynamic Sass code:
201      # mixins, variables, control directives, and so forth.
202      # This doesn't modify this node or any of its children.
203      #
204      # \{#perform} shouldn't be overridden directly;
205      # instead, override \{#\_perform} or \{#perform!}.
206      #
207      # @param environment [Sass::Environment] The lexical environment containing
208      #   variable and mixin values
209      # @return [Tree::Node] The resulting tree of static nodes
210      # @raise [Sass::SyntaxError] if some element of the tree is invalid
211      # @see Sass::Tree
212      def perform(environment)
213        _perform(environment)
214      rescue Sass::SyntaxError => e
215        e.modify_backtrace(:filename => filename, :line => line)
216        raise e
217      end
218
219      # Iterates through each node in the tree rooted at this node
220      # in a pre-order walk.
221      #
222      # @yield node
223      # @yieldparam node [Node] a node in the tree
224      def each(&block)
225        yield self
226        children.each {|c| c.each(&block)}
227      end
228
229      # Converts a node to Sass code that will generate it.
230      #
231      # @param tabs [Fixnum] The amount of tabulation to use for the Sass code
232      # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
233      # @return [String] The Sass code corresponding to the node
234      def to_sass(tabs = 0, opts = {})
235        to_src(tabs, opts, :sass)
236      end
237
238      # Converts a node to SCSS code that will generate it.
239      #
240      # @param tabs [Fixnum] The amount of tabulation to use for the SCSS code
241      # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
242      # @return [String] The Sass code corresponding to the node
243      def to_scss(tabs = 0, opts = {})
244        to_src(tabs, opts, :scss)
245      end
246
247      protected
248
249      # Computes the CSS corresponding to this particular Sass node.
250      #
251      # This method should never raise {Sass::SyntaxError}s.
252      # Such errors will not be properly annotated with Sass backtrace information.
253      # All error conditions should be checked in earlier transformations,
254      # such as \{#cssize} and \{#perform}.
255      #
256      # @param args [Array] ignored
257      # @return [String, nil] The resulting CSS
258      # @see #to_s
259      # @see Sass::Tree
260      def _to_s
261        raise NotImplementedError.new("All static-node subclasses of Sass::Tree::Node must override #_to_s or #to_s.")
262      end
263
264      # Converts this static Sass node into a static CSS node,
265      # returning the new node.
266      # This doesn't modify this node or any of its children.
267      #
268      # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
269      #   The extensions defined for this tree
270      # @param parent [Node, nil] The parent node of this node.
271      #   This should only be non-nil if the parent is the same class as this node
272      # @return [Tree::Node, Array<Tree::Node>] The resulting static CSS nodes
273      # @raise [Sass::SyntaxError] if some element of the tree is invalid
274      # @see #cssize
275      # @see Sass::Tree
276      def _cssize(extends, parent)
277        node = dup
278        node.cssize!(extends, parent)
279        node
280      end
281
282      # Destructively converts this static Sass node into a static CSS node.
283      # This *does* modify this node,
284      # but will be run non-destructively by \{#\_cssize\}.
285      #
286      # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
287      #   The extensions defined for this tree
288      # @param parent [Node, nil] The parent node of this node.
289      #   This should only be non-nil if the parent is the same class as this node
290      # @see #cssize
291      def cssize!(extends, parent)
292        self.children = children.map {|c| c.cssize(extends, self)}.flatten
293      end
294
295      # Runs any dynamic Sass code in this particular node.
296      # This doesn't modify this node or any of its children.
297      #
298      # @param environment [Sass::Environment] The lexical environment containing
299      #   variable and mixin values
300      # @return [Tree::Node, Array<Tree::Node>] The resulting static nodes
301      # @see #perform
302      # @see Sass::Tree
303      def _perform(environment)
304        node = dup
305        node.perform!(environment)
306        node
307      end
308
309      # Destructively runs dynamic Sass code in this particular node.
310      # This *does* modify this node,
311      # but will be run non-destructively by \{#\_perform\}.
312      #
313      # @param environment [Sass::Environment] The lexical environment containing
314      #   variable and mixin values
315      # @see #perform
316      def perform!(environment)
317        self.children = perform_children(Environment.new(environment))
318        self.children.each {|c| check_child! c}
319      end
320
321      # Non-destructively runs \{#perform} on all children of the current node.
322      #
323      # @param environment [Sass::Environment] The lexical environment containing
324      #   variable and mixin values
325      # @return [Array<Tree::Node>] The resulting static nodes
326      def perform_children(environment)
327        children.map {|c| c.perform(environment)}.flatten
328      end
329
330      # Replaces SassScript in a chunk of text
331      # with the resulting value.
332      #
333      # @param text [Array<String, Sass::Script::Node>] The text to interpolate
334      # @param environment [Sass::Environment] The lexical environment containing
335      #   variable and mixin values
336      # @return [String] The interpolated text
337      def run_interp(text, environment)
338        text.map do |r|
339          next r if r.is_a?(String)
340          val = r.perform(environment)
341          # Interpolated strings should never render with quotes
342          next val.value if val.is_a?(Sass::Script::String)
343          val.to_s
344        end.join.strip
345      end
346
347      # @see Haml::Shared.balance
348      # @raise [Sass::SyntaxError] if the brackets aren't balanced
349      def balance(*args)
350        res = Haml::Shared.balance(*args)
351        return res if res
352        raise Sass::SyntaxError.new("Unbalanced brackets.", :line => line)
353      end
354
355      # Returns an error message if the given child node is invalid,
356      # and false otherwise.
357      #
358      # By default, all child nodes except those only allowed under specific nodes
359      # ({Tree::MixinDefNode}, {Tree::ImportNode}, {Tree::ExtendNode}) are valid.
360      # This is expected to be overriden by subclasses
361      # for which some children are invalid.
362      #
363      # @param child [Tree::Node] A potential child node
364      # @return [Boolean, String] Whether or not the child node is valid,
365      #   as well as the error message to display if it is invalid
366      def invalid_child?(child)
367        case child
368        when Tree::MixinDefNode
369          "Mixins may only be defined at the root of a document."
370        when Tree::ImportNode
371          "Import directives may only be used at the root of a document."
372        end
373      end
374
375      # Converts a node to Sass or SCSS code that will generate it.
376      #
377      # This method is called by the default \{#to\_sass} and \{#to\_scss} methods,
378      # so that the same code can be used for both with minor variations.
379      #
380      # @param tabs [Fixnum] The amount of tabulation to use for the SCSS code
381      # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
382      # @param fmt [Symbol] `:sass` or `:scss`
383      # @return [String] The Sass or SCSS code corresponding to the node
384      def to_src(tabs, opts, fmt)
385        raise NotImplementedError.new("All static-node subclasses of Sass::Tree::Node must override #to_#{fmt}.")
386      end
387
388      # Converts the children of this node to a Sass or SCSS string.
389      # This will return the trailing newline for the previous line,
390      # including brackets if this is SCSS.
391      #
392      # @param tabs [Fixnum] The amount of tabulation to use for the Sass code
393      # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
394      # @param fmt [Symbol] `:sass` or `:scss`
395      # @return [String] The Sass or SCSS code corresponding to the children
396      def children_to_src(tabs, opts, fmt)
397        return fmt == :sass ? "\n" : " {}\n" if children.empty?
398
399        (fmt == :sass ? "\n" : " {\n") +
400          children.map {|c| c.send("to_#{fmt}", tabs + 1, opts)}.join.rstrip +
401          (fmt == :sass ? "\n" : " }\n")
402      end
403
404      # Converts a selector to a Sass or SCSS string.
405      #
406      # @param sel [Array<String, Sass::Script::Node>] The selector to convert
407      # @param tabs [Fixnum] The indentation of the selector
408      # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
409      # @param fmt [Symbol] `:sass` or `:scss`
410      # @return [String] The Sass or SCSS code corresponding to the selector
411      def selector_to_src(sel, tabs, opts, fmt)
412        fmt == :sass ? selector_to_sass(sel, opts) : selector_to_scss(sel, tabs, opts)
413      end
414
415      # Converts a selector to a Sass string.
416      #
417      # @param sel [Array<String, Sass::Script::Node>] The selector to convert
418      # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
419      # @return [String] The Sass code corresponding to the selector
420      def selector_to_sass(sel, opts)
421        sel.map do |r|
422          if r.is_a?(String)
423            r.gsub(/(,[ \t]*)?\n\s*/) {$1 ? $1 + "\n" : " "}
424          else
425            "\#{#{r.to_sass(opts)}}"
426          end
427        end.join
428      end
429
430      # Converts a selector to a SCSS string.
431      #
432      # @param sel [Array<String, Sass::Script::Node>] The selector to convert
433      # @param tabs [Fixnum] The indentation of the selector
434      # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
435      # @return [String] The SCSS code corresponding to the selector
436      def selector_to_scss(sel, tabs, opts)
437        sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(opts)}}"}.
438          join.gsub(/^[ \t]*/, '  ' * tabs)
439      end
440
441      # Convert any underscores in a string into hyphens,
442      # but only if the `:dasherize` option is set.
443      #
444      # @param s [String] The string to convert
445      # @param opts [{Symbol => Object}] The options hash
446      # @return [String] The converted string
447      def dasherize(s, opts)
448        if opts[:dasherize]
449          s.gsub('_', '-')
450        else
451          s
452        end
453      end
454
455      # Returns a semicolon if this is SCSS, or an empty string if this is Sass.
456      #
457      # @param fmt [Symbol] `:sass` or `:scss`
458      # @return [String] A semicolon or the empty string
459      def semi(fmt)
460        fmt == :sass ? "" : ";"
461      end
462    end
463  end
464end