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,78 @@
1
+ require 'strscan'
2
+
3
+ module Haml
4
+ # This module contains functionality that's shared between Haml and Sass.
5
+ module Shared
6
+ extend self
7
+
8
+ # Scans through a string looking for the interoplation-opening `#{`
9
+ # and, when it's found, yields the scanner to the calling code
10
+ # so it can handle it properly.
11
+ #
12
+ # The scanner will have any backslashes immediately in front of the `#{`
13
+ # as the second capture group (`scan[2]`),
14
+ # and the text prior to that as the first (`scan[1]`).
15
+ #
16
+ # @yieldparam scan [StringScanner] The scanner scanning through the string
17
+ # @return [String] The text remaining in the scanner after all `#{`s have been processed
18
+ def handle_interpolation(str)
19
+ scan = StringScanner.new(str)
20
+ yield scan while scan.scan(/(.*?)(\\*)\#\{/)
21
+ scan.rest
22
+ end
23
+
24
+ # Moves a scanner through a balanced pair of characters.
25
+ # For example:
26
+ #
27
+ # Foo (Bar (Baz bang) bop) (Bang (bop bip))
28
+ # ^ ^
29
+ # from to
30
+ #
31
+ # @param scanner [StringScanner] The string scanner to move
32
+ # @param start [Character] The character opening the balanced pair.
33
+ # A `Fixnum` in 1.8, a `String` in 1.9
34
+ # @param finish [Character] The character closing the balanced pair.
35
+ # A `Fixnum` in 1.8, a `String` in 1.9
36
+ # @param count [Fixnum] The number of opening characters matched
37
+ # before calling this method
38
+ # @return [(String, String)] The string matched within the balanced pair
39
+ # and the rest of the string.
40
+ # `["Foo (Bar (Baz bang) bop)", " (Bang (bop bip))"]` in the example above.
41
+ def balance(scanner, start, finish, count = 0)
42
+ str = ''
43
+ scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
44
+ regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
45
+ while scanner.scan(regexp)
46
+ str << scanner.matched
47
+ count += 1 if scanner.matched[-1] == start
48
+ count -= 1 if scanner.matched[-1] == finish
49
+ return [str.strip, scanner.rest] if count == 0
50
+ end
51
+ end
52
+
53
+ # Formats a string for use in error messages about indentation.
54
+ #
55
+ # @param indentation [String] The string used for indentation
56
+ # @param was [Boolean] Whether or not to add `"was"` or `"were"`
57
+ # (depending on how many characters were in `indentation`)
58
+ # @return [String] The name of the indentation (e.g. `"12 spaces"`, `"1 tab"`)
59
+ def human_indentation(indentation, was = false)
60
+ if !indentation.include?(?\t)
61
+ noun = 'space'
62
+ elsif !indentation.include?(?\s)
63
+ noun = 'tab'
64
+ else
65
+ return indentation.inspect + (was ? ' was' : '')
66
+ end
67
+
68
+ singular = indentation.length == 1
69
+ if was
70
+ was = singular ? ' was' : ' were'
71
+ else
72
+ was = ''
73
+ end
74
+
75
+ "#{indentation.length} #{noun}#{'s' unless singular}#{was}"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,51 @@
1
+ require 'haml/engine'
2
+
3
+ module Haml
4
+ # The class that keeps track of the global options for Haml within Rails.
5
+ module Template
6
+ extend self
7
+
8
+ @options = {}
9
+ # The options hash for Haml when used within Rails.
10
+ # See {file:HAML_REFERENCE.md#haml_options the Haml options documentation}.
11
+ #
12
+ # @return [Hash<Symbol, Object>]
13
+ attr_accessor :options
14
+ end
15
+ end
16
+
17
+ if defined?(RAILS_ENV) && RAILS_ENV == "production"
18
+ Haml::Template.options[:ugly] = true
19
+ end
20
+
21
+ # Decide how we want to load Haml into Rails.
22
+ # Patching was necessary for versions <= 2.0.1,
23
+ # but we can make it a normal handler for higher versions.
24
+ if defined?(ActionView::TemplateHandler)
25
+ require 'haml/template/plugin'
26
+ else
27
+ require 'haml/template/patch'
28
+ end
29
+
30
+ if defined?(RAILS_ROOT)
31
+ # Update init.rb to the current version
32
+ # if it's out of date.
33
+ #
34
+ # We can probably remove this as of v1.9,
35
+ # because the new init file is sufficiently flexible
36
+ # to not need updating.
37
+ rails_init_file = File.join(RAILS_ROOT, 'vendor', 'plugins', 'haml', 'init.rb')
38
+ haml_init_file = Haml::Util.scope('init.rb')
39
+ begin
40
+ if File.exists?(rails_init_file)
41
+ require 'fileutils'
42
+ FileUtils.cp(haml_init_file, rails_init_file) unless FileUtils.cmp(rails_init_file, haml_init_file)
43
+ end
44
+ rescue SystemCallError
45
+ warn <<END
46
+ HAML WARNING:
47
+ #{rails_init_file} is out of date and couldn't be automatically updated.
48
+ Please run `haml --rails #{File.expand_path(RAILS_ROOT)}' to update it.
49
+ END
50
+ end
51
+ end
@@ -0,0 +1,58 @@
1
+ # This file makes Haml work with Rails
2
+ # by monkeypatching the core template-compilation methods.
3
+ # This is necessary for versions <= 2.0.1 because the template handler API
4
+ # wasn't sufficiently powerful to deal with caching and so forth.
5
+
6
+ # This module refers to the ActionView module that's part of Ruby on Rails.
7
+ # Haml can be used as an alternate templating engine for it,
8
+ # and includes several modifications to make it more Haml-friendly.
9
+ # The documentation can be found
10
+ # here[http://rubyonrails.org/api/classes/ActionView/Base.html].
11
+ module ActionView
12
+ class Base
13
+ def delegate_template_exists_with_haml(template_path)
14
+ template_exists?(template_path, :haml) && [:haml]
15
+ end
16
+ alias_method :delegate_template_exists_without_haml, :delegate_template_exists?
17
+ alias_method :delegate_template_exists?, :delegate_template_exists_with_haml
18
+
19
+ def compile_template_with_haml(extension, template, file_name, local_assigns)
20
+ return compile_haml(template, file_name, local_assigns) if extension.to_s == "haml"
21
+ compile_template_without_haml(extension, template, file_name, local_assigns)
22
+ end
23
+ alias_method :compile_template_without_haml, :compile_template
24
+ alias_method :compile_template, :compile_template_with_haml
25
+
26
+ def compile_haml(template, file_name, local_assigns)
27
+ render_symbol = assign_method_name(:haml, template, file_name)
28
+ locals = local_assigns.keys
29
+
30
+ @@template_args[render_symbol] ||= {}
31
+ locals_keys = @@template_args[render_symbol].keys | locals
32
+ @@template_args[render_symbol] = Haml::Util.to_hash(locals_keys.map {|k| [k, true]})
33
+
34
+ options = Haml::Template.options.dup
35
+ options[:filename] = file_name || 'compiled-template'
36
+
37
+ begin
38
+ Haml::Engine.new(template, options).def_method(CompiledTemplates, render_symbol, *locals_keys)
39
+ rescue Exception => e
40
+ if logger
41
+ logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
42
+ logger.debug "Backtrace: #{e.backtrace.join("\n")}"
43
+ end
44
+
45
+ base_path = if defined?(extract_base_path_from)
46
+ # Rails 2.0.x
47
+ extract_base_path_from(file_name) || view_paths.first
48
+ else
49
+ # Rails <=1.2.6
50
+ @base_path
51
+ end
52
+ raise ActionView::TemplateError.new(base_path, file_name || template, @assigns, template, e)
53
+ end
54
+
55
+ @@compile_time[render_symbol] = Time.now
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,71 @@
1
+ # This file makes Haml work with Rails
2
+ # using the > 2.0.1 template handler API.
3
+
4
+ module Haml
5
+ class Plugin < ActionView::TemplateHandler
6
+ include ActionView::TemplateHandlers::Compilable if defined?(ActionView::TemplateHandlers::Compilable)
7
+
8
+ def compile(template)
9
+ options = Haml::Template.options.dup
10
+
11
+ # template is a template object in Rails >=2.1.0,
12
+ # a source string previously
13
+ if template.respond_to? :source
14
+ # Template has a generic identifier in Rails >=3.0.0
15
+ options[:filename] = template.respond_to?(:identifier) ? template.identifier : template.filename
16
+ source = template.source
17
+ else
18
+ source = template
19
+ end
20
+
21
+ Haml::Engine.new(source, options).send(:precompiled_with_ambles, [])
22
+ end
23
+
24
+ def cache_fragment(block, name = {}, options = nil)
25
+ @view.fragment_for(block, name, options) do
26
+ eval("_hamlout.buffer", block.binding)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ if defined? ActionView::Template and ActionView::Template.respond_to? :register_template_handler
33
+ ActionView::Template
34
+ else
35
+ ActionView::Base
36
+ end.register_template_handler(:haml, Haml::Plugin)
37
+
38
+ # In Rails 2.0.2, ActionView::TemplateError took arguments
39
+ # that we can't fill in from the Haml::Plugin context.
40
+ # Thus, we've got to monkeypatch ActionView::Base to catch the error.
41
+ if ActionView::TemplateError.instance_method(:initialize).arity == 5
42
+ class ActionView::Base
43
+ def compile_template(handler, template, file_name, local_assigns)
44
+ render_symbol = assign_method_name(handler, template, file_name)
45
+
46
+ # Move begin up two lines so it captures compilation exceptions.
47
+ begin
48
+ render_source = create_template_source(handler, template, render_symbol, local_assigns.keys)
49
+ line_offset = @@template_args[render_symbol].size + handler.line_offset
50
+
51
+ file_name = 'compiled-template' if file_name.blank?
52
+ CompiledTemplates.module_eval(render_source, file_name, -line_offset)
53
+ rescue Exception => e # errors from template code
54
+ if logger
55
+ logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
56
+ logger.debug "Function body: #{render_source}"
57
+ logger.debug "Backtrace: #{e.backtrace.join("\n")}"
58
+ end
59
+
60
+ # There's no way to tell Haml about the filename,
61
+ # so we've got to insert it ourselves.
62
+ e.backtrace[0].gsub!('(haml)', file_name) if e.is_a?(Haml::Error)
63
+
64
+ raise ActionView::TemplateError.new(extract_base_path_from(file_name) || view_paths.first, file_name || template, @assigns, template, e)
65
+ end
66
+
67
+ @@compile_time[render_symbol] = Time.now
68
+ # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,244 @@
1
+ require 'erb'
2
+ require 'set'
3
+ require 'enumerator'
4
+
5
+ module Haml
6
+ # A module containing various useful functions.
7
+ module Util
8
+ extend self
9
+
10
+ # An array of ints representing the Ruby version number.
11
+ RUBY_VERSION = ::RUBY_VERSION.split(".").map {|s| s.to_i}
12
+
13
+ # Returns the path of a file relative to the Haml root directory.
14
+ #
15
+ # @param file [String] The filename relative to the Haml root
16
+ # @return [String] The filename relative to the the working directory
17
+ def scope(file)
18
+ File.join(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__)))), file)
19
+ end
20
+
21
+ # Converts an array of `[key, value]` pairs to a hash.
22
+ # For example:
23
+ #
24
+ # to_hash([[:foo, "bar"], [:baz, "bang"]])
25
+ # #=> {:foo => "bar", :baz => "bang"}
26
+ #
27
+ # @param arr [Array<(Object, Object)>] An array of pairs
28
+ # @return [Hash] A hash
29
+ def to_hash(arr)
30
+ arr.compact.inject({}) {|h, (k, v)| h[k] = v; h}
31
+ end
32
+
33
+ # Maps the keys in a hash according to a block.
34
+ # For example:
35
+ #
36
+ # map_keys({:foo => "bar", :baz => "bang"}) {|k| k.to_s}
37
+ # #=> {"foo" => "bar", "baz" => "bang"}
38
+ #
39
+ # @param hash [Hash] The hash to map
40
+ # @yield [key] A block in which the keys are transformed
41
+ # @yieldparam key [Object] The key that should be mapped
42
+ # @yieldreturn [Object] The new value for the key
43
+ # @return [Hash] The mapped hash
44
+ # @see #map_vals
45
+ # @see #map_hash
46
+ def map_keys(hash)
47
+ to_hash(hash.map {|k, v| [yield(k), v]})
48
+ end
49
+
50
+ # Maps the values in a hash according to a block.
51
+ # For example:
52
+ #
53
+ # map_values({:foo => "bar", :baz => "bang"}) {|v| v.to_sym}
54
+ # #=> {:foo => :bar, :baz => :bang}
55
+ #
56
+ # @param hash [Hash] The hash to map
57
+ # @yield [value] A block in which the values are transformed
58
+ # @yieldparam value [Object] The value that should be mapped
59
+ # @yieldreturn [Object] The new value for the value
60
+ # @return [Hash] The mapped hash
61
+ # @see #map_keys
62
+ # @see #map_hash
63
+ def map_vals(hash)
64
+ to_hash(hash.map {|k, v| [k, yield(v)]})
65
+ end
66
+
67
+ # Maps the key-value pairs of a hash according to a block.
68
+ # For example:
69
+ #
70
+ # map_hash({:foo => "bar", :baz => "bang"}) {|k, v| [k.to_s, v.to_sym]}
71
+ # #=> {"foo" => :bar, "baz" => :bang}
72
+ #
73
+ # @param hash [Hash] The hash to map
74
+ # @yield [key, value] A block in which the key-value pairs are transformed
75
+ # @yieldparam [key] The hash key
76
+ # @yieldparam [value] The hash value
77
+ # @yieldreturn [(Object, Object)] The new value for the `[key, value]` pair
78
+ # @return [Hash] The mapped hash
79
+ # @see #map_keys
80
+ # @see #map_vals
81
+ def map_hash(hash, &block)
82
+ to_hash(hash.map(&block))
83
+ end
84
+
85
+ # Computes the powerset of the given array.
86
+ # This is the set of all subsets of the array.
87
+ # For example:
88
+ #
89
+ # powerset([1, 2, 3]) #=>
90
+ # Set[Set[], Set[1], Set[2], Set[3], Set[1, 2], Set[2, 3], Set[1, 3], Set[1, 2, 3]]
91
+ #
92
+ # @param arr [Enumerable]
93
+ # @return [Set<Set>] The subsets of `arr`
94
+ def powerset(arr)
95
+ arr.inject([Set.new].to_set) do |powerset, el|
96
+ new_powerset = Set.new
97
+ powerset.each do |subset|
98
+ new_powerset << subset
99
+ new_powerset << subset + [el]
100
+ end
101
+ new_powerset
102
+ end
103
+ end
104
+
105
+ # Concatenates all strings that are adjacent in an array,
106
+ # while leaving other elements as they are.
107
+ # For example:
108
+ #
109
+ # merge_adjacent_strings([1, "foo", "bar", 2, "baz"])
110
+ # #=> [1, "foobar", 2, "baz"]
111
+ #
112
+ # @param enum [Enumerable]
113
+ # @return [Array] The enumerable with strings merged
114
+ def merge_adjacent_strings(enum)
115
+ e = enum.inject([]) do |a, e|
116
+ if e.is_a?(String) && a.last.is_a?(String)
117
+ a.last << e
118
+ else
119
+ a << e
120
+ end
121
+ a
122
+ end
123
+ end
124
+
125
+ # Whether or not this is running under Ruby 1.8 or lower.
126
+ #
127
+ # @return [Boolean]
128
+ def ruby1_8?
129
+ Haml::Util::RUBY_VERSION[0] == 1 && Haml::Util::RUBY_VERSION[1] < 9
130
+ end
131
+
132
+ def check_encoding(str)
133
+ return if ruby1_8?
134
+ return if str.valid_encoding?
135
+ encoding = str.encoding
136
+ newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding("binary"))
137
+ str.force_encoding("binary").split(newlines).each_with_index do |line, i|
138
+ begin
139
+ line.encode(encoding)
140
+ rescue Encoding::UndefinedConversionError => e
141
+ yield <<MSG.rstrip, i + 1
142
+ Invalid #{encoding.name} character #{e.error_char.dump}
143
+ MSG
144
+ end
145
+ end
146
+ end
147
+
148
+ # Checks to see if a class has a given method.
149
+ # For example:
150
+ #
151
+ # Haml::Util.has?(:public_instance_method, String, :gsub) #=> true
152
+ #
153
+ # Method collections like `Class#instance_methods`
154
+ # return strings in Ruby 1.8 and symbols in Ruby 1.9 and on,
155
+ # so this handles checking for them in a compatible way.
156
+ #
157
+ # @param attr [#to_s] The (singular) name of the method-collection method
158
+ # (e.g. `:instance_methods`, `:private_methods`)
159
+ # @param klass [Module] The class to check the methods of which to check
160
+ # @param method [String, Symbol] The name of the method do check for
161
+ # @return [Boolean] Whether or not the given collection has the given method
162
+ def has?(attr, klass, method)
163
+ klass.send("#{attr}s").include?(ruby1_8? ? method.to_s : method.to_sym)
164
+ end
165
+
166
+ # A version of `Enumerable#enum_with_index` that works in Ruby 1.8 and 1.9.
167
+ #
168
+ # @param enum [Enumerable] The enumerable to get the enumerator for
169
+ # @return [Enumerator] The with-index enumerator
170
+ def enum_with_index(enum)
171
+ ruby1_8? ? enum.enum_with_index : enum.each_with_index
172
+ end
173
+
174
+ # The context in which the ERB for \{#def\_static\_method} will be run.
175
+ class StaticConditionalContext
176
+ # @param set [#include?] The set of variables that are defined for this context.
177
+ def initialize(set)
178
+ @set = set
179
+ end
180
+
181
+ # Checks whether or not a variable is defined for this context.
182
+ #
183
+ # @param name [Symbol] The name of the variable
184
+ # @return [Boolean]
185
+ def method_missing(name, *args, &block)
186
+ super unless args.empty? && block.nil?
187
+ @set.include?(name)
188
+ end
189
+ end
190
+
191
+ # This is used for methods in {Haml::Buffer} that need to be very fast,
192
+ # and take a lot of boolean parameters
193
+ # that are known at compile-time.
194
+ # Instead of passing the parameters in normally,
195
+ # a separate method is defined for every possible combination of those parameters;
196
+ # these are then called using \{#static\_method\_name}.
197
+ #
198
+ # To define a static method, an ERB template for the method is provided.
199
+ # All conditionals based on the static parameters
200
+ # are done as embedded Ruby within this template.
201
+ # For example:
202
+ #
203
+ # def_static_method(Foo, :my_static_method, [:foo, :bar], :baz, :bang, <<RUBY)
204
+ # <% if baz && bang %>
205
+ # return foo + bar
206
+ # <% elsif baz || bang %>
207
+ # return foo - bar
208
+ # <% else %>
209
+ # return 17
210
+ # <% end %>
211
+ # RUBY
212
+ #
213
+ # \{#static\_method\_name} can be used to call static methods.
214
+ #
215
+ # @overload def_static_method(klass, name, args, *vars, erb)
216
+ # @param klass [Module] The class on which to define the static method
217
+ # @param name [#to_s] The (base) name of the static method
218
+ # @param args [Array<Symbol>] The names of the arguments to the defined methods
219
+ # (**not** to the ERB template)
220
+ # @param vars [Array<Symbol>] The names of the static boolean variables
221
+ # to be made available to the ERB template
222
+ # @param erb [String] The template for the method code
223
+ def def_static_method(klass, name, args, *vars)
224
+ erb = vars.pop
225
+ powerset(vars).each do |set|
226
+ context = StaticConditionalContext.new(set).instance_eval {binding}
227
+ klass.class_eval(<<METHOD)
228
+ def #{static_method_name(name, *vars.map {|v| set.include?(v)})}(#{args.join(', ')})
229
+ #{ERB.new(erb).result(context)}
230
+ end
231
+ METHOD
232
+ end
233
+ end
234
+
235
+ # Computes the name for a method defined via \{#def\_static\_method}.
236
+ #
237
+ # @param name [String] The base name of the static method
238
+ # @param vars [Array<Boolean>] The static variable assignment
239
+ # @return [String] The real name of the static method
240
+ def static_method_name(name, *vars)
241
+ "#{name}_#{vars.map {|v| !!v}.join('_')}"
242
+ end
243
+ end
244
+ end