vendor/plugins/haml/lib/sass/tree/node.rb in radiantcms-couchrest_model-0.1.3 vs vendor/plugins/haml/lib/sass/tree/node.rb in radiantcms-couchrest_model-0.1.4

- old
+ new

@@ -1,47 +1,30 @@ module Sass # A namespace for nodes in the Sass parse tree. # - # The Sass parse tree has three states: dynamic, static Sass, and static CSS. - # - # When it's first parsed, a Sass document is in the dynamic state. - # It has nodes for mixin definitions and `@for` loops and so forth, + # The Sass parse tree has two states. + # When it's first parsed, it has nodes for mixin definitions + # and for loops and so forth, # in addition to nodes for CSS rules and properties. - # Nodes that only appear in this state are called **dynamic nodes**. # - # {Tree::Node#perform} returns a static Sass tree, which is different. - # It still has nodes for CSS rules and properties + # However, {Tree::Node#perform} returns a different sort of tree. + # This tree maps more closely to the resulting CSS document + # than it does to the original Sass document. + # It still has nodes for CSS rules and properties, # but it doesn't have any dynamic-generation-related nodes. - # The nodes in this state are in the same structure as the Sass document: - # rules and properties are nested beneath one another. - # Nodes that can be in this state or in the dynamic state - # are called **static nodes**. # - # {Tree::Node#cssize} then returns a static CSS tree. - # This is like a static Sass tree, - # but the structure exactly mirrors that of the generated CSS. - # Rules and properties can't be nested beneath one another in this state. - # - # Finally, {Tree::Node#to_s} can be called on a static CSS tree - # to get the actual CSS code as a string. + # Nodes that only appear in the pre-perform state are called **dynamic nodes**; + # those that appear in both states are called **static nodes**. module Tree - # The abstract superclass of all parse-tree nodes. + # This class doubles as the root node of the parse tree + # and the superclass of all other parse-tree nodes. class Node - include Enumerable - # The child nodes of this node. # # @return [Array<Tree::Node>] attr_accessor :children - # Whether or not this node has child nodes. - # This may be true even when \{#children} is empty, - # in which case this node has an empty block (e.g. `{}`). - # - # @return [Boolean] - attr_accessor :has_children - # The line of the document on which this node appeared. # # @return [Fixnum] attr_accessor :line @@ -67,48 +50,36 @@ def options=(options) children.each {|c| c.options = options} @options = options end - # @private - def children=(children) - self.has_children ||= !children.empty? - @children = children - end - # The name of the document on which this node appeared. # # @return [String] def filename @filename || (@options && @options[:filename]) end # Appends a child to the node. # - # @param child [Tree::Node, Array<Tree::Node>] The child node or nodes + # @param child [Tree::Node] The child node # @raise [Sass::SyntaxError] if `child` is invalid # @see #invalid_child? def <<(child) - return if child.nil? - if child.is_a?(Array) - child.each {|c| self << c} - else - check_child! child - self.has_children = true - @children << child + if msg = invalid_child?(child) + raise Sass::SyntaxError.new(msg, child.line) end + @children << child end - # Raises an error if the given child node is invalid. + # Return the last child node. # - # @param child [Tree::Node] The child node - # @raise [Sass::SyntaxError] if `child` is invalid - # @see #invalid_child? - def check_child!(child) - if msg = invalid_child?(child) - raise Sass::SyntaxError.new(msg, :line => child.line) - end + # We need this because {Tree::Node} duck types as an Array for {Sass::Engine}. + # + # @return [Tree::Node] The last child node + def last + children.last end # Compares this node and another object (only other {Tree::Node}s will be equal). # This does a structural comparison; # if the contents of the nodes and all the child nodes are equivalent, @@ -122,178 +93,83 @@ # @see Sass::Tree def ==(other) self.class == other.class && other.children == children end + # Runs the dynamic Sass code *and* computes the CSS for the tree. + # + # @see #perform + # @see #to_s + def render + perform(Environment.new).to_s + end + # True if \{#to\_s} will return `nil`; # that is, if the node shouldn't be rendered. # Should only be called in a static tree. # # @return [Boolean] def invisible?; false; end - # The output style. See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}. + # Computes the CSS corresponding to this Sass tree. # - # @return [Symbol] - def style - @options[:style] - end - - # Computes the CSS corresponding to this static CSS tree. - # - # \{#to_s} shouldn't be overridden directly; instead, override \{#\_to\_s}. # Only static-node subclasses need to implement \{#to\_s}. # # This may return `nil`, but it will only do so if \{#invisible?} is true. # - # @param args [Array] Passed on to \{#\_to\_s} # @return [String, nil] The resulting CSS - # @see Sass::Tree - def to_s(*args) - _to_s(*args) - rescue Sass::SyntaxError => e - e.modify_backtrace(:filename => filename, :line => line) - raise e - end - - # Converts a static CSS tree (e.g. the output of \{#cssize}) - # into another static CSS tree, - # with the given extensions applied to all relevant {RuleNode}s. - # - # @todo Link this to the reference documentation on `@extend` - # when such a thing exists. - # - # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}] - # The extensions to perform on this tree - # @return [Tree::Node] The resulting tree of static CSS nodes. - # @raise [Sass::SyntaxError] Only if there's a programmer error - # and this is not a static CSS tree - def do_extend(extends) - node = dup - node.children = children.map {|c| c.do_extend(extends)} - node - rescue Sass::SyntaxError => e - e.modify_backtrace(:filename => filename, :line => line) - raise e - end - - # Converts a static Sass tree (e.g. the output of \{#perform}) - # into a static CSS tree. - # - # \{#cssize} shouldn't be overridden directly; - # instead, override \{#\_cssize} or \{#cssize!}. - # - # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}] - # The extensions defined for this tree - # @param parent [Node, nil] The parent node of this node. - # This should only be non-nil if the parent is the same class as this node - # @return [Tree::Node] The resulting tree of static nodes # @raise [Sass::SyntaxError] if some element of the tree is invalid # @see Sass::Tree - def cssize(extends, parent = nil) - _cssize(extends, (parent if parent.class == self.class)) - rescue Sass::SyntaxError => e - e.modify_backtrace(:filename => filename, :line => line) - raise e + def to_s + result = String.new + children.each do |child| + if child.is_a? PropNode + message = "Properties aren't allowed at the root of a document." + + child.pseudo_class_selector_message + raise Sass::SyntaxError.new(message, child.line) + else + next if child.invisible? + child_str = child.to_s(1) + result << child_str + (style == :compressed ? '' : "\n") + end + end + result.rstrip! + return "" if result.empty? + return result + "\n" + rescue Sass::SyntaxError => e; e.add_metadata(filename, line) end - # Converts a dynamic tree into a static Sass tree. - # That is, runs the dynamic Sass code: + # Runs the dynamic Sass code: # mixins, variables, control directives, and so forth. # This doesn't modify this node or any of its children. # # \{#perform} shouldn't be overridden directly; - # instead, override \{#\_perform} or \{#perform!}. + # if you want to return a new node (or list of nodes), + # override \{#\_perform}; + # if you want to destructively modify this node, + # override \{#perform!}. # # @param environment [Sass::Environment] The lexical environment containing # variable and mixin values # @return [Tree::Node] The resulting tree of static nodes # @raise [Sass::SyntaxError] if some element of the tree is invalid # @see Sass::Tree def perform(environment) + environment.options = @options if self.class == Tree::Node _perform(environment) - rescue Sass::SyntaxError => e - e.modify_backtrace(:filename => filename, :line => line) - raise e + rescue Sass::SyntaxError => e; e.add_metadata(filename, line) end - # Iterates through each node in the tree rooted at this node - # in a pre-order walk. + # The output style. See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}. # - # @yield node - # @yieldparam node [Node] a node in the tree - def each(&block) - yield self - children.each {|c| c.each(&block)} + # @return [Symbol] + def style + @options[:style] end - # Converts a node to Sass code that will generate it. - # - # @param tabs [Fixnum] The amount of tabulation to use for the Sass code - # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}) - # @return [String] The Sass code corresponding to the node - def to_sass(tabs = 0, opts = {}) - to_src(tabs, opts, :sass) - end - - # Converts a node to SCSS code that will generate it. - # - # @param tabs [Fixnum] The amount of tabulation to use for the SCSS code - # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}) - # @return [String] The Sass code corresponding to the node - def to_scss(tabs = 0, opts = {}) - to_src(tabs, opts, :scss) - end - protected - # Computes the CSS corresponding to this particular Sass node. - # - # This method should never raise {Sass::SyntaxError}s. - # Such errors will not be properly annotated with Sass backtrace information. - # All error conditions should be checked in earlier transformations, - # such as \{#cssize} and \{#perform}. - # - # @param args [Array] ignored - # @return [String, nil] The resulting CSS - # @see #to_s - # @see Sass::Tree - def _to_s - raise NotImplementedError.new("All static-node subclasses of Sass::Tree::Node must override #_to_s or #to_s.") - end - - # Converts this static Sass node into a static CSS node, - # returning the new node. - # This doesn't modify this node or any of its children. - # - # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}] - # The extensions defined for this tree - # @param parent [Node, nil] The parent node of this node. - # This should only be non-nil if the parent is the same class as this node - # @return [Tree::Node, Array<Tree::Node>] The resulting static CSS nodes - # @raise [Sass::SyntaxError] if some element of the tree is invalid - # @see #cssize - # @see Sass::Tree - def _cssize(extends, parent) - node = dup - node.cssize!(extends, parent) - node - end - - # Destructively converts this static Sass node into a static CSS node. - # This *does* modify this node, - # but will be run non-destructively by \{#\_cssize\}. - # - # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}] - # The extensions defined for this tree - # @param parent [Node, nil] The parent node of this node. - # This should only be non-nil if the parent is the same class as this node - # @see #cssize - def cssize!(extends, parent) - self.children = children.map {|c| c.cssize(extends, self)}.flatten - end - # Runs any dynamic Sass code in this particular node. # This doesn't modify this node or any of its children. # # @param environment [Sass::Environment] The lexical environment containing # variable and mixin values @@ -324,142 +200,52 @@ # @return [Array<Tree::Node>] The resulting static nodes def perform_children(environment) children.map {|c| c.perform(environment)}.flatten end - # Replaces SassScript in a chunk of text + # Replaces SassScript in a chunk of text (via `#{}`) # with the resulting value. # - # @param text [Array<String, Sass::Script::Node>] The text to interpolate + # @param text [String] The text to interpolate # @param environment [Sass::Environment] The lexical environment containing # variable and mixin values # @return [String] The interpolated text - def run_interp(text, environment) - text.map do |r| - next r if r.is_a?(String) - val = r.perform(environment) - # Interpolated strings should never render with quotes - next val.value if val.is_a?(Sass::Script::String) - val.to_s - end.join.strip + def interpolate(text, environment) + res = '' + rest = Haml::Shared.handle_interpolation text do |scan| + escapes = scan[2].size + res << scan.matched[0...-2 - escapes] + if escapes % 2 == 1 + res << "\\" * (escapes - 1) << '#{' + else + res << "\\" * [0, escapes - 1].max + res << Script::Parser.new(scan, line, scan.pos - scan.matched_size, filename). + parse_interpolated.perform(environment).to_s + end + end + res + rest end # @see Haml::Shared.balance # @raise [Sass::SyntaxError] if the brackets aren't balanced def balance(*args) res = Haml::Shared.balance(*args) return res if res - raise Sass::SyntaxError.new("Unbalanced brackets.", :line => line) + raise Sass::SyntaxError.new("Unbalanced brackets.", line) end # Returns an error message if the given child node is invalid, # and false otherwise. # - # By default, all child nodes except those only allowed under specific nodes - # ({Tree::MixinDefNode}, {Tree::ImportNode}, {Tree::ExtendNode}) are valid. + # By default, all child nodes are valid. # This is expected to be overriden by subclasses # for which some children are invalid. # # @param child [Tree::Node] A potential child node # @return [Boolean, String] Whether or not the child node is valid, # as well as the error message to display if it is invalid def invalid_child?(child) - case child - when Tree::MixinDefNode - "Mixins may only be defined at the root of a document." - when Tree::ImportNode - "Import directives may only be used at the root of a document." - when Tree::ExtendNode - "Extend directives may only be used within rules." - end - end - - # Converts a node to Sass or SCSS code that will generate it. - # - # This method is called by the default \{#to\_sass} and \{#to\_scss} methods, - # so that the same code can be used for both with minor variations. - # - # @param tabs [Fixnum] The amount of tabulation to use for the SCSS code - # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}) - # @param fmt [Symbol] `:sass` or `:scss` - # @return [String] The Sass or SCSS code corresponding to the node - def to_src(tabs, opts, fmt) - raise NotImplementedError.new("All static-node subclasses of Sass::Tree::Node must override #to_#{fmt}.") - end - - # Converts the children of this node to a Sass or SCSS string. - # This will return the trailing newline for the previous line, - # including brackets if this is SCSS. - # - # @param tabs [Fixnum] The amount of tabulation to use for the Sass code - # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}) - # @param fmt [Symbol] `:sass` or `:scss` - # @return [String] The Sass or SCSS code corresponding to the children - def children_to_src(tabs, opts, fmt) - return fmt == :sass ? "\n" : " {}\n" if children.empty? - - (fmt == :sass ? "\n" : " {\n") + - children.map {|c| c.send("to_#{fmt}", tabs + 1, opts)}.join.rstrip + - (fmt == :sass ? "\n" : " }\n") - end - - # Converts a selector to a Sass or SCSS string. - # - # @param sel [Array<String, Sass::Script::Node>] The selector to convert - # @param tabs [Fixnum] The indentation of the selector - # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}) - # @param fmt [Symbol] `:sass` or `:scss` - # @return [String] The Sass or SCSS code corresponding to the selector - def selector_to_src(sel, tabs, opts, fmt) - fmt == :sass ? selector_to_sass(sel, opts) : selector_to_scss(sel, tabs, opts) - end - - # Converts a selector to a Sass string. - # - # @param sel [Array<String, Sass::Script::Node>] The selector to convert - # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}) - # @return [String] The Sass code corresponding to the selector - def selector_to_sass(sel, opts) - sel.map do |r| - if r.is_a?(String) - r.gsub(/(,[ \t]*)?\n\s*/) {$1 ? $1 + "\n" : " "} - else - "\#{#{r.to_sass(opts)}}" - end - end.join - end - - # Converts a selector to a SCSS string. - # - # @param sel [Array<String, Sass::Script::Node>] The selector to convert - # @param tabs [Fixnum] The indentation of the selector - # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}) - # @return [String] The SCSS code corresponding to the selector - def selector_to_scss(sel, tabs, opts) - sel.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass(opts)}}"}. - join.gsub(/^[ \t]*/, ' ' * tabs) - end - - # Convert any underscores in a string into hyphens, - # but only if the `:dasherize` option is set. - # - # @param s [String] The string to convert - # @param opts [{Symbol => Object}] The options hash - # @return [String] The converted string - def dasherize(s, opts) - if opts[:dasherize] - s.gsub('_', '-') - else - s - end - end - - # Returns a semicolon if this is SCSS, or an empty string if this is Sass. - # - # @param fmt [Symbol] `:sass` or `:scss` - # @return [String] A semicolon or the empty string - def semi(fmt) - fmt == :sass ? "" : ";" + false end end end end