haml-edge 2.1.1

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