motion-kramdown 0.5.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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +84 -0
  3. data/lib/kramdown/compatibility.rb +36 -0
  4. data/lib/kramdown/converter/base.rb +259 -0
  5. data/lib/kramdown/converter/html.rb +461 -0
  6. data/lib/kramdown/converter/kramdown.rb +423 -0
  7. data/lib/kramdown/converter/latex.rb +600 -0
  8. data/lib/kramdown/converter/math_engine/itex2mml.rb +39 -0
  9. data/lib/kramdown/converter/math_engine/mathjax.rb +33 -0
  10. data/lib/kramdown/converter/math_engine/ritex.rb +38 -0
  11. data/lib/kramdown/converter/pdf.rb +624 -0
  12. data/lib/kramdown/converter/remove_html_tags.rb +53 -0
  13. data/lib/kramdown/converter/syntax_highlighter/coderay.rb +78 -0
  14. data/lib/kramdown/converter/syntax_highlighter/rouge.rb +37 -0
  15. data/lib/kramdown/converter/toc.rb +69 -0
  16. data/lib/kramdown/converter.rb +69 -0
  17. data/lib/kramdown/document.rb +144 -0
  18. data/lib/kramdown/element.rb +515 -0
  19. data/lib/kramdown/error.rb +17 -0
  20. data/lib/kramdown/options.rb +584 -0
  21. data/lib/kramdown/parser/base.rb +130 -0
  22. data/lib/kramdown/parser/gfm.rb +55 -0
  23. data/lib/kramdown/parser/html.rb +575 -0
  24. data/lib/kramdown/parser/kramdown/abbreviation.rb +67 -0
  25. data/lib/kramdown/parser/kramdown/autolink.rb +37 -0
  26. data/lib/kramdown/parser/kramdown/blank_line.rb +30 -0
  27. data/lib/kramdown/parser/kramdown/block_boundary.rb +33 -0
  28. data/lib/kramdown/parser/kramdown/blockquote.rb +39 -0
  29. data/lib/kramdown/parser/kramdown/codeblock.rb +56 -0
  30. data/lib/kramdown/parser/kramdown/codespan.rb +44 -0
  31. data/lib/kramdown/parser/kramdown/emphasis.rb +61 -0
  32. data/lib/kramdown/parser/kramdown/eob.rb +26 -0
  33. data/lib/kramdown/parser/kramdown/escaped_chars.rb +25 -0
  34. data/lib/kramdown/parser/kramdown/extensions.rb +201 -0
  35. data/lib/kramdown/parser/kramdown/footnote.rb +56 -0
  36. data/lib/kramdown/parser/kramdown/header.rb +59 -0
  37. data/lib/kramdown/parser/kramdown/horizontal_rule.rb +27 -0
  38. data/lib/kramdown/parser/kramdown/html.rb +160 -0
  39. data/lib/kramdown/parser/kramdown/html_entity.rb +33 -0
  40. data/lib/kramdown/parser/kramdown/line_break.rb +25 -0
  41. data/lib/kramdown/parser/kramdown/link.rb +139 -0
  42. data/lib/kramdown/parser/kramdown/list.rb +256 -0
  43. data/lib/kramdown/parser/kramdown/math.rb +54 -0
  44. data/lib/kramdown/parser/kramdown/paragraph.rb +54 -0
  45. data/lib/kramdown/parser/kramdown/smart_quotes.rb +174 -0
  46. data/lib/kramdown/parser/kramdown/table.rb +171 -0
  47. data/lib/kramdown/parser/kramdown/typographic_symbol.rb +44 -0
  48. data/lib/kramdown/parser/kramdown.rb +359 -0
  49. data/lib/kramdown/parser/markdown.rb +56 -0
  50. data/lib/kramdown/parser.rb +27 -0
  51. data/lib/kramdown/utils/configurable.rb +44 -0
  52. data/lib/kramdown/utils/entities.rb +347 -0
  53. data/lib/kramdown/utils/html.rb +75 -0
  54. data/lib/kramdown/utils/ordered_hash.rb +87 -0
  55. data/lib/kramdown/utils/string_scanner.rb +74 -0
  56. data/lib/kramdown/utils/unidecoder.rb +51 -0
  57. data/lib/kramdown/utils.rb +58 -0
  58. data/lib/kramdown/version.rb +15 -0
  59. data/lib/kramdown.rb +10 -0
  60. data/lib/motion-kramdown.rb +47 -0
  61. data/lib/rubymotion/encodings.rb +37 -0
  62. data/lib/rubymotion/rexml_shim.rb +25 -0
  63. data/lib/rubymotion/set.rb +1349 -0
  64. data/lib/rubymotion/version.rb +6 -0
  65. data/spec/document_tree.rb +48 -0
  66. data/spec/gfm_to_html.rb +95 -0
  67. data/spec/helpers/it_behaves_like.rb +27 -0
  68. data/spec/helpers/option_file.rb +46 -0
  69. data/spec/helpers/spec_options.rb +37 -0
  70. data/spec/helpers/tidy.rb +12 -0
  71. data/spec/html_to_html.rb +40 -0
  72. data/spec/html_to_kramdown_to_html.rb +46 -0
  73. data/spec/kramdown_to_xxx.rb +40 -0
  74. data/spec/test_location.rb +203 -0
  75. data/spec/test_string_scanner_kramdown.rb +19 -0
  76. data/spec/text_to_kramdown_to_html.rb +52 -0
  77. data/spec/text_to_latex.rb +33 -0
  78. metadata +164 -0
@@ -0,0 +1,359 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ #--
4
+ # Copyright (C) 2009-2014 Thomas Leitner <t_leitner@gmx.at>
5
+ #
6
+ # This file is part of kramdown which is licensed under the MIT.
7
+ #++
8
+ #
9
+
10
+ # RM require 'strscan'
11
+ # RM require 'stringio'
12
+
13
+ #TODO: use [[:alpha:]] in all regexp to allow parsing of international values in 1.9.1
14
+ #NOTE: use @src.pre_match only before other check/match?/... operations, otherwise the content is changed
15
+
16
+ module Kramdown
17
+
18
+ module Parser
19
+
20
+ # Used for parsing a document in kramdown format.
21
+ #
22
+ # If you want to extend the functionality of the parser, you need to do the following:
23
+ #
24
+ # * Create a new subclass
25
+ # * add the needed parser methods
26
+ # * modify the @block_parsers and @span_parsers variables and add the names of your parser
27
+ # methods
28
+ #
29
+ # Here is a small example for an extended parser class that parses ERB style tags as raw text if
30
+ # they are used as span-level elements (an equivalent block-level parser should probably also be
31
+ # made to handle the block case):
32
+ #
33
+ # require 'kramdown/parser/kramdown'
34
+ #
35
+ # class Kramdown::Parser::ERBKramdown < Kramdown::Parser::Kramdown
36
+ #
37
+ # def initialize(source, options)
38
+ # super
39
+ # @span_parsers.unshift(:erb_tags)
40
+ # end
41
+ #
42
+ # ERB_TAGS_START = /<%.*?%>/
43
+ #
44
+ # def parse_erb_tags
45
+ # @src.pos += @src.matched_size
46
+ # @tree.children << Element.new(:raw, @src.matched)
47
+ # end
48
+ # define_parser(:erb_tags, ERB_TAGS_START, '<%')
49
+ #
50
+ # end
51
+ #
52
+ # The new parser can be used like this:
53
+ #
54
+ # require 'kramdown/document'
55
+ # # require the file with the above parser class
56
+ #
57
+ # Kramdown::Document.new(input_text, :input => 'ERBKramdown').to_html
58
+ #
59
+ class Kramdown < Base
60
+
61
+ include ::Kramdown
62
+
63
+ # Create a new Kramdown parser object with the given +options+.
64
+ def initialize(source, options)
65
+ super
66
+
67
+ reset_env
68
+
69
+ @root.options[:abbrev_defs] = {}
70
+ @alds = {}
71
+ @footnotes = {}
72
+ @link_defs = {}
73
+ update_link_definitions(@options[:link_defs])
74
+
75
+ @block_parsers = [:blank_line, :codeblock, :codeblock_fenced, :blockquote, :atx_header,
76
+ :horizontal_rule, :list, :definition_list, :block_html, :setext_header,
77
+ :block_math, :table, :footnote_definition, :link_definition, :abbrev_definition,
78
+ :block_extensions, :eob_marker, :paragraph]
79
+ @span_parsers = [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link, :smart_quotes, :inline_math,
80
+ :span_extensions, :html_entity, :typographic_syms, :line_break, :escaped_chars]
81
+
82
+ end
83
+ private_class_method(:new, :allocate)
84
+
85
+
86
+ # The source string provided on initialization is parsed into the @root element.
87
+ def parse
88
+ configure_parser
89
+ parse_blocks(@root, adapt_source(source))
90
+ update_tree(@root)
91
+ replace_abbreviations(@root)
92
+ @footnotes.each {|name,data| update_tree(data[:content])}
93
+ end
94
+
95
+ #######
96
+ protected
97
+ #######
98
+
99
+ # :doc:
100
+ #
101
+ # Update the parser specific link definitions with the data from +link_defs+ (the value of the
102
+ # :link_defs option).
103
+ #
104
+ # The parameter +link_defs+ is a hash where the keys are possibly unnormalized link IDs and
105
+ # the values are two element arrays consisting of the link target and a title (can be +nil+).
106
+ def update_link_definitions(link_defs)
107
+ link_defs.each {|k,v| @link_defs[normalize_link_id(k)] = v}
108
+ end
109
+
110
+ # Adapt the object to allow parsing like specified in the options.
111
+ def configure_parser
112
+ @parsers = {}
113
+ (@block_parsers + @span_parsers).each do |name|
114
+ if self.class.has_parser?(name)
115
+ @parsers[name] = self.class.parser(name)
116
+ else
117
+ raise Kramdown::Error, "Unknown parser: #{name}"
118
+ end
119
+ end
120
+ @span_start, @span_start_re = span_parser_regexps
121
+ end
122
+
123
+ # Create the needed span parser regexps.
124
+ def span_parser_regexps(parsers = @span_parsers)
125
+ span_start = /#{parsers.map {|name| @parsers[name].span_start}.join('|')}/
126
+ [span_start, /(?=#{span_start})/]
127
+ end
128
+
129
+ # Parse all block-level elements in +text+ into the element +el+.
130
+ def parse_blocks(el, text = nil)
131
+ @stack.push([@tree, @src, @block_ial])
132
+ @tree, @block_ial = el, nil
133
+ @src = (text.nil? ? @src : ::Kramdown::Utils::StringScanner.new(text, el.options[:location]))
134
+
135
+ status = catch(:stop_block_parsing) do
136
+ while !@src.eos?
137
+ block_ial_set = @block_ial
138
+ @block_parsers.any? do |name|
139
+ if @src.check(@parsers[name].start_re)
140
+ send(@parsers[name].method)
141
+ else
142
+ false
143
+ end
144
+ end || begin
145
+ warning('Warning: this should not occur - no block parser handled the line')
146
+ add_text(@src.scan(/.*\n/))
147
+ end
148
+ @block_ial = nil if block_ial_set
149
+ end
150
+ end
151
+
152
+ @tree, @src, @block_ial = *@stack.pop
153
+ status
154
+ end
155
+
156
+ # Update the tree by parsing all :+raw_text+ elements with the span-level parser (resets the
157
+ # environment) and by updating the attributes from the IALs.
158
+ def update_tree(element)
159
+ last_blank = nil
160
+ element.children.map! do |child|
161
+ if child.type == :raw_text
162
+ last_blank = nil
163
+ reset_env(:src => ::Kramdown::Utils::StringScanner.new(child.value, element.options[:location]),
164
+ :text_type => :text)
165
+ parse_spans(child)
166
+ child.children
167
+ elsif child.type == :eob
168
+ []
169
+ elsif child.type == :blank
170
+ if last_blank
171
+ last_blank.value << child.value
172
+ []
173
+ else
174
+ last_blank = child
175
+ child
176
+ end
177
+ else
178
+ last_blank = nil
179
+ update_tree(child)
180
+ update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
181
+ update_raw_header_text(child) if child.type == :header
182
+ child
183
+ end
184
+ end.flatten!
185
+ end
186
+
187
+ # Parse all span-level elements in the source string of @src into +el+.
188
+ #
189
+ # If the parameter +stop_re+ (a regexp) is used, parsing is immediately stopped if the regexp
190
+ # matches and if no block is given or if a block is given and it returns +true+.
191
+ #
192
+ # The parameter +parsers+ can be used to specify the (span-level) parsing methods that should
193
+ # be used for parsing.
194
+ #
195
+ # The parameter +text_type+ specifies the type which should be used for created text nodes.
196
+ def parse_spans(el, stop_re = nil, parsers = nil, text_type = @text_type)
197
+ @stack.push([@tree, @text_type]) unless @tree.nil?
198
+ @tree, @text_type = el, text_type
199
+
200
+ span_start = @span_start
201
+ span_start_re = @span_start_re
202
+ span_start, span_start_re = span_parser_regexps(parsers) if parsers
203
+ parsers = parsers || @span_parsers
204
+
205
+ used_re = (stop_re.nil? ? span_start_re : /(?=#{Regexp.union(stop_re, span_start)})/)
206
+ stop_re_found = false
207
+ while !@src.eos? && !stop_re_found
208
+ if result = @src.scan_until(used_re)
209
+ add_text(result)
210
+ if stop_re && @src.check(stop_re)
211
+ stop_re_found = (block_given? ? yield : true)
212
+ end
213
+ processed = parsers.any? do |name|
214
+ if @src.check(@parsers[name].start_re)
215
+ send(@parsers[name].method)
216
+ true
217
+ else
218
+ false
219
+ end
220
+ end unless stop_re_found
221
+ add_text(@src.getch) if !processed && !stop_re_found
222
+ else
223
+ (add_text(@src.rest); @src.terminate) unless stop_re
224
+ break
225
+ end
226
+ end
227
+
228
+ @tree, @text_type = @stack.pop
229
+
230
+ stop_re_found
231
+ end
232
+
233
+ # Reset the current parsing environment. The parameter +env+ can be used to set initial
234
+ # values for one or more environment variables.
235
+ def reset_env(opts = {})
236
+ opts = {:text_type => :raw_text, :stack => []}.merge(opts)
237
+ @src = opts[:src]
238
+ @tree = opts[:tree]
239
+ @block_ial = opts[:block_ial]
240
+ @stack = opts[:stack]
241
+ @text_type = opts[:text_type]
242
+ end
243
+
244
+ # Return the current parsing environment.
245
+ def save_env
246
+ [@src, @tree, @block_ial, @stack, @text_type]
247
+ end
248
+
249
+ # Restore the current parsing environment.
250
+ def restore_env(env)
251
+ @src, @tree, @block_ial, @stack, @text_type = *env
252
+ end
253
+
254
+ # Update the given attributes hash +attr+ with the information from the inline attribute list
255
+ # +ial+ and all referenced ALDs.
256
+ def update_attr_with_ial(attr, ial)
257
+ ial[:refs].each do |ref|
258
+ update_attr_with_ial(attr, ref) if ref = @alds[ref]
259
+ end if ial[:refs]
260
+ ial.each do |k,v|
261
+ if k == IAL_CLASS_ATTR
262
+ attr[k] = (attr[k] || '') << " #{v}"
263
+ attr[k].lstrip!
264
+ elsif k.kind_of?(String)
265
+ attr[k] = v
266
+ end
267
+ end
268
+ end
269
+
270
+ # Update the raw header text for automatic ID generation.
271
+ def update_raw_header_text(header)
272
+ # DEPRECATED: option auto_id_stripping will be removed in 2.0 because then this will be the
273
+ # default behaviour
274
+ return unless @options[:auto_id_stripping]
275
+ raw_text = ''
276
+
277
+ append_text = lambda do |child|
278
+ if child.type == :text
279
+ raw_text << child.value
280
+ else
281
+ child.children.each {|c| append_text.call(c)}
282
+ end
283
+ end
284
+
285
+ append_text.call(header)
286
+ header.options[:raw_text] = raw_text
287
+ end
288
+
289
+ # Create a new block-level element, taking care of applying a preceding block IAL if it
290
+ # exists. This method should always be used for creating a block-level element!
291
+ def new_block_el(*args)
292
+ el = Element.new(*args)
293
+ el.options[:ial] = @block_ial if @block_ial && el.type != :blank && el.type != :eob
294
+ el
295
+ end
296
+
297
+ @@parsers = {}
298
+
299
+ # Struct class holding all the needed data for one block/span-level parser method.
300
+ Data = Struct.new(:name, :start_re, :span_start, :method)
301
+
302
+ # Add a parser method
303
+ #
304
+ # * with the given +name+,
305
+ # * using +start_re+ as start regexp
306
+ # * and, for span parsers, +span_start+ as a String that can be used in a regexp and
307
+ # which identifies the starting character(s)
308
+ #
309
+ # to the registry. The method name is automatically derived from the +name+ or can explicitly
310
+ # be set by using the +meth_name+ parameter.
311
+ def self.define_parser(name, start_re, span_start = nil, meth_name = "parse_#{name}")
312
+ raise "A parser with the name #{name} already exists!" if @@parsers.has_key?(name)
313
+ @@parsers[name] = Data.new(name, start_re, span_start, meth_name)
314
+ end
315
+
316
+ # Return the Data structure for the parser +name+.
317
+ def self.parser(name = nil)
318
+ @@parsers[name]
319
+ end
320
+
321
+ # Return +true+ if there is a parser called +name+.
322
+ def self.has_parser?(name)
323
+ @@parsers.has_key?(name)
324
+ end
325
+
326
+ # Regexp for matching indentation (one tab or four spaces)
327
+ INDENT = /^(?:\t| {4})/m # RM Oniguruma -> ICU
328
+ # Regexp for matching the optional space (zero or up to three spaces)
329
+ OPT_SPACE = / {0,3}/
330
+
331
+ # RM require 'kramdown/parser/kramdown/blank_line'
332
+ # RM require 'kramdown/parser/kramdown/eob'
333
+ # RM require 'kramdown/parser/kramdown/paragraph'
334
+ # RM require 'kramdown/parser/kramdown/header'
335
+ # RM require 'kramdown/parser/kramdown/blockquote'
336
+ # RM require 'kramdown/parser/kramdown/table'
337
+ # RM require 'kramdown/parser/kramdown/codeblock'
338
+ # RM require 'kramdown/parser/kramdown/horizontal_rule'
339
+ # RM require 'kramdown/parser/kramdown/list'
340
+ # RM require 'kramdown/parser/kramdown/link'
341
+ # RM require 'kramdown/parser/kramdown/extensions'
342
+ # RM require 'kramdown/parser/kramdown/footnote'
343
+ # RM require 'kramdown/parser/kramdown/html'
344
+ # RM require 'kramdown/parser/kramdown/escaped_chars'
345
+ # RM require 'kramdown/parser/kramdown/html_entity'
346
+ # RM require 'kramdown/parser/kramdown/line_break'
347
+ # RM require 'kramdown/parser/kramdown/typographic_symbol'
348
+ # RM require 'kramdown/parser/kramdown/autolink'
349
+ # RM require 'kramdown/parser/kramdown/codespan'
350
+ # RM require 'kramdown/parser/kramdown/emphasis'
351
+ # RM require 'kramdown/parser/kramdown/smart_quotes'
352
+ # RM require 'kramdown/parser/kramdown/math'
353
+ # RM require 'kramdown/parser/kramdown/abbreviation'
354
+
355
+ end
356
+
357
+ end
358
+
359
+ end
@@ -0,0 +1,56 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ #--
4
+ # Copyright (C) 2009-2014 Thomas Leitner <t_leitner@gmx.at>
5
+ #
6
+ # This file is part of kramdown which is licensed under the MIT.
7
+ #++
8
+ #
9
+
10
+ # RM require 'kramdown/parser/kramdown'
11
+
12
+ module Kramdown
13
+
14
+ module Parser
15
+
16
+ # Used for parsing a document in Markdown format.
17
+ #
18
+ # This parser is based on the kramdown parser and removes the parser methods for the additional
19
+ # non-Markdown features. However, since some things are handled differently by the kramdown
20
+ # parser methods (like deciding when a list item contains just text), this parser differs from
21
+ # real Markdown parsers in some respects.
22
+ #
23
+ # Note, though, that the parser basically fails just one of the Markdown test cases (some others
24
+ # also fail but those failures are negligible).
25
+ class Markdown < Kramdown
26
+
27
+ # Array with all the parsing methods that should be removed from the standard kramdown parser.
28
+ EXTENDED = [:codeblock_fenced, :table, :definition_list, :footnote_definition, :abbrev_definition, :block_math,
29
+ :block_extensions,
30
+ :footnote_marker, :smart_quotes, :inline_math, :span_extensions, :typographic_syms]
31
+
32
+ def initialize(source, options) # :nodoc:
33
+ super
34
+ @block_parsers.delete_if {|i| EXTENDED.include?(i)}
35
+ @span_parsers.delete_if {|i| EXTENDED.include?(i)}
36
+ end
37
+
38
+ # :stopdoc:
39
+
40
+ BLOCK_BOUNDARY = /#{BLANK_LINE}|#{EOB_MARKER}|\Z/
41
+ LAZY_END = /#{BLANK_LINE}|#{EOB_MARKER}|^#{OPT_SPACE}#{LAZY_END_HTML_STOP}|^#{OPT_SPACE}#{LAZY_END_HTML_START}|\Z/
42
+ CODEBLOCK_MATCH = /(?:#{BLANK_LINE}?(?:#{INDENT}[ \t]*\S.*\n)+)*/
43
+ PARAGRAPH_END = LAZY_END
44
+
45
+ IAL_RAND_CHARS = (('a'..'z').to_a + ('0'..'9').to_a)
46
+ IAL_RAND_STRING = (1..20).collect {|a| IAL_RAND_CHARS[rand(IAL_RAND_CHARS.size)]}.join
47
+ LIST_ITEM_IAL = /^\s*(#{IAL_RAND_STRING})?\s*\n/
48
+ IAL_SPAN_START = LIST_ITEM_IAL
49
+
50
+ # :startdoc:
51
+
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,27 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ #--
4
+ # Copyright (C) 2009-2014 Thomas Leitner <t_leitner@gmx.at>
5
+ #
6
+ # This file is part of kramdown which is licensed under the MIT.
7
+ #++
8
+ #
9
+
10
+ module Kramdown
11
+
12
+ # This module contains all available parsers. A parser takes an input string and converts the
13
+ # string to an element tree.
14
+ #
15
+ # New parsers should be derived from the Base class which provides common functionality - see its
16
+ # API documentation for how to create a custom converter class.
17
+ module Parser
18
+
19
+ # RM autoload :Base, 'kramdown/parser/base'
20
+ # RM autoload :Kramdown, 'kramdown/parser/kramdown'
21
+ # RM autoload :Html, 'kramdown/parser/html'
22
+ # RM autoload :Markdown, 'kramdown/parser/markdown'
23
+ # RM autoload :GFM, 'kramdown/parser/gfm'
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ #--
4
+ # Copyright (C) 2009-2014 Thomas Leitner <t_leitner@gmx.at>
5
+ #
6
+ # This file is part of kramdown which is licensed under the MIT.
7
+ #++
8
+ #
9
+
10
+ module Kramdown
11
+ module Utils
12
+
13
+ # Methods for registering configurable extensions.
14
+ module Configurable
15
+
16
+ # Create a new configurable extension called +name+.
17
+ #
18
+ # Three methods will be defined on the calling object which allow to use this configurable
19
+ # extension:
20
+ #
21
+ # configurables:: Returns a hash of hashes that is used to store all configurables of the
22
+ # object.
23
+ #
24
+ # <name>(ext_name):: Return the configured extension +ext_name+.
25
+ #
26
+ # add_<name>(ext_name, data=nil, &block):: Define an extension +ext_name+ by specifying either
27
+ # the data as argument or by using a block.
28
+ def configurable(name)
29
+ singleton_class = (class << self; self; end)
30
+ singleton_class.send(:define_method, :configurables) do
31
+ @_configurables ||= Hash.new {|h, k| h[k] = {}}
32
+ end
33
+ singleton_class.send(:define_method, name) do |data|
34
+ configurables[name][data]
35
+ end
36
+ singleton_class.send(:define_method, "add_#{name}".intern) do |data, *args, &block|
37
+ configurables[name][data] = args.first || block
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end