hamlit 2.9.3

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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +45 -0
  4. data/CHANGELOG.md +676 -0
  5. data/Gemfile +28 -0
  6. data/LICENSE.txt +44 -0
  7. data/README.md +150 -0
  8. data/REFERENCE.md +266 -0
  9. data/Rakefile +117 -0
  10. data/benchmark/boolean_attribute.haml +6 -0
  11. data/benchmark/class_attribute.haml +5 -0
  12. data/benchmark/common_attribute.haml +3 -0
  13. data/benchmark/data_attribute.haml +4 -0
  14. data/benchmark/dynamic_attributes/boolean_attribute.haml +4 -0
  15. data/benchmark/dynamic_attributes/class_attribute.haml +4 -0
  16. data/benchmark/dynamic_attributes/common_attribute.haml +2 -0
  17. data/benchmark/dynamic_attributes/data_attribute.haml +2 -0
  18. data/benchmark/dynamic_attributes/id_attribute.haml +2 -0
  19. data/benchmark/dynamic_boolean_attribute.haml +4 -0
  20. data/benchmark/etc/attribute_builder.haml +5 -0
  21. data/benchmark/etc/real_sample.haml +888 -0
  22. data/benchmark/etc/real_sample.rb +11 -0
  23. data/benchmark/etc/static_analyzer.haml +1 -0
  24. data/benchmark/etc/string_interpolation.haml +2 -0
  25. data/benchmark/etc/tags.haml +3 -0
  26. data/benchmark/etc/tags_loop.haml +2 -0
  27. data/benchmark/ext/build_data.rb +17 -0
  28. data/benchmark/ext/build_id.rb +13 -0
  29. data/benchmark/id_attribute.haml +3 -0
  30. data/benchmark/plain.haml +4 -0
  31. data/benchmark/script.haml +4 -0
  32. data/benchmark/slim/LICENSE +21 -0
  33. data/benchmark/slim/context.rb +11 -0
  34. data/benchmark/slim/run-benchmarks.rb +94 -0
  35. data/benchmark/slim/view.erb +23 -0
  36. data/benchmark/slim/view.haml +18 -0
  37. data/benchmark/slim/view.slim +17 -0
  38. data/benchmark/utils/benchmark_ips_extension.rb +43 -0
  39. data/bin/bench +77 -0
  40. data/bin/console +11 -0
  41. data/bin/ruby +3 -0
  42. data/bin/setup +7 -0
  43. data/bin/stackprof +27 -0
  44. data/bin/test +24 -0
  45. data/exe/hamlit +6 -0
  46. data/ext/hamlit/extconf.rb +10 -0
  47. data/ext/hamlit/hamlit.c +553 -0
  48. data/ext/hamlit/hescape.c +108 -0
  49. data/ext/hamlit/hescape.h +20 -0
  50. data/hamlit.gemspec +45 -0
  51. data/lib/hamlit.rb +11 -0
  52. data/lib/hamlit/attribute_builder.rb +173 -0
  53. data/lib/hamlit/attribute_compiler.rb +123 -0
  54. data/lib/hamlit/attribute_parser.rb +110 -0
  55. data/lib/hamlit/cli.rb +130 -0
  56. data/lib/hamlit/compiler.rb +97 -0
  57. data/lib/hamlit/compiler/children_compiler.rb +112 -0
  58. data/lib/hamlit/compiler/comment_compiler.rb +36 -0
  59. data/lib/hamlit/compiler/doctype_compiler.rb +46 -0
  60. data/lib/hamlit/compiler/script_compiler.rb +102 -0
  61. data/lib/hamlit/compiler/silent_script_compiler.rb +24 -0
  62. data/lib/hamlit/compiler/tag_compiler.rb +74 -0
  63. data/lib/hamlit/engine.rb +37 -0
  64. data/lib/hamlit/error.rb +15 -0
  65. data/lib/hamlit/escapable.rb +13 -0
  66. data/lib/hamlit/filters.rb +75 -0
  67. data/lib/hamlit/filters/base.rb +12 -0
  68. data/lib/hamlit/filters/cdata.rb +20 -0
  69. data/lib/hamlit/filters/coffee.rb +17 -0
  70. data/lib/hamlit/filters/css.rb +33 -0
  71. data/lib/hamlit/filters/erb.rb +10 -0
  72. data/lib/hamlit/filters/escaped.rb +22 -0
  73. data/lib/hamlit/filters/javascript.rb +33 -0
  74. data/lib/hamlit/filters/less.rb +20 -0
  75. data/lib/hamlit/filters/markdown.rb +10 -0
  76. data/lib/hamlit/filters/plain.rb +29 -0
  77. data/lib/hamlit/filters/preserve.rb +22 -0
  78. data/lib/hamlit/filters/ruby.rb +10 -0
  79. data/lib/hamlit/filters/sass.rb +15 -0
  80. data/lib/hamlit/filters/scss.rb +15 -0
  81. data/lib/hamlit/filters/text_base.rb +25 -0
  82. data/lib/hamlit/filters/tilt_base.rb +49 -0
  83. data/lib/hamlit/force_escapable.rb +29 -0
  84. data/lib/hamlit/helpers.rb +15 -0
  85. data/lib/hamlit/html.rb +14 -0
  86. data/lib/hamlit/identity.rb +13 -0
  87. data/lib/hamlit/object_ref.rb +30 -0
  88. data/lib/hamlit/parser.rb +49 -0
  89. data/lib/hamlit/parser/MIT-LICENSE +20 -0
  90. data/lib/hamlit/parser/README.md +30 -0
  91. data/lib/hamlit/parser/haml_buffer.rb +348 -0
  92. data/lib/hamlit/parser/haml_compiler.rb +553 -0
  93. data/lib/hamlit/parser/haml_error.rb +61 -0
  94. data/lib/hamlit/parser/haml_helpers.rb +727 -0
  95. data/lib/hamlit/parser/haml_options.rb +286 -0
  96. data/lib/hamlit/parser/haml_parser.rb +800 -0
  97. data/lib/hamlit/parser/haml_util.rb +288 -0
  98. data/lib/hamlit/parser/haml_xss_mods.rb +109 -0
  99. data/lib/hamlit/rails_helpers.rb +51 -0
  100. data/lib/hamlit/rails_template.rb +59 -0
  101. data/lib/hamlit/railtie.rb +10 -0
  102. data/lib/hamlit/ruby_expression.rb +32 -0
  103. data/lib/hamlit/string_splitter.rb +88 -0
  104. data/lib/hamlit/template.rb +28 -0
  105. data/lib/hamlit/utils.rb +18 -0
  106. data/lib/hamlit/version.rb +4 -0
  107. metadata +361 -0
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ # Hamlit::Parser uses original Haml::Parser to generate Haml AST.
3
+ # hamlit/parser/haml_* are modules originally in haml gem.
4
+
5
+ require 'hamlit/parser/haml_error'
6
+ require 'hamlit/parser/haml_util'
7
+ require 'hamlit/parser/haml_buffer'
8
+ require 'hamlit/parser/haml_compiler'
9
+ require 'hamlit/parser/haml_parser'
10
+ require 'hamlit/parser/haml_helpers'
11
+ require 'hamlit/parser/haml_options'
12
+
13
+ module Hamlit
14
+ class Parser
15
+ AVAILABLE_OPTIONS = %i[
16
+ autoclose
17
+ escape_html
18
+ escape_attrs
19
+ ].freeze
20
+
21
+ def initialize(options = {})
22
+ @options = HamlOptions.defaults.dup
23
+ AVAILABLE_OPTIONS.each do |key|
24
+ @options[key] = options[key]
25
+ end
26
+ end
27
+
28
+ def call(template)
29
+ template = Hamlit::HamlUtil.check_haml_encoding(template) do |msg, line|
30
+ raise Hamlit::Error.new(msg, line)
31
+ end
32
+ HamlParser.new(template, HamlOptions.new(@options)).parse
33
+ rescue ::Hamlit::HamlError => e
34
+ error_with_lineno(e)
35
+ end
36
+
37
+ private
38
+
39
+ def error_with_lineno(error)
40
+ return error if error.line
41
+
42
+ trace = error.backtrace.first
43
+ return error unless trace
44
+
45
+ line = trace.match(/\d+\z/).to_s.to_i
46
+ HamlSyntaxError.new(error.message, line)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006-2009 Hampton Catlin and Natalie Weizenbaum
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,30 @@
1
+ # lib/hamlit/parser/haml\_\*.rb
2
+
3
+ Hamlit::HamlFoo is originally Haml::Foo in haml gem.
4
+
5
+ This directory is NOT intended to be updated other than following Haml's changes.
6
+
7
+ ## License
8
+
9
+ lib/hamlit/parser/haml\_\*.rb is:
10
+
11
+ Copyright (c) 2006-2009 Hampton Catlin and Natalie Weizenbaum
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining
14
+ a copy of this software and associated documentation files (the
15
+ "Software"), to deal in the Software without restriction, including
16
+ without limitation the rights to use, copy, modify, merge, publish,
17
+ distribute, sublicense, and/or sell copies of the Software, and to
18
+ permit persons to whom the Software is furnished to do so, subject to
19
+ the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be
22
+ included in all copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,348 @@
1
+ require 'hamlit/parser/haml_helpers'
2
+ require 'hamlit/parser/haml_util'
3
+ require 'hamlit/parser/haml_compiler'
4
+
5
+ module Hamlit
6
+ # This class is used only internally. It holds the buffer of HTML that
7
+ # is eventually output as the resulting document.
8
+ # It's called from within the precompiled code,
9
+ # and helps reduce the amount of processing done within `instance_eval`ed code.
10
+ class HamlBuffer
11
+ ID_KEY = 'id'.freeze
12
+ CLASS_KEY = 'class'.freeze
13
+ DATA_KEY = 'data'.freeze
14
+
15
+ include ::Hamlit::HamlHelpers
16
+ include ::Hamlit::HamlUtil
17
+
18
+ # The string that holds the compiled HTML. This is aliased as
19
+ # `_erbout` for compatibility with ERB-specific code.
20
+ #
21
+ # @return [String]
22
+ attr_accessor :buffer
23
+
24
+ # The options hash passed in from {Haml::Engine}.
25
+ #
26
+ # @return [{String => Object}]
27
+ # @see Haml::Options#for_buffer
28
+ attr_accessor :options
29
+
30
+ # The {Buffer} for the enclosing Haml document.
31
+ # This is set for partials and similar sorts of nested templates.
32
+ # It's `nil` at the top level (see \{#toplevel?}).
33
+ #
34
+ # @return [Buffer]
35
+ attr_accessor :upper
36
+
37
+ # nil if there's no capture_haml block running,
38
+ # and the position at which it's beginning the capture if there is one.
39
+ #
40
+ # @return [Fixnum, nil]
41
+ attr_accessor :capture_position
42
+
43
+ # @return [Boolean]
44
+ # @see #active?
45
+ attr_writer :active
46
+
47
+ # @return [Boolean] Whether or not the format is XHTML
48
+ def xhtml?
49
+ not html?
50
+ end
51
+
52
+ # @return [Boolean] Whether or not the format is any flavor of HTML
53
+ def html?
54
+ html4? or html5?
55
+ end
56
+
57
+ # @return [Boolean] Whether or not the format is HTML4
58
+ def html4?
59
+ @options[:format] == :html4
60
+ end
61
+
62
+ # @return [Boolean] Whether or not the format is HTML5.
63
+ def html5?
64
+ @options[:format] == :html5
65
+ end
66
+
67
+ # @return [Boolean] Whether or not this buffer is a top-level template,
68
+ # as opposed to a nested partial
69
+ def toplevel?
70
+ upper.nil?
71
+ end
72
+
73
+ # Whether or not this buffer is currently being used to render a Haml template.
74
+ # Returns `false` if a subtemplate is being rendered,
75
+ # even if it's a subtemplate of this buffer's template.
76
+ #
77
+ # @return [Boolean]
78
+ def active?
79
+ @active
80
+ end
81
+
82
+ # @return [Fixnum] The current indentation level of the document
83
+ def tabulation
84
+ @real_tabs + @tabulation
85
+ end
86
+
87
+ # Sets the current tabulation of the document.
88
+ #
89
+ # @param val [Fixnum] The new tabulation
90
+ def tabulation=(val)
91
+ val = val - @real_tabs
92
+ @tabulation = val > -1 ? val : 0
93
+ end
94
+
95
+ # @param upper [Buffer] The parent buffer
96
+ # @param options [{Symbol => Object}] An options hash.
97
+ # See {Haml::Engine#options\_for\_buffer}
98
+ def initialize(upper = nil, options = {})
99
+ @active = true
100
+ @upper = upper
101
+ @options = options
102
+ @buffer = new_encoded_string
103
+ @tabulation = 0
104
+
105
+ # The number of tabs that Engine thinks we should have
106
+ # @real_tabs + @tabulation is the number of tabs actually output
107
+ @real_tabs = 0
108
+ end
109
+
110
+ # Appends text to the buffer, properly tabulated.
111
+ # Also modifies the document's indentation.
112
+ #
113
+ # @param text [String] The text to append
114
+ # @param tab_change [Fixnum] The number of tabs by which to increase
115
+ # or decrease the document's indentation
116
+ # @param dont_tab_up [Boolean] If true, don't indent the first line of `text`
117
+ def push_text(text, tab_change, dont_tab_up)
118
+ if @tabulation > 0
119
+ # Have to push every line in by the extra user set tabulation.
120
+ # Don't push lines with just whitespace, though,
121
+ # because that screws up precompiled indentation.
122
+ text.gsub!(/^(?!\s+$)/m, tabs)
123
+ text.sub!(tabs, '') if dont_tab_up
124
+ end
125
+
126
+ @real_tabs += tab_change
127
+ @buffer << text
128
+ end
129
+
130
+ # Modifies the indentation of the document.
131
+ #
132
+ # @param tab_change [Fixnum] The number of tabs by which to increase
133
+ # or decrease the document's indentation
134
+ def adjust_tabs(tab_change)
135
+ @real_tabs += tab_change
136
+ end
137
+
138
+ # the number of arguments here is insane, but passing in an options hash instead of named arguments
139
+ # causes a significant performance regression
140
+ def format_script(result, preserve_script, in_tag, preserve_tag, escape_html, nuke_inner_whitespace, interpolated, ugly)
141
+ result_name = escape_html ? html_escape(result.to_s) : result.to_s
142
+
143
+ if ugly
144
+ result = nuke_inner_whitespace ? result_name.strip : result_name
145
+ result = preserve(result, preserve_script, preserve_tag)
146
+ fix_textareas!(result) if toplevel? && result.include?('<textarea')
147
+ return result
148
+ end
149
+
150
+ # If we're interpolated,
151
+ # then the custom tabulation is handled in #push_text.
152
+ # The easiest way to avoid it here is to reset @tabulation.
153
+ if interpolated
154
+ old_tabulation = @tabulation
155
+ @tabulation = 0
156
+ end
157
+
158
+ in_tag_no_nuke = in_tag && !nuke_inner_whitespace
159
+ preserved_no_nuke = in_tag_no_nuke && preserve_tag
160
+ tabulation = !preserved_no_nuke && @real_tabs
161
+
162
+ result = nuke_inner_whitespace ? result_name.strip : result_name.rstrip
163
+ result = preserve(result, preserve_script, preserve_tag)
164
+
165
+ has_newline = !preserved_no_nuke && result.include?("\n")
166
+
167
+ if in_tag_no_nuke && (preserve_tag || !has_newline)
168
+ @real_tabs -= 1
169
+ @tabulation = old_tabulation if interpolated
170
+ return result
171
+ end
172
+
173
+ unless preserved_no_nuke
174
+ # Precompiled tabulation may be wrong
175
+ result = "#{tabs}#{result}" if !interpolated && !in_tag && @tabulation > 0
176
+
177
+ if has_newline
178
+ result.gsub! "\n", "\n#{tabs(tabulation)}"
179
+
180
+ # Add tabulation if it wasn't precompiled
181
+ result = "#{tabs(tabulation)}#{result}" if in_tag_no_nuke
182
+ end
183
+
184
+ fix_textareas!(result) if toplevel? && result.include?('<textarea')
185
+
186
+ if in_tag_no_nuke
187
+ result = "\n#{result}\n#{tabs(tabulation-1)}"
188
+ @real_tabs -= 1
189
+ end
190
+ @tabulation = old_tabulation if interpolated
191
+ result
192
+ end
193
+ end
194
+
195
+ def attributes(class_id, obj_ref, *attributes_hashes)
196
+ attributes = class_id
197
+ attributes_hashes.each do |old|
198
+ self.class.merge_attrs(attributes, Hash[old.map {|k, v| [k.to_s, v]}])
199
+ end
200
+ self.class.merge_attrs(attributes, parse_object_ref(obj_ref)) if obj_ref
201
+ ::Hamlit::HamlCompiler.build_attributes(
202
+ html?, @options[:attr_wrapper], @options[:escape_attrs], @options[:hyphenate_data_attrs], attributes)
203
+ end
204
+
205
+ # Remove the whitespace from the right side of the buffer string.
206
+ # Doesn't do anything if we're at the beginning of a capture_haml block.
207
+ def rstrip!
208
+ if capture_position.nil?
209
+ buffer.rstrip!
210
+ return
211
+ end
212
+
213
+ buffer << buffer.slice!(capture_position..-1).rstrip
214
+ end
215
+
216
+ # Merges two attribute hashes.
217
+ # This is the same as `to.merge!(from)`,
218
+ # except that it merges id, class, and data attributes.
219
+ #
220
+ # ids are concatenated with `"_"`,
221
+ # and classes are concatenated with `" "`.
222
+ # data hashes are simply merged.
223
+ #
224
+ # Destructively modifies both `to` and `from`.
225
+ #
226
+ # @param to [{String => String}] The attribute hash to merge into
227
+ # @param from [{String => #to_s}] The attribute hash to merge from
228
+ # @return [{String => String}] `to`, after being merged
229
+ def self.merge_attrs(to, from)
230
+ from[ID_KEY] = ::Hamlit::HamlCompiler.filter_and_join(from[ID_KEY], '_') if from[ID_KEY]
231
+ if to[ID_KEY] && from[ID_KEY]
232
+ to[ID_KEY] << "_#{from.delete(ID_KEY)}"
233
+ elsif to[ID_KEY] || from[ID_KEY]
234
+ from[ID_KEY] ||= to[ID_KEY]
235
+ end
236
+
237
+ from[CLASS_KEY] = ::Hamlit::HamlCompiler.filter_and_join(from[CLASS_KEY], ' ') if from[CLASS_KEY]
238
+ if to[CLASS_KEY] && from[CLASS_KEY]
239
+ # Make sure we don't duplicate class names
240
+ from[CLASS_KEY] = (from[CLASS_KEY].to_s.split(' ') | to[CLASS_KEY].split(' ')).sort.join(' ')
241
+ elsif to[CLASS_KEY] || from[CLASS_KEY]
242
+ from[CLASS_KEY] ||= to[CLASS_KEY]
243
+ end
244
+
245
+ from.keys.each do |key|
246
+ next unless from[key].kind_of?(Hash) || to[key].kind_of?(Hash)
247
+
248
+ from_data = from.delete(key)
249
+ # forces to_data & from_data into a hash
250
+ from_data = { nil => from_data } if from_data && !from_data.is_a?(Hash)
251
+ to[key] = { nil => to[key] } if to[key] && !to[key].is_a?(Hash)
252
+
253
+ if from_data && !to[key]
254
+ to[key] = from_data
255
+ elsif from_data && to[key]
256
+ to[key].merge! from_data
257
+ end
258
+ end
259
+
260
+ to.merge!(from)
261
+ end
262
+
263
+ private
264
+
265
+ def preserve(result, preserve_script, preserve_tag)
266
+ return ::Hamlit::HamlHelpers.preserve(result) if preserve_tag
267
+ return ::Hamlit::HamlHelpers.find_and_preserve(result, options[:preserve]) if preserve_script
268
+ result
269
+ end
270
+
271
+ # Works like #{find_and_preserve}, but allows the first newline after a
272
+ # preserved opening tag to remain unencoded, and then outdents the content.
273
+ # This change was motivated primarily by the change in Rails 3.2.3 to emit
274
+ # a newline after textarea helpers.
275
+ #
276
+ # @param input [String] The text to process
277
+ # @since Haml 4.0.1
278
+ # @private
279
+ def fix_textareas!(input)
280
+ pattern = /<(textarea)([^>]*)>(\n|&#x000A;)(.*?)<\/textarea>/im
281
+ input.gsub!(pattern) do |s|
282
+ match = pattern.match(s)
283
+ content = match[4]
284
+ if match[3] == '&#x000A;'
285
+ content.sub!(/\A /, '&#x0020;')
286
+ else
287
+ content.sub!(/\A[ ]*/, '')
288
+ end
289
+ "<#{match[1]}#{match[2]}>\n#{content}</#{match[1]}>"
290
+ end
291
+ end
292
+
293
+ def new_encoded_string
294
+ "".encode(Encoding.find(options[:encoding]))
295
+ end
296
+
297
+ @@tab_cache = {}
298
+ # Gets `count` tabs. Mostly for internal use.
299
+ def tabs(count = 0)
300
+ tabs = [count + @tabulation, 0].max
301
+ @@tab_cache[tabs] ||= ' ' * tabs
302
+ end
303
+
304
+ # Takes an array of objects and uses the class and id of the first
305
+ # one to create an attributes hash.
306
+ # The second object, if present, is used as a prefix,
307
+ # just like you can do with `dom_id()` and `dom_class()` in Rails
308
+ def parse_object_ref(ref)
309
+ prefix = ref[1]
310
+ ref = ref[0]
311
+ # Let's make sure the value isn't nil. If it is, return the default Hash.
312
+ return {} if ref.nil?
313
+ class_name =
314
+ if ref.respond_to?(:haml_object_ref)
315
+ ref.haml_object_ref
316
+ else
317
+ underscore(ref.class)
318
+ end
319
+ ref_id =
320
+ if ref.respond_to?(:to_key)
321
+ key = ref.to_key
322
+ key.join('_') unless key.nil?
323
+ else
324
+ ref.id
325
+ end
326
+ id = "#{class_name}_#{ref_id || 'new'}"
327
+ if prefix
328
+ class_name = "#{ prefix }_#{ class_name}"
329
+ id = "#{ prefix }_#{ id }"
330
+ end
331
+
332
+ {ID_KEY => id, CLASS_KEY => class_name}
333
+ end
334
+
335
+ # Changes a word from camel case to underscores.
336
+ # Based on the method of the same name in Rails' Inflector,
337
+ # but copied here so it'll run properly without Rails.
338
+ def underscore(camel_cased_word)
339
+ word = camel_cased_word.to_s.dup
340
+ word.gsub!(/::/, '_')
341
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
342
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
343
+ word.tr!('-', '_')
344
+ word.downcase!
345
+ word
346
+ end
347
+ end
348
+ end