padrino-helpers 0.10.7 → 0.11.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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/lib/padrino-helpers.rb +2 -1
  3. data/lib/padrino-helpers/asset_tag_helpers.rb +57 -65
  4. data/lib/padrino-helpers/breadcrumb_helpers.rb +171 -0
  5. data/lib/padrino-helpers/form_builder/abstract_form_builder.rb +82 -24
  6. data/lib/padrino-helpers/form_helpers.rb +84 -17
  7. data/lib/padrino-helpers/format_helpers.rb +2 -1
  8. data/lib/padrino-helpers/locale/fr.yml +12 -12
  9. data/lib/padrino-helpers/locale/pt_br.yml +2 -2
  10. data/lib/padrino-helpers/output_helpers.rb +42 -2
  11. data/lib/padrino-helpers/output_helpers/erb_handler.rb +1 -1
  12. data/lib/padrino-helpers/output_helpers/slim_handler.rb +4 -5
  13. data/lib/padrino-helpers/render_helpers.rb +2 -2
  14. data/lib/padrino-helpers/tag_helpers.rb +33 -5
  15. data/padrino-helpers.gemspec +0 -0
  16. data/test/fixtures/markup_app/app.rb +13 -6
  17. data/test/fixtures/markup_app/views/capture_concat.erb +2 -2
  18. data/test/fixtures/markup_app/views/capture_concat.haml +2 -2
  19. data/test/fixtures/markup_app/views/capture_concat.slim +4 -5
  20. data/test/fixtures/markup_app/views/content_for.slim +4 -4
  21. data/test/fixtures/markup_app/views/content_tag.slim +5 -5
  22. data/test/fixtures/markup_app/views/current_engine.haml +1 -1
  23. data/test/fixtures/markup_app/views/fields_for.slim +13 -13
  24. data/test/fixtures/markup_app/views/form_for.slim +43 -43
  25. data/test/fixtures/markup_app/views/form_tag.slim +57 -57
  26. data/test/fixtures/markup_app/views/link_to.slim +2 -2
  27. data/test/fixtures/markup_app/views/mail_to.slim +2 -2
  28. data/test/fixtures/markup_app/views/meta_tag.slim +2 -2
  29. data/test/fixtures/markup_app/views/partials/_slim.slim +1 -1
  30. data/test/fixtures/markup_app/views/simple_partial.slim +1 -1
  31. data/test/fixtures/render_app/app.rb +3 -0
  32. data/test/test_asset_tag_helpers.rb +17 -4
  33. data/test/test_form_builder.rb +35 -1
  34. data/test/test_form_helpers.rb +29 -0
  35. data/test/test_format_helpers.rb +4 -0
  36. data/test/test_output_helpers.rb +5 -3
  37. data/test/test_tag_helpers.rb +11 -0
  38. metadata +10 -15
@@ -1,3 +1,5 @@
1
+ require 'securerandom'
2
+
1
3
  module Padrino
2
4
  module Helpers
3
5
  ##
@@ -28,8 +30,8 @@ module Padrino
28
30
  #
29
31
  # @api public
30
32
  def form_for(object, url, settings={}, &block)
31
- form_html = capture_html(builder_instance(object, settings), &block)
32
- form_tag(url, settings) { form_html }
33
+ instance = builder_instance(object, settings)
34
+ form_tag(url, settings) { capture_html(instance, &block) }
33
35
  end
34
36
 
35
37
  ##
@@ -54,7 +56,7 @@ module Padrino
54
56
  instance = builder_instance(object, settings)
55
57
  fields_html = capture_html(instance, &block)
56
58
  fields_html << instance.hidden_field(:id) if instance.send(:nested_object_id)
57
- concat_content fields_html
59
+ concat_safe_content fields_html
58
60
  end
59
61
 
60
62
  ##
@@ -79,8 +81,9 @@ module Padrino
79
81
  options.reverse_merge!(:method => 'post', :action => url)
80
82
  options[:enctype] = 'multipart/form-data' if options.delete(:multipart)
81
83
  options['accept-charset'] ||= 'UTF-8'
82
- inner_form_html = hidden_form_method_field(desired_method)
83
- inner_form_html += capture_html(&block)
84
+ inner_form_html = hidden_form_method_field(desired_method)
85
+ inner_form_html << csrf_token_field
86
+ inner_form_html << mark_safe(capture_html(&block))
84
87
  concat_content content_tag(:form, inner_form_html, options)
85
88
  end
86
89
 
@@ -100,7 +103,7 @@ module Padrino
100
103
  #
101
104
  # @api semipublic
102
105
  def hidden_form_method_field(desired_method)
103
- return '' if desired_method.blank? || desired_method.to_s =~ /get|post/i
106
+ return ActiveSupport::SafeBuffer.new if desired_method.blank? || desired_method.to_s =~ /get|post/i
104
107
  hidden_field_tag(:_method, :value => desired_method)
105
108
  end
106
109
 
@@ -125,8 +128,8 @@ module Padrino
125
128
  def field_set_tag(*args, &block)
126
129
  options = args.extract_options!
127
130
  legend_text = args[0].is_a?(String) ? args.first : nil
128
- legend_html = legend_text.blank? ? '' : content_tag(:legend, legend_text)
129
- field_set_content = legend_html + capture_html(&block)
131
+ legend_html = legend_text.blank? ? ActiveSupport::SafeBuffer.new : content_tag(:legend, legend_text)
132
+ field_set_content = legend_html + mark_safe(capture_html(&block))
130
133
  concat_content content_tag(:fieldset, field_set_content, options)
131
134
  end
132
135
 
@@ -199,10 +202,10 @@ module Padrino
199
202
  }
200
203
  }.join
201
204
 
202
- contents = ''
205
+ contents = ActiveSupport::SafeBuffer.new
203
206
  contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
204
207
  contents << content_tag(:p, message) unless message.blank?
205
- contents << content_tag(:ul, error_messages)
208
+ contents << safe_content_tag(:ul, error_messages)
206
209
 
207
210
  content_tag(:div, contents, html)
208
211
  end
@@ -276,10 +279,11 @@ module Padrino
276
279
  # @api public
277
280
  def label_tag(name, options={}, &block)
278
281
  options.reverse_merge!(:caption => "#{name.to_s.humanize}: ", :for => name)
279
- caption_text = options.delete(:caption)
280
- caption_text << "<span class='required'>*</span> " if options.delete(:required)
282
+ caption_text = options.delete(:caption).html_safe
283
+ caption_text.safe_concat "<span class='required'>*</span> " if options.delete(:required)
284
+
281
285
  if block_given? # label with inner content
282
- label_content = caption_text + capture_html(&block)
286
+ label_content = caption_text.concat capture_html(&block)
283
287
  concat_content(content_tag(:label, label_content, options))
284
288
  else # regular label
285
289
  content_tag(:label, caption_text, options)
@@ -409,10 +413,10 @@ module Padrino
409
413
  # # => <input name="age" min="18" max="120" step="1" type="number">
410
414
  #
411
415
  # @api public
412
- def number_field_tag(name, options={})
416
+ def number_field_tag(name, options={})
413
417
  input_tag(:number, options.reverse_merge(:name => name))
414
418
  end
415
-
419
+
416
420
  ##
417
421
  # Creates a telephone field input with the given name and options
418
422
  #
@@ -452,7 +456,7 @@ module Padrino
452
456
  def email_field_tag(name, options={})
453
457
  input_tag(:email, options.reverse_merge(:name => name))
454
458
  end
455
-
459
+
456
460
  ##
457
461
  # Creates a search field input with the given name and options
458
462
  #
@@ -573,6 +577,7 @@ module Padrino
573
577
  #
574
578
  # @api public
575
579
  def file_field_tag(name, options={})
580
+ name = "#{name}[]" if options[:multiple]
576
581
  options.reverse_merge!(:name => name)
577
582
  input_tag(:file, options)
578
583
  end
@@ -630,7 +635,7 @@ module Padrino
630
635
  end
631
636
  select_options_html = select_options_html.unshift(blank_option(prompt)) if select_options_html.is_a?(Array)
632
637
  options.merge!(:name => "#{options[:name]}[]") if options[:multiple]
633
- content_tag(:select, select_options_html, options)
638
+ safe_content_tag(:select, select_options_html, options)
634
639
  end
635
640
 
636
641
  ##
@@ -689,6 +694,68 @@ module Padrino
689
694
  input_tag(:image, options)
690
695
  end
691
696
 
697
+ # Constructs a hidden field containing a CSRF token.
698
+ #
699
+ # @param [String] token
700
+ # The token to use. Will be read from the session by default.
701
+ #
702
+ # @return [String] The hidden field with CSRF token as value.
703
+ #
704
+ # @example
705
+ # csrf_token_field
706
+ #
707
+ # @api public
708
+ def csrf_token_field(token = nil)
709
+ if defined? session
710
+ token ||= (session[:csrf] ||= SecureRandom.hex(32))
711
+ end
712
+
713
+ hidden_field_tag :authenticity_token, :value => token
714
+ end
715
+
716
+ ##
717
+ # Creates a form containing a single button that submits to the url.
718
+ #
719
+ # @overload button_to(name, url, options={})
720
+ # @param [String] caption The text caption.
721
+ # @param [String] url The url href.
722
+ # @param [Hash] options The html options.
723
+ # @overload button_to(name, options={}, &block)
724
+ # @param [String] url The url href.
725
+ # @param [Hash] options The html options.
726
+ # @param [Proc] block The button content.
727
+ #
728
+ # @option options [Boolean] :multipart
729
+ # If true, this form will support multipart encoding.
730
+ # @option options [String] :remote
731
+ # Instructs ujs handler to handle the submit as ajax.
732
+ # @option options [Symbol] :method
733
+ # Instructs ujs handler to use different http method (i.e :post, :delete).
734
+ #
735
+ # @return [String] Form and button html with specified +options+.
736
+ #
737
+ # @example
738
+ # button_to 'Delete', url(:accounts_destroy, :id => account), :method => :delete, :class => :form
739
+ # # Generates:
740
+ # # <form class="form" action="/admin/accounts/destroy/2" method="post">
741
+ # # <input type="hidden" value="delete" name="_method" />
742
+ # # <input type="submit" value="Delete" />
743
+ # # </form>
744
+ #
745
+ # @api public
746
+ def button_to(*args, &block)
747
+ name, url = args[0], args[1]
748
+ options = args.extract_options!
749
+ options['data-remote'] = 'true' if options.delete(:remote)
750
+ if block_given?
751
+ form_tag(url, options, &block)
752
+ else
753
+ form_tag(url, options) do
754
+ submit_tag(name)
755
+ end
756
+ end
757
+ end
758
+
692
759
  protected
693
760
 
694
761
  ##
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module Padrino
2
3
  module Helpers
3
4
  ###
@@ -373,7 +374,7 @@ module Padrino
373
374
  def js_escape_html(html_content)
374
375
  return '' unless html_content
375
376
  javascript_mapping = { '\\' => '\\\\', '</' => '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'" }
376
- html_content.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { javascript_mapping[$1] }
377
+ html_content.gsub(/(\\|<\/|\r\n|[\n\r"'])/){|m| javascript_mapping[m]}
377
378
  end
378
379
  alias :escape_javascript :js_escape_html
379
380
  end # FormatHelpers
@@ -27,25 +27,25 @@ fr:
27
27
  format: "%n %u"
28
28
  units:
29
29
  byte:
30
- one: "Byte"
31
- other: "Bytes"
32
- kb: "KB"
33
- mb: "MB"
34
- gb: "GB"
35
- tb: "TB"
30
+ one: "Octet"
31
+ other: "Octets"
32
+ kb: "Ko"
33
+ mb: "Mo"
34
+ gb: "Go"
35
+ tb: "To"
36
36
 
37
37
  datetime:
38
38
  distance_in_words:
39
39
  half_a_minute: "une demi minute"
40
40
  less_than_x_seconds:
41
- one: "infèrieur à une seconde"
42
- other: "infèrieur à %{count} secondes"
41
+ one: "inférieur à une seconde"
42
+ other: "inférieur à %{count} secondes"
43
43
  x_seconds:
44
44
  one: "1 seconde"
45
45
  other: "%{count} secondes"
46
46
  less_than_x_minutes:
47
- one: "infèrieur à 1 minute"
48
- other: "infèrieur à %{count} minutes"
47
+ one: "inférieur à 1 minute"
48
+ other: "inférieur à %{count} minutes"
49
49
  x_minutes:
50
50
  one: "1 minute"
51
51
  other: "%{count} minutes"
@@ -68,8 +68,8 @@ fr:
68
68
  one: "plus d'un an"
69
69
  other: "plus de %{count} ans"
70
70
  almost_x_years:
71
- one: "près d'un ans"
72
- other: "près de %{count} years"
71
+ one: "près d'un an"
72
+ other: "près de %{count} ans"
73
73
  models:
74
74
  errors:
75
75
  template:
@@ -17,8 +17,8 @@ pt_br:
17
17
  format: "%u%n"
18
18
  unit: "R$"
19
19
  # These three are to override number.format and are optional
20
- separator: "."
21
- delimiter: ","
20
+ separator: ","
21
+ delimiter: "."
22
22
  precision: 2
23
23
 
24
24
  # Used in number_to_percentage()
@@ -26,6 +26,8 @@ module Padrino
26
26
  ##
27
27
  # Captures the html from a block of template code for any available handler.
28
28
  #
29
+ # Be aware that trusting the html is up to the caller.
30
+ #
29
31
  # @param [Object] *args
30
32
  # Objects yield to the captured block
31
33
  # @param [Proc] &block
@@ -37,6 +39,12 @@ module Padrino
37
39
  # capture_html(&block) => "...html..."
38
40
  # capture_html(object_for_block, &block) => "...html..."
39
41
  #
42
+ # @example
43
+ # ActiveSupport::SafeBuffer.new + capture_html { "<foo>" }
44
+ # # => "&lt;foo&gt;"
45
+ # ActiveSupport::SafeBuffer.safe_concat + capture_html { "<foo>" }
46
+ # # => "<foo>"
47
+ #
40
48
  # @api semipublic
41
49
  def capture_html(*args, &block)
42
50
  handler = find_proper_handler
@@ -53,7 +61,9 @@ module Padrino
53
61
  ##
54
62
  # Outputs the given text to the templates buffer directly.
55
63
  #
56
- # @param [String] text
64
+ # The output might be subject to escaping, if it is not marked as safe.
65
+ #
66
+ # @param [String,SafeBuffer] text
57
67
  # Text to concatenate to the buffer.
58
68
  #
59
69
  # @example
@@ -70,6 +80,21 @@ module Padrino
70
80
  end
71
81
  alias :concat :concat_content
72
82
 
83
+ ##
84
+ # Outputs the given text to the templates buffer directly,
85
+ # assuming that it is safe.
86
+ #
87
+ # @param [String] text
88
+ # Text to concatenate to the buffer.
89
+ #
90
+ # @example
91
+ # concat_safe_content("This will be output to the template buffer")
92
+ #
93
+ # @api semipublic
94
+ def concat_safe_content(text="")
95
+ concat_content text.html_safe
96
+ end
97
+
73
98
  ##
74
99
  # Returns true if the block is from a supported template type; false otherwise.
75
100
  # Used to determine if html should be returned or concatenated to the view.
@@ -146,7 +171,7 @@ module Padrino
146
171
  def yield_content(key, *args)
147
172
  blocks = content_blocks[key.to_sym]
148
173
  return nil if blocks.empty?
149
- blocks.map { |content| capture_html(*args, &content) }.join
174
+ mark_safe(blocks.map { |content| capture_html(*args, &content) }.join)
150
175
  end
151
176
 
152
177
  protected
@@ -170,6 +195,21 @@ module Padrino
170
195
  def find_proper_handler
171
196
  OutputHelpers.handlers.map { |h| h.new(self) }.find { |h| h.engines.include?(current_engine) && h.is_type? }
172
197
  end
198
+
199
+ ##
200
+ # Marks a String or a collection of Strings as safe. `nil` is accepted
201
+ # but ignored.
202
+ #
203
+ # @param [String, Array<String>] the values to be marked safe.
204
+ #
205
+ # @return [ActiveSupport::SafeBuffer, Array<ActiveSupport::SafeBuffer>]
206
+ def mark_safe(value)
207
+ if value.respond_to? :map!
208
+ value.map!{|v| v.html_safe if v }
209
+ else
210
+ value.html_safe if value
211
+ end
212
+ end
173
213
  end # OutputHelpers
174
214
  end # Helpers
175
215
  end # Padrino
@@ -28,7 +28,7 @@ module Padrino
28
28
  # @handler.capture_from_template(&block) => "...html..."
29
29
  #
30
30
  def capture_from_template(*args, &block)
31
- self.output_buffer, _buf_was = "", self.output_buffer
31
+ self.output_buffer, _buf_was = ActiveSupport::SafeBuffer.new, self.output_buffer
32
32
  block.call(*args)
33
33
  ret = eval("@_out_buf", block.binding)
34
34
  self.output_buffer = _buf_was
@@ -1,5 +1,4 @@
1
1
  # Make slim works with sinatra/padrino
2
- Slim::Engine.set_default_options(:buffer => '@_out_buf', :generator => Temple::Generators::StringBuffer) if defined?(Slim)
3
2
 
4
3
  module Padrino
5
4
  module Helpers
@@ -31,9 +30,9 @@ module Padrino
31
30
  # @handler.capture_from_template(&block) => "...html..."
32
31
  #
33
32
  def capture_from_template(*args, &block)
34
- self.output_buffer, _buf_was = '', self.output_buffer
33
+ self.output_buffer, _buf_was = ActiveSupport::SafeBuffer.new, self.output_buffer
35
34
  block.call(*args)
36
- ret = eval('@_out_buf', block.binding)
35
+ ret = eval("@_out_buf", block.binding)
37
36
  self.output_buffer = _buf_was
38
37
  ret
39
38
  end
@@ -73,9 +72,9 @@ module Padrino
73
72
  def output_buffer=(val)
74
73
  template.instance_variable_set(:@_out_buf, val)
75
74
  end
76
- end # SlimHandler
75
+ end # SlimHandler
77
76
 
78
- OutputHelpers.register(SlimHandler)
77
+ OutputHelpers.register(SlimHandler)
79
78
  end # OutputHelpers
80
79
  end # Helpers
81
80
  end # Padrino
@@ -46,12 +46,12 @@ module Padrino
46
46
  counter += 1
47
47
  options[:locals].merge!(object_name => member, "#{object_name}_counter".to_sym => counter)
48
48
  render(explicit_engine, template_path, options.dup)
49
- }.join("\n")
49
+ }.join("\n").html_safe
50
50
  else
51
51
  if member = options.delete(:object)
52
52
  options[:locals].merge!(object_name => member)
53
53
  end
54
- render(explicit_engine, template_path, options.dup)
54
+ render(explicit_engine, template_path, options.dup).html_safe
55
55
  end
56
56
  end
57
57
  alias :render_partial :partial
@@ -13,6 +13,12 @@ module Padrino
13
13
  '"' => "&quot;"
14
14
  }
15
15
 
16
+ ##
17
+ # Cached Regexp for escaping values to avoid rebuilding one
18
+ # on every escape operation.
19
+ #
20
+ ESCAPE_REGEXP = Regexp.union(*ESCAPE_VALUES.keys)
21
+
16
22
  BOOLEAN_ATTRIBUTES = [
17
23
  :autoplay,
18
24
  :autofocus,
@@ -42,6 +48,12 @@ module Padrino
42
48
  :confirm
43
49
  ]
44
50
 
51
+ ##
52
+ # A html_safe newline string to avoid allocating a new on each
53
+ # concatenation.
54
+ #
55
+ NEWLINE = "\n".html_safe.freeze
56
+
45
57
  ##
46
58
  # Creates an HTML tag with given name, content, and options
47
59
  #
@@ -104,14 +116,30 @@ module Padrino
104
116
  content = capture_html(&block)
105
117
  end
106
118
 
107
- content = content.join("\n") if content.respond_to?(:join)
108
-
109
119
  options = parse_data_options(name, options)
110
120
  attributes = tag_attributes(options)
111
- output = "<#{name}#{attributes}>#{content}</#{name}>"
121
+ output = ActiveSupport::SafeBuffer.new
122
+ output.safe_concat "<#{name}#{attributes}>"
123
+ if content.respond_to?(:each) && !content.is_a?(String)
124
+ content.each { |c| output.concat c; output.safe_concat NEWLINE }
125
+ else
126
+ output.concat content
127
+ end
128
+ output.safe_concat "</#{name}>"
129
+
112
130
  block_is_template?(block) ? concat_content(output) : output
113
131
  end
114
132
 
133
+ ##
134
+ # Like #content_tag, but assumes its input to be safe and doesn't
135
+ # escape. It also returns safe html.
136
+ #
137
+ # @see #content_tag
138
+ #
139
+ def safe_content_tag(name, content = nil, options = nil, &block)
140
+ mark_safe(content_tag(name, mark_safe(content), options, &block))
141
+ end
142
+
115
143
  ##
116
144
  # Creates an HTML input field with the given type and options
117
145
  #
@@ -200,7 +228,7 @@ module Padrino
200
228
  def tag(name, options = nil, open = false)
201
229
  options = parse_data_options(name, options)
202
230
  attributes = tag_attributes(options)
203
- "<#{name}#{attributes}#{open ? '>' : ' />'}"
231
+ "<#{name}#{attributes}#{open ? '>' : ' />'}".html_safe
204
232
  end
205
233
 
206
234
  private
@@ -226,7 +254,7 @@ module Padrino
226
254
  # Escape tag values to their HTML/XML entities.
227
255
  ##
228
256
  def escape_value(string)
229
- string.to_s.gsub(Regexp.union(*ESCAPE_VALUES.keys)) { |c| ESCAPE_VALUES[c] }
257
+ string.to_s.gsub(ESCAPE_REGEXP) { |c| ESCAPE_VALUES[c] }
230
258
  end
231
259
 
232
260
  ##