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.
Files changed (190) hide show
  1. data/.yardopts +5 -0
  2. data/CONTRIBUTING +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +347 -0
  5. data/REVISION +1 -0
  6. data/Rakefile +371 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/css2sass +7 -0
  10. data/bin/haml +9 -0
  11. data/bin/html2haml +7 -0
  12. data/bin/sass +8 -0
  13. data/extra/haml-mode.el +663 -0
  14. data/extra/sass-mode.el +205 -0
  15. data/extra/update_watch.rb +13 -0
  16. data/init.rb +8 -0
  17. data/lib/haml.rb +40 -0
  18. data/lib/haml/buffer.rb +307 -0
  19. data/lib/haml/engine.rb +301 -0
  20. data/lib/haml/error.rb +22 -0
  21. data/lib/haml/exec.rb +470 -0
  22. data/lib/haml/filters.rb +341 -0
  23. data/lib/haml/helpers.rb +560 -0
  24. data/lib/haml/helpers/action_view_extensions.rb +40 -0
  25. data/lib/haml/helpers/action_view_mods.rb +176 -0
  26. data/lib/haml/herb.rb +96 -0
  27. data/lib/haml/html.rb +308 -0
  28. data/lib/haml/precompiler.rb +997 -0
  29. data/lib/haml/shared.rb +78 -0
  30. data/lib/haml/template.rb +51 -0
  31. data/lib/haml/template/patch.rb +58 -0
  32. data/lib/haml/template/plugin.rb +71 -0
  33. data/lib/haml/util.rb +244 -0
  34. data/lib/haml/version.rb +64 -0
  35. data/lib/sass.rb +24 -0
  36. data/lib/sass/css.rb +423 -0
  37. data/lib/sass/engine.rb +491 -0
  38. data/lib/sass/environment.rb +79 -0
  39. data/lib/sass/error.rb +162 -0
  40. data/lib/sass/files.rb +133 -0
  41. data/lib/sass/plugin.rb +170 -0
  42. data/lib/sass/plugin/merb.rb +57 -0
  43. data/lib/sass/plugin/rails.rb +23 -0
  44. data/lib/sass/repl.rb +58 -0
  45. data/lib/sass/script.rb +55 -0
  46. data/lib/sass/script/bool.rb +17 -0
  47. data/lib/sass/script/color.rb +183 -0
  48. data/lib/sass/script/funcall.rb +50 -0
  49. data/lib/sass/script/functions.rb +199 -0
  50. data/lib/sass/script/lexer.rb +191 -0
  51. data/lib/sass/script/literal.rb +177 -0
  52. data/lib/sass/script/node.rb +14 -0
  53. data/lib/sass/script/number.rb +381 -0
  54. data/lib/sass/script/operation.rb +45 -0
  55. data/lib/sass/script/parser.rb +222 -0
  56. data/lib/sass/script/string.rb +12 -0
  57. data/lib/sass/script/unary_operation.rb +34 -0
  58. data/lib/sass/script/variable.rb +31 -0
  59. data/lib/sass/tree/comment_node.rb +84 -0
  60. data/lib/sass/tree/debug_node.rb +30 -0
  61. data/lib/sass/tree/directive_node.rb +70 -0
  62. data/lib/sass/tree/for_node.rb +48 -0
  63. data/lib/sass/tree/if_node.rb +54 -0
  64. data/lib/sass/tree/import_node.rb +69 -0
  65. data/lib/sass/tree/mixin_def_node.rb +29 -0
  66. data/lib/sass/tree/mixin_node.rb +48 -0
  67. data/lib/sass/tree/node.rb +252 -0
  68. data/lib/sass/tree/prop_node.rb +106 -0
  69. data/lib/sass/tree/root_node.rb +56 -0
  70. data/lib/sass/tree/rule_node.rb +220 -0
  71. data/lib/sass/tree/variable_node.rb +34 -0
  72. data/lib/sass/tree/while_node.rb +31 -0
  73. data/rails/init.rb +1 -0
  74. data/test/benchmark.rb +99 -0
  75. data/test/haml/engine_test.rb +1129 -0
  76. data/test/haml/helper_test.rb +282 -0
  77. data/test/haml/html2haml_test.rb +258 -0
  78. data/test/haml/markaby/standard.mab +52 -0
  79. data/test/haml/mocks/article.rb +6 -0
  80. data/test/haml/results/content_for_layout.xhtml +12 -0
  81. data/test/haml/results/eval_suppressed.xhtml +9 -0
  82. data/test/haml/results/filters.xhtml +62 -0
  83. data/test/haml/results/helpers.xhtml +93 -0
  84. data/test/haml/results/helpful.xhtml +10 -0
  85. data/test/haml/results/just_stuff.xhtml +68 -0
  86. data/test/haml/results/list.xhtml +12 -0
  87. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  88. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  89. data/test/haml/results/original_engine.xhtml +20 -0
  90. data/test/haml/results/partial_layout.xhtml +5 -0
  91. data/test/haml/results/partials.xhtml +21 -0
  92. data/test/haml/results/render_layout.xhtml +3 -0
  93. data/test/haml/results/silent_script.xhtml +74 -0
  94. data/test/haml/results/standard.xhtml +162 -0
  95. data/test/haml/results/tag_parsing.xhtml +23 -0
  96. data/test/haml/results/very_basic.xhtml +5 -0
  97. data/test/haml/results/whitespace_handling.xhtml +89 -0
  98. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  99. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  100. data/test/haml/rhtml/action_view.rhtml +62 -0
  101. data/test/haml/rhtml/standard.rhtml +54 -0
  102. data/test/haml/spec_test.rb +44 -0
  103. data/test/haml/template_test.rb +217 -0
  104. data/test/haml/templates/_av_partial_1.haml +9 -0
  105. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  106. data/test/haml/templates/_av_partial_2.haml +5 -0
  107. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  108. data/test/haml/templates/_layout.erb +3 -0
  109. data/test/haml/templates/_layout_for_partial.haml +3 -0
  110. data/test/haml/templates/_partial.haml +8 -0
  111. data/test/haml/templates/_text_area.haml +3 -0
  112. data/test/haml/templates/action_view.haml +47 -0
  113. data/test/haml/templates/action_view_ugly.haml +47 -0
  114. data/test/haml/templates/breakage.haml +8 -0
  115. data/test/haml/templates/content_for_layout.haml +8 -0
  116. data/test/haml/templates/eval_suppressed.haml +11 -0
  117. data/test/haml/templates/filters.haml +66 -0
  118. data/test/haml/templates/helpers.haml +95 -0
  119. data/test/haml/templates/helpful.haml +11 -0
  120. data/test/haml/templates/just_stuff.haml +83 -0
  121. data/test/haml/templates/list.haml +12 -0
  122. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  123. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  124. data/test/haml/templates/original_engine.haml +17 -0
  125. data/test/haml/templates/partial_layout.haml +3 -0
  126. data/test/haml/templates/partialize.haml +1 -0
  127. data/test/haml/templates/partials.haml +12 -0
  128. data/test/haml/templates/render_layout.haml +2 -0
  129. data/test/haml/templates/silent_script.haml +40 -0
  130. data/test/haml/templates/standard.haml +42 -0
  131. data/test/haml/templates/standard_ugly.haml +42 -0
  132. data/test/haml/templates/tag_parsing.haml +21 -0
  133. data/test/haml/templates/very_basic.haml +4 -0
  134. data/test/haml/templates/whitespace_handling.haml +87 -0
  135. data/test/haml/util_test.rb +92 -0
  136. data/test/linked_rails.rb +12 -0
  137. data/test/sass/css2sass_test.rb +294 -0
  138. data/test/sass/engine_test.rb +956 -0
  139. data/test/sass/functions_test.rb +126 -0
  140. data/test/sass/more_results/more1.css +9 -0
  141. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  142. data/test/sass/more_results/more_import.css +29 -0
  143. data/test/sass/more_templates/_more_partial.sass +2 -0
  144. data/test/sass/more_templates/more1.sass +23 -0
  145. data/test/sass/more_templates/more_import.sass +11 -0
  146. data/test/sass/plugin_test.rb +229 -0
  147. data/test/sass/results/alt.css +4 -0
  148. data/test/sass/results/basic.css +9 -0
  149. data/test/sass/results/compact.css +5 -0
  150. data/test/sass/results/complex.css +87 -0
  151. data/test/sass/results/compressed.css +1 -0
  152. data/test/sass/results/expanded.css +19 -0
  153. data/test/sass/results/import.css +29 -0
  154. data/test/sass/results/line_numbers.css +49 -0
  155. data/test/sass/results/mixins.css +95 -0
  156. data/test/sass/results/multiline.css +24 -0
  157. data/test/sass/results/nested.css +22 -0
  158. data/test/sass/results/parent_ref.css +13 -0
  159. data/test/sass/results/script.css +16 -0
  160. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  161. data/test/sass/results/subdir/subdir.css +3 -0
  162. data/test/sass/results/units.css +11 -0
  163. data/test/sass/script_test.rb +261 -0
  164. data/test/sass/templates/_partial.sass +2 -0
  165. data/test/sass/templates/alt.sass +16 -0
  166. data/test/sass/templates/basic.sass +23 -0
  167. data/test/sass/templates/bork1.sass +2 -0
  168. data/test/sass/templates/bork2.sass +2 -0
  169. data/test/sass/templates/bork3.sass +2 -0
  170. data/test/sass/templates/compact.sass +17 -0
  171. data/test/sass/templates/complex.sass +307 -0
  172. data/test/sass/templates/compressed.sass +15 -0
  173. data/test/sass/templates/expanded.sass +17 -0
  174. data/test/sass/templates/import.sass +11 -0
  175. data/test/sass/templates/importee.sass +19 -0
  176. data/test/sass/templates/line_numbers.sass +13 -0
  177. data/test/sass/templates/mixins.sass +76 -0
  178. data/test/sass/templates/multiline.sass +20 -0
  179. data/test/sass/templates/nested.sass +25 -0
  180. data/test/sass/templates/nested_bork1.sass +2 -0
  181. data/test/sass/templates/nested_bork2.sass +2 -0
  182. data/test/sass/templates/nested_bork3.sass +2 -0
  183. data/test/sass/templates/parent_ref.sass +25 -0
  184. data/test/sass/templates/script.sass +101 -0
  185. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  186. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  187. data/test/sass/templates/subdir/subdir.sass +6 -0
  188. data/test/sass/templates/units.sass +11 -0
  189. data/test/test_helper.rb +44 -0
  190. metadata +298 -0
@@ -0,0 +1,301 @@
1
+ require 'haml/helpers'
2
+ require 'haml/buffer'
3
+ require 'haml/precompiler'
4
+ require 'haml/filters'
5
+ require 'haml/error'
6
+
7
+ module Haml
8
+ # This is the frontend for using Haml programmatically.
9
+ # It can be directly used by the user by creating a
10
+ # new instance and calling \{#render} to render the template.
11
+ # For example:
12
+ #
13
+ # template = File.read('templates/really_cool_template.haml')
14
+ # haml_engine = Haml::Engine.new(template)
15
+ # output = haml_engine.render
16
+ # puts output
17
+ class Engine
18
+ include Precompiler
19
+
20
+ # The options hash.
21
+ # See {file:HAML_REFERENCE.md#haml_options the Haml options documentation}.
22
+ #
23
+ # @return [Hash<Symbol, Object>]
24
+ attr_accessor :options
25
+
26
+ # The indentation used in the Haml document,
27
+ # or `nil` if the indentation is ambiguous
28
+ # (for example, for a single-level document).
29
+ #
30
+ # @return [String]
31
+ attr_accessor :indentation
32
+
33
+ # @return [Boolean] Whether or not the format is XHTML.
34
+ def xhtml?
35
+ not html?
36
+ end
37
+
38
+ # @return [Boolean] Whether or not the format is any flavor of HTML.
39
+ def html?
40
+ html4? or html5?
41
+ end
42
+
43
+ # @return [Boolean] Whether or not the format is HTML4.
44
+ def html4?
45
+ @options[:format] == :html4
46
+ end
47
+
48
+ # @return [Boolean] Whether or not the format is HTML5.
49
+ def html5?
50
+ @options[:format] == :html5
51
+ end
52
+
53
+ # The source code that is evaluated to produce the Haml document.
54
+ #
55
+ # In Ruby 1.9, this is automatically converted to the correct encoding
56
+ # (see {file:HAML_REFERENCE.md#encoding-option the `:encoding` option}).
57
+ #
58
+ # @return [String]
59
+ def precompiled
60
+ return @precompiled if ruby1_8?
61
+ return @precompiled.encode(Encoding.find(@options[:encoding]))
62
+ end
63
+
64
+ # Precompiles the Haml template.
65
+ #
66
+ # @param template [String] The Haml template
67
+ # @param options [Hash<Symbol, Object>] An options hash;
68
+ # see {file:HAML_REFERENCE.md#haml_options the Haml options documentation}
69
+ # @raise [Haml::Error] if there's a Haml syntax error in the template
70
+ def initialize(template, options = {})
71
+ @options = {
72
+ :suppress_eval => false,
73
+ :attr_wrapper => "'",
74
+
75
+ # Don't forget to update the docs in lib/haml.rb if you update these
76
+ :autoclose => %w[meta img link br hr input area param col base],
77
+ :preserve => %w[textarea pre],
78
+
79
+ :filename => '(haml)',
80
+ :line => 1,
81
+ :ugly => false,
82
+ :format => :xhtml,
83
+ :escape_html => false,
84
+ }
85
+ unless ruby1_8?
86
+ @options[:encoding] = Encoding.default_internal || "utf-8"
87
+ end
88
+ @options.merge! options
89
+ @index = 0
90
+
91
+ unless [:xhtml, :html4, :html5].include?(@options[:format])
92
+ raise Haml::Error, "Invalid format #{@options[:format].inspect}"
93
+ end
94
+
95
+ if @options[:encoding] && @options[:encoding].is_a?(Encoding)
96
+ @options[:encoding] = @options[:encoding].name
97
+ end
98
+
99
+ check_encoding(template) {|msg, line| raise Haml::Error.new(msg, line)}
100
+
101
+ # :eod is a special end-of-document marker
102
+ @template = (template.rstrip).split(/\r\n|\r|\n/) + [:eod, :eod]
103
+ @template_index = 0
104
+ @to_close_stack = []
105
+ @output_tabs = 0
106
+ @template_tabs = 0
107
+ @flat = false
108
+ @newlines = 0
109
+ @precompiled = ''
110
+ @to_merge = []
111
+ @tab_change = 0
112
+ @temp_count = 0
113
+
114
+ precompile
115
+ rescue Haml::Error => e
116
+ if @index || e.line
117
+ e.backtrace.unshift "#{@options[:filename]}:#{(e.line ? e.line + 1 : @index) + @options[:line] - 1}"
118
+ end
119
+ raise
120
+ end
121
+
122
+ # Processes the template and returns the result as a string.
123
+ #
124
+ # `scope` is the context in which the template is evaluated.
125
+ # If it's a `Binding` or `Proc` object,
126
+ # Haml uses it as the second argument to `Kernel#eval`;
127
+ # otherwise, Haml just uses its `#instance_eval` context.
128
+ #
129
+ # Note that Haml modifies the evaluation context
130
+ # (either the scope object or the `self` object of the scope binding).
131
+ # It extends {Haml::Helpers}, and various instance variables are set
132
+ # (all prefixed with `haml_`).
133
+ # For example:
134
+ #
135
+ # s = "foobar"
136
+ # Haml::Engine.new("%p= upcase").render(s) #=> "<p>FOOBAR</p>"
137
+ #
138
+ # # s now extends Haml::Helpers
139
+ # s.responds_to?(:html_attrs) #=> true
140
+ #
141
+ # `locals` is a hash of local variables to make available to the template.
142
+ # For example:
143
+ #
144
+ # Haml::Engine.new("%p= foo").render(Object.new, :foo => "Hello, world!") #=> "<p>Hello, world!</p>"
145
+ #
146
+ # If a block is passed to render,
147
+ # that block is run when `yield` is called
148
+ # within the template.
149
+ #
150
+ # Due to some Ruby quirks,
151
+ # if `scope` is a `Binding` or `Proc` object and a block is given,
152
+ # the evaluation context may not be quite what the user expects.
153
+ # In particular, it's equivalent to passing `eval("self", scope)` as `scope`.
154
+ # This won't have an effect in most cases,
155
+ # but if you're relying on local variables defined in the context of `scope`,
156
+ # they won't work.
157
+ #
158
+ # @param scope [Binding, Proc, Object] The context in which the template is evaluated
159
+ # @param locals [Hash<Symbol, Object>] Local variables that will be made available
160
+ # to the template
161
+ # @param block [#to_proc] A block that can be yielded to within the template
162
+ # @return [String] The rendered template
163
+ def render(scope = Object.new, locals = {}, &block)
164
+ buffer = Haml::Buffer.new(scope.instance_variable_get('@haml_buffer'), options_for_buffer)
165
+
166
+ if scope.is_a?(Binding) || scope.is_a?(Proc)
167
+ scope_object = eval("self", scope)
168
+ scope = scope_object.instance_eval{binding} if block_given?
169
+ else
170
+ scope_object = scope
171
+ scope = scope_object.instance_eval{binding}
172
+ end
173
+
174
+ set_locals(locals.merge(:_hamlout => buffer, :_erbout => buffer.buffer), scope, scope_object)
175
+
176
+ scope_object.instance_eval do
177
+ extend Haml::Helpers
178
+ @haml_buffer = buffer
179
+ end
180
+
181
+ eval(precompiled, scope, @options[:filename], @options[:line])
182
+
183
+ # Get rid of the current buffer
184
+ scope_object.instance_eval do
185
+ @haml_buffer = buffer.upper
186
+ end
187
+
188
+ buffer.buffer
189
+ end
190
+ alias_method :to_html, :render
191
+
192
+ # Returns a proc that, when called,
193
+ # renders the template and returns the result as a string.
194
+ #
195
+ # `scope` works the same as it does for render.
196
+ #
197
+ # The first argument of the returned proc is a hash of local variable names to values.
198
+ # However, due to an unfortunate Ruby quirk,
199
+ # the local variables which can be assigned must be pre-declared.
200
+ # This is done with the `local_names` argument.
201
+ # For example:
202
+ #
203
+ # # This works
204
+ # Haml::Engine.new("%p= foo").render_proc(Object.new, :foo).call :foo => "Hello!"
205
+ # #=> "<p>Hello!</p>"
206
+ #
207
+ # # This doesn't
208
+ # Haml::Engine.new("%p= foo").render_proc.call :foo => "Hello!"
209
+ # #=> NameError: undefined local variable or method `foo'
210
+ #
211
+ # The proc doesn't take a block; any yields in the template will fail.
212
+ #
213
+ # @param scope [Binding, Proc, Object] The context in which the template is evaluated
214
+ # @param local_names [Array<Symbol>] The names of the locals that can be passed to the proc
215
+ # @return [Proc] The proc that will run the template
216
+ def render_proc(scope = Object.new, *local_names)
217
+ if scope.is_a?(Binding) || scope.is_a?(Proc)
218
+ scope_object = eval("self", scope)
219
+ else
220
+ scope_object = scope
221
+ scope = scope_object.instance_eval{binding}
222
+ end
223
+
224
+ eval("Proc.new { |*_haml_locals| _haml_locals = _haml_locals[0] || {};" +
225
+ precompiled_with_ambles(local_names) + "}\n", scope, @options[:filename], @options[:line])
226
+ end
227
+
228
+ # Defines a method on `object` with the given name
229
+ # that renders the template and returns the result as a string.
230
+ #
231
+ # If `object` is a class or module,
232
+ # the method will instead by defined as an instance method.
233
+ # For example:
234
+ #
235
+ # t = Time.now
236
+ # Haml::Engine.new("%p\n Today's date is\n .date= self.to_s").def_method(t, :render)
237
+ # t.render #=> "<p>\n Today's date is\n <div class='date'>Fri Nov 23 18:28:29 -0800 2007</div>\n</p>\n"
238
+ #
239
+ # Haml::Engine.new(".upcased= upcase").def_method(String, :upcased_div)
240
+ # "foobar".upcased_div #=> "<div class='upcased'>FOOBAR</div>\n"
241
+ #
242
+ # The first argument of the defined method is a hash of local variable names to values.
243
+ # However, due to an unfortunate Ruby quirk,
244
+ # the local variables which can be assigned must be pre-declared.
245
+ # This is done with the `local_names` argument.
246
+ # For example:
247
+ #
248
+ # # This works
249
+ # obj = Object.new
250
+ # Haml::Engine.new("%p= foo").def_method(obj, :render, :foo)
251
+ # obj.render(:foo => "Hello!") #=> "<p>Hello!</p>"
252
+ #
253
+ # # This doesn't
254
+ # obj = Object.new
255
+ # Haml::Engine.new("%p= foo").def_method(obj, :render)
256
+ # obj.render(:foo => "Hello!") #=> NameError: undefined local variable or method `foo'
257
+ #
258
+ # Note that Haml modifies the evaluation context
259
+ # (either the scope object or the `self` object of the scope binding).
260
+ # It extends {Haml::Helpers}, and various instance variables are set
261
+ # (all prefixed with `haml_`).
262
+ #
263
+ # @param object [Object, Module] The object on which to define the method
264
+ # @param name [String, Symbol] The name of the method to define
265
+ # @param local_names [Array<Symbol>] The names of the locals that can be passed to the proc
266
+ def def_method(object, name, *local_names)
267
+ method = object.is_a?(Module) ? :module_eval : :instance_eval
268
+
269
+ object.send(method, "def #{name}(_haml_locals = {}); #{precompiled_with_ambles(local_names)}; end",
270
+ @options[:filename], @options[:line])
271
+ end
272
+
273
+ protected
274
+
275
+ # Returns a subset of \{#options}: those that {Haml::Buffer} cares about.
276
+ # All of the values here are such that when `#inspect` is called on the hash,
277
+ # it can be `Kernel#eval`ed to get the same result back.
278
+ #
279
+ # See {file:HAML_REFERENCE.md#haml_options the Haml options documentation}.
280
+ #
281
+ # @return [Hash<Symbol, Object>] The options hash
282
+ def options_for_buffer
283
+ {
284
+ :autoclose => @options[:autoclose],
285
+ :preserve => @options[:preserve],
286
+ :attr_wrapper => @options[:attr_wrapper],
287
+ :ugly => @options[:ugly],
288
+ :format => @options[:format],
289
+ :encoding => @options[:encoding],
290
+ }
291
+ end
292
+
293
+ private
294
+
295
+ def set_locals(locals, scope, scope_object)
296
+ scope_object.send(:instance_variable_set, '@_haml_locals', locals)
297
+ set_locals = locals.keys.map { |k| "#{k} = @_haml_locals[#{k.inspect}]" }.join("\n")
298
+ eval(set_locals, scope)
299
+ end
300
+ end
301
+ end
@@ -0,0 +1,22 @@
1
+ module Haml
2
+ # An exception raised by Haml code.
3
+ class Error < StandardError
4
+ # The line of the template on which the error occurred.
5
+ #
6
+ # @return [Fixnum]
7
+ attr_reader :line
8
+
9
+ # @param message [String] The error message
10
+ # @param line [Fixnum] See \{#line}
11
+ def initialize(message = nil, line = nil)
12
+ super(message)
13
+ @line = line
14
+ end
15
+ end
16
+
17
+ # SyntaxError is the type of exception raised when Haml encounters an
18
+ # ill-formatted document.
19
+ # It's not particularly interesting,
20
+ # except in that it's a subclass of {Haml::Error}.
21
+ class SyntaxError < Haml::Error; end
22
+ end
@@ -0,0 +1,470 @@
1
+ require 'optparse'
2
+ require 'fileutils'
3
+
4
+ module Haml
5
+ # This module handles the various Haml executables (`haml`, `sass`, `css2sass`, etc).
6
+ module Exec
7
+ # An abstract class that encapsulates the executable code for all three executables.
8
+ class Generic
9
+ # @param args [Array<String>] The command-line arguments
10
+ def initialize(args)
11
+ @args = args
12
+ @options = {}
13
+ end
14
+
15
+ # Parses the command-line arguments and runs the executable.
16
+ # Calls `Kernel#exit` at the end, so it never returns.
17
+ def parse!
18
+ begin
19
+ @opts = OptionParser.new(&method(:set_opts))
20
+ @opts.parse!(@args)
21
+
22
+ process_result
23
+
24
+ @options
25
+ rescue Exception => e
26
+ raise e if @options[:trace] || e.is_a?(SystemExit)
27
+
28
+ $stderr.puts e.message
29
+ exit 1
30
+ end
31
+ exit 0
32
+ end
33
+
34
+ # @return [String] A description of the executable
35
+ def to_s
36
+ @opts.to_s
37
+ end
38
+
39
+ protected
40
+
41
+ # Finds the line of the source template
42
+ # on which an exception was raised.
43
+ #
44
+ # @param exception [Exception] The exception
45
+ # @return [String] The line number
46
+ def get_line(exception)
47
+ # SyntaxErrors have weird line reporting
48
+ # when there's trailing whitespace,
49
+ # which there is for Haml documents.
50
+ return exception.message.scan(/:(\d+)/).first.first if exception.is_a?(::SyntaxError)
51
+ exception.backtrace[0].scan(/:(\d+)/).first.first
52
+ end
53
+
54
+ # Tells optparse how to parse the arguments
55
+ # available for all executables.
56
+ #
57
+ # This is meant to be overridden by subclasses
58
+ # so they can add their own options.
59
+ #
60
+ # @param opts [OptionParser]
61
+ def set_opts(opts)
62
+ opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
63
+ @options[:input] = $stdin
64
+ end
65
+
66
+ opts.on('--trace', :NONE, 'Show a full traceback on error') do
67
+ @options[:trace] = true
68
+ end
69
+
70
+ opts.on_tail("-?", "-h", "--help", "Show this message") do
71
+ puts opts
72
+ exit
73
+ end
74
+
75
+ opts.on_tail("-v", "--version", "Print version") do
76
+ puts("Haml/Sass #{::Haml.version[:string]}")
77
+ exit
78
+ end
79
+ end
80
+
81
+ # Processes the options set by the command-line arguments.
82
+ # In particular, sets `@options[:input]` and `@options[:output]`
83
+ # to appropriate IO streams.
84
+ #
85
+ # This is meant to be overridden by subclasses
86
+ # so they can run their respective programs.
87
+ def process_result
88
+ input, output = @options[:input], @options[:output]
89
+ input_file, output_file = if input
90
+ [nil, open_file(@args[0], 'w')]
91
+ else
92
+ @options[:filename] = @args[0]
93
+ [open_file(@args[0]), open_file(@args[1], 'w')]
94
+ end
95
+
96
+ input ||= input_file
97
+ output ||= output_file
98
+ input ||= $stdin
99
+ output ||= $stdout
100
+
101
+ @options[:input], @options[:output] = input, output
102
+ end
103
+
104
+ private
105
+
106
+ def open_file(filename, flag = 'r')
107
+ return if filename.nil?
108
+ File.open(filename, flag)
109
+ end
110
+ end
111
+
112
+ # An abstrac class that encapsulates the code
113
+ # specific to the `haml` and `sass` executables.
114
+ class HamlSass < Generic
115
+ # @param args [Array<String>] The command-line arguments
116
+ def initialize(args)
117
+ super
118
+ @options[:for_engine] = {}
119
+ end
120
+
121
+ protected
122
+
123
+ # Tells optparse how to parse the arguments
124
+ # available for the `haml` and `sass` executables.
125
+ #
126
+ # This is meant to be overridden by subclasses
127
+ # so they can add their own options.
128
+ #
129
+ # @param opts [OptionParser]
130
+ def set_opts(opts)
131
+ opts.banner = <<END
132
+ Usage: #{@name.downcase} [options] [INPUT] [OUTPUT]
133
+
134
+ Description:
135
+ Uses the #{@name} engine to parse the specified template
136
+ and outputs the result to the specified file.
137
+
138
+ Options:
139
+ END
140
+
141
+ opts.on('--rails RAILS_DIR', "Install Haml and Sass from the Gem to a Rails project") do |dir|
142
+ original_dir = dir
143
+
144
+ dir = File.join(dir, 'vendor', 'plugins')
145
+
146
+ unless File.exists?(dir)
147
+ puts "Directory #{dir} doesn't exist"
148
+ exit
149
+ end
150
+
151
+ dir = File.join(dir, 'haml')
152
+
153
+ if File.exists?(dir)
154
+ print "Directory #{dir} already exists, overwrite [y/N]? "
155
+ exit if gets !~ /y/i
156
+ FileUtils.rm_rf(dir)
157
+ end
158
+
159
+ begin
160
+ Dir.mkdir(dir)
161
+ rescue SystemCallError
162
+ puts "Cannot create #{dir}"
163
+ exit
164
+ end
165
+
166
+ File.open(File.join(dir, 'init.rb'), 'w') do |file|
167
+ file << File.read(File.dirname(__FILE__) + "/../../init.rb")
168
+ end
169
+
170
+ puts "Haml plugin added to #{original_dir}"
171
+ exit
172
+ end
173
+
174
+ opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
175
+ require 'stringio'
176
+ @options[:check_syntax] = true
177
+ @options[:output] = StringIO.new
178
+ end
179
+
180
+ super
181
+ end
182
+
183
+ # Processes the options set by the command-line arguments.
184
+ # In particular, sets `@options[:for_engine][:filename]` to the input filename
185
+ # and requires the appropriate file.
186
+ #
187
+ # This is meant to be overridden by subclasses
188
+ # so they can run their respective programs.
189
+ def process_result
190
+ super
191
+ @options[:for_engine][:filename] = @options[:filename] if @options[:filename]
192
+ require File.dirname(__FILE__) + "/../#{@name.downcase}"
193
+ end
194
+ end
195
+
196
+ # The `sass` executable.
197
+ class Sass < HamlSass
198
+ # @param args [Array<String>] The command-line arguments
199
+ def initialize(args)
200
+ super
201
+ @name = "Sass"
202
+ @options[:for_engine][:load_paths] = ['.'] + (ENV['SASSPATH'] || '').split(File::PATH_SEPARATOR)
203
+ end
204
+
205
+ protected
206
+
207
+ # Tells optparse how to parse the arguments.
208
+ #
209
+ # @param opts [OptionParser]
210
+ def set_opts(opts)
211
+ super
212
+
213
+ opts.on('-t', '--style NAME',
214
+ 'Output style. Can be nested (default), compact, compressed, or expanded.') do |name|
215
+ @options[:for_engine][:style] = name.to_sym
216
+ end
217
+ opts.on('-l', '--line-comments',
218
+ 'Line Comments. Emit comments in the generated CSS indicating the corresponding sass line.') do
219
+ @options[:for_engine][:line_comments] = true
220
+ end
221
+ opts.on('-i', '--interactive',
222
+ 'Run an interactive SassScript shell.') do
223
+ @options[:interactive] = true
224
+ end
225
+ opts.on('-I', '--load-path PATH', 'Add a sass import path.') do |path|
226
+ @options[:for_engine][:load_paths] << path
227
+ end
228
+ opts.on('--cache-location', 'The path to put cached Sass files. Defaults to .sass-cache.') do |loc|
229
+ @options[:for_engine][:cache_location] = path
230
+ end
231
+ opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
232
+ @options[:for_engine][:cache] = false
233
+ end
234
+ end
235
+
236
+ # Processes the options set by the command-line arguments,
237
+ # and runs the Sass compiler appropriately.
238
+ def process_result
239
+ if @options[:interactive]
240
+ require 'sass'
241
+ require 'sass/repl'
242
+ ::Sass::Repl.new(@options).run
243
+ return
244
+ end
245
+
246
+ super
247
+ input = @options[:input]
248
+ output = @options[:output]
249
+
250
+ tree =
251
+ if input.is_a?(File) && !@options[:check_syntax]
252
+ ::Sass::Files.tree_for(input.path, @options[:for_engine])
253
+ else
254
+ # We don't need to do any special handling of @options[:check_syntax] here,
255
+ # because the Sass syntax checking happens alongside evaluation
256
+ # and evaluation doesn't actually evaluate any code anyway.
257
+ ::Sass::Engine.new(input.read(), @options[:for_engine]).to_tree
258
+ end
259
+
260
+ input.close() if input.is_a?(File)
261
+
262
+ output.write(tree.render)
263
+ output.close() if output.is_a? File
264
+ rescue ::Sass::SyntaxError => e
265
+ raise e if @options[:trace]
266
+ raise e.sass_backtrace_str("standard input")
267
+ end
268
+ end
269
+
270
+ # The `haml` executable.
271
+ class Haml < HamlSass
272
+ # @param args [Array<String>] The command-line arguments
273
+ def initialize(args)
274
+ super
275
+ @name = "Haml"
276
+ @options[:requires] = []
277
+ @options[:load_paths] = []
278
+ end
279
+
280
+ # Tells optparse how to parse the arguments.
281
+ #
282
+ # @param opts [OptionParser]
283
+ def set_opts(opts)
284
+ super
285
+
286
+ opts.on('-t', '--style NAME',
287
+ 'Output style. Can be indented (default) or ugly.') do |name|
288
+ @options[:for_engine][:ugly] = true if name.to_sym == :ugly
289
+ end
290
+
291
+ opts.on('-f', '--format NAME',
292
+ 'Output format. Can be xhtml (default), html4, or html5.') do |name|
293
+ @options[:for_engine][:format] = name.to_sym
294
+ end
295
+
296
+ opts.on('-e', '--escape-html',
297
+ 'Escape HTML characters (like ampersands and angle brackets) by default.') do
298
+ @options[:for_engine][:escape_html] = true
299
+ end
300
+
301
+ opts.on('-q', '--double-quote-attributes',
302
+ 'Set attribute wrapper to double-quotes (default is single).') do
303
+ @options[:for_engine][:attr_wrapper] = '"'
304
+ end
305
+
306
+ opts.on('-r', '--require FILE', "Same as 'ruby -r'.") do |file|
307
+ @options[:requires] << file
308
+ end
309
+
310
+ opts.on('-I', '--load-path PATH', "Same as 'ruby -I'.") do |path|
311
+ @options[:load_paths] << path
312
+ end
313
+
314
+ opts.on('--debug', "Print out the precompiled Ruby source.") do
315
+ @options[:debug] = true
316
+ end
317
+ end
318
+
319
+ # Processes the options set by the command-line arguments,
320
+ # and runs the Haml compiler appropriately.
321
+ def process_result
322
+ super
323
+ input = @options[:input]
324
+ output = @options[:output]
325
+
326
+ template = input.read()
327
+ input.close() if input.is_a? File
328
+
329
+ begin
330
+ engine = ::Haml::Engine.new(template, @options[:for_engine])
331
+ if @options[:check_syntax]
332
+ puts "Syntax OK"
333
+ return
334
+ end
335
+
336
+ @options[:load_paths].each {|p| $LOAD_PATH << p}
337
+ @options[:requires].each {|f| require f}
338
+
339
+ if @options[:debug]
340
+ puts engine.precompiled
341
+ puts '=' * 100
342
+ end
343
+
344
+ result = engine.to_html
345
+ rescue Exception => e
346
+ raise e if @options[:trace]
347
+
348
+ case e
349
+ when ::Haml::SyntaxError; raise "Syntax error on line #{get_line e}: #{e.message}"
350
+ when ::Haml::Error; raise "Haml error on line #{get_line e}: #{e.message}"
351
+ else raise "Exception on line #{get_line e}: #{e.message}\n Use --trace for backtrace."
352
+ end
353
+ end
354
+
355
+ output.write(result)
356
+ output.close() if output.is_a? File
357
+ end
358
+ end
359
+
360
+ # The `html2haml` executable.
361
+ class HTML2Haml < Generic
362
+ # @param args [Array<String>] The command-line arguments
363
+ def initialize(args)
364
+ super
365
+
366
+ @module_opts = {}
367
+
368
+ begin
369
+ require 'haml/html'
370
+ rescue LoadError => err
371
+ dep = err.message.scan(/^no such file to load -- (.*)/)[0]
372
+ raise err if @options[:trace] || dep.nil? || dep.empty?
373
+ $stderr.puts "Required dependency #{dep} not found!\n Use --trace for backtrace."
374
+ exit 1
375
+ end
376
+ end
377
+
378
+ # Tells optparse how to parse the arguments.
379
+ #
380
+ # @param opts [OptionParser]
381
+ def set_opts(opts)
382
+ opts.banner = <<END
383
+ Usage: html2haml [options] [INPUT] [OUTPUT]
384
+
385
+ Description: Transforms an HTML file into corresponding Haml code.
386
+
387
+ Options:
388
+ END
389
+
390
+ opts.on('-r', '--rhtml', 'Parse RHTML tags.') do
391
+ @module_opts[:rhtml] = true
392
+ end
393
+
394
+ opts.on('--no-rhtml', "Don't parse RHTML tags.") do
395
+ @options[:no_rhtml] = true
396
+ end
397
+
398
+ opts.on('-x', '--xhtml', 'Parse the input using the more strict XHTML parser.') do
399
+ @module_opts[:xhtml] = true
400
+ end
401
+
402
+ super
403
+ end
404
+
405
+ # Processes the options set by the command-line arguments,
406
+ # and runs the HTML compiler appropriately.
407
+ def process_result
408
+ super
409
+
410
+ input = @options[:input]
411
+ output = @options[:output]
412
+
413
+ @module_opts[:rhtml] ||= input.respond_to?(:path) && input.path =~ /\.(rhtml|erb)$/
414
+ @module_opts[:rhtml] &&= @options[:no_rhtml] != false
415
+
416
+ output.write(::Haml::HTML.new(input, @module_opts).render)
417
+ rescue ::Haml::Error => e
418
+ raise "#{e.is_a?(::Haml::SyntaxError) ? "Syntax error" : "Error"} on line " +
419
+ "#{get_line e}: #{e.message}"
420
+ end
421
+ end
422
+
423
+ # The `css2sass` executable.
424
+ class CSS2Sass < Generic
425
+ # @param args [Array<String>] The command-line arguments
426
+ def initialize(args)
427
+ super
428
+
429
+ @module_opts = {}
430
+
431
+ require 'sass/css'
432
+ end
433
+
434
+ # Tells optparse how to parse the arguments.
435
+ #
436
+ # @param opts [OptionParser]
437
+ def set_opts(opts)
438
+ opts.banner = <<END
439
+ Usage: css2sass [options] [INPUT] [OUTPUT]
440
+
441
+ Description: Transforms a CSS file into corresponding Sass code.
442
+
443
+ Options:
444
+ END
445
+
446
+ opts.on('--old', 'Output the old-style ":prop val" property syntax') do
447
+ @module_opts[:old] = true
448
+ end
449
+
450
+ opts.on_tail('-a', '--alternate', 'Ignored') {}
451
+
452
+ super
453
+ end
454
+
455
+ # Processes the options set by the command-line arguments,
456
+ # and runs the CSS compiler appropriately.
457
+ def process_result
458
+ super
459
+
460
+ input = @options[:input]
461
+ output = @options[:output]
462
+
463
+ output.write(::Sass::CSS.new(input, @module_opts).render)
464
+ rescue ::Sass::SyntaxError => e
465
+ raise e if @options[:trace]
466
+ raise "Syntax error on line #{get_line e}: #{e.message}\n Use --trace for backtrace"
467
+ end
468
+ end
469
+ end
470
+ end