padrino-helpers-cj 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +21 -0
  4. data/.yardopts +1 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +239 -0
  7. data/Rakefile +5 -0
  8. data/lib/padrino-helpers.rb +62 -0
  9. data/lib/padrino-helpers/asset_tag_helpers.rb +390 -0
  10. data/lib/padrino-helpers/form_builder/abstract_form_builder.rb +283 -0
  11. data/lib/padrino-helpers/form_builder/deprecated_builder_methods.rb +92 -0
  12. data/lib/padrino-helpers/form_builder/standard_form_builder.rb +40 -0
  13. data/lib/padrino-helpers/form_helpers.rb +633 -0
  14. data/lib/padrino-helpers/form_helpers/errors.rb +138 -0
  15. data/lib/padrino-helpers/form_helpers/options.rb +97 -0
  16. data/lib/padrino-helpers/form_helpers/security.rb +70 -0
  17. data/lib/padrino-helpers/format_helpers.rb +372 -0
  18. data/lib/padrino-helpers/locale/cs.yml +103 -0
  19. data/lib/padrino-helpers/locale/da.yml +91 -0
  20. data/lib/padrino-helpers/locale/de.yml +81 -0
  21. data/lib/padrino-helpers/locale/en.yml +103 -0
  22. data/lib/padrino-helpers/locale/es.yml +103 -0
  23. data/lib/padrino-helpers/locale/fr.yml +79 -0
  24. data/lib/padrino-helpers/locale/hu.yml +103 -0
  25. data/lib/padrino-helpers/locale/it.yml +89 -0
  26. data/lib/padrino-helpers/locale/ja.yml +103 -0
  27. data/lib/padrino-helpers/locale/lv.yml +103 -0
  28. data/lib/padrino-helpers/locale/nl.yml +82 -0
  29. data/lib/padrino-helpers/locale/no.yml +91 -0
  30. data/lib/padrino-helpers/locale/pl.yml +95 -0
  31. data/lib/padrino-helpers/locale/pt_br.yml +103 -0
  32. data/lib/padrino-helpers/locale/ro.yml +103 -0
  33. data/lib/padrino-helpers/locale/ru.yml +103 -0
  34. data/lib/padrino-helpers/locale/sv.yml +103 -0
  35. data/lib/padrino-helpers/locale/tr.yml +103 -0
  36. data/lib/padrino-helpers/locale/uk.yml +103 -0
  37. data/lib/padrino-helpers/locale/zh_cn.yml +103 -0
  38. data/lib/padrino-helpers/locale/zh_tw.yml +103 -0
  39. data/lib/padrino-helpers/number_helpers.rb +283 -0
  40. data/lib/padrino-helpers/output_helpers.rb +226 -0
  41. data/lib/padrino-helpers/output_helpers/abstract_handler.rb +61 -0
  42. data/lib/padrino-helpers/output_helpers/erb_handler.rb +27 -0
  43. data/lib/padrino-helpers/output_helpers/haml_handler.rb +25 -0
  44. data/lib/padrino-helpers/output_helpers/slim_handler.rb +18 -0
  45. data/lib/padrino-helpers/render_helpers.rb +63 -0
  46. data/lib/padrino-helpers/tag_helpers.rb +294 -0
  47. data/lib/padrino-helpers/translation_helpers.rb +36 -0
  48. data/lib/padrino/rendering.rb +397 -0
  49. data/lib/padrino/rendering/erubis_template.rb +66 -0
  50. data/lib/padrino/rendering/haml_template.rb +26 -0
  51. data/lib/padrino/rendering/slim_template.rb +20 -0
  52. data/padrino-helpers.gemspec +29 -0
  53. data/test/fixtures/apps/.components +6 -0
  54. data/test/fixtures/apps/.gitignore +7 -0
  55. data/test/fixtures/apps/render.rb +25 -0
  56. data/test/fixtures/apps/views/article/comment/show.slim +1 -0
  57. data/test/fixtures/apps/views/blog/post.erb +1 -0
  58. data/test/fixtures/apps/views/layouts/specific.erb +1 -0
  59. data/test/fixtures/apps/views/test/post.erb +1 -0
  60. data/test/fixtures/layouts/layout.erb +1 -0
  61. data/test/fixtures/markup_app/app.rb +87 -0
  62. data/test/fixtures/markup_app/views/button_to.erb +8 -0
  63. data/test/fixtures/markup_app/views/button_to.haml +5 -0
  64. data/test/fixtures/markup_app/views/button_to.slim +6 -0
  65. data/test/fixtures/markup_app/views/capture_concat.erb +14 -0
  66. data/test/fixtures/markup_app/views/capture_concat.haml +12 -0
  67. data/test/fixtures/markup_app/views/capture_concat.slim +12 -0
  68. data/test/fixtures/markup_app/views/content_for.erb +23 -0
  69. data/test/fixtures/markup_app/views/content_for.haml +19 -0
  70. data/test/fixtures/markup_app/views/content_for.slim +19 -0
  71. data/test/fixtures/markup_app/views/content_tag.erb +13 -0
  72. data/test/fixtures/markup_app/views/content_tag.haml +11 -0
  73. data/test/fixtures/markup_app/views/content_tag.slim +11 -0
  74. data/test/fixtures/markup_app/views/current_engine.erb +5 -0
  75. data/test/fixtures/markup_app/views/current_engine.haml +5 -0
  76. data/test/fixtures/markup_app/views/current_engine.slim +5 -0
  77. data/test/fixtures/markup_app/views/fields_for.erb +20 -0
  78. data/test/fixtures/markup_app/views/fields_for.haml +15 -0
  79. data/test/fixtures/markup_app/views/fields_for.slim +15 -0
  80. data/test/fixtures/markup_app/views/form_for.erb +72 -0
  81. data/test/fixtures/markup_app/views/form_for.haml +59 -0
  82. data/test/fixtures/markup_app/views/form_for.slim +59 -0
  83. data/test/fixtures/markup_app/views/form_tag.erb +95 -0
  84. data/test/fixtures/markup_app/views/form_tag.haml +78 -0
  85. data/test/fixtures/markup_app/views/form_tag.slim +79 -0
  86. data/test/fixtures/markup_app/views/link_to.erb +5 -0
  87. data/test/fixtures/markup_app/views/link_to.haml +4 -0
  88. data/test/fixtures/markup_app/views/link_to.slim +4 -0
  89. data/test/fixtures/markup_app/views/mail_to.erb +3 -0
  90. data/test/fixtures/markup_app/views/mail_to.haml +3 -0
  91. data/test/fixtures/markup_app/views/mail_to.slim +3 -0
  92. data/test/fixtures/markup_app/views/meta_tag.erb +3 -0
  93. data/test/fixtures/markup_app/views/meta_tag.haml +3 -0
  94. data/test/fixtures/markup_app/views/meta_tag.slim +3 -0
  95. data/test/fixtures/markup_app/views/partials/_erb.erb +1 -0
  96. data/test/fixtures/markup_app/views/partials/_haml.haml +1 -0
  97. data/test/fixtures/markup_app/views/partials/_slim.slim +1 -0
  98. data/test/fixtures/markup_app/views/simple_partial.erb +1 -0
  99. data/test/fixtures/markup_app/views/simple_partial.haml +1 -0
  100. data/test/fixtures/markup_app/views/simple_partial.slim +1 -0
  101. data/test/fixtures/render_app/app.rb +94 -0
  102. data/test/fixtures/render_app/views/_deep.erb +3 -0
  103. data/test/fixtures/render_app/views/_deep.haml +2 -0
  104. data/test/fixtures/render_app/views/_deep.slim +2 -0
  105. data/test/fixtures/render_app/views/_partial_block_erb.erb +10 -0
  106. data/test/fixtures/render_app/views/_partial_block_haml.haml +7 -0
  107. data/test/fixtures/render_app/views/_partial_block_slim.slim +7 -0
  108. data/test/fixtures/render_app/views/_unsafe.html.builder +2 -0
  109. data/test/fixtures/render_app/views/_unsafe_object.html.builder +2 -0
  110. data/test/fixtures/render_app/views/current_engine.haml +5 -0
  111. data/test/fixtures/render_app/views/current_engines/_erb.erb +1 -0
  112. data/test/fixtures/render_app/views/current_engines/_haml.haml +1 -0
  113. data/test/fixtures/render_app/views/current_engines/_slim.slim +1 -0
  114. data/test/fixtures/render_app/views/double_capture_erb.erb +3 -0
  115. data/test/fixtures/render_app/views/double_capture_haml.haml +2 -0
  116. data/test/fixtures/render_app/views/double_capture_slim.slim +2 -0
  117. data/test/fixtures/render_app/views/erb/test.erb +1 -0
  118. data/test/fixtures/render_app/views/explicit_engine.haml +5 -0
  119. data/test/fixtures/render_app/views/haml/test.haml +1 -0
  120. data/test/fixtures/render_app/views/render_block_erb.erb +5 -0
  121. data/test/fixtures/render_app/views/render_block_haml.haml +4 -0
  122. data/test/fixtures/render_app/views/render_block_slim.slim +4 -0
  123. data/test/fixtures/render_app/views/ruby_block_capture_erb.erb +1 -0
  124. data/test/fixtures/render_app/views/ruby_block_capture_haml.haml +1 -0
  125. data/test/fixtures/render_app/views/ruby_block_capture_slim.slim +1 -0
  126. data/test/fixtures/render_app/views/template/_user.haml +7 -0
  127. data/test/fixtures/render_app/views/template/haml_template.haml +1 -0
  128. data/test/fixtures/render_app/views/template/some_template.haml +2 -0
  129. data/test/fixtures/render_app/views/wrong_capture_erb.erb +3 -0
  130. data/test/fixtures/render_app/views/wrong_capture_haml.haml +2 -0
  131. data/test/fixtures/render_app/views/wrong_capture_slim.slim +2 -0
  132. data/test/helper.rb +131 -0
  133. data/test/test_asset_tag_helpers.rb +406 -0
  134. data/test/test_form_builder.rb +1216 -0
  135. data/test/test_form_helpers.rb +1056 -0
  136. data/test/test_format_helpers.rb +251 -0
  137. data/test/test_helpers.rb +10 -0
  138. data/test/test_locale.rb +20 -0
  139. data/test/test_number_helpers.rb +142 -0
  140. data/test/test_output_helpers.rb +157 -0
  141. data/test/test_render_helpers.rb +215 -0
  142. data/test/test_rendering.rb +694 -0
  143. data/test/test_rendering_extensions.rb +14 -0
  144. data/test/test_tag_helpers.rb +131 -0
  145. metadata +241 -0
@@ -0,0 +1,61 @@
1
+ module Padrino
2
+ module Helpers
3
+ module OutputHelpers
4
+ class AbstractHandler
5
+ attr_reader :template, :output_buffer
6
+
7
+ def initialize(template)
8
+ @template = template
9
+ @output_buffer = template.instance_variable_get(:@_out_buf)
10
+ end
11
+
12
+ ##
13
+ # Returns true if the block given is of the handler's template type; false otherwise.
14
+ #
15
+ # @example
16
+ # @handler.engine_matches?(block) => true
17
+ #
18
+ def engine_matches?(block)
19
+ end
20
+
21
+ ##
22
+ # Captures the html from a block of template code for this handler.
23
+ #
24
+ # This method is called to capture content of a block-loving helpers in templates.
25
+ # Haml has a special method to do this, for Erb and Slim we save original buffer,
26
+ # call the block and then restore the buffer.
27
+ #
28
+ # @example
29
+ # @handler.capture_from_template(&block) => "...html..."
30
+ #
31
+ def capture_from_template(*args, &block)
32
+ self.output_buffer, _buf_was = ActiveSupport::SafeBuffer.new, self.output_buffer
33
+ raw = block.call(*args)
34
+ captured = template.instance_variable_get(:@_out_buf)
35
+ self.output_buffer = _buf_was
36
+ engine_matches?(block) && !captured.empty? ? captured : raw.to_s
37
+ end
38
+
39
+ ##
40
+ # Outputs the given text to the template.
41
+ #
42
+ # This method is called when template uses block-aware helpers. For Slim and Haml such
43
+ # helpers just return output to use with `=`. For Erb this method is implemented in
44
+ # ErbHandler by concatenating given text to output buffer.
45
+ #
46
+ # @example
47
+ # @handler.concat_to_template("This will be output to the template buffer")
48
+ #
49
+ def concat_to_template(text="")
50
+ text
51
+ end
52
+
53
+ protected
54
+
55
+ def output_buffer=(val)
56
+ template.instance_variable_set(:@_out_buf, val)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,27 @@
1
+ module Padrino
2
+ module Helpers
3
+ module OutputHelpers
4
+ ##
5
+ # Handler for Erb template.
6
+ #
7
+ class ErbHandler < AbstractHandler
8
+ ##
9
+ # Outputs the given text to the templates buffer directly.
10
+ #
11
+ def concat_to_template(text="")
12
+ output_buffer << text
13
+ nil
14
+ end
15
+
16
+ ##
17
+ # Returns true if the block is Erb.
18
+ #
19
+ def engine_matches?(block)
20
+ block.binding.eval('defined? __in_erb_template')
21
+ end
22
+ end
23
+ OutputHelpers.register(:erb, ErbHandler)
24
+ OutputHelpers.register(:erubis, ErbHandler)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ module Padrino
2
+ module Helpers
3
+ module OutputHelpers
4
+ ##
5
+ # Handler for Haml templates.
6
+ #
7
+ class HamlHandler < AbstractHandler
8
+ ##
9
+ # Returns true if the block is for Haml
10
+ #
11
+ def engine_matches?(block)
12
+ template.block_is_haml?(block)
13
+ end
14
+
15
+ ##
16
+ # Captures the html from a block of template code for this handler.
17
+ #
18
+ def capture_from_template(*args, &block)
19
+ engine_matches?(block) ? template.capture_haml(*args, &block) : block.call(*args)
20
+ end
21
+ end
22
+ OutputHelpers.register(:haml, HamlHandler)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ module Padrino
2
+ module Helpers
3
+ module OutputHelpers
4
+ ##
5
+ # Handler for Slim templates.
6
+ #
7
+ class SlimHandler < AbstractHandler
8
+ ##
9
+ # Returns true if the block is for Slim.
10
+ #
11
+ def engine_matches?(block)
12
+ block.binding.eval('defined? __in_slim_template')
13
+ end
14
+ end
15
+ OutputHelpers.register(:slim, SlimHandler)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,63 @@
1
+ module Padrino
2
+ module Helpers
3
+ ##
4
+ # Helpers related to rendering within templates (i.e partials).
5
+ #
6
+ module RenderHelpers
7
+ ##
8
+ # Render a partials with collections support.
9
+ #
10
+ # @param [String] template
11
+ # Relative path to partial template.
12
+ # @param [Hash] options
13
+ # Options hash for rendering options.
14
+ # @option options [Object] :object
15
+ # Object rendered in partial.
16
+ # @option options [Array<Object>] :collection
17
+ # Partial is rendered for each object in this collection.
18
+ # @option options [Hash] :locals ({})
19
+ # Local variables accessible in the partial.
20
+ # @option options [Symbol] :engine
21
+ # Explicit rendering engine to use for this partial.
22
+ #
23
+ # @return [String] The html generated from this partial.
24
+ #
25
+ # @example
26
+ # partial 'photo/item', :object => @photo
27
+ # partial 'photo/item', :collection => @photos
28
+ # partial 'photo/item', :locals => { :foo => :bar }
29
+ # partial 'photo/item', :engine => :erb
30
+ #
31
+ # @note If using this from Sinatra, pass explicit +:engine+ option
32
+ #
33
+ def partial(template, options={}, &block)
34
+ options = { :locals => {}, :layout => false }.update(options)
35
+ explicit_engine = options.delete(:engine)
36
+
37
+ path,_,name = template.to_s.rpartition(File::SEPARATOR)
38
+ template_path = File.join(path,"_#{name}").to_sym
39
+ object_name = name.partition('.').first.to_sym
40
+
41
+ objects, counter = if options[:collection].respond_to?(:inject)
42
+ [options.delete(:collection), 0]
43
+ else
44
+ [[options.delete(:object)], nil]
45
+ end
46
+
47
+ locals = options[:locals]
48
+ objects.each_with_object(ActiveSupport::SafeBuffer.new) do |object,html|
49
+ locals[object_name] = object if object
50
+ locals["#{object_name}_counter".to_sym] = counter += 1 if counter
51
+ content =
52
+ if block_given?
53
+ concat_content render(explicit_engine, template_path, options){ capture_html(&block) }
54
+ else
55
+ render(explicit_engine, template_path, options)
56
+ end
57
+ html.safe_concat content if content
58
+ end
59
+ end
60
+ alias :render_partial :partial
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,294 @@
1
+ module Padrino
2
+ module Helpers
3
+ ##
4
+ # Helpers related to producing html tags within templates.
5
+ #
6
+ module TagHelpers
7
+ ##
8
+ # Tag values escaped to html entities.
9
+ #
10
+ ESCAPE_VALUES = {
11
+ "&" => "&amp;",
12
+ "<" => "&lt;",
13
+ ">" => "&gt;",
14
+ '"' => "&quot;"
15
+ }.freeze
16
+
17
+ ##
18
+ # Cached Regexp for escaping values to avoid rebuilding one
19
+ # on every escape operation.
20
+ #
21
+ ESCAPE_REGEXP = Regexp.union(*ESCAPE_VALUES.keys).freeze
22
+
23
+ BOOLEAN_ATTRIBUTES = [
24
+ :autoplay,
25
+ :autofocus,
26
+ :formnovalidate,
27
+ :checked,
28
+ :disabled,
29
+ :hidden,
30
+ :loop,
31
+ :multiple,
32
+ :muted,
33
+ :readonly,
34
+ :required,
35
+ :selected,
36
+ :declare,
37
+ :defer,
38
+ :ismap,
39
+ :itemscope,
40
+ :noresize,
41
+ :novalidate
42
+ ].freeze
43
+
44
+ ##
45
+ # Custom data attributes,
46
+ # feel free to update with yours:
47
+ #
48
+ # Padrino::Helpers::TagHelpers::DATA_ATTRIBUTES.push(:dialog)
49
+ # text_field :foo, :dialog => true
50
+ # # Generates: <input type="text" data-dialog="true" name="foo" />
51
+ #
52
+ DATA_ATTRIBUTES = [
53
+ :method,
54
+ :remote,
55
+ :confirm
56
+ ].freeze
57
+
58
+ ##
59
+ # A html_safe newline string to avoid allocating a new on each
60
+ # concatenation.
61
+ #
62
+ NEWLINE = "\n".html_safe.freeze
63
+
64
+ ##
65
+ # Creates an HTML tag with given name, content, and options.
66
+ #
67
+ # @overload content_tag(name, content, options = nil)
68
+ # @param [Symbol] name
69
+ # The name of the HTML tag to create.
70
+ # @param [String] content
71
+ # The content inside of the tag.
72
+ # @param [Hash] options
73
+ # The HTML options to include in this tag.
74
+ #
75
+ # @overload content_tag(name, options = nil, &block)
76
+ # @param [Symbol] name
77
+ # The name of the HTML tag to create.
78
+ # @param [Hash] options
79
+ # The HTML options to include in this tag.
80
+ # @param [Proc] block
81
+ # The block returning HTML content.
82
+ #
83
+ # @macro [new] global_html_attributes
84
+ # @option options [String] :id
85
+ # Specifies a unique identifier for the element.
86
+ # @option options [String] :class
87
+ # Specifies the stylesheet class of the element.
88
+ # @option options [String] :title
89
+ # Specifies the title for the element.
90
+ # @option options [String] :accesskey
91
+ # Specifies a shortcut key to access the element.
92
+ # @option options [Symbol] :dropzone
93
+ # Specifies what happens when dragged items are dropped on the element. (:copy, :link, :move)
94
+ # @option options [Boolean] :hidden
95
+ # Specifies whether or not the element is hidden from view.
96
+ # @option options [Boolean] :draggable
97
+ # Specifies whether or not the element is draggable. (true, false, :auto)
98
+ # @option options [Boolean] :contenteditable
99
+ # Specifies whether or not the element is editable.
100
+ #
101
+ # @return [String]
102
+ # Generated HTML with specified +options+.
103
+ #
104
+ # @example
105
+ # content_tag(:p, 'Hello World', :class => 'light')
106
+ #
107
+ # # => <p class="light">
108
+ # # => Hello World
109
+ # # => </p>
110
+ #
111
+ # content_tag(:p, :class => 'dark') do
112
+ # link_to 'Padrino', 'http://www.padrinorb.com'
113
+ # end
114
+ #
115
+ # # => <p class="dark">
116
+ # # => <a href="http://www.padrinorb.com">Padrino</a>
117
+ # # => </p>
118
+ #
119
+ def content_tag(name, content = nil, options = nil, &block)
120
+ if block_given?
121
+ options = content if content.is_a?(Hash)
122
+ content = capture_html(&block)
123
+ end
124
+
125
+ options = parse_data_options(name, options)
126
+ attributes = tag_attributes(options)
127
+ output = ActiveSupport::SafeBuffer.new
128
+ output.safe_concat "<#{name}#{attributes}>"
129
+ if content.respond_to?(:each) && !content.is_a?(String)
130
+ content.each{ |item| output.concat item; output.safe_concat NEWLINE }
131
+ else
132
+ output.concat content.to_s
133
+ end
134
+ output.safe_concat "</#{name}>"
135
+
136
+ block_is_template?(block) ? concat_content(output) : output
137
+ end
138
+
139
+ ##
140
+ # Like #content_tag, but assumes its input to be safe and doesn't
141
+ # escape. It also returns safe HTML.
142
+ #
143
+ # @see #content_tag
144
+ #
145
+ def safe_content_tag(name, content = nil, options = nil, &block)
146
+ mark_safe(content_tag(name, mark_safe(content), options, &block))
147
+ end
148
+
149
+ ##
150
+ # Creates an HTML input field with the given type and options.
151
+ #
152
+ # @param [Symbol] type
153
+ # The type of input to create.
154
+ # @param [Hash] options
155
+ # The HTML options to include in this input.
156
+ #
157
+ # @option options [String] :id
158
+ # Specifies a unique identifier for the input.
159
+ # @option options [String] :class
160
+ # Specifies the stylesheet class of the input.
161
+ # @option options [String] :name
162
+ # Specifies the name of the input.
163
+ # @option options [String] :accesskey
164
+ # Specifies a shortcut key to access the input.
165
+ # @option options [Integer] :tabindex
166
+ # Specifies the tab order of the input.
167
+ # @option options [Boolean] :hidden
168
+ # Specifies whether or not the input is hidden from view.
169
+ # @option options [Boolean] :spellcheck
170
+ # Specifies whether or not the input should have it's spelling and grammar checked for errors.
171
+ # @option options [Boolean] :draggable
172
+ # Specifies whether or not the input is draggable. (true, false, :auto)
173
+ # @option options [String] :pattern
174
+ # Specifies the regular expression pattern that the input's value is checked against.
175
+ # @option options [Symbol] :autocomplete
176
+ # Specifies whether or not the input should have autocomplete enabled. (:on, :off)
177
+ # @option options [Boolean] :autofocus
178
+ # Specifies whether or not the input should automatically get focus when the page loads.
179
+ # @option options [Boolean] :required
180
+ # Specifies whether or not the input is required to be completed before the form is submitted.
181
+ # @option options [Boolean] :readonly
182
+ # Specifies whether or not the input is read only.
183
+ # @option options [Boolean] :disabled
184
+ # Specifies whether or not the input is disabled.
185
+ #
186
+ # @return [String]
187
+ # Generated HTML with specified +options+.
188
+ #
189
+ # @example
190
+ # input_tag :text, :name => 'handle'
191
+ # # => <input type="test" name="handle" />
192
+ #
193
+ # input_tag :password, :name => 'password', :size => 20
194
+ # # => <input type="password" name="password" size="20" />
195
+ #
196
+ # input_tag :text, :name => 'username', :required => true, :autofocus => true
197
+ # # => <input type="text" name="username" required autofocus />
198
+ #
199
+ # input_tag :number, :name => 'credit_card', :autocomplete => :off
200
+ # # => <input type="number" autocomplete="off" />
201
+ #
202
+ def input_tag(type, options = {})
203
+ tag(:input, { :type => type }.update(options))
204
+ end
205
+
206
+ ##
207
+ # Creates an HTML tag with the given name and options.
208
+ #
209
+ # @param [Symbol] name
210
+ # The name of the HTML tag to create.
211
+ # @param [Hash] options
212
+ # The HTML options to include in this tag.
213
+ #
214
+ # @macro global_html_attributes
215
+ #
216
+ # @return [String]
217
+ # Generated HTML with specified +options+.
218
+ #
219
+ # @example
220
+ # tag :hr, :class => 'dotted'
221
+ # # => <hr class="dotted" />
222
+ #
223
+ # tag :input, :name => 'username', :type => :text
224
+ # # => <input name="username" type="text" />
225
+ #
226
+ # tag :img, :src => 'images/pony.jpg', :alt => 'My Little Pony'
227
+ # # => <img src="images/pony.jpg" alt="My Little Pony" />
228
+ #
229
+ # tag :img, :src => 'sinatra.jpg, :data => { :nsfw => false, :geo => [34.087, -118.407] }
230
+ # # => <img src="sinatra.jpg" data-nsfw="false" data-geo="34.087 -118.407" />
231
+ #
232
+ def tag(name, options = nil, open = false)
233
+ options = parse_data_options(name, options)
234
+ attributes = tag_attributes(options)
235
+ "<#{name}#{attributes}#{open ? '>' : ' />'}".html_safe
236
+ end
237
+
238
+ private
239
+
240
+ ##
241
+ # Returns a compiled list of HTML attributes.
242
+ #
243
+ def tag_attributes(options)
244
+ return '' unless options
245
+ options.inject('') do |all,(key,value)|
246
+ next all unless value
247
+ all << ' ' if all.empty?
248
+ all << if value.is_a?(Hash)
249
+ nested_values(key, value)
250
+ elsif BOOLEAN_ATTRIBUTES.include?(key)
251
+ %(#{key}="#{key}" )
252
+ else
253
+ %(#{key}="#{escape_value(value)}" )
254
+ end
255
+ end.chomp!(' ')
256
+ end
257
+
258
+ ##
259
+ # Escape tag values to their HTML/XML entities.
260
+ #
261
+ def escape_value(string)
262
+ string.to_s.gsub(ESCAPE_REGEXP) { |char| ESCAPE_VALUES[char] }
263
+ end
264
+
265
+ ##
266
+ # Iterate through nested values.
267
+ #
268
+ def nested_values(attribute, hash)
269
+ hash.inject('') do |all,(key,value)|
270
+ attribute_with_name = "#{attribute}-#{key.to_s.dasherize}"
271
+ all << if value.is_a?(Hash)
272
+ nested_values(attribute_with_name, value)
273
+ else
274
+ %(#{attribute_with_name}="#{escape_value(value)}" )
275
+ end
276
+ end
277
+ end
278
+
279
+ ##
280
+ # Parses custom data attributes.
281
+ #
282
+ def parse_data_options(tag, options)
283
+ return unless options
284
+ parsed_options = options.dup
285
+ options.each do |key, value|
286
+ next if !DATA_ATTRIBUTES.include?(key) || (tag.to_s == 'form' && key == :method)
287
+ parsed_options["data-#{key}"] = parsed_options.delete(key)
288
+ parsed_options[:rel] = 'nofollow' if key == :method
289
+ end
290
+ parsed_options
291
+ end
292
+ end
293
+ end
294
+ end