wireframe-haml 2.1.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.
Files changed (169) hide show
  1. data/README.rdoc +332 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/css2sass +7 -0
  4. data/bin/haml +9 -0
  5. data/bin/html2haml +7 -0
  6. data/bin/sass +8 -0
  7. data/lib/haml/buffer.rb +255 -0
  8. data/lib/haml/engine.rb +268 -0
  9. data/lib/haml/error.rb +22 -0
  10. data/lib/haml/exec.rb +395 -0
  11. data/lib/haml/filters.rb +276 -0
  12. data/lib/haml/helpers/action_view_extensions.rb +45 -0
  13. data/lib/haml/helpers/action_view_mods.rb +181 -0
  14. data/lib/haml/helpers.rb +468 -0
  15. data/lib/haml/html.rb +218 -0
  16. data/lib/haml/precompiler.rb +889 -0
  17. data/lib/haml/shared.rb +45 -0
  18. data/lib/haml/template/patch.rb +58 -0
  19. data/lib/haml/template/plugin.rb +72 -0
  20. data/lib/haml/template.rb +51 -0
  21. data/lib/haml/util.rb +77 -0
  22. data/lib/haml/version.rb +47 -0
  23. data/lib/haml.rb +1042 -0
  24. data/lib/sass/css.rb +388 -0
  25. data/lib/sass/engine.rb +499 -0
  26. data/lib/sass/environment.rb +33 -0
  27. data/lib/sass/error.rb +35 -0
  28. data/lib/sass/plugin/merb.rb +56 -0
  29. data/lib/sass/plugin/rails.rb +24 -0
  30. data/lib/sass/plugin.rb +203 -0
  31. data/lib/sass/repl.rb +51 -0
  32. data/lib/sass/script/bool.rb +13 -0
  33. data/lib/sass/script/color.rb +97 -0
  34. data/lib/sass/script/funcall.rb +28 -0
  35. data/lib/sass/script/functions.rb +122 -0
  36. data/lib/sass/script/lexer.rb +152 -0
  37. data/lib/sass/script/literal.rb +60 -0
  38. data/lib/sass/script/number.rb +231 -0
  39. data/lib/sass/script/operation.rb +30 -0
  40. data/lib/sass/script/parser.rb +142 -0
  41. data/lib/sass/script/string.rb +42 -0
  42. data/lib/sass/script/unary_operation.rb +21 -0
  43. data/lib/sass/script/variable.rb +20 -0
  44. data/lib/sass/script.rb +38 -0
  45. data/lib/sass/tree/attr_node.rb +64 -0
  46. data/lib/sass/tree/comment_node.rb +34 -0
  47. data/lib/sass/tree/debug_node.rb +22 -0
  48. data/lib/sass/tree/directive_node.rb +50 -0
  49. data/lib/sass/tree/file_node.rb +27 -0
  50. data/lib/sass/tree/for_node.rb +29 -0
  51. data/lib/sass/tree/if_node.rb +27 -0
  52. data/lib/sass/tree/mixin_def_node.rb +18 -0
  53. data/lib/sass/tree/mixin_node.rb +34 -0
  54. data/lib/sass/tree/node.rb +99 -0
  55. data/lib/sass/tree/rule_node.rb +120 -0
  56. data/lib/sass/tree/variable_node.rb +24 -0
  57. data/lib/sass/tree/while_node.rb +20 -0
  58. data/lib/sass.rb +1062 -0
  59. data/test/benchmark.rb +99 -0
  60. data/test/haml/engine_test.rb +734 -0
  61. data/test/haml/helper_test.rb +224 -0
  62. data/test/haml/html2haml_test.rb +92 -0
  63. data/test/haml/markaby/standard.mab +52 -0
  64. data/test/haml/mocks/article.rb +6 -0
  65. data/test/haml/results/content_for_layout.xhtml +15 -0
  66. data/test/haml/results/eval_suppressed.xhtml +9 -0
  67. data/test/haml/results/filters.xhtml +62 -0
  68. data/test/haml/results/helpers.xhtml +93 -0
  69. data/test/haml/results/helpful.xhtml +10 -0
  70. data/test/haml/results/just_stuff.xhtml +68 -0
  71. data/test/haml/results/list.xhtml +12 -0
  72. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  73. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  74. data/test/haml/results/original_engine.xhtml +20 -0
  75. data/test/haml/results/partial_layout.xhtml +5 -0
  76. data/test/haml/results/partials.xhtml +21 -0
  77. data/test/haml/results/render_layout.xhtml +3 -0
  78. data/test/haml/results/silent_script.xhtml +74 -0
  79. data/test/haml/results/standard.xhtml +42 -0
  80. data/test/haml/results/tag_parsing.xhtml +23 -0
  81. data/test/haml/results/very_basic.xhtml +5 -0
  82. data/test/haml/results/whitespace_handling.xhtml +89 -0
  83. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  84. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  85. data/test/haml/rhtml/action_view.rhtml +62 -0
  86. data/test/haml/rhtml/standard.rhtml +54 -0
  87. data/test/haml/template_test.rb +204 -0
  88. data/test/haml/templates/_av_partial_1.haml +9 -0
  89. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  90. data/test/haml/templates/_av_partial_2.haml +5 -0
  91. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  92. data/test/haml/templates/_layout.erb +3 -0
  93. data/test/haml/templates/_layout_for_partial.haml +3 -0
  94. data/test/haml/templates/_partial.haml +8 -0
  95. data/test/haml/templates/_text_area.haml +3 -0
  96. data/test/haml/templates/action_view.haml +47 -0
  97. data/test/haml/templates/action_view_ugly.haml +47 -0
  98. data/test/haml/templates/breakage.haml +8 -0
  99. data/test/haml/templates/content_for_layout.haml +10 -0
  100. data/test/haml/templates/eval_suppressed.haml +11 -0
  101. data/test/haml/templates/filters.haml +66 -0
  102. data/test/haml/templates/helpers.haml +95 -0
  103. data/test/haml/templates/helpful.haml +11 -0
  104. data/test/haml/templates/just_stuff.haml +83 -0
  105. data/test/haml/templates/list.haml +12 -0
  106. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  107. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  108. data/test/haml/templates/original_engine.haml +17 -0
  109. data/test/haml/templates/partial_layout.haml +3 -0
  110. data/test/haml/templates/partialize.haml +1 -0
  111. data/test/haml/templates/partials.haml +12 -0
  112. data/test/haml/templates/render_layout.haml +2 -0
  113. data/test/haml/templates/silent_script.haml +40 -0
  114. data/test/haml/templates/standard.haml +42 -0
  115. data/test/haml/templates/standard_ugly.haml +1 -0
  116. data/test/haml/templates/tag_parsing.haml +21 -0
  117. data/test/haml/templates/very_basic.haml +4 -0
  118. data/test/haml/templates/whitespace_handling.haml +87 -0
  119. data/test/linked_rails.rb +12 -0
  120. data/test/sass/css2sass_test.rb +193 -0
  121. data/test/sass/engine_test.rb +786 -0
  122. data/test/sass/functions_test.rb +96 -0
  123. data/test/sass/more_results/more1.css +9 -0
  124. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  125. data/test/sass/more_results/more_import.css +29 -0
  126. data/test/sass/more_templates/_more_partial.sass +2 -0
  127. data/test/sass/more_templates/more1.sass +23 -0
  128. data/test/sass/more_templates/more_import.sass +11 -0
  129. data/test/sass/plugin_test.rb +208 -0
  130. data/test/sass/results/alt.css +4 -0
  131. data/test/sass/results/basic.css +9 -0
  132. data/test/sass/results/compact.css +5 -0
  133. data/test/sass/results/complex.css +87 -0
  134. data/test/sass/results/compressed.css +1 -0
  135. data/test/sass/results/expanded.css +19 -0
  136. data/test/sass/results/import.css +29 -0
  137. data/test/sass/results/line_numbers.css +49 -0
  138. data/test/sass/results/mixins.css +95 -0
  139. data/test/sass/results/multiline.css +24 -0
  140. data/test/sass/results/nested.css +22 -0
  141. data/test/sass/results/parent_ref.css +13 -0
  142. data/test/sass/results/script.css +16 -0
  143. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  144. data/test/sass/results/subdir/subdir.css +3 -0
  145. data/test/sass/results/units.css +11 -0
  146. data/test/sass/script_test.rb +153 -0
  147. data/test/sass/templates/_partial.sass +2 -0
  148. data/test/sass/templates/alt.sass +16 -0
  149. data/test/sass/templates/basic.sass +23 -0
  150. data/test/sass/templates/bork.sass +2 -0
  151. data/test/sass/templates/bork2.sass +2 -0
  152. data/test/sass/templates/compact.sass +17 -0
  153. data/test/sass/templates/complex.sass +309 -0
  154. data/test/sass/templates/compressed.sass +15 -0
  155. data/test/sass/templates/expanded.sass +17 -0
  156. data/test/sass/templates/import.sass +11 -0
  157. data/test/sass/templates/importee.sass +19 -0
  158. data/test/sass/templates/line_numbers.sass +13 -0
  159. data/test/sass/templates/mixins.sass +76 -0
  160. data/test/sass/templates/multiline.sass +20 -0
  161. data/test/sass/templates/nested.sass +25 -0
  162. data/test/sass/templates/parent_ref.sass +25 -0
  163. data/test/sass/templates/script.sass +101 -0
  164. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  165. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  166. data/test/sass/templates/subdir/subdir.sass +6 -0
  167. data/test/sass/templates/units.sass +11 -0
  168. data/test/test_helper.rb +21 -0
  169. metadata +247 -0
@@ -0,0 +1,499 @@
1
+ require 'strscan'
2
+ require 'sass/tree/node'
3
+ require 'sass/tree/rule_node'
4
+ require 'sass/tree/comment_node'
5
+ require 'sass/tree/attr_node'
6
+ require 'sass/tree/directive_node'
7
+ require 'sass/tree/variable_node'
8
+ require 'sass/tree/mixin_def_node'
9
+ require 'sass/tree/mixin_node'
10
+ require 'sass/tree/if_node'
11
+ require 'sass/tree/while_node'
12
+ require 'sass/tree/for_node'
13
+ require 'sass/tree/debug_node'
14
+ require 'sass/tree/file_node'
15
+ require 'sass/environment'
16
+ require 'sass/script'
17
+ require 'sass/error'
18
+ require 'haml/shared'
19
+
20
+ module Sass
21
+ # :stopdoc:
22
+ Mixin = Struct.new(:name, :args, :environment, :tree)
23
+ # :startdoc:
24
+
25
+ # This is the class where all the parsing and processing of the Sass
26
+ # template is done. It can be directly used by the user by creating a
27
+ # new instance and calling <tt>render</tt> to render the template. For example:
28
+ #
29
+ # template = File.load('stylesheets/sassy.sass')
30
+ # sass_engine = Sass::Engine.new(template)
31
+ # output = sass_engine.render
32
+ # puts output
33
+ class Engine
34
+ include Haml::Util
35
+ Line = Struct.new(:text, :tabs, :index, :offset, :filename, :children)
36
+
37
+ # The character that begins a CSS attribute.
38
+ ATTRIBUTE_CHAR = ?:
39
+
40
+ # The character that designates that
41
+ # an attribute should be assigned to a SassScript expression.
42
+ SCRIPT_CHAR = ?=
43
+
44
+ # The character that designates the beginning of a comment,
45
+ # either Sass or CSS.
46
+ COMMENT_CHAR = ?/
47
+
48
+ # The character that follows the general COMMENT_CHAR and designates a Sass comment,
49
+ # which is not output as a CSS comment.
50
+ SASS_COMMENT_CHAR = ?/
51
+
52
+ # The character that follows the general COMMENT_CHAR and designates a CSS comment,
53
+ # which is embedded in the CSS document.
54
+ CSS_COMMENT_CHAR = ?*
55
+
56
+ # The character used to denote a compiler directive.
57
+ DIRECTIVE_CHAR = ?@
58
+
59
+ # Designates a non-parsed rule.
60
+ ESCAPE_CHAR = ?\\
61
+
62
+ # Designates block as mixin definition rather than CSS rules to output
63
+ MIXIN_DEFINITION_CHAR = ?=
64
+
65
+ # Includes named mixin declared using MIXIN_DEFINITION_CHAR
66
+ MIXIN_INCLUDE_CHAR = ?+
67
+
68
+ # The regex that matches and extracts data from
69
+ # attributes of the form <tt>:name attr</tt>.
70
+ ATTRIBUTE = /^:([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/
71
+
72
+ # The regex that matches attributes of the form <tt>name: attr</tt>.
73
+ ATTRIBUTE_ALTERNATE_MATCHER = /^[^\s:]+\s*[=:](\s|$)/
74
+
75
+ # The regex that matches and extracts data from
76
+ # attributes of the form <tt>name: attr</tt>.
77
+ ATTRIBUTE_ALTERNATE = /^([^\s=:]+)(\s*=|:)(?:\s+|$)(.*)/
78
+
79
+ # Creates a new instace of Sass::Engine that will compile the given
80
+ # template string when <tt>render</tt> is called.
81
+ # See README.rdoc for available options.
82
+ #
83
+ #--
84
+ #
85
+ # TODO: Add current options to REFRENCE. Remember :filename!
86
+ #
87
+ # When adding options, remember to add information about them
88
+ # to README.rdoc!
89
+ #++
90
+ #
91
+ def initialize(template, options={})
92
+ @options = {
93
+ :style => :nested,
94
+ :load_paths => ['.']
95
+ }.merge! options
96
+ @template = template
97
+ @environment = Environment.new
98
+ @environment.set_var("important", Script::String.new("!important"))
99
+ end
100
+
101
+ # Processes the template and returns the result as a string.
102
+ def render
103
+ begin
104
+ render_to_tree.perform(@environment).to_s
105
+ rescue SyntaxError => err
106
+ err.sass_line = @line unless err.sass_line
107
+ unless err.sass_filename
108
+ err.add_backtrace_entry(@options[:filename])
109
+ end
110
+ raise err
111
+ end
112
+ end
113
+
114
+ alias_method :to_css, :render
115
+
116
+ protected
117
+
118
+ def environment
119
+ @environment
120
+ end
121
+
122
+ def render_to_tree
123
+ root = Tree::Node.new(@options)
124
+ append_children(root, tree(tabulate(@template)).first, true)
125
+ root
126
+ end
127
+
128
+ private
129
+
130
+ def tabulate(string)
131
+ tab_str = nil
132
+ first = true
133
+ enum_with_index(string.gsub(/\r|\n|\r\n|\r\n/, "\n").scan(/^.*?$/)).map do |line, index|
134
+ index += 1
135
+ next if line.strip.empty?
136
+
137
+ line_tab_str = line[/^\s*/]
138
+ unless line_tab_str.empty?
139
+ tab_str ||= line_tab_str
140
+
141
+ raise SyntaxError.new("Indenting at the beginning of the document is illegal.", index) if first
142
+ if tab_str.include?(?\s) && tab_str.include?(?\t)
143
+ raise SyntaxError.new("Indentation can't use both tabs and spaces.", index)
144
+ end
145
+ end
146
+ first &&= !tab_str.nil?
147
+ next Line.new(line.strip, 0, index, 0, @options[:filename], []) if tab_str.nil?
148
+
149
+ line_tabs = line_tab_str.scan(tab_str).size
150
+ raise SyntaxError.new(<<END.strip.gsub("\n", ' '), index) if tab_str * line_tabs != line_tab_str
151
+ Inconsistent indentation: #{Haml::Shared.human_indentation line_tab_str, true} used for indentation,
152
+ but the rest of the document was indented using #{Haml::Shared.human_indentation tab_str}.
153
+ END
154
+ Line.new(line.strip, line_tabs, index, tab_str.size, @options[:filename], [])
155
+ end.compact
156
+ end
157
+
158
+ def tree(arr, i = 0)
159
+ return [], i if arr[i].nil?
160
+
161
+ base = arr[i].tabs
162
+ nodes = []
163
+ while (line = arr[i]) && line.tabs >= base
164
+ if line.tabs > base
165
+ if line.tabs > base + 1
166
+ raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.", line.index)
167
+ end
168
+
169
+ nodes.last.children, i = tree(arr, i)
170
+ else
171
+ nodes << line
172
+ i += 1
173
+ end
174
+ end
175
+ return nodes, i
176
+ end
177
+
178
+ def build_tree(parent, line, root = false)
179
+ @line = line.index
180
+ node = parse_line(parent, line, root)
181
+
182
+ # Node is a symbol if it's non-outputting, like a variable assignment,
183
+ # or an array if it's a group of nodes to add
184
+ return node unless node.is_a? Tree::Node
185
+
186
+ node.line = line.index
187
+ node.filename = line.filename
188
+
189
+ unless node.is_a?(Tree::CommentNode)
190
+ append_children(node, line.children, false)
191
+ else
192
+ node.children = line.children
193
+ end
194
+ return node
195
+ end
196
+
197
+ def append_children(parent, children, root)
198
+ continued_rule = nil
199
+ children.each do |line|
200
+ child = build_tree(parent, line, root)
201
+
202
+ if child.is_a?(Tree::RuleNode) && child.continued?
203
+ raise SyntaxError.new("Rules can't end in commas.", child.line) unless child.children.empty?
204
+ if continued_rule
205
+ continued_rule.add_rules child
206
+ else
207
+ continued_rule = child
208
+ end
209
+ next
210
+ end
211
+
212
+ if continued_rule
213
+ raise SyntaxError.new("Rules can't end in commas.", continued_rule.line) unless child.is_a?(Tree::RuleNode)
214
+ continued_rule.add_rules child
215
+ continued_rule.children = child.children
216
+ continued_rule, child = nil, continued_rule
217
+ end
218
+
219
+ validate_and_append_child(parent, child, line, root)
220
+ end
221
+
222
+ raise SyntaxError.new("Rules can't end in commas.", continued_rule.line) if continued_rule
223
+
224
+ parent
225
+ end
226
+
227
+ def validate_and_append_child(parent, child, line, root)
228
+ unless root
229
+ case child
230
+ when Tree::MixinDefNode
231
+ raise SyntaxError.new("Mixins may only be defined at the root of a document.", line.index)
232
+ when Tree::DirectiveNode
233
+ raise SyntaxError.new("Import directives may only be used at the root of a document.", line.index)
234
+ end
235
+ end
236
+
237
+ case child
238
+ when Array
239
+ child.each {|c| validate_and_append_child(parent, c, line, root)}
240
+ when Tree::Node
241
+ parent << child
242
+ end
243
+ end
244
+
245
+ def parse_line(parent, line, root)
246
+ case line.text[0]
247
+ when ATTRIBUTE_CHAR
248
+ if line.text[1] != ATTRIBUTE_CHAR
249
+ parse_attribute(line, ATTRIBUTE)
250
+ else
251
+ # Support CSS3-style pseudo-elements,
252
+ # which begin with ::
253
+ Tree::RuleNode.new(line.text, @options)
254
+ end
255
+ when Script::VARIABLE_CHAR
256
+ parse_variable(line)
257
+ when COMMENT_CHAR
258
+ parse_comment(line.text)
259
+ when DIRECTIVE_CHAR
260
+ parse_directive(parent, line, root)
261
+ when ESCAPE_CHAR
262
+ Tree::RuleNode.new(line.text[1..-1], @options)
263
+ when MIXIN_DEFINITION_CHAR
264
+ parse_mixin_definition(line)
265
+ when MIXIN_INCLUDE_CHAR
266
+ if line.text[1].nil?
267
+ Tree::RuleNode.new(line.text, @options)
268
+ else
269
+ parse_mixin_include(line, root)
270
+ end
271
+ else
272
+ if line.text =~ ATTRIBUTE_ALTERNATE_MATCHER
273
+ parse_attribute(line, ATTRIBUTE_ALTERNATE)
274
+ else
275
+ Tree::RuleNode.new(line.text, @options)
276
+ end
277
+ end
278
+ end
279
+
280
+ def parse_attribute(line, attribute_regx)
281
+ if @options[:attribute_syntax] == :normal &&
282
+ attribute_regx == ATTRIBUTE_ALTERNATE
283
+ raise SyntaxError.new("Illegal attribute syntax: can't use alternate syntax when :attribute_syntax => :normal is set.")
284
+ elsif @options[:attribute_syntax] == :alternate &&
285
+ attribute_regx == ATTRIBUTE
286
+ raise SyntaxError.new("Illegal attribute syntax: can't use normal syntax when :attribute_syntax => :alternate is set.")
287
+ end
288
+
289
+ name, eq, value = line.text.scan(attribute_regx)[0]
290
+
291
+ if name.nil? || value.nil?
292
+ raise SyntaxError.new("Invalid attribute: \"#{line.text}\".", @line)
293
+ end
294
+ expr = if (eq.strip[0] == SCRIPT_CHAR)
295
+ parse_script(value, :offset => line.offset + line.text.index(value))
296
+ else
297
+ value
298
+ end
299
+ Tree::AttrNode.new(name, expr, @options)
300
+ end
301
+
302
+ def parse_variable(line)
303
+ name, op, value = line.text.scan(Script::MATCH)[0]
304
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.", @line + 1) unless line.children.empty?
305
+ raise SyntaxError.new("Invalid variable: \"#{line.text}\".", @line) unless name && value
306
+
307
+ Tree::VariableNode.new(name, parse_script(value, :offset => line.offset + line.text.index(value)), op == '||=', @options)
308
+ end
309
+
310
+ def parse_comment(line)
311
+ if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
312
+ Tree::CommentNode.new(line, @options.merge(:silent => (line[1] == SASS_COMMENT_CHAR)))
313
+ else
314
+ Tree::RuleNode.new(line, @options)
315
+ end
316
+ end
317
+
318
+ def parse_directive(parent, line, root)
319
+ directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
320
+ offset = directive.size + whitespace.size + 1 if whitespace
321
+
322
+ # If value begins with url( or ",
323
+ # it's a CSS @import rule and we don't want to touch it.
324
+ if directive == "import" && value !~ /^(url\(|")/
325
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.", @line + 1) unless line.children.empty?
326
+ import(value)
327
+ elsif directive == "for"
328
+ parse_for(line, root, value)
329
+ elsif directive == "else"
330
+ parse_else(parent, line, value)
331
+ elsif directive == "while"
332
+ raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
333
+ Tree::WhileNode.new(parse_script(value, :offset => offset), @options)
334
+ elsif directive == "if"
335
+ raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
336
+ Tree::IfNode.new(parse_script(value, :offset => offset), @options)
337
+ elsif directive == "debug"
338
+ raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
339
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.", @line + 1) unless line.children.empty?
340
+ offset = line.offset + line.text.index(value).to_i
341
+ Tree::DebugNode.new(parse_script(value, :offset => offset), @options)
342
+ else
343
+ Tree::DirectiveNode.new(line.text, @options)
344
+ end
345
+ end
346
+
347
+ def parse_for(line, root, text)
348
+ var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
349
+
350
+ if var.nil? # scan failed, try to figure out why for error message
351
+ if text !~ /^[^\s]+/
352
+ expected = "variable name"
353
+ elsif text !~ /^[^\s]+\s+from\s+.+/
354
+ expected = "'from <expr>'"
355
+ else
356
+ expected = "'to <expr>' or 'through <expr>'"
357
+ end
358
+ raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.", @line)
359
+ end
360
+ raise SyntaxError.new("Invalid variable \"#{var}\".", @line) unless var =~ Script::VALIDATE
361
+
362
+ parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
363
+ parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
364
+ Tree::ForNode.new(var[1..-1], parsed_from, parsed_to, to_name == 'to', @options)
365
+ end
366
+
367
+ def parse_else(parent, line, text)
368
+ previous = parent.last
369
+ raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
370
+
371
+ if text
372
+ if text !~ /^if\s+(.+)/
373
+ raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.", @line)
374
+ end
375
+ expr = parse_script($1, :offset => line.offset + line.text.index($1))
376
+ end
377
+
378
+ node = Tree::IfNode.new(expr, @options)
379
+ append_children(node, line.children, false)
380
+ previous.add_else node
381
+ nil
382
+ end
383
+
384
+ # parses out the arguments between the commas and cleans up the mixin arguments
385
+ # returns nil if it fails to parse, otherwise an array.
386
+ def parse_mixin_arguments(arg_string)
387
+ arg_string = arg_string.strip
388
+ return [] if arg_string.empty?
389
+ return nil unless (arg_string[0] == ?( && arg_string[-1] == ?))
390
+ arg_string = arg_string[1...-1]
391
+ arg_string.split(",", -1).map {|a| a.strip}
392
+ end
393
+
394
+ def parse_mixin_definition(line)
395
+ name, arg_string = line.text.scan(/^=\s*([^(]+)(.*)$/).first
396
+ args = parse_mixin_arguments(arg_string)
397
+ raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".", @line) if name.nil? || args.nil?
398
+ default_arg_found = false
399
+ required_arg_count = 0
400
+ args.map! do |arg|
401
+ raise SyntaxError.new("Mixin arguments can't be empty.", @line) if arg.empty? || arg == "!"
402
+ unless arg[0] == Script::VARIABLE_CHAR
403
+ raise SyntaxError.new("Mixin argument \"#{arg}\" must begin with an exclamation point (!).", @line)
404
+ end
405
+ arg, default = arg.split(/\s*=\s*/, 2)
406
+ required_arg_count += 1 unless default
407
+ default_arg_found ||= default
408
+ raise SyntaxError.new("Invalid variable \"#{arg}\".", @line) unless arg =~ Script::VALIDATE
409
+ raise SyntaxError.new("Required arguments must not follow optional arguments \"#{arg}\".", @line) if default_arg_found && !default
410
+ default = parse_script(default, :offset => line.offset + line.text.index(default)) if default
411
+ { :name => arg[1..-1], :default_value => default }
412
+ end
413
+ Tree::MixinDefNode.new(name, args, @options)
414
+ end
415
+
416
+ def parse_mixin_include(line, root)
417
+ name, arg_string = line.text.scan(/^\+\s*([^(]+)(.*)$/).first
418
+ args = parse_mixin_arguments(arg_string)
419
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.", @line + 1) unless line.children.empty?
420
+ raise SyntaxError.new("Invalid mixin include \"#{line.text}\".", @line) if name.nil? || args.nil?
421
+ args.each {|a| raise SyntaxError.new("Mixin arguments can't be empty.", @line) if a.empty?}
422
+
423
+ Tree::MixinNode.new(name, args.map {|s| parse_script(s, :offset => line.offset + line.text.index(s))}, @options)
424
+ end
425
+
426
+ def parse_script(script, options = {})
427
+ line = options[:line] || @line
428
+ offset = options[:offset] || 0
429
+ Script.parse(script, line, offset, @options[:filename])
430
+ end
431
+
432
+ def import_paths
433
+ paths = @options[:load_paths] || []
434
+ paths.unshift(File.dirname(@options[:filename])) if @options[:filename]
435
+ paths
436
+ end
437
+
438
+ def import(files)
439
+ files.split(/,\s*/).map do |filename|
440
+ engine = nil
441
+
442
+ begin
443
+ filename = self.class.find_file_to_import(filename, import_paths)
444
+ rescue Exception => e
445
+ raise SyntaxError.new(e.message, @line)
446
+ end
447
+
448
+ next Tree::DirectiveNode.new("@import url(#{filename})", @options) if filename =~ /\.css$/
449
+
450
+ File.open(filename) do |file|
451
+ new_options = @options.dup
452
+ new_options[:filename] = filename
453
+ engine = Sass::Engine.new(file.read, new_options)
454
+ end
455
+
456
+ begin
457
+ root = engine.render_to_tree
458
+ rescue Sass::SyntaxError => err
459
+ err.add_backtrace_entry(filename)
460
+ raise err
461
+ end
462
+ Tree::FileNode.new(filename, root.children, @options)
463
+ end.flatten
464
+ end
465
+
466
+ def self.find_file_to_import(filename, load_paths)
467
+ was_sass = false
468
+ original_filename = filename
469
+
470
+ if filename[-5..-1] == ".sass"
471
+ filename = filename[0...-5]
472
+ was_sass = true
473
+ elsif filename[-4..-1] == ".css"
474
+ return filename
475
+ end
476
+
477
+ new_filename = find_full_path("#{filename}.sass", load_paths)
478
+
479
+ return new_filename if new_filename
480
+ return filename + '.css' unless was_sass
481
+ raise SyntaxError.new("File to import not found or unreadable: #{original_filename}.", @line)
482
+ end
483
+
484
+ def self.find_full_path(filename, load_paths)
485
+ segments = filename.split(File::SEPARATOR)
486
+ segments.push "_#{segments.pop}"
487
+ partial_name = segments.join(File::SEPARATOR)
488
+ load_paths.each do |path|
489
+ [partial_name, filename].each do |name|
490
+ full_path = File.join(path, name)
491
+ if File.readable?(full_path)
492
+ return full_path
493
+ end
494
+ end
495
+ end
496
+ nil
497
+ end
498
+ end
499
+ end
@@ -0,0 +1,33 @@
1
+ module Sass
2
+ class Environment
3
+ attr_reader :parent
4
+
5
+ def initialize(parent = nil)
6
+ @vars = {}
7
+ @mixins = {}
8
+ @parent = parent
9
+ end
10
+
11
+ def self.inherited_hash(name)
12
+ class_eval <<RUBY, __FILE__, __LINE__ + 1
13
+ def #{name}(name)
14
+ @#{name}s[name] || @parent && @parent.#{name}(name)
15
+ end
16
+
17
+ def set_#{name}(name, value)
18
+ if @parent && @parent.#{name}(name)
19
+ @parent.set_#{name}(name, value)
20
+ else
21
+ @#{name}s[name] = value
22
+ end
23
+ end
24
+
25
+ def set_local_#{name}(name, value)
26
+ @#{name}s[name] = value
27
+ end
28
+ RUBY
29
+ end
30
+ inherited_hash :var
31
+ inherited_hash :mixin
32
+ end
33
+ end
data/lib/sass/error.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Sass
2
+ # Sass::SyntaxError encapsulates information about the exception,
3
+ # such as the line of the Sass template it was raised on
4
+ # and the Sass file that was being parsed (if applicable).
5
+ # It also provides a handy way to rescue only exceptions raised
6
+ # because of a faulty template.
7
+ class SyntaxError < StandardError
8
+ # The line of the Sass template on which the exception was thrown.
9
+ attr_accessor :sass_line
10
+
11
+ # The name of the file that was being parsed when the exception was raised.
12
+ # This will be nil unless Sass is being used as an ActionView plugin.
13
+ attr_reader :sass_filename
14
+
15
+ # Creates a new SyntaxError.
16
+ # +lineno+ should be the line of the Sass template on which the error occurred.
17
+ def initialize(msg, lineno = nil)
18
+ @message = msg
19
+ @sass_line = lineno
20
+ end
21
+
22
+ # Adds a properly formatted entry to the exception's backtrace.
23
+ # +filename+ should be the file in which the error occurred,
24
+ # if applicable (defaults to "(sass)").
25
+ def add_backtrace_entry(filename) # :nodoc:
26
+ @sass_filename ||= filename
27
+ self.backtrace ||= []
28
+ self.backtrace.unshift "#{@sass_filename || '(sass)'}:#{@sass_line}"
29
+ end
30
+
31
+ def to_s # :nodoc:
32
+ @message
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,56 @@
1
+ unless defined?(Sass::MERB_LOADED)
2
+ Sass::MERB_LOADED = true
3
+
4
+ version = Merb::VERSION.split('.').map { |n| n.to_i }
5
+ if version[0] <= 0 && version[1] < 5
6
+ root = MERB_ROOT
7
+ env = MERB_ENV
8
+ else
9
+ root = Merb.root.to_s
10
+ env = Merb.environment
11
+ end
12
+
13
+ Sass::Plugin.options.merge!(:template_location => root + '/public/stylesheets/sass',
14
+ :css_location => root + '/public/stylesheets',
15
+ :always_check => env != "production",
16
+ :full_exception => env != "production")
17
+ config = Merb::Plugins.config[:sass] || Merb::Plugins.config["sass"] || {}
18
+
19
+ if defined? config.symbolize_keys!
20
+ config.symbolize_keys!
21
+ end
22
+
23
+ Sass::Plugin.options.merge!(config)
24
+
25
+ if version[0] > 0 || version[1] >= 9
26
+
27
+ class Merb::Rack::Application # :nodoc:
28
+ def call_with_sass(env)
29
+ if !Sass::Plugin.checked_for_updates ||
30
+ Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
31
+ Sass::Plugin.update_stylesheets
32
+ end
33
+
34
+ call_without_sass(env)
35
+ end
36
+ alias_method :call_without_sass, :call
37
+ alias_method :call, :call_with_sass
38
+ end
39
+
40
+ else
41
+
42
+ class MerbHandler # :nodoc:
43
+ def process_with_sass(request, response)
44
+ if !Sass::Plugin.checked_for_updates ||
45
+ Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
46
+ Sass::Plugin.update_stylesheets
47
+ end
48
+
49
+ process_without_sass(request, response)
50
+ end
51
+ alias_method :process_without_sass, :process
52
+ alias_method :process, :process_with_sass
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ unless defined?(Sass::RAILS_LOADED)
2
+ Sass::RAILS_LOADED = true
3
+
4
+ Sass::Plugin.options.merge!(:template_location => RAILS_ROOT + '/public/stylesheets/sass',
5
+ :css_location => RAILS_ROOT + '/public/stylesheets',
6
+ :always_check => RAILS_ENV != "production",
7
+ :full_exception => RAILS_ENV != "production")
8
+
9
+ # :stopdoc:
10
+ module ActionController
11
+ class Base
12
+ alias_method :sass_old_process, :process
13
+ def process(*args)
14
+ if !Sass::Plugin.checked_for_updates ||
15
+ Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
16
+ Sass::Plugin.update_stylesheets
17
+ end
18
+
19
+ sass_old_process(*args)
20
+ end
21
+ end
22
+ end
23
+ # :startdoc:
24
+ end