haml 3.0.21 → 3.0.22
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of haml might be problematic. Click here for more details.
- data/Rakefile +1 -41
- data/VERSION +1 -1
- data/lib/haml/precompiler.rb +8 -6
- data/lib/haml/template/plugin.rb +16 -6
- data/lib/sass/less.rb +31 -12
- data/lib/sass/script/funcall.rb +2 -1
- data/lib/sass/script/operation.rb +10 -5
- data/lib/sass/script/parser.rb +9 -0
- data/test/haml/engine_test.rb +25 -0
- data/test/sass/less_conversion_test.rb +24 -3
- data/test/sass/script_conversion_test.rb +60 -0
- metadata +243 -426
- data/vendor/sass/CONTRIBUTING +0 -3
- data/vendor/sass/MIT-LICENSE +0 -20
- data/vendor/sass/README.md +0 -201
- data/vendor/sass/Rakefile +0 -363
- data/vendor/sass/TODO +0 -39
- data/vendor/sass/VERSION +0 -1
- data/vendor/sass/VERSION_NAME +0 -1
- data/vendor/sass/bin/css2sass +0 -13
- data/vendor/sass/bin/sass +0 -8
- data/vendor/sass/bin/sass-convert +0 -7
- data/vendor/sass/doc-src/FAQ.md +0 -35
- data/vendor/sass/doc-src/INDENTED_SYNTAX.md +0 -210
- data/vendor/sass/doc-src/SASS_CHANGELOG.md +0 -1870
- data/vendor/sass/doc-src/SASS_REFERENCE.md +0 -1713
- data/vendor/sass/doc-src/SCSS_FOR_SASS_USERS.md +0 -155
- data/vendor/sass/ext/extconf.rb +0 -10
- data/vendor/sass/extra/update_watch.rb +0 -13
- data/vendor/sass/init.rb +0 -18
- data/vendor/sass/lib/sass.rb +0 -71
- data/vendor/sass/lib/sass/cache_store.rb +0 -208
- data/vendor/sass/lib/sass/callbacks.rb +0 -66
- data/vendor/sass/lib/sass/css.rb +0 -294
- data/vendor/sass/lib/sass/engine.rb +0 -792
- data/vendor/sass/lib/sass/environment.rb +0 -143
- data/vendor/sass/lib/sass/error.rb +0 -201
- data/vendor/sass/lib/sass/exec.rb +0 -619
- data/vendor/sass/lib/sass/importers.rb +0 -22
- data/vendor/sass/lib/sass/importers/base.rb +0 -138
- data/vendor/sass/lib/sass/importers/filesystem.rb +0 -121
- data/vendor/sass/lib/sass/less.rb +0 -363
- data/vendor/sass/lib/sass/plugin.rb +0 -126
- data/vendor/sass/lib/sass/plugin/compiler.rb +0 -346
- data/vendor/sass/lib/sass/plugin/configuration.rb +0 -123
- data/vendor/sass/lib/sass/plugin/generic.rb +0 -15
- data/vendor/sass/lib/sass/plugin/merb.rb +0 -48
- data/vendor/sass/lib/sass/plugin/rack.rb +0 -47
- data/vendor/sass/lib/sass/plugin/rails.rb +0 -41
- data/vendor/sass/lib/sass/plugin/staleness_checker.rb +0 -145
- data/vendor/sass/lib/sass/railtie.rb +0 -8
- data/vendor/sass/lib/sass/repl.rb +0 -58
- data/vendor/sass/lib/sass/root.rb +0 -7
- data/vendor/sass/lib/sass/script.rb +0 -63
- data/vendor/sass/lib/sass/script/bool.rb +0 -18
- data/vendor/sass/lib/sass/script/color.rb +0 -491
- data/vendor/sass/lib/sass/script/css_lexer.rb +0 -29
- data/vendor/sass/lib/sass/script/css_parser.rb +0 -31
- data/vendor/sass/lib/sass/script/funcall.rb +0 -79
- data/vendor/sass/lib/sass/script/functions.rb +0 -852
- data/vendor/sass/lib/sass/script/interpolation.rb +0 -70
- data/vendor/sass/lib/sass/script/lexer.rb +0 -337
- data/vendor/sass/lib/sass/script/literal.rb +0 -236
- data/vendor/sass/lib/sass/script/node.rb +0 -101
- data/vendor/sass/lib/sass/script/number.rb +0 -423
- data/vendor/sass/lib/sass/script/operation.rb +0 -92
- data/vendor/sass/lib/sass/script/parser.rb +0 -392
- data/vendor/sass/lib/sass/script/string.rb +0 -67
- data/vendor/sass/lib/sass/script/string_interpolation.rb +0 -93
- data/vendor/sass/lib/sass/script/unary_operation.rb +0 -57
- data/vendor/sass/lib/sass/script/variable.rb +0 -48
- data/vendor/sass/lib/sass/scss.rb +0 -17
- data/vendor/sass/lib/sass/scss/css_parser.rb +0 -51
- data/vendor/sass/lib/sass/scss/parser.rb +0 -838
- data/vendor/sass/lib/sass/scss/rx.rb +0 -126
- data/vendor/sass/lib/sass/scss/sass_parser.rb +0 -11
- data/vendor/sass/lib/sass/scss/script_lexer.rb +0 -15
- data/vendor/sass/lib/sass/scss/script_parser.rb +0 -25
- data/vendor/sass/lib/sass/scss/static_parser.rb +0 -40
- data/vendor/sass/lib/sass/selector.rb +0 -361
- data/vendor/sass/lib/sass/selector/abstract_sequence.rb +0 -62
- data/vendor/sass/lib/sass/selector/comma_sequence.rb +0 -82
- data/vendor/sass/lib/sass/selector/sequence.rb +0 -236
- data/vendor/sass/lib/sass/selector/simple.rb +0 -113
- data/vendor/sass/lib/sass/selector/simple_sequence.rb +0 -135
- data/vendor/sass/lib/sass/shared.rb +0 -78
- data/vendor/sass/lib/sass/tree/comment_node.rb +0 -128
- data/vendor/sass/lib/sass/tree/debug_node.rb +0 -36
- data/vendor/sass/lib/sass/tree/directive_node.rb +0 -75
- data/vendor/sass/lib/sass/tree/extend_node.rb +0 -65
- data/vendor/sass/lib/sass/tree/for_node.rb +0 -67
- data/vendor/sass/lib/sass/tree/if_node.rb +0 -81
- data/vendor/sass/lib/sass/tree/import_node.rb +0 -124
- data/vendor/sass/lib/sass/tree/mixin_def_node.rb +0 -60
- data/vendor/sass/lib/sass/tree/mixin_node.rb +0 -123
- data/vendor/sass/lib/sass/tree/node.rb +0 -490
- data/vendor/sass/lib/sass/tree/prop_node.rb +0 -220
- data/vendor/sass/lib/sass/tree/root_node.rb +0 -125
- data/vendor/sass/lib/sass/tree/rule_node.rb +0 -273
- data/vendor/sass/lib/sass/tree/variable_node.rb +0 -39
- data/vendor/sass/lib/sass/tree/warn_node.rb +0 -42
- data/vendor/sass/lib/sass/tree/while_node.rb +0 -48
- data/vendor/sass/lib/sass/util.rb +0 -700
- data/vendor/sass/lib/sass/util/subset_map.rb +0 -101
- data/vendor/sass/lib/sass/version.rb +0 -109
- data/vendor/sass/rails/init.rb +0 -1
- data/vendor/sass/sass.gemspec +0 -32
- data/vendor/sass/test/sass/cache_test.rb +0 -74
- data/vendor/sass/test/sass/callbacks_test.rb +0 -61
- data/vendor/sass/test/sass/conversion_test.rb +0 -1210
- data/vendor/sass/test/sass/css2sass_test.rb +0 -364
- data/vendor/sass/test/sass/data/hsl-rgb.txt +0 -319
- data/vendor/sass/test/sass/engine_test.rb +0 -2283
- data/vendor/sass/test/sass/extend_test.rb +0 -1348
- data/vendor/sass/test/sass/functions_test.rb +0 -565
- data/vendor/sass/test/sass/importer_test.rb +0 -104
- data/vendor/sass/test/sass/less_conversion_test.rb +0 -632
- data/vendor/sass/test/sass/mock_importer.rb +0 -49
- data/vendor/sass/test/sass/more_results/more1.css +0 -9
- data/vendor/sass/test/sass/more_results/more1_with_line_comments.css +0 -26
- data/vendor/sass/test/sass/more_results/more_import.css +0 -29
- data/vendor/sass/test/sass/more_templates/_more_partial.sass +0 -2
- data/vendor/sass/test/sass/more_templates/more1.sass +0 -23
- data/vendor/sass/test/sass/more_templates/more_import.sass +0 -11
- data/vendor/sass/test/sass/plugin_test.rb +0 -430
- data/vendor/sass/test/sass/results/alt.css +0 -4
- data/vendor/sass/test/sass/results/basic.css +0 -9
- data/vendor/sass/test/sass/results/compact.css +0 -5
- data/vendor/sass/test/sass/results/complex.css +0 -86
- data/vendor/sass/test/sass/results/compressed.css +0 -1
- data/vendor/sass/test/sass/results/expanded.css +0 -19
- data/vendor/sass/test/sass/results/import.css +0 -31
- data/vendor/sass/test/sass/results/line_numbers.css +0 -49
- data/vendor/sass/test/sass/results/mixins.css +0 -95
- data/vendor/sass/test/sass/results/multiline.css +0 -24
- data/vendor/sass/test/sass/results/nested.css +0 -22
- data/vendor/sass/test/sass/results/options.css +0 -1
- data/vendor/sass/test/sass/results/parent_ref.css +0 -13
- data/vendor/sass/test/sass/results/script.css +0 -16
- data/vendor/sass/test/sass/results/scss_import.css +0 -31
- data/vendor/sass/test/sass/results/scss_importee.css +0 -2
- data/vendor/sass/test/sass/results/subdir/nested_subdir/nested_subdir.css +0 -1
- data/vendor/sass/test/sass/results/subdir/subdir.css +0 -3
- data/vendor/sass/test/sass/results/units.css +0 -11
- data/vendor/sass/test/sass/results/warn.css +0 -0
- data/vendor/sass/test/sass/results/warn_imported.css +0 -0
- data/vendor/sass/test/sass/script_conversion_test.rb +0 -254
- data/vendor/sass/test/sass/script_test.rb +0 -470
- data/vendor/sass/test/sass/scss/css_test.rb +0 -897
- data/vendor/sass/test/sass/scss/rx_test.rb +0 -156
- data/vendor/sass/test/sass/scss/scss_test.rb +0 -1088
- data/vendor/sass/test/sass/scss/test_helper.rb +0 -37
- data/vendor/sass/test/sass/templates/_partial.sass +0 -2
- data/vendor/sass/test/sass/templates/alt.sass +0 -16
- data/vendor/sass/test/sass/templates/basic.sass +0 -23
- data/vendor/sass/test/sass/templates/bork1.sass +0 -2
- data/vendor/sass/test/sass/templates/bork2.sass +0 -2
- data/vendor/sass/test/sass/templates/bork3.sass +0 -2
- data/vendor/sass/test/sass/templates/bork4.sass +0 -2
- data/vendor/sass/test/sass/templates/compact.sass +0 -17
- data/vendor/sass/test/sass/templates/complex.sass +0 -305
- data/vendor/sass/test/sass/templates/compressed.sass +0 -15
- data/vendor/sass/test/sass/templates/expanded.sass +0 -17
- data/vendor/sass/test/sass/templates/import.sass +0 -12
- data/vendor/sass/test/sass/templates/importee.less +0 -2
- data/vendor/sass/test/sass/templates/importee.sass +0 -19
- data/vendor/sass/test/sass/templates/line_numbers.sass +0 -13
- data/vendor/sass/test/sass/templates/mixin_bork.sass +0 -5
- data/vendor/sass/test/sass/templates/mixins.sass +0 -76
- data/vendor/sass/test/sass/templates/multiline.sass +0 -20
- data/vendor/sass/test/sass/templates/nested.sass +0 -25
- data/vendor/sass/test/sass/templates/nested_bork1.sass +0 -2
- data/vendor/sass/test/sass/templates/nested_bork2.sass +0 -2
- data/vendor/sass/test/sass/templates/nested_bork3.sass +0 -2
- data/vendor/sass/test/sass/templates/nested_bork4.sass +0 -2
- data/vendor/sass/test/sass/templates/nested_mixin_bork.sass +0 -6
- data/vendor/sass/test/sass/templates/options.sass +0 -2
- data/vendor/sass/test/sass/templates/parent_ref.sass +0 -25
- data/vendor/sass/test/sass/templates/script.sass +0 -101
- data/vendor/sass/test/sass/templates/scss_import.scss +0 -11
- data/vendor/sass/test/sass/templates/scss_importee.scss +0 -1
- data/vendor/sass/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +0 -2
- data/vendor/sass/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +0 -3
- data/vendor/sass/test/sass/templates/subdir/subdir.sass +0 -6
- data/vendor/sass/test/sass/templates/units.sass +0 -11
- data/vendor/sass/test/sass/templates/warn.sass +0 -3
- data/vendor/sass/test/sass/templates/warn_imported.sass +0 -4
- data/vendor/sass/test/sass/test_helper.rb +0 -8
- data/vendor/sass/test/sass/util/subset_map_test.rb +0 -91
- data/vendor/sass/test/sass/util_test.rb +0 -275
- data/vendor/sass/test/test_helper.rb +0 -64
- data/vendor/sass/yard/callbacks.rb +0 -29
- data/vendor/sass/yard/default/fulldoc/html/css/common.sass +0 -26
- data/vendor/sass/yard/default/layout/html/footer.erb +0 -12
- data/vendor/sass/yard/inherited_hash.rb +0 -41
@@ -1,66 +0,0 @@
|
|
1
|
-
module Sass
|
2
|
-
# A lightweight infrastructure for defining and running callbacks.
|
3
|
-
# Callbacks are defined using \{#define\_callback\} at the class level,
|
4
|
-
# and called using `run_#{name}` at the instance level.
|
5
|
-
#
|
6
|
-
# Clients can add callbacks by calling the generated `on_#{name}` method,
|
7
|
-
# and passing in a block that's run when the callback is activated.
|
8
|
-
#
|
9
|
-
# @example Define a callback
|
10
|
-
# class Munger
|
11
|
-
# extend Sass::Callbacks
|
12
|
-
# define_callback :string_munged
|
13
|
-
#
|
14
|
-
# def munge(str)
|
15
|
-
# res = str.gsub(/[a-z]/, '\1\1')
|
16
|
-
# run_string_munged str, res
|
17
|
-
# res
|
18
|
-
# end
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# @example Use a callback
|
22
|
-
# m = Munger.new
|
23
|
-
# m.on_string_munged {|str, res| puts "#{str} was munged into #{res}!"}
|
24
|
-
# m.munge "bar" #=> bar was munged into bbaarr!
|
25
|
-
module Callbacks
|
26
|
-
# Automatically includes {InstanceMethods}
|
27
|
-
# when something extends this module.
|
28
|
-
#
|
29
|
-
# @param base [Module]
|
30
|
-
def self.extended(base)
|
31
|
-
base.send(:include, InstanceMethods)
|
32
|
-
end
|
33
|
-
protected
|
34
|
-
|
35
|
-
module InstanceMethods
|
36
|
-
# Removes all callbacks registered against this object.
|
37
|
-
def clear_callbacks!
|
38
|
-
@_sass_callbacks = {}
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# Define a callback with the given name.
|
43
|
-
# This will define an `on_#{name}` method
|
44
|
-
# that registers a block,
|
45
|
-
# and a `run_#{name}` method that runs that block
|
46
|
-
# (optionall with some arguments).
|
47
|
-
#
|
48
|
-
# @param name [Symbol] The name of the callback
|
49
|
-
# @return [void]
|
50
|
-
def define_callback(name)
|
51
|
-
class_eval <<RUBY
|
52
|
-
def on_#{name}(&block)
|
53
|
-
@_sass_callbacks ||= {}
|
54
|
-
(@_sass_callbacks[#{name.inspect}] ||= []) << block
|
55
|
-
end
|
56
|
-
|
57
|
-
def run_#{name}(*args)
|
58
|
-
return unless @_sass_callbacks
|
59
|
-
return unless @_sass_callbacks[#{name.inspect}]
|
60
|
-
@_sass_callbacks[#{name.inspect}].each {|c| c[*args]}
|
61
|
-
end
|
62
|
-
private :run_#{name}
|
63
|
-
RUBY
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
data/vendor/sass/lib/sass/css.rb
DELETED
@@ -1,294 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../sass'
|
2
|
-
require 'sass/tree/node'
|
3
|
-
require 'sass/scss/css_parser'
|
4
|
-
require 'strscan'
|
5
|
-
|
6
|
-
module Sass
|
7
|
-
# This class converts CSS documents into Sass or SCSS templates.
|
8
|
-
# It works by parsing the CSS document into a {Sass::Tree} structure,
|
9
|
-
# and then applying various transformations to the structure
|
10
|
-
# to produce more concise and idiomatic Sass/SCSS.
|
11
|
-
#
|
12
|
-
# Example usage:
|
13
|
-
#
|
14
|
-
# Sass::CSS.new("p { color: blue }").render(:sass) #=> "p\n color: blue"
|
15
|
-
# Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n color: blue; }"
|
16
|
-
class CSS
|
17
|
-
# @param template [String] The CSS stylesheet.
|
18
|
-
# This stylesheet can be encoded using any encoding
|
19
|
-
# that can be converted to Unicode.
|
20
|
-
# If the stylesheet contains an `@charset` declaration,
|
21
|
-
# that overrides the Ruby encoding
|
22
|
-
# (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
|
23
|
-
# @option options :old [Boolean] (false)
|
24
|
-
# Whether or not to output old property syntax
|
25
|
-
# (`:color blue` as opposed to `color: blue`).
|
26
|
-
# This is only meaningful when generating Sass code,
|
27
|
-
# rather than SCSS.
|
28
|
-
def initialize(template, options = {})
|
29
|
-
if template.is_a? IO
|
30
|
-
template = template.read
|
31
|
-
end
|
32
|
-
|
33
|
-
@options = options.dup
|
34
|
-
# Backwards compatibility
|
35
|
-
@options[:old] = true if @options[:alternate] == false
|
36
|
-
@template = template
|
37
|
-
end
|
38
|
-
|
39
|
-
# Converts the CSS template into Sass or SCSS code.
|
40
|
-
#
|
41
|
-
# @param fmt [Symbol] `:sass` or `:scss`, designating the format to return.
|
42
|
-
# @return [String] The resulting Sass or SCSS code
|
43
|
-
# @raise [Sass::SyntaxError] if there's an error parsing the CSS template
|
44
|
-
def render(fmt = :sass)
|
45
|
-
check_encoding!
|
46
|
-
build_tree.send("to_#{fmt}", @options).strip + "\n"
|
47
|
-
rescue Sass::SyntaxError => err
|
48
|
-
err.modify_backtrace(:filename => @options[:filename] || '(css)')
|
49
|
-
raise err
|
50
|
-
end
|
51
|
-
|
52
|
-
# Returns the original encoding of the document,
|
53
|
-
# or `nil` under Ruby 1.8.
|
54
|
-
#
|
55
|
-
# @return [Encoding, nil]
|
56
|
-
# @raise [Encoding::UndefinedConversionError] if the source encoding
|
57
|
-
# cannot be converted to UTF-8
|
58
|
-
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
59
|
-
def source_encoding
|
60
|
-
check_encoding!
|
61
|
-
@original_encoding
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def check_encoding!
|
67
|
-
return if @checked_encoding
|
68
|
-
@checked_encoding = true
|
69
|
-
@template, @original_encoding = Sass::Util.check_sass_encoding(@template) do |msg, line|
|
70
|
-
raise Sass::SyntaxError.new(msg, :line => line)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Parses the CSS template and applies various transformations
|
75
|
-
#
|
76
|
-
# @return [Tree::Node] The root node of the parsed tree
|
77
|
-
def build_tree
|
78
|
-
root = Sass::SCSS::CssParser.new(@template).parse
|
79
|
-
expand_commas root
|
80
|
-
parent_ref_rules root
|
81
|
-
remove_parent_refs root
|
82
|
-
flatten_rules root
|
83
|
-
fold_commas root
|
84
|
-
root
|
85
|
-
end
|
86
|
-
|
87
|
-
# Transform
|
88
|
-
#
|
89
|
-
# foo, bar, baz
|
90
|
-
# color: blue
|
91
|
-
#
|
92
|
-
# into
|
93
|
-
#
|
94
|
-
# foo
|
95
|
-
# color: blue
|
96
|
-
# bar
|
97
|
-
# color: blue
|
98
|
-
# baz
|
99
|
-
# color: blue
|
100
|
-
#
|
101
|
-
# @param root [Tree::Node] The parent node
|
102
|
-
def expand_commas(root)
|
103
|
-
root.children.map! do |child|
|
104
|
-
unless child.is_a?(Tree::RuleNode) && child.rule.first.include?(',')
|
105
|
-
expand_commas(child) if child.is_a?(Tree::DirectiveNode)
|
106
|
-
next child
|
107
|
-
end
|
108
|
-
child.rule.first.split(',').map do |rule|
|
109
|
-
node = Tree::RuleNode.new([rule.strip])
|
110
|
-
node.children = child.children
|
111
|
-
node
|
112
|
-
end
|
113
|
-
end
|
114
|
-
root.children.flatten!
|
115
|
-
end
|
116
|
-
|
117
|
-
# Make rules use parent refs so that
|
118
|
-
#
|
119
|
-
# foo
|
120
|
-
# color: green
|
121
|
-
# foo.bar
|
122
|
-
# color: blue
|
123
|
-
#
|
124
|
-
# becomes
|
125
|
-
#
|
126
|
-
# foo
|
127
|
-
# color: green
|
128
|
-
# &.bar
|
129
|
-
# color: blue
|
130
|
-
#
|
131
|
-
# This has the side effect of nesting rules,
|
132
|
-
# so that
|
133
|
-
#
|
134
|
-
# foo
|
135
|
-
# color: green
|
136
|
-
# foo bar
|
137
|
-
# color: red
|
138
|
-
# foo baz
|
139
|
-
# color: blue
|
140
|
-
#
|
141
|
-
# becomes
|
142
|
-
#
|
143
|
-
# foo
|
144
|
-
# color: green
|
145
|
-
# & bar
|
146
|
-
# color: red
|
147
|
-
# & baz
|
148
|
-
# color: blue
|
149
|
-
#
|
150
|
-
# @param root [Tree::Node] The parent node
|
151
|
-
def parent_ref_rules(root)
|
152
|
-
current_rule = nil
|
153
|
-
root.children.map! do |child|
|
154
|
-
unless child.is_a?(Tree::RuleNode)
|
155
|
-
parent_ref_rules(child) if child.is_a?(Tree::DirectiveNode)
|
156
|
-
next child
|
157
|
-
end
|
158
|
-
|
159
|
-
first, rest = child.rule.first.scan(/\A(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?\Z/m).first
|
160
|
-
|
161
|
-
if current_rule.nil? || current_rule.rule.first != first
|
162
|
-
current_rule = Tree::RuleNode.new([first])
|
163
|
-
end
|
164
|
-
|
165
|
-
if rest
|
166
|
-
child.rule = ["&" + rest]
|
167
|
-
current_rule << child
|
168
|
-
else
|
169
|
-
current_rule.children += child.children
|
170
|
-
end
|
171
|
-
|
172
|
-
current_rule
|
173
|
-
end
|
174
|
-
root.children.compact!
|
175
|
-
root.children.uniq!
|
176
|
-
|
177
|
-
root.children.each { |v| parent_ref_rules(v) }
|
178
|
-
end
|
179
|
-
|
180
|
-
# Remove useless parent refs so that
|
181
|
-
#
|
182
|
-
# foo
|
183
|
-
# & bar
|
184
|
-
# color: blue
|
185
|
-
#
|
186
|
-
# becomes
|
187
|
-
#
|
188
|
-
# foo
|
189
|
-
# bar
|
190
|
-
# color: blue
|
191
|
-
#
|
192
|
-
# @param root [Tree::Node] The parent node
|
193
|
-
def remove_parent_refs(root)
|
194
|
-
root.children.each do |child|
|
195
|
-
case child
|
196
|
-
when Tree::RuleNode
|
197
|
-
child.rule.first.gsub! /^& +/, ''
|
198
|
-
remove_parent_refs child
|
199
|
-
when Tree::DirectiveNode
|
200
|
-
remove_parent_refs child
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
# Flatten rules so that
|
206
|
-
#
|
207
|
-
# foo
|
208
|
-
# bar
|
209
|
-
# color: red
|
210
|
-
#
|
211
|
-
# becomes
|
212
|
-
#
|
213
|
-
# foo bar
|
214
|
-
# color: red
|
215
|
-
#
|
216
|
-
# and
|
217
|
-
#
|
218
|
-
# foo
|
219
|
-
# &.bar
|
220
|
-
# color: blue
|
221
|
-
#
|
222
|
-
# becomes
|
223
|
-
#
|
224
|
-
# foo.bar
|
225
|
-
# color: blue
|
226
|
-
#
|
227
|
-
# @param root [Tree::Node] The parent node
|
228
|
-
def flatten_rules(root)
|
229
|
-
root.children.each do |child|
|
230
|
-
case child
|
231
|
-
when Tree::RuleNode
|
232
|
-
flatten_rule(child)
|
233
|
-
when Tree::DirectiveNode
|
234
|
-
flatten_rules(child)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
# Flattens a single rule
|
240
|
-
#
|
241
|
-
# @param rule [Tree::RuleNode] The candidate for flattening
|
242
|
-
# @see #flatten_rules
|
243
|
-
def flatten_rule(rule)
|
244
|
-
while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
|
245
|
-
child = rule.children.first
|
246
|
-
|
247
|
-
if child.rule.first[0] == ?&
|
248
|
-
rule.rule = [child.rule.first.gsub(/^&/, rule.rule.first)]
|
249
|
-
else
|
250
|
-
rule.rule = ["#{rule.rule.first} #{child.rule.first}"]
|
251
|
-
end
|
252
|
-
|
253
|
-
rule.children = child.children
|
254
|
-
end
|
255
|
-
|
256
|
-
flatten_rules(rule)
|
257
|
-
end
|
258
|
-
|
259
|
-
# Transform
|
260
|
-
#
|
261
|
-
# foo
|
262
|
-
# bar
|
263
|
-
# color: blue
|
264
|
-
# baz
|
265
|
-
# color: blue
|
266
|
-
#
|
267
|
-
# into
|
268
|
-
#
|
269
|
-
# foo
|
270
|
-
# bar, baz
|
271
|
-
# color: blue
|
272
|
-
#
|
273
|
-
# @param rule [Tree::RuleNode] The candidate for flattening
|
274
|
-
def fold_commas(root)
|
275
|
-
prev_rule = nil
|
276
|
-
root.children.map! do |child|
|
277
|
-
unless child.is_a?(Tree::RuleNode)
|
278
|
-
fold_commas(child) if child.is_a?(Tree::DirectiveNode)
|
279
|
-
next child
|
280
|
-
end
|
281
|
-
|
282
|
-
if prev_rule && prev_rule.children == child.children
|
283
|
-
prev_rule.rule.first << ", #{child.rule.first}"
|
284
|
-
next nil
|
285
|
-
end
|
286
|
-
|
287
|
-
fold_commas(child)
|
288
|
-
prev_rule = child
|
289
|
-
child
|
290
|
-
end
|
291
|
-
root.children.compact!
|
292
|
-
end
|
293
|
-
end
|
294
|
-
end
|
@@ -1,792 +0,0 @@
|
|
1
|
-
require 'strscan'
|
2
|
-
require 'digest/sha1'
|
3
|
-
require 'sass/cache_store'
|
4
|
-
require 'sass/tree/node'
|
5
|
-
require 'sass/tree/root_node'
|
6
|
-
require 'sass/tree/rule_node'
|
7
|
-
require 'sass/tree/comment_node'
|
8
|
-
require 'sass/tree/prop_node'
|
9
|
-
require 'sass/tree/directive_node'
|
10
|
-
require 'sass/tree/variable_node'
|
11
|
-
require 'sass/tree/mixin_def_node'
|
12
|
-
require 'sass/tree/mixin_node'
|
13
|
-
require 'sass/tree/extend_node'
|
14
|
-
require 'sass/tree/if_node'
|
15
|
-
require 'sass/tree/while_node'
|
16
|
-
require 'sass/tree/for_node'
|
17
|
-
require 'sass/tree/debug_node'
|
18
|
-
require 'sass/tree/warn_node'
|
19
|
-
require 'sass/tree/import_node'
|
20
|
-
require 'sass/selector'
|
21
|
-
require 'sass/environment'
|
22
|
-
require 'sass/script'
|
23
|
-
require 'sass/scss'
|
24
|
-
require 'sass/error'
|
25
|
-
require 'sass/importers'
|
26
|
-
require 'sass/shared'
|
27
|
-
|
28
|
-
module Sass
|
29
|
-
|
30
|
-
# A Sass mixin.
|
31
|
-
#
|
32
|
-
# `name`: `String`
|
33
|
-
# : The name of the mixin.
|
34
|
-
#
|
35
|
-
# `args`: `Array<(String, Script::Node)>`
|
36
|
-
# : The arguments for the mixin.
|
37
|
-
# Each element is a tuple containing the name of the argument
|
38
|
-
# and the parse tree for the default value of the argument.
|
39
|
-
#
|
40
|
-
# `environment`: {Sass::Environment}
|
41
|
-
# : The environment in which the mixin was defined.
|
42
|
-
# This is captured so that the mixin can have access
|
43
|
-
# to local variables defined in its scope.
|
44
|
-
#
|
45
|
-
# `tree`: {Sass::Tree::Node}
|
46
|
-
# : The parse tree for the mixin.
|
47
|
-
Mixin = Struct.new(:name, :args, :environment, :tree)
|
48
|
-
|
49
|
-
# This class handles the parsing and compilation of the Sass template.
|
50
|
-
# Example usage:
|
51
|
-
#
|
52
|
-
# template = File.load('stylesheets/sassy.sass')
|
53
|
-
# sass_engine = Sass::Engine.new(template)
|
54
|
-
# output = sass_engine.render
|
55
|
-
# puts output
|
56
|
-
class Engine
|
57
|
-
include Sass::Util
|
58
|
-
|
59
|
-
# A line of Sass code.
|
60
|
-
#
|
61
|
-
# `text`: `String`
|
62
|
-
# : The text in the line, without any whitespace at the beginning or end.
|
63
|
-
#
|
64
|
-
# `tabs`: `Fixnum`
|
65
|
-
# : The level of indentation of the line.
|
66
|
-
#
|
67
|
-
# `index`: `Fixnum`
|
68
|
-
# : The line number in the original document.
|
69
|
-
#
|
70
|
-
# `offset`: `Fixnum`
|
71
|
-
# : The number of bytes in on the line that the text begins.
|
72
|
-
# This ends up being the number of bytes of leading whitespace.
|
73
|
-
#
|
74
|
-
# `filename`: `String`
|
75
|
-
# : The name of the file in which this line appeared.
|
76
|
-
#
|
77
|
-
# `children`: `Array<Line>`
|
78
|
-
# : The lines nested below this one.
|
79
|
-
class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children)
|
80
|
-
def comment?
|
81
|
-
text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# The character that begins a CSS property.
|
86
|
-
PROPERTY_CHAR = ?:
|
87
|
-
|
88
|
-
# The character that designates that
|
89
|
-
# a property should be assigned to a SassScript expression.
|
90
|
-
SCRIPT_CHAR = ?=
|
91
|
-
|
92
|
-
# The character that designates the beginning of a comment,
|
93
|
-
# either Sass or CSS.
|
94
|
-
COMMENT_CHAR = ?/
|
95
|
-
|
96
|
-
# The character that follows the general COMMENT_CHAR and designates a Sass comment,
|
97
|
-
# which is not output as a CSS comment.
|
98
|
-
SASS_COMMENT_CHAR = ?/
|
99
|
-
|
100
|
-
# The character that follows the general COMMENT_CHAR and designates a CSS comment,
|
101
|
-
# which is embedded in the CSS document.
|
102
|
-
CSS_COMMENT_CHAR = ?*
|
103
|
-
|
104
|
-
# The character used to denote a compiler directive.
|
105
|
-
DIRECTIVE_CHAR = ?@
|
106
|
-
|
107
|
-
# Designates a non-parsed rule.
|
108
|
-
ESCAPE_CHAR = ?\\
|
109
|
-
|
110
|
-
# Designates block as mixin definition rather than CSS rules to output
|
111
|
-
MIXIN_DEFINITION_CHAR = ?=
|
112
|
-
|
113
|
-
# Includes named mixin declared using MIXIN_DEFINITION_CHAR
|
114
|
-
MIXIN_INCLUDE_CHAR = ?+
|
115
|
-
|
116
|
-
# The regex that matches properties of the form `name: prop`.
|
117
|
-
PROPERTY_NEW_MATCHER = /^[^\s:"\[]+\s*[=:](\s|$)/
|
118
|
-
|
119
|
-
# The regex that matches and extracts data from
|
120
|
-
# properties of the form `name: prop`.
|
121
|
-
PROPERTY_NEW = /^([^\s=:"]+)\s*(=|:)(?:\s+|$)(.*)/
|
122
|
-
|
123
|
-
# The regex that matches and extracts data from
|
124
|
-
# properties of the form `:name prop`.
|
125
|
-
PROPERTY_OLD = /^:([^\s=:"]+)\s*(=?)(?:\s+|$)(.*)/
|
126
|
-
|
127
|
-
# The default options for Sass::Engine.
|
128
|
-
# @api public
|
129
|
-
DEFAULT_OPTIONS = {
|
130
|
-
:style => :nested,
|
131
|
-
:load_paths => ['.'],
|
132
|
-
:cache => true,
|
133
|
-
:cache_location => './.sass-cache',
|
134
|
-
:syntax => :sass,
|
135
|
-
:filesystem_importer => Sass::Importers::Filesystem
|
136
|
-
}.freeze
|
137
|
-
|
138
|
-
# Converts a Sass options hash into a standard form, filling in
|
139
|
-
# default values and resolving aliases.
|
140
|
-
#
|
141
|
-
# @param options [{Symbol => Object}] The options hash;
|
142
|
-
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
|
143
|
-
# @return [{Symbol => Object}] The normalized options hash.
|
144
|
-
# @private
|
145
|
-
def self.normalize_options(options)
|
146
|
-
options = DEFAULT_OPTIONS.merge(options.reject {|k, v| v.nil?})
|
147
|
-
|
148
|
-
# If the `:filename` option is passed in without an importer,
|
149
|
-
# assume it's using the default filesystem importer.
|
150
|
-
options[:importer] ||= options[:filesystem_importer].new(".") if options[:filename]
|
151
|
-
|
152
|
-
# Tracks the original filename of the top-level Sass file
|
153
|
-
options[:original_filename] = options[:original_filename] || options[:filename]
|
154
|
-
|
155
|
-
options[:cache_store] ||= Sass::FileCacheStore.new(options[:cache_location])
|
156
|
-
# Support both, because the docs said one and the other actually worked
|
157
|
-
# for quite a long time.
|
158
|
-
options[:line_comments] ||= options[:line_numbers]
|
159
|
-
|
160
|
-
options[:load_paths] = options[:load_paths].map do |p|
|
161
|
-
next p unless p.is_a?(String)
|
162
|
-
options[:filesystem_importer].new(p)
|
163
|
-
end
|
164
|
-
|
165
|
-
# Backwards compatibility
|
166
|
-
options[:property_syntax] ||= options[:attribute_syntax]
|
167
|
-
case options[:property_syntax]
|
168
|
-
when :alternate; options[:property_syntax] = :new
|
169
|
-
when :normal; options[:property_syntax] = :old
|
170
|
-
end
|
171
|
-
|
172
|
-
options
|
173
|
-
end
|
174
|
-
|
175
|
-
# Returns the {Sass::Engine} for the given file.
|
176
|
-
# This is preferable to Sass::Engine.new when reading from a file
|
177
|
-
# because it properly sets up the Engine's metadata,
|
178
|
-
# enables parse-tree caching,
|
179
|
-
# and infers the syntax from the filename.
|
180
|
-
#
|
181
|
-
# @param filename [String] The path to the Sass or SCSS file
|
182
|
-
# @param options [{Symbol => Object}] The options hash;
|
183
|
-
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
184
|
-
# @return [Sass::Engine] The Engine for the given Sass or SCSS file.
|
185
|
-
# @raise [Sass::SyntaxError] if there's an error in the document.
|
186
|
-
def self.for_file(filename, options)
|
187
|
-
had_syntax = options[:syntax]
|
188
|
-
|
189
|
-
if had_syntax
|
190
|
-
# Use what was explicitly specificed
|
191
|
-
elsif filename =~ /\.scss$/
|
192
|
-
options.merge!(:syntax => :scss)
|
193
|
-
elsif filename =~ /\.sass$/
|
194
|
-
options.merge!(:syntax => :sass)
|
195
|
-
end
|
196
|
-
|
197
|
-
Sass::Engine.new(File.read(filename), options.merge(:filename => filename))
|
198
|
-
end
|
199
|
-
|
200
|
-
# The options for the Sass engine.
|
201
|
-
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
202
|
-
#
|
203
|
-
# @return [{Symbol => Object}]
|
204
|
-
attr_reader :options
|
205
|
-
|
206
|
-
# Creates a new Engine. Note that Engine should only be used directly
|
207
|
-
# when compiling in-memory Sass code.
|
208
|
-
# If you're compiling a single Sass file from the filesystem,
|
209
|
-
# use \{Sass::Engine.for\_file}.
|
210
|
-
# If you're compiling multiple files from the filesystem,
|
211
|
-
# use {Sass::Plugin.
|
212
|
-
#
|
213
|
-
# @param template [String] The Sass template.
|
214
|
-
# This template can be encoded using any encoding
|
215
|
-
# that can be converted to Unicode.
|
216
|
-
# If the template contains an `@charset` declaration,
|
217
|
-
# that overrides the Ruby encoding
|
218
|
-
# (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
|
219
|
-
# @param options [{Symbol => Object}] An options hash.
|
220
|
-
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
221
|
-
# @see {Sass::Engine.for_file}
|
222
|
-
# @see {Sass::Plugin}
|
223
|
-
def initialize(template, options={})
|
224
|
-
@options = self.class.normalize_options(options)
|
225
|
-
@template = template
|
226
|
-
end
|
227
|
-
|
228
|
-
# Render the template to CSS.
|
229
|
-
#
|
230
|
-
# @return [String] The CSS
|
231
|
-
# @raise [Sass::SyntaxError] if there's an error in the document
|
232
|
-
# @raise [Encoding::UndefinedConversionError] if the source encoding
|
233
|
-
# cannot be converted to UTF-8
|
234
|
-
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
235
|
-
def render
|
236
|
-
return _render unless @options[:quiet]
|
237
|
-
Sass::Util.silence_sass_warnings {_render}
|
238
|
-
end
|
239
|
-
alias_method :to_css, :render
|
240
|
-
|
241
|
-
# Parses the document into its parse tree.
|
242
|
-
#
|
243
|
-
# @return [Sass::Tree::Node] The root of the parse tree.
|
244
|
-
# @raise [Sass::SyntaxError] if there's an error in the document
|
245
|
-
def to_tree
|
246
|
-
return _to_tree unless @options[:quiet]
|
247
|
-
Sass::Util.silence_sass_warnings {_to_tree}
|
248
|
-
end
|
249
|
-
|
250
|
-
# Returns the original encoding of the document,
|
251
|
-
# or `nil` under Ruby 1.8.
|
252
|
-
#
|
253
|
-
# @return [Encoding, nil]
|
254
|
-
# @raise [Encoding::UndefinedConversionError] if the source encoding
|
255
|
-
# cannot be converted to UTF-8
|
256
|
-
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
257
|
-
def source_encoding
|
258
|
-
check_encoding!
|
259
|
-
@original_encoding
|
260
|
-
end
|
261
|
-
|
262
|
-
private
|
263
|
-
|
264
|
-
def _render
|
265
|
-
rendered = _to_tree.render
|
266
|
-
return rendered if ruby1_8?
|
267
|
-
return rendered.encode(source_encoding)
|
268
|
-
end
|
269
|
-
|
270
|
-
def _to_tree
|
271
|
-
if (@options[:cache] || @options[:read_cache]) &&
|
272
|
-
@options[:filename] && @options[:importer]
|
273
|
-
key = sassc_key
|
274
|
-
sha = Digest::SHA1.hexdigest(@template)
|
275
|
-
|
276
|
-
if root = @options[:cache_store].retrieve(key, sha)
|
277
|
-
@options = root.options.merge(@options)
|
278
|
-
root.options = @options
|
279
|
-
return root
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
check_encoding!
|
284
|
-
|
285
|
-
if @options[:syntax] == :scss
|
286
|
-
root = Sass::SCSS::Parser.new(@template).parse
|
287
|
-
else
|
288
|
-
root = Tree::RootNode.new(@template)
|
289
|
-
append_children(root, tree(tabulate(@template)).first, true)
|
290
|
-
end
|
291
|
-
|
292
|
-
root.options = @options
|
293
|
-
@options[:cache_store].store(key, sha, root) if @options[:cache] && key && sha
|
294
|
-
root
|
295
|
-
rescue SyntaxError => e
|
296
|
-
e.modify_backtrace(:filename => @options[:filename], :line => @line)
|
297
|
-
e.sass_template = @template
|
298
|
-
raise e
|
299
|
-
end
|
300
|
-
|
301
|
-
def sassc_key
|
302
|
-
@options[:cache_store].key(*@options[:importer].key(@options[:filename], @options))
|
303
|
-
end
|
304
|
-
|
305
|
-
def check_encoding!
|
306
|
-
return if @checked_encoding
|
307
|
-
@checked_encoding = true
|
308
|
-
@template, @original_encoding = check_sass_encoding(@template) do |msg, line|
|
309
|
-
raise Sass::SyntaxError.new(msg, :line => line)
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
def tabulate(string)
|
314
|
-
tab_str = nil
|
315
|
-
comment_tab_str = nil
|
316
|
-
first = true
|
317
|
-
lines = []
|
318
|
-
string.gsub(/\r|\n|\r\n|\r\n/, "\n").scan(/^.*?$/).each_with_index do |line, index|
|
319
|
-
index += (@options[:line] || 1)
|
320
|
-
if line.strip.empty?
|
321
|
-
lines.last.text << "\n" if lines.last && lines.last.comment?
|
322
|
-
next
|
323
|
-
end
|
324
|
-
|
325
|
-
line_tab_str = line[/^\s*/]
|
326
|
-
unless line_tab_str.empty?
|
327
|
-
if tab_str.nil?
|
328
|
-
comment_tab_str ||= line_tab_str
|
329
|
-
next if try_comment(line, lines.last, "", comment_tab_str, index)
|
330
|
-
comment_tab_str = nil
|
331
|
-
end
|
332
|
-
|
333
|
-
tab_str ||= line_tab_str
|
334
|
-
|
335
|
-
raise SyntaxError.new("Indenting at the beginning of the document is illegal.",
|
336
|
-
:line => index) if first
|
337
|
-
|
338
|
-
raise SyntaxError.new("Indentation can't use both tabs and spaces.",
|
339
|
-
:line => index) if tab_str.include?(?\s) && tab_str.include?(?\t)
|
340
|
-
end
|
341
|
-
first &&= !tab_str.nil?
|
342
|
-
if tab_str.nil?
|
343
|
-
lines << Line.new(line.strip, 0, index, 0, @options[:filename], [])
|
344
|
-
next
|
345
|
-
end
|
346
|
-
|
347
|
-
comment_tab_str ||= line_tab_str
|
348
|
-
if try_comment(line, lines.last, tab_str * lines.last.tabs, comment_tab_str, index)
|
349
|
-
next
|
350
|
-
else
|
351
|
-
comment_tab_str = nil
|
352
|
-
end
|
353
|
-
|
354
|
-
line_tabs = line_tab_str.scan(tab_str).size
|
355
|
-
if tab_str * line_tabs != line_tab_str
|
356
|
-
message = <<END.strip.gsub("\n", ' ')
|
357
|
-
Inconsistent indentation: #{Sass::Shared.human_indentation line_tab_str, true} used for indentation,
|
358
|
-
but the rest of the document was indented using #{Sass::Shared.human_indentation tab_str}.
|
359
|
-
END
|
360
|
-
raise SyntaxError.new(message, :line => index)
|
361
|
-
end
|
362
|
-
|
363
|
-
lines << Line.new(line.strip, line_tabs, index, tab_str.size, @options[:filename], [])
|
364
|
-
end
|
365
|
-
lines
|
366
|
-
end
|
367
|
-
|
368
|
-
def try_comment(line, last, tab_str, comment_tab_str, index)
|
369
|
-
return unless last && last.comment?
|
370
|
-
# Nested comment stuff must be at least one whitespace char deeper
|
371
|
-
# than the normal indentation
|
372
|
-
return unless line =~ /^#{tab_str}\s/
|
373
|
-
unless line =~ /^(?:#{comment_tab_str})(.*)$/
|
374
|
-
raise SyntaxError.new(<<MSG.strip.gsub("\n", " "), :line => index)
|
375
|
-
Inconsistent indentation:
|
376
|
-
previous line was indented by #{Sass::Shared.human_indentation comment_tab_str},
|
377
|
-
but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
|
378
|
-
MSG
|
379
|
-
end
|
380
|
-
|
381
|
-
last.text << "\n" << $1
|
382
|
-
true
|
383
|
-
end
|
384
|
-
|
385
|
-
def tree(arr, i = 0)
|
386
|
-
return [], i if arr[i].nil?
|
387
|
-
|
388
|
-
base = arr[i].tabs
|
389
|
-
nodes = []
|
390
|
-
while (line = arr[i]) && line.tabs >= base
|
391
|
-
if line.tabs > base
|
392
|
-
raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.",
|
393
|
-
:line => line.index) if line.tabs > base + 1
|
394
|
-
|
395
|
-
nodes.last.children, i = tree(arr, i)
|
396
|
-
else
|
397
|
-
nodes << line
|
398
|
-
i += 1
|
399
|
-
end
|
400
|
-
end
|
401
|
-
return nodes, i
|
402
|
-
end
|
403
|
-
|
404
|
-
def build_tree(parent, line, root = false)
|
405
|
-
@line = line.index
|
406
|
-
node_or_nodes = parse_line(parent, line, root)
|
407
|
-
|
408
|
-
Array(node_or_nodes).each do |node|
|
409
|
-
# Node is a symbol if it's non-outputting, like a variable assignment
|
410
|
-
next unless node.is_a? Tree::Node
|
411
|
-
|
412
|
-
node.line = line.index
|
413
|
-
node.filename = line.filename
|
414
|
-
|
415
|
-
append_children(node, line.children, false)
|
416
|
-
end
|
417
|
-
|
418
|
-
node_or_nodes
|
419
|
-
end
|
420
|
-
|
421
|
-
def append_children(parent, children, root)
|
422
|
-
continued_rule = nil
|
423
|
-
continued_comment = nil
|
424
|
-
children.each do |line|
|
425
|
-
child = build_tree(parent, line, root)
|
426
|
-
|
427
|
-
if child.is_a?(Tree::RuleNode) && child.continued?
|
428
|
-
raise SyntaxError.new("Rules can't end in commas.",
|
429
|
-
:line => child.line) unless child.children.empty?
|
430
|
-
if continued_rule
|
431
|
-
continued_rule.add_rules child
|
432
|
-
else
|
433
|
-
continued_rule = child
|
434
|
-
end
|
435
|
-
next
|
436
|
-
end
|
437
|
-
|
438
|
-
if continued_rule
|
439
|
-
raise SyntaxError.new("Rules can't end in commas.",
|
440
|
-
:line => continued_rule.line) unless child.is_a?(Tree::RuleNode)
|
441
|
-
continued_rule.add_rules child
|
442
|
-
continued_rule.children = child.children
|
443
|
-
continued_rule, child = nil, continued_rule
|
444
|
-
end
|
445
|
-
|
446
|
-
if child.is_a?(Tree::CommentNode) && child.silent
|
447
|
-
if continued_comment &&
|
448
|
-
child.line == continued_comment.line +
|
449
|
-
continued_comment.value.count("\n") + 1
|
450
|
-
continued_comment.value << "\n" << child.value
|
451
|
-
next
|
452
|
-
end
|
453
|
-
|
454
|
-
continued_comment = child
|
455
|
-
end
|
456
|
-
|
457
|
-
check_for_no_children(child)
|
458
|
-
validate_and_append_child(parent, child, line, root)
|
459
|
-
end
|
460
|
-
|
461
|
-
raise SyntaxError.new("Rules can't end in commas.",
|
462
|
-
:line => continued_rule.line) if continued_rule
|
463
|
-
|
464
|
-
parent
|
465
|
-
end
|
466
|
-
|
467
|
-
def validate_and_append_child(parent, child, line, root)
|
468
|
-
case child
|
469
|
-
when Array
|
470
|
-
child.each {|c| validate_and_append_child(parent, c, line, root)}
|
471
|
-
when Tree::Node
|
472
|
-
parent << child
|
473
|
-
end
|
474
|
-
end
|
475
|
-
|
476
|
-
def check_for_no_children(node)
|
477
|
-
return unless node.is_a?(Tree::RuleNode) && node.children.empty?
|
478
|
-
Sass::Util.sass_warn(<<WARNING.strip)
|
479
|
-
WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
|
480
|
-
This selector doesn't have any properties and will not be rendered.
|
481
|
-
WARNING
|
482
|
-
end
|
483
|
-
|
484
|
-
def parse_line(parent, line, root)
|
485
|
-
case line.text[0]
|
486
|
-
when PROPERTY_CHAR
|
487
|
-
if line.text[1] == PROPERTY_CHAR ||
|
488
|
-
(@options[:property_syntax] == :new &&
|
489
|
-
line.text =~ PROPERTY_OLD && $3.empty?)
|
490
|
-
# Support CSS3-style pseudo-elements,
|
491
|
-
# which begin with ::,
|
492
|
-
# as well as pseudo-classes
|
493
|
-
# if we're using the new property syntax
|
494
|
-
Tree::RuleNode.new(parse_interp(line.text))
|
495
|
-
else
|
496
|
-
name, eq, value = line.text.scan(PROPERTY_OLD)[0]
|
497
|
-
raise SyntaxError.new("Invalid property: \"#{line.text}\".",
|
498
|
-
:line => @line) if name.nil? || value.nil?
|
499
|
-
parse_property(name, parse_interp(name), eq, value, :old, line)
|
500
|
-
end
|
501
|
-
when ?!, ?$
|
502
|
-
parse_variable(line)
|
503
|
-
when COMMENT_CHAR
|
504
|
-
parse_comment(line.text)
|
505
|
-
when DIRECTIVE_CHAR
|
506
|
-
parse_directive(parent, line, root)
|
507
|
-
when ESCAPE_CHAR
|
508
|
-
Tree::RuleNode.new(parse_interp(line.text[1..-1]))
|
509
|
-
when MIXIN_DEFINITION_CHAR
|
510
|
-
parse_mixin_definition(line)
|
511
|
-
when MIXIN_INCLUDE_CHAR
|
512
|
-
if line.text[1].nil? || line.text[1] == ?\s
|
513
|
-
Tree::RuleNode.new(parse_interp(line.text))
|
514
|
-
else
|
515
|
-
parse_mixin_include(line, root)
|
516
|
-
end
|
517
|
-
else
|
518
|
-
parse_property_or_rule(line)
|
519
|
-
end
|
520
|
-
end
|
521
|
-
|
522
|
-
def parse_property_or_rule(line)
|
523
|
-
scanner = StringScanner.new(line.text)
|
524
|
-
hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
|
525
|
-
parser = Sass::SCSS::SassParser.new(scanner, @line)
|
526
|
-
|
527
|
-
unless res = parser.parse_interp_ident
|
528
|
-
return Tree::RuleNode.new(parse_interp(line.text))
|
529
|
-
end
|
530
|
-
res.unshift(hack_char) if hack_char
|
531
|
-
if comment = scanner.scan(Sass::SCSS::RX::COMMENT)
|
532
|
-
res << comment
|
533
|
-
end
|
534
|
-
|
535
|
-
name = line.text[0...scanner.pos]
|
536
|
-
if scanner.scan(/\s*([:=])(?:\s|$)/)
|
537
|
-
parse_property(name, res, scanner[1], scanner.rest, :new, line)
|
538
|
-
else
|
539
|
-
res.pop if comment
|
540
|
-
Tree::RuleNode.new(res + parse_interp(scanner.rest))
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
def parse_property(name, parsed_name, eq, value, prop, line)
|
545
|
-
if value.strip.empty?
|
546
|
-
expr = Sass::Script::String.new("")
|
547
|
-
else
|
548
|
-
expr = parse_script(value, :offset => line.offset + line.text.index(value))
|
549
|
-
|
550
|
-
if eq.strip[0] == SCRIPT_CHAR
|
551
|
-
expr.context = :equals
|
552
|
-
Script.equals_warning("properties", name,
|
553
|
-
Sass::Tree::PropNode.val_to_sass(expr, @options), false,
|
554
|
-
@line, line.offset + 1, @options[:filename])
|
555
|
-
end
|
556
|
-
end
|
557
|
-
Tree::PropNode.new(parse_interp(name), expr, prop)
|
558
|
-
end
|
559
|
-
|
560
|
-
def parse_variable(line)
|
561
|
-
name, op, value, default = line.text.scan(Script::MATCH)[0]
|
562
|
-
guarded = op =~ /^\|\|/
|
563
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
|
564
|
-
:line => @line + 1) unless line.children.empty?
|
565
|
-
raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
|
566
|
-
:line => @line) unless name && value
|
567
|
-
Script.var_warning(name, @line, line.offset + 1, @options[:filename]) if line.text[0] == ?!
|
568
|
-
|
569
|
-
expr = parse_script(value, :offset => line.offset + line.text.index(value))
|
570
|
-
if op =~ /=$/
|
571
|
-
expr.context = :equals
|
572
|
-
type = guarded ? "variable defaults" : "variables"
|
573
|
-
Script.equals_warning(type, "$#{name}", expr.to_sass,
|
574
|
-
guarded, @line, line.offset + 1, @options[:filename])
|
575
|
-
end
|
576
|
-
|
577
|
-
Tree::VariableNode.new(name, expr, default || guarded)
|
578
|
-
end
|
579
|
-
|
580
|
-
def parse_comment(line)
|
581
|
-
if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
|
582
|
-
silent = line[1] == SASS_COMMENT_CHAR
|
583
|
-
Tree::CommentNode.new(
|
584
|
-
format_comment_text(line[2..-1], silent),
|
585
|
-
silent)
|
586
|
-
else
|
587
|
-
Tree::RuleNode.new(parse_interp(line))
|
588
|
-
end
|
589
|
-
end
|
590
|
-
|
591
|
-
def parse_directive(parent, line, root)
|
592
|
-
directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
|
593
|
-
offset = directive.size + whitespace.size + 1 if whitespace
|
594
|
-
|
595
|
-
# If value begins with url( or ",
|
596
|
-
# it's a CSS @import rule and we don't want to touch it.
|
597
|
-
if directive == "import"
|
598
|
-
parse_import(line, value)
|
599
|
-
elsif directive == "mixin"
|
600
|
-
parse_mixin_definition(line)
|
601
|
-
elsif directive == "include"
|
602
|
-
parse_mixin_include(line, root)
|
603
|
-
elsif directive == "for"
|
604
|
-
parse_for(line, root, value)
|
605
|
-
elsif directive == "else"
|
606
|
-
parse_else(parent, line, value)
|
607
|
-
elsif directive == "while"
|
608
|
-
raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
|
609
|
-
Tree::WhileNode.new(parse_script(value, :offset => offset))
|
610
|
-
elsif directive == "if"
|
611
|
-
raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
|
612
|
-
Tree::IfNode.new(parse_script(value, :offset => offset))
|
613
|
-
elsif directive == "debug"
|
614
|
-
raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
|
615
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
|
616
|
-
:line => @line + 1) unless line.children.empty?
|
617
|
-
offset = line.offset + line.text.index(value).to_i
|
618
|
-
Tree::DebugNode.new(parse_script(value, :offset => offset))
|
619
|
-
elsif directive == "extend"
|
620
|
-
raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
|
621
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
|
622
|
-
:line => @line + 1) unless line.children.empty?
|
623
|
-
offset = line.offset + line.text.index(value).to_i
|
624
|
-
Tree::ExtendNode.new(parse_interp(value, offset))
|
625
|
-
elsif directive == "warn"
|
626
|
-
raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
|
627
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
|
628
|
-
:line => @line + 1) unless line.children.empty?
|
629
|
-
offset = line.offset + line.text.index(value).to_i
|
630
|
-
Tree::WarnNode.new(parse_script(value, :offset => offset))
|
631
|
-
else
|
632
|
-
Tree::DirectiveNode.new(line.text)
|
633
|
-
end
|
634
|
-
end
|
635
|
-
|
636
|
-
def parse_for(line, root, text)
|
637
|
-
var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
|
638
|
-
|
639
|
-
if var.nil? # scan failed, try to figure out why for error message
|
640
|
-
if text !~ /^[^\s]+/
|
641
|
-
expected = "variable name"
|
642
|
-
elsif text !~ /^[^\s]+\s+from\s+.+/
|
643
|
-
expected = "'from <expr>'"
|
644
|
-
else
|
645
|
-
expected = "'to <expr>' or 'through <expr>'"
|
646
|
-
end
|
647
|
-
raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
|
648
|
-
end
|
649
|
-
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
|
650
|
-
if var.slice!(0) == ?!
|
651
|
-
offset = line.offset + line.text.index("!" + var) + 1
|
652
|
-
Script.var_warning(var, @line, offset, @options[:filename])
|
653
|
-
end
|
654
|
-
|
655
|
-
parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
|
656
|
-
parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
|
657
|
-
Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
|
658
|
-
end
|
659
|
-
|
660
|
-
def parse_else(parent, line, text)
|
661
|
-
previous = parent.children.last
|
662
|
-
raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
|
663
|
-
|
664
|
-
if text
|
665
|
-
if text !~ /^if\s+(.+)/
|
666
|
-
raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.")
|
667
|
-
end
|
668
|
-
expr = parse_script($1, :offset => line.offset + line.text.index($1))
|
669
|
-
end
|
670
|
-
|
671
|
-
node = Tree::IfNode.new(expr)
|
672
|
-
append_children(node, line.children, false)
|
673
|
-
previous.add_else node
|
674
|
-
nil
|
675
|
-
end
|
676
|
-
|
677
|
-
def parse_import(line, value)
|
678
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
|
679
|
-
:line => @line + 1) unless line.children.empty?
|
680
|
-
|
681
|
-
scanner = StringScanner.new(value)
|
682
|
-
values = []
|
683
|
-
|
684
|
-
loop do
|
685
|
-
unless node = parse_import_arg(scanner)
|
686
|
-
raise SyntaxError.new("Invalid @import: expected file to import, was #{scanner.rest.inspect}",
|
687
|
-
:line => @line)
|
688
|
-
end
|
689
|
-
values << node
|
690
|
-
break unless scanner.scan(/,\s*/)
|
691
|
-
end
|
692
|
-
|
693
|
-
return values
|
694
|
-
end
|
695
|
-
|
696
|
-
def parse_import_arg(scanner)
|
697
|
-
return if scanner.eos?
|
698
|
-
unless (str = scanner.scan(Sass::SCSS::RX::STRING)) ||
|
699
|
-
(uri = scanner.scan(Sass::SCSS::RX::URI))
|
700
|
-
return Tree::ImportNode.new(scanner.scan(/[^,]+/))
|
701
|
-
end
|
702
|
-
|
703
|
-
val = scanner[1] || scanner[2]
|
704
|
-
scanner.scan(/\s*/)
|
705
|
-
if media = scanner.scan(/[^,].*/)
|
706
|
-
Tree::DirectiveNode.new("@import #{str || uri} #{media}")
|
707
|
-
elsif uri
|
708
|
-
Tree::DirectiveNode.new("@import #{uri}")
|
709
|
-
elsif val =~ /^http:\/\//
|
710
|
-
Tree::DirectiveNode.new("@import url(#{val})")
|
711
|
-
else
|
712
|
-
Tree::ImportNode.new(val)
|
713
|
-
end
|
714
|
-
end
|
715
|
-
|
716
|
-
MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
717
|
-
def parse_mixin_definition(line)
|
718
|
-
name, arg_string = line.text.scan(MIXIN_DEF_RE).first
|
719
|
-
raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
|
720
|
-
|
721
|
-
offset = line.offset + line.text.size - arg_string.size
|
722
|
-
args = Script::Parser.new(arg_string.strip, @line, offset, @options).
|
723
|
-
parse_mixin_definition_arglist
|
724
|
-
default_arg_found = false
|
725
|
-
Tree::MixinDefNode.new(name, args)
|
726
|
-
end
|
727
|
-
|
728
|
-
MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
729
|
-
def parse_mixin_include(line, root)
|
730
|
-
name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
|
731
|
-
raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
|
732
|
-
|
733
|
-
offset = line.offset + line.text.size - arg_string.size
|
734
|
-
args = Script::Parser.new(arg_string.strip, @line, offset, @options).
|
735
|
-
parse_mixin_include_arglist
|
736
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.",
|
737
|
-
:line => @line + 1) unless line.children.empty?
|
738
|
-
Tree::MixinNode.new(name, args)
|
739
|
-
end
|
740
|
-
|
741
|
-
def parse_script(script, options = {})
|
742
|
-
line = options[:line] || @line
|
743
|
-
offset = options[:offset] || 0
|
744
|
-
Script.parse(script, line, offset, @options)
|
745
|
-
end
|
746
|
-
|
747
|
-
def format_comment_text(text, silent)
|
748
|
-
content = text.split("\n")
|
749
|
-
|
750
|
-
if content.first && content.first.strip.empty?
|
751
|
-
removed_first = true
|
752
|
-
content.shift
|
753
|
-
end
|
754
|
-
|
755
|
-
return silent ? "//" : "/* */" if content.empty?
|
756
|
-
content.last.gsub!(%r{ ?\*/ *$}, '')
|
757
|
-
content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
|
758
|
-
content.first.gsub!(/^ /, '') unless removed_first
|
759
|
-
if silent
|
760
|
-
"//" + content.join("\n//")
|
761
|
-
else
|
762
|
-
# The #gsub fixes the case of a trailing */
|
763
|
-
"/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */"
|
764
|
-
end
|
765
|
-
end
|
766
|
-
|
767
|
-
def parse_interp(text, offset = 0)
|
768
|
-
self.class.parse_interp(text, @line, offset, :filename => @filename)
|
769
|
-
end
|
770
|
-
|
771
|
-
# It's important that this have strings (at least)
|
772
|
-
# at the beginning, the end, and between each Script::Node.
|
773
|
-
#
|
774
|
-
# @private
|
775
|
-
def self.parse_interp(text, line, offset, options)
|
776
|
-
res = []
|
777
|
-
rest = Sass::Shared.handle_interpolation text do |scan|
|
778
|
-
escapes = scan[2].size
|
779
|
-
res << scan.matched[0...-2 - escapes]
|
780
|
-
if escapes % 2 == 1
|
781
|
-
res << "\\" * (escapes - 1) << '#{'
|
782
|
-
else
|
783
|
-
res << "\\" * [0, escapes - 1].max
|
784
|
-
res << Script::Parser.new(
|
785
|
-
scan, line, offset + scan.pos - scan.matched_size, options).
|
786
|
-
parse_interpolated
|
787
|
-
end
|
788
|
-
end
|
789
|
-
res << rest
|
790
|
-
end
|
791
|
-
end
|
792
|
-
end
|