drnic-haml 2.3.0
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.
- data/.yardopts +5 -0
- data/CONTRIBUTING +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +347 -0
- data/REVISION +1 -0
- data/Rakefile +371 -0
- data/VERSION +1 -0
- data/VERSION_NAME +1 -0
- data/bin/css2sass +7 -0
- data/bin/haml +9 -0
- data/bin/html2haml +7 -0
- data/bin/sass +8 -0
- data/extra/haml-mode.el +663 -0
- data/extra/sass-mode.el +205 -0
- data/extra/update_watch.rb +13 -0
- data/init.rb +8 -0
- data/lib/haml.rb +40 -0
- data/lib/haml/buffer.rb +307 -0
- data/lib/haml/engine.rb +301 -0
- data/lib/haml/error.rb +22 -0
- data/lib/haml/exec.rb +470 -0
- data/lib/haml/filters.rb +341 -0
- data/lib/haml/helpers.rb +560 -0
- data/lib/haml/helpers/action_view_extensions.rb +40 -0
- data/lib/haml/helpers/action_view_mods.rb +176 -0
- data/lib/haml/herb.rb +96 -0
- data/lib/haml/html.rb +308 -0
- data/lib/haml/precompiler.rb +997 -0
- data/lib/haml/shared.rb +78 -0
- data/lib/haml/template.rb +51 -0
- data/lib/haml/template/patch.rb +58 -0
- data/lib/haml/template/plugin.rb +71 -0
- data/lib/haml/util.rb +244 -0
- data/lib/haml/version.rb +64 -0
- data/lib/sass.rb +24 -0
- data/lib/sass/css.rb +423 -0
- data/lib/sass/engine.rb +491 -0
- data/lib/sass/environment.rb +79 -0
- data/lib/sass/error.rb +162 -0
- data/lib/sass/files.rb +133 -0
- data/lib/sass/plugin.rb +170 -0
- data/lib/sass/plugin/merb.rb +57 -0
- data/lib/sass/plugin/rails.rb +23 -0
- data/lib/sass/repl.rb +58 -0
- data/lib/sass/script.rb +55 -0
- data/lib/sass/script/bool.rb +17 -0
- data/lib/sass/script/color.rb +183 -0
- data/lib/sass/script/funcall.rb +50 -0
- data/lib/sass/script/functions.rb +199 -0
- data/lib/sass/script/lexer.rb +191 -0
- data/lib/sass/script/literal.rb +177 -0
- data/lib/sass/script/node.rb +14 -0
- data/lib/sass/script/number.rb +381 -0
- data/lib/sass/script/operation.rb +45 -0
- data/lib/sass/script/parser.rb +222 -0
- data/lib/sass/script/string.rb +12 -0
- data/lib/sass/script/unary_operation.rb +34 -0
- data/lib/sass/script/variable.rb +31 -0
- data/lib/sass/tree/comment_node.rb +84 -0
- data/lib/sass/tree/debug_node.rb +30 -0
- data/lib/sass/tree/directive_node.rb +70 -0
- data/lib/sass/tree/for_node.rb +48 -0
- data/lib/sass/tree/if_node.rb +54 -0
- data/lib/sass/tree/import_node.rb +69 -0
- data/lib/sass/tree/mixin_def_node.rb +29 -0
- data/lib/sass/tree/mixin_node.rb +48 -0
- data/lib/sass/tree/node.rb +252 -0
- data/lib/sass/tree/prop_node.rb +106 -0
- data/lib/sass/tree/root_node.rb +56 -0
- data/lib/sass/tree/rule_node.rb +220 -0
- data/lib/sass/tree/variable_node.rb +34 -0
- data/lib/sass/tree/while_node.rb +31 -0
- data/rails/init.rb +1 -0
- data/test/benchmark.rb +99 -0
- data/test/haml/engine_test.rb +1129 -0
- data/test/haml/helper_test.rb +282 -0
- data/test/haml/html2haml_test.rb +258 -0
- data/test/haml/markaby/standard.mab +52 -0
- data/test/haml/mocks/article.rb +6 -0
- data/test/haml/results/content_for_layout.xhtml +12 -0
- data/test/haml/results/eval_suppressed.xhtml +9 -0
- data/test/haml/results/filters.xhtml +62 -0
- data/test/haml/results/helpers.xhtml +93 -0
- data/test/haml/results/helpful.xhtml +10 -0
- data/test/haml/results/just_stuff.xhtml +68 -0
- data/test/haml/results/list.xhtml +12 -0
- data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +20 -0
- data/test/haml/results/partial_layout.xhtml +5 -0
- data/test/haml/results/partials.xhtml +21 -0
- data/test/haml/results/render_layout.xhtml +3 -0
- data/test/haml/results/silent_script.xhtml +74 -0
- data/test/haml/results/standard.xhtml +162 -0
- data/test/haml/results/tag_parsing.xhtml +23 -0
- data/test/haml/results/very_basic.xhtml +5 -0
- data/test/haml/results/whitespace_handling.xhtml +89 -0
- data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
- data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
- data/test/haml/rhtml/action_view.rhtml +62 -0
- data/test/haml/rhtml/standard.rhtml +54 -0
- data/test/haml/spec_test.rb +44 -0
- data/test/haml/template_test.rb +217 -0
- data/test/haml/templates/_av_partial_1.haml +9 -0
- data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
- data/test/haml/templates/_av_partial_2.haml +5 -0
- data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
- data/test/haml/templates/_layout.erb +3 -0
- data/test/haml/templates/_layout_for_partial.haml +3 -0
- data/test/haml/templates/_partial.haml +8 -0
- data/test/haml/templates/_text_area.haml +3 -0
- data/test/haml/templates/action_view.haml +47 -0
- data/test/haml/templates/action_view_ugly.haml +47 -0
- data/test/haml/templates/breakage.haml +8 -0
- data/test/haml/templates/content_for_layout.haml +8 -0
- data/test/haml/templates/eval_suppressed.haml +11 -0
- data/test/haml/templates/filters.haml +66 -0
- data/test/haml/templates/helpers.haml +95 -0
- data/test/haml/templates/helpful.haml +11 -0
- data/test/haml/templates/just_stuff.haml +83 -0
- data/test/haml/templates/list.haml +12 -0
- data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/original_engine.haml +17 -0
- data/test/haml/templates/partial_layout.haml +3 -0
- data/test/haml/templates/partialize.haml +1 -0
- data/test/haml/templates/partials.haml +12 -0
- data/test/haml/templates/render_layout.haml +2 -0
- data/test/haml/templates/silent_script.haml +40 -0
- data/test/haml/templates/standard.haml +42 -0
- data/test/haml/templates/standard_ugly.haml +42 -0
- data/test/haml/templates/tag_parsing.haml +21 -0
- data/test/haml/templates/very_basic.haml +4 -0
- data/test/haml/templates/whitespace_handling.haml +87 -0
- data/test/haml/util_test.rb +92 -0
- data/test/linked_rails.rb +12 -0
- data/test/sass/css2sass_test.rb +294 -0
- data/test/sass/engine_test.rb +956 -0
- data/test/sass/functions_test.rb +126 -0
- data/test/sass/more_results/more1.css +9 -0
- data/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/test/sass/more_results/more_import.css +29 -0
- data/test/sass/more_templates/_more_partial.sass +2 -0
- data/test/sass/more_templates/more1.sass +23 -0
- data/test/sass/more_templates/more_import.sass +11 -0
- data/test/sass/plugin_test.rb +229 -0
- data/test/sass/results/alt.css +4 -0
- data/test/sass/results/basic.css +9 -0
- data/test/sass/results/compact.css +5 -0
- data/test/sass/results/complex.css +87 -0
- data/test/sass/results/compressed.css +1 -0
- data/test/sass/results/expanded.css +19 -0
- data/test/sass/results/import.css +29 -0
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/results/nested.css +22 -0
- data/test/sass/results/parent_ref.css +13 -0
- data/test/sass/results/script.css +16 -0
- data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
- data/test/sass/results/subdir/subdir.css +3 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/script_test.rb +261 -0
- data/test/sass/templates/_partial.sass +2 -0
- data/test/sass/templates/alt.sass +16 -0
- data/test/sass/templates/basic.sass +23 -0
- data/test/sass/templates/bork1.sass +2 -0
- data/test/sass/templates/bork2.sass +2 -0
- data/test/sass/templates/bork3.sass +2 -0
- data/test/sass/templates/compact.sass +17 -0
- data/test/sass/templates/complex.sass +307 -0
- data/test/sass/templates/compressed.sass +15 -0
- data/test/sass/templates/expanded.sass +17 -0
- data/test/sass/templates/import.sass +11 -0
- data/test/sass/templates/importee.sass +19 -0
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- data/test/sass/templates/nested.sass +25 -0
- data/test/sass/templates/nested_bork1.sass +2 -0
- data/test/sass/templates/nested_bork2.sass +2 -0
- data/test/sass/templates/nested_bork3.sass +2 -0
- data/test/sass/templates/parent_ref.sass +25 -0
- data/test/sass/templates/script.sass +101 -0
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
- data/test/sass/templates/subdir/subdir.sass +6 -0
- data/test/sass/templates/units.sass +11 -0
- data/test/test_helper.rb +44 -0
- metadata +298 -0
data/lib/sass/engine.rb
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
require 'strscan'
|
|
2
|
+
require 'digest/sha1'
|
|
3
|
+
require 'sass/tree/node'
|
|
4
|
+
require 'sass/tree/root_node'
|
|
5
|
+
require 'sass/tree/rule_node'
|
|
6
|
+
require 'sass/tree/comment_node'
|
|
7
|
+
require 'sass/tree/prop_node'
|
|
8
|
+
require 'sass/tree/directive_node'
|
|
9
|
+
require 'sass/tree/variable_node'
|
|
10
|
+
require 'sass/tree/mixin_def_node'
|
|
11
|
+
require 'sass/tree/mixin_node'
|
|
12
|
+
require 'sass/tree/if_node'
|
|
13
|
+
require 'sass/tree/while_node'
|
|
14
|
+
require 'sass/tree/for_node'
|
|
15
|
+
require 'sass/tree/debug_node'
|
|
16
|
+
require 'sass/tree/import_node'
|
|
17
|
+
require 'sass/environment'
|
|
18
|
+
require 'sass/script'
|
|
19
|
+
require 'sass/error'
|
|
20
|
+
require 'sass/files'
|
|
21
|
+
require 'haml/shared'
|
|
22
|
+
|
|
23
|
+
module Sass
|
|
24
|
+
# A Sass mixin.
|
|
25
|
+
#
|
|
26
|
+
# `name`: `String`
|
|
27
|
+
# : The name of the mixin.
|
|
28
|
+
#
|
|
29
|
+
# `args`: `Array<(String, Script::Node)>`
|
|
30
|
+
# : The arguments for the mixin.
|
|
31
|
+
# Each element is a tuple containing the name of the argument
|
|
32
|
+
# and the parse tree for the default value of the argument.
|
|
33
|
+
#
|
|
34
|
+
# `environment`: {Sass::Environment}
|
|
35
|
+
# : The environment in which the mixin was defined.
|
|
36
|
+
# This is captured so that the mixin can have access
|
|
37
|
+
# to local variables defined in its scope.
|
|
38
|
+
#
|
|
39
|
+
# `tree`: {Sass::Tree::Node}
|
|
40
|
+
# : The parse tree for the mixin.
|
|
41
|
+
Mixin = Struct.new(:name, :args, :environment, :tree)
|
|
42
|
+
|
|
43
|
+
# This class handles the parsing and compilation of the Sass template.
|
|
44
|
+
# Example usage:
|
|
45
|
+
#
|
|
46
|
+
# template = File.load('stylesheets/sassy.sass')
|
|
47
|
+
# sass_engine = Sass::Engine.new(template)
|
|
48
|
+
# output = sass_engine.render
|
|
49
|
+
# puts output
|
|
50
|
+
class Engine
|
|
51
|
+
include Haml::Util
|
|
52
|
+
|
|
53
|
+
# A line of Sass code.
|
|
54
|
+
#
|
|
55
|
+
# `text`: `String`
|
|
56
|
+
# : The text in the line, without any whitespace at the beginning or end.
|
|
57
|
+
#
|
|
58
|
+
# `tabs`: `Fixnum`
|
|
59
|
+
# : The level of indentation of the line.
|
|
60
|
+
#
|
|
61
|
+
# `index`: `Fixnum`
|
|
62
|
+
# : The line number in the original document.
|
|
63
|
+
#
|
|
64
|
+
# `offset`: `Fixnum`
|
|
65
|
+
# : The number of bytes in on the line that the text begins.
|
|
66
|
+
# This ends up being the number of bytes of leading whitespace.
|
|
67
|
+
#
|
|
68
|
+
# `filename`: `String`
|
|
69
|
+
# : The name of the file in which this line appeared.
|
|
70
|
+
#
|
|
71
|
+
# `children`: `Array<Line>`
|
|
72
|
+
# : The lines nested below this one.
|
|
73
|
+
class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children)
|
|
74
|
+
def comment?
|
|
75
|
+
text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The character that begins a CSS property.
|
|
80
|
+
PROPERTY_CHAR = ?:
|
|
81
|
+
|
|
82
|
+
# The character that designates that
|
|
83
|
+
# a property should be assigned to a SassScript expression.
|
|
84
|
+
SCRIPT_CHAR = ?=
|
|
85
|
+
|
|
86
|
+
# The character that designates the beginning of a comment,
|
|
87
|
+
# either Sass or CSS.
|
|
88
|
+
COMMENT_CHAR = ?/
|
|
89
|
+
|
|
90
|
+
# The character that follows the general COMMENT_CHAR and designates a Sass comment,
|
|
91
|
+
# which is not output as a CSS comment.
|
|
92
|
+
SASS_COMMENT_CHAR = ?/
|
|
93
|
+
|
|
94
|
+
# The character that follows the general COMMENT_CHAR and designates a CSS comment,
|
|
95
|
+
# which is embedded in the CSS document.
|
|
96
|
+
CSS_COMMENT_CHAR = ?*
|
|
97
|
+
|
|
98
|
+
# The character used to denote a compiler directive.
|
|
99
|
+
DIRECTIVE_CHAR = ?@
|
|
100
|
+
|
|
101
|
+
# Designates a non-parsed rule.
|
|
102
|
+
ESCAPE_CHAR = ?\\
|
|
103
|
+
|
|
104
|
+
# Designates block as mixin definition rather than CSS rules to output
|
|
105
|
+
MIXIN_DEFINITION_CHAR = ?=
|
|
106
|
+
|
|
107
|
+
# Includes named mixin declared using MIXIN_DEFINITION_CHAR
|
|
108
|
+
MIXIN_INCLUDE_CHAR = ?+
|
|
109
|
+
|
|
110
|
+
# The regex that matches properties of the form <tt>name: prop</tt>.
|
|
111
|
+
PROPERTY_NEW_MATCHER = /^[^\s:"]+\s*[=:](\s|$)/
|
|
112
|
+
|
|
113
|
+
# The regex that matches and extracts data from
|
|
114
|
+
# properties of the form <tt>name: prop</tt>.
|
|
115
|
+
PROPERTY_NEW = /^([^\s=:"]+)(\s*=|:)(?:\s+|$)(.*)/
|
|
116
|
+
|
|
117
|
+
# The regex that matches and extracts data from
|
|
118
|
+
# properties of the form <tt>:name prop</tt>.
|
|
119
|
+
PROPERTY_OLD = /^:([^\s=:"]+)\s*(=?)(?:\s+|$)(.*)/
|
|
120
|
+
|
|
121
|
+
# The default options for Sass::Engine.
|
|
122
|
+
DEFAULT_OPTIONS = {
|
|
123
|
+
:style => :nested,
|
|
124
|
+
:load_paths => ['.'],
|
|
125
|
+
:cache => true,
|
|
126
|
+
:cache_location => './.sass-cache',
|
|
127
|
+
}.freeze
|
|
128
|
+
|
|
129
|
+
# @param template [String] The Sass template.
|
|
130
|
+
# @param options [Hash<Symbol, Object>] An options hash;
|
|
131
|
+
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
|
|
132
|
+
def initialize(template, options={})
|
|
133
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
|
134
|
+
@template = template
|
|
135
|
+
|
|
136
|
+
# Backwards compatibility
|
|
137
|
+
@options[:property_syntax] ||= @options[:attribute_syntax]
|
|
138
|
+
case @options[:property_syntax]
|
|
139
|
+
when :alternate; @options[:property_syntax] = :new
|
|
140
|
+
when :normal; @options[:property_syntax] = :old
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Render the template to CSS.
|
|
145
|
+
#
|
|
146
|
+
# @return [String] The CSS
|
|
147
|
+
# @raise [Sass::SyntaxError] if there's an error in the document
|
|
148
|
+
def render
|
|
149
|
+
to_tree.render
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
alias_method :to_css, :render
|
|
153
|
+
|
|
154
|
+
# Parses the document into its parse tree.
|
|
155
|
+
#
|
|
156
|
+
# @return [Sass::Tree::Node] The root of the parse tree.
|
|
157
|
+
# @raise [Sass::SyntaxError] if there's an error in the document
|
|
158
|
+
def to_tree
|
|
159
|
+
check_encoding(@template) {|msg, line| raise Sass::SyntaxError.new(msg, :line => line)}
|
|
160
|
+
|
|
161
|
+
root = Tree::RootNode.new(@template)
|
|
162
|
+
append_children(root, tree(tabulate(@template)).first, true)
|
|
163
|
+
root.options = @options
|
|
164
|
+
root
|
|
165
|
+
rescue SyntaxError => e
|
|
166
|
+
e.modify_backtrace(:filename => @options[:filename], :line => @line)
|
|
167
|
+
e.sass_template = @template
|
|
168
|
+
raise e
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def tabulate(string)
|
|
174
|
+
tab_str = nil
|
|
175
|
+
first = true
|
|
176
|
+
lines = []
|
|
177
|
+
string.gsub(/\r|\n|\r\n|\r\n/, "\n").scan(/^.*?$/).each_with_index do |line, index|
|
|
178
|
+
index += (@options[:line] || 1)
|
|
179
|
+
if line.strip.empty?
|
|
180
|
+
lines.last.text << "\n" if lines.last && lines.last.comment?
|
|
181
|
+
next
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
line_tab_str = line[/^\s*/]
|
|
185
|
+
unless line_tab_str.empty?
|
|
186
|
+
tab_str ||= line_tab_str
|
|
187
|
+
|
|
188
|
+
raise SyntaxError.new("Indenting at the beginning of the document is illegal.",
|
|
189
|
+
:line => index) if first
|
|
190
|
+
|
|
191
|
+
raise SyntaxError.new("Indentation can't use both tabs and spaces.",
|
|
192
|
+
:line => index) if tab_str.include?(?\s) && tab_str.include?(?\t)
|
|
193
|
+
end
|
|
194
|
+
first &&= !tab_str.nil?
|
|
195
|
+
if tab_str.nil?
|
|
196
|
+
lines << Line.new(line.strip, 0, index, 0, @options[:filename], [])
|
|
197
|
+
next
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
if lines.last && lines.last.comment? && line =~ /^(?:#{tab_str}){#{lines.last.tabs + 1}}(.*)$/
|
|
201
|
+
lines.last.text << "\n" << $1
|
|
202
|
+
next
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
line_tabs = line_tab_str.scan(tab_str).size
|
|
206
|
+
if tab_str * line_tabs != line_tab_str
|
|
207
|
+
message = <<END.strip.gsub("\n", ' ')
|
|
208
|
+
Inconsistent indentation: #{Haml::Shared.human_indentation line_tab_str, true} used for indentation,
|
|
209
|
+
but the rest of the document was indented using #{Haml::Shared.human_indentation tab_str}.
|
|
210
|
+
END
|
|
211
|
+
raise SyntaxError.new(message, :line => index)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
lines << Line.new(line.strip, line_tabs, index, tab_str.size, @options[:filename], [])
|
|
215
|
+
end
|
|
216
|
+
lines
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def tree(arr, i = 0)
|
|
220
|
+
return [], i if arr[i].nil?
|
|
221
|
+
|
|
222
|
+
base = arr[i].tabs
|
|
223
|
+
nodes = []
|
|
224
|
+
while (line = arr[i]) && line.tabs >= base
|
|
225
|
+
if line.tabs > base
|
|
226
|
+
raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.",
|
|
227
|
+
:line => line.index) if line.tabs > base + 1
|
|
228
|
+
|
|
229
|
+
nodes.last.children, i = tree(arr, i)
|
|
230
|
+
else
|
|
231
|
+
nodes << line
|
|
232
|
+
i += 1
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
return nodes, i
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def build_tree(parent, line, root = false)
|
|
239
|
+
@line = line.index
|
|
240
|
+
node_or_nodes = parse_line(parent, line, root)
|
|
241
|
+
|
|
242
|
+
Array(node_or_nodes).each do |node|
|
|
243
|
+
# Node is a symbol if it's non-outputting, like a variable assignment
|
|
244
|
+
next unless node.is_a? Tree::Node
|
|
245
|
+
|
|
246
|
+
node.line = line.index
|
|
247
|
+
node.filename = line.filename
|
|
248
|
+
|
|
249
|
+
if node.is_a?(Tree::CommentNode)
|
|
250
|
+
node.lines = line.children
|
|
251
|
+
else
|
|
252
|
+
append_children(node, line.children, false)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
node_or_nodes
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def append_children(parent, children, root)
|
|
260
|
+
continued_rule = nil
|
|
261
|
+
children.each do |line|
|
|
262
|
+
child = build_tree(parent, line, root)
|
|
263
|
+
|
|
264
|
+
if child.is_a?(Tree::RuleNode) && child.continued?
|
|
265
|
+
raise SyntaxError.new("Rules can't end in commas.",
|
|
266
|
+
:line => child.line) unless child.children.empty?
|
|
267
|
+
if continued_rule
|
|
268
|
+
continued_rule.add_rules child
|
|
269
|
+
else
|
|
270
|
+
continued_rule = child
|
|
271
|
+
end
|
|
272
|
+
next
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
if continued_rule
|
|
276
|
+
raise SyntaxError.new("Rules can't end in commas.",
|
|
277
|
+
:line => continued_rule.line) unless child.is_a?(Tree::RuleNode)
|
|
278
|
+
continued_rule.add_rules child
|
|
279
|
+
continued_rule.children = child.children
|
|
280
|
+
continued_rule, child = nil, continued_rule
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
check_for_no_children(child)
|
|
284
|
+
validate_and_append_child(parent, child, line, root)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
raise SyntaxError.new("Rules can't end in commas.",
|
|
288
|
+
:line => continued_rule.line) if continued_rule
|
|
289
|
+
|
|
290
|
+
parent
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def validate_and_append_child(parent, child, line, root)
|
|
294
|
+
unless root
|
|
295
|
+
case child
|
|
296
|
+
when Tree::MixinDefNode
|
|
297
|
+
raise SyntaxError.new("Mixins may only be defined at the root of a document.",
|
|
298
|
+
:line => line.index)
|
|
299
|
+
when Tree::ImportNode
|
|
300
|
+
raise SyntaxError.new("Import directives may only be used at the root of a document.",
|
|
301
|
+
:line => line.index)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
case child
|
|
306
|
+
when Array
|
|
307
|
+
child.each {|c| validate_and_append_child(parent, c, line, root)}
|
|
308
|
+
when Tree::Node
|
|
309
|
+
parent << child
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def check_for_no_children(node)
|
|
314
|
+
return unless node.is_a?(Tree::RuleNode) && node.children.empty?
|
|
315
|
+
warning = (node.rules.size == 1) ? <<SHORT : <<LONG
|
|
316
|
+
WARNING on line #{node.line}:
|
|
317
|
+
Selector #{node.rules.first.inspect} doesn't have any properties and will not be rendered.
|
|
318
|
+
SHORT
|
|
319
|
+
|
|
320
|
+
WARNING on line #{node.line}:
|
|
321
|
+
Selector
|
|
322
|
+
#{node.rules.join("\n ")}
|
|
323
|
+
doesn't have any properties and will not be rendered.
|
|
324
|
+
LONG
|
|
325
|
+
|
|
326
|
+
warn(warning.strip)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def parse_line(parent, line, root)
|
|
330
|
+
case line.text[0]
|
|
331
|
+
when PROPERTY_CHAR
|
|
332
|
+
if line.text[1] != PROPERTY_CHAR
|
|
333
|
+
parse_property(line, PROPERTY_OLD)
|
|
334
|
+
else
|
|
335
|
+
# Support CSS3-style pseudo-elements,
|
|
336
|
+
# which begin with ::
|
|
337
|
+
Tree::RuleNode.new(line.text)
|
|
338
|
+
end
|
|
339
|
+
when Script::VARIABLE_CHAR
|
|
340
|
+
parse_variable(line)
|
|
341
|
+
when COMMENT_CHAR
|
|
342
|
+
parse_comment(line.text)
|
|
343
|
+
when DIRECTIVE_CHAR
|
|
344
|
+
parse_directive(parent, line, root)
|
|
345
|
+
when ESCAPE_CHAR
|
|
346
|
+
Tree::RuleNode.new(line.text[1..-1])
|
|
347
|
+
when MIXIN_DEFINITION_CHAR
|
|
348
|
+
parse_mixin_definition(line)
|
|
349
|
+
when MIXIN_INCLUDE_CHAR
|
|
350
|
+
if line.text[1].nil? || line.text[1] == ?\s
|
|
351
|
+
Tree::RuleNode.new(line.text)
|
|
352
|
+
else
|
|
353
|
+
parse_mixin_include(line, root)
|
|
354
|
+
end
|
|
355
|
+
else
|
|
356
|
+
if line.text =~ PROPERTY_NEW_MATCHER
|
|
357
|
+
parse_property(line, PROPERTY_NEW)
|
|
358
|
+
else
|
|
359
|
+
Tree::RuleNode.new(line.text)
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def parse_property(line, property_regx)
|
|
365
|
+
name, eq, value = line.text.scan(property_regx)[0]
|
|
366
|
+
|
|
367
|
+
raise SyntaxError.new("Invalid property: \"#{line.text}\".",
|
|
368
|
+
:line => @line) if name.nil? || value.nil?
|
|
369
|
+
|
|
370
|
+
expr = if (eq.strip[0] == SCRIPT_CHAR)
|
|
371
|
+
parse_script(value, :offset => line.offset + line.text.index(value))
|
|
372
|
+
else
|
|
373
|
+
value
|
|
374
|
+
end
|
|
375
|
+
Tree::PropNode.new(name, expr, property_regx == PROPERTY_OLD ? :old : :new)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def parse_variable(line)
|
|
379
|
+
name, op, value = line.text.scan(Script::MATCH)[0]
|
|
380
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
|
|
381
|
+
:line => @line + 1) unless line.children.empty?
|
|
382
|
+
raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
|
|
383
|
+
:line => @line) unless name && value
|
|
384
|
+
|
|
385
|
+
Tree::VariableNode.new(name, parse_script(value, :offset => line.offset + line.text.index(value)), op == '||=')
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def parse_comment(line)
|
|
389
|
+
if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
|
|
390
|
+
Tree::CommentNode.new(line, line[1] == SASS_COMMENT_CHAR)
|
|
391
|
+
else
|
|
392
|
+
Tree::RuleNode.new(line)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def parse_directive(parent, line, root)
|
|
397
|
+
directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
|
|
398
|
+
offset = directive.size + whitespace.size + 1 if whitespace
|
|
399
|
+
|
|
400
|
+
# If value begins with url( or ",
|
|
401
|
+
# it's a CSS @import rule and we don't want to touch it.
|
|
402
|
+
if directive == "import" && value !~ /^(url\(|")/
|
|
403
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
|
|
404
|
+
:line => @line + 1) unless line.children.empty?
|
|
405
|
+
value.split(/,\s*/).map {|f| Tree::ImportNode.new(f)}
|
|
406
|
+
elsif directive == "for"
|
|
407
|
+
parse_for(line, root, value)
|
|
408
|
+
elsif directive == "else"
|
|
409
|
+
parse_else(parent, line, value)
|
|
410
|
+
elsif directive == "while"
|
|
411
|
+
raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
|
|
412
|
+
Tree::WhileNode.new(parse_script(value, :offset => offset))
|
|
413
|
+
elsif directive == "if"
|
|
414
|
+
raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
|
|
415
|
+
Tree::IfNode.new(parse_script(value, :offset => offset))
|
|
416
|
+
elsif directive == "debug"
|
|
417
|
+
raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
|
|
418
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
|
|
419
|
+
:line => @line + 1) unless line.children.empty?
|
|
420
|
+
offset = line.offset + line.text.index(value).to_i
|
|
421
|
+
Tree::DebugNode.new(parse_script(value, :offset => offset))
|
|
422
|
+
else
|
|
423
|
+
Tree::DirectiveNode.new(line.text)
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def parse_for(line, root, text)
|
|
428
|
+
var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
|
|
429
|
+
|
|
430
|
+
if var.nil? # scan failed, try to figure out why for error message
|
|
431
|
+
if text !~ /^[^\s]+/
|
|
432
|
+
expected = "variable name"
|
|
433
|
+
elsif text !~ /^[^\s]+\s+from\s+.+/
|
|
434
|
+
expected = "'from <expr>'"
|
|
435
|
+
else
|
|
436
|
+
expected = "'to <expr>' or 'through <expr>'"
|
|
437
|
+
end
|
|
438
|
+
raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
|
|
439
|
+
end
|
|
440
|
+
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
|
|
441
|
+
|
|
442
|
+
parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
|
|
443
|
+
parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
|
|
444
|
+
Tree::ForNode.new(var[1..-1], parsed_from, parsed_to, to_name == 'to')
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def parse_else(parent, line, text)
|
|
448
|
+
previous = parent.last
|
|
449
|
+
raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
|
|
450
|
+
|
|
451
|
+
if text
|
|
452
|
+
if text !~ /^if\s+(.+)/
|
|
453
|
+
raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.")
|
|
454
|
+
end
|
|
455
|
+
expr = parse_script($1, :offset => line.offset + line.text.index($1))
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
node = Tree::IfNode.new(expr)
|
|
459
|
+
append_children(node, line.children, false)
|
|
460
|
+
previous.add_else node
|
|
461
|
+
nil
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def parse_mixin_definition(line)
|
|
465
|
+
name, arg_string = line.text.scan(/^=\s*([^(]+)(.*)$/).first
|
|
466
|
+
raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
|
|
467
|
+
|
|
468
|
+
offset = line.offset + line.text.size - arg_string.size
|
|
469
|
+
args = Script::Parser.new(arg_string.strip, @line, offset).parse_mixin_definition_arglist
|
|
470
|
+
default_arg_found = false
|
|
471
|
+
Tree::MixinDefNode.new(name, args)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def parse_mixin_include(line, root)
|
|
475
|
+
name, arg_string = line.text.scan(/^\+\s*([^(]+)(.*)$/).first
|
|
476
|
+
raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
|
|
477
|
+
|
|
478
|
+
offset = line.offset + line.text.size - arg_string.size
|
|
479
|
+
args = Script::Parser.new(arg_string.strip, @line, offset).parse_mixin_include_arglist
|
|
480
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.",
|
|
481
|
+
:line => @line + 1) unless line.children.empty?
|
|
482
|
+
Tree::MixinNode.new(name, args)
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def parse_script(script, options = {})
|
|
486
|
+
line = options[:line] || @line
|
|
487
|
+
offset = options[:offset] || 0
|
|
488
|
+
Script.parse(script, line, offset, @options[:filename])
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
end
|