actionpack 3.2.19 → 4.0.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 (263) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +850 -401
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -288
  5. data/lib/abstract_controller/asset_paths.rb +2 -2
  6. data/lib/abstract_controller/base.rb +39 -37
  7. data/lib/abstract_controller/callbacks.rb +101 -82
  8. data/lib/abstract_controller/collector.rb +7 -3
  9. data/lib/abstract_controller/helpers.rb +25 -13
  10. data/lib/abstract_controller/layouts.rb +74 -74
  11. data/lib/abstract_controller/logger.rb +1 -2
  12. data/lib/abstract_controller/rendering.rb +30 -13
  13. data/lib/abstract_controller/translation.rb +16 -1
  14. data/lib/abstract_controller/url_for.rb +6 -6
  15. data/lib/abstract_controller/view_paths.rb +1 -1
  16. data/lib/abstract_controller.rb +1 -8
  17. data/lib/action_controller/base.rb +46 -22
  18. data/lib/action_controller/caching/fragments.rb +23 -53
  19. data/lib/action_controller/caching.rb +46 -33
  20. data/lib/action_controller/deprecated/integration_test.rb +3 -0
  21. data/lib/action_controller/deprecated.rb +5 -1
  22. data/lib/action_controller/log_subscriber.rb +16 -8
  23. data/lib/action_controller/metal/conditional_get.rb +76 -32
  24. data/lib/action_controller/metal/data_streaming.rb +20 -26
  25. data/lib/action_controller/metal/exceptions.rb +19 -6
  26. data/lib/action_controller/metal/flash.rb +24 -9
  27. data/lib/action_controller/metal/force_ssl.rb +70 -12
  28. data/lib/action_controller/metal/head.rb +25 -4
  29. data/lib/action_controller/metal/helpers.rb +5 -9
  30. data/lib/action_controller/metal/hide_actions.rb +0 -1
  31. data/lib/action_controller/metal/http_authentication.rb +107 -83
  32. data/lib/action_controller/metal/implicit_render.rb +1 -1
  33. data/lib/action_controller/metal/instrumentation.rb +2 -1
  34. data/lib/action_controller/metal/live.rb +175 -0
  35. data/lib/action_controller/metal/mime_responds.rb +161 -47
  36. data/lib/action_controller/metal/params_wrapper.rb +112 -74
  37. data/lib/action_controller/metal/rack_delegation.rb +9 -3
  38. data/lib/action_controller/metal/redirecting.rb +15 -20
  39. data/lib/action_controller/metal/renderers.rb +11 -9
  40. data/lib/action_controller/metal/rendering.rb +9 -1
  41. data/lib/action_controller/metal/request_forgery_protection.rb +112 -19
  42. data/lib/action_controller/metal/responder.rb +20 -19
  43. data/lib/action_controller/metal/streaming.rb +12 -18
  44. data/lib/action_controller/metal/strong_parameters.rb +520 -0
  45. data/lib/action_controller/metal/testing.rb +13 -18
  46. data/lib/action_controller/metal/url_for.rb +28 -25
  47. data/lib/action_controller/metal.rb +17 -32
  48. data/lib/action_controller/model_naming.rb +12 -0
  49. data/lib/action_controller/railtie.rb +33 -17
  50. data/lib/action_controller/railties/helpers.rb +22 -0
  51. data/lib/action_controller/record_identifier.rb +18 -72
  52. data/lib/action_controller/test_case.rb +251 -131
  53. data/lib/action_controller/vendor/html-scanner.rb +4 -19
  54. data/lib/action_controller.rb +15 -6
  55. data/lib/action_dispatch/http/cache.rb +63 -11
  56. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  57. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  58. data/lib/action_dispatch/http/headers.rb +49 -17
  59. data/lib/action_dispatch/http/mime_negotiation.rb +24 -1
  60. data/lib/action_dispatch/http/mime_type.rb +154 -100
  61. data/lib/action_dispatch/http/mime_types.rb +1 -1
  62. data/lib/action_dispatch/http/parameter_filter.rb +44 -46
  63. data/lib/action_dispatch/http/parameters.rb +28 -28
  64. data/lib/action_dispatch/http/rack_cache.rb +2 -3
  65. data/lib/action_dispatch/http/request.rb +64 -18
  66. data/lib/action_dispatch/http/response.rb +130 -35
  67. data/lib/action_dispatch/http/upload.rb +63 -20
  68. data/lib/action_dispatch/http/url.rb +98 -35
  69. data/lib/action_dispatch/journey/backwards.rb +5 -0
  70. data/lib/action_dispatch/journey/formatter.rb +146 -0
  71. data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
  72. data/lib/action_dispatch/journey/gtg/simulator.rb +44 -0
  73. data/lib/action_dispatch/journey/gtg/transition_table.rb +156 -0
  74. data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
  75. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  76. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  77. data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
  78. data/lib/action_dispatch/journey/nodes/node.rb +124 -0
  79. data/lib/action_dispatch/journey/parser.rb +206 -0
  80. data/lib/action_dispatch/journey/parser.y +47 -0
  81. data/lib/action_dispatch/journey/parser_extras.rb +23 -0
  82. data/lib/action_dispatch/journey/path/pattern.rb +196 -0
  83. data/lib/action_dispatch/journey/route.rb +124 -0
  84. data/lib/action_dispatch/journey/router/strexp.rb +24 -0
  85. data/lib/action_dispatch/journey/router/utils.rb +54 -0
  86. data/lib/action_dispatch/journey/router.rb +166 -0
  87. data/lib/action_dispatch/journey/routes.rb +75 -0
  88. data/lib/action_dispatch/journey/scanner.rb +61 -0
  89. data/lib/action_dispatch/journey/visitors.rb +197 -0
  90. data/lib/action_dispatch/journey/visualizer/fsm.css +34 -0
  91. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  92. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  93. data/lib/action_dispatch/journey.rb +5 -0
  94. data/lib/action_dispatch/middleware/callbacks.rb +9 -4
  95. data/lib/action_dispatch/middleware/cookies.rb +259 -114
  96. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -17
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -3
  98. data/lib/action_dispatch/middleware/flash.rb +58 -58
  99. data/lib/action_dispatch/middleware/params_parser.rb +14 -29
  100. data/lib/action_dispatch/middleware/public_exceptions.rb +30 -14
  101. data/lib/action_dispatch/middleware/reloader.rb +6 -6
  102. data/lib/action_dispatch/middleware/remote_ip.rb +145 -39
  103. data/lib/action_dispatch/middleware/request_id.rb +2 -6
  104. data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
  105. data/lib/action_dispatch/middleware/session/cookie_store.rb +82 -28
  106. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
  107. data/lib/action_dispatch/middleware/show_exceptions.rb +12 -45
  108. data/lib/action_dispatch/middleware/ssl.rb +70 -0
  109. data/lib/action_dispatch/middleware/stack.rb +6 -1
  110. data/lib/action_dispatch/middleware/static.rb +2 -1
  111. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +14 -11
  112. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +25 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +7 -9
  114. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +15 -9
  115. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +127 -5
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +7 -2
  117. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +30 -15
  118. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +39 -13
  119. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +6 -2
  120. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  121. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +144 -0
  122. data/lib/action_dispatch/railtie.rb +16 -6
  123. data/lib/action_dispatch/request/session.rb +181 -0
  124. data/lib/action_dispatch/routing/inspector.rb +240 -0
  125. data/lib/action_dispatch/routing/mapper.rb +540 -291
  126. data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -20
  127. data/lib/action_dispatch/routing/redirection.rb +46 -29
  128. data/lib/action_dispatch/routing/route_set.rb +207 -164
  129. data/lib/action_dispatch/routing/routes_proxy.rb +2 -0
  130. data/lib/action_dispatch/routing/url_for.rb +48 -33
  131. data/lib/action_dispatch/routing.rb +48 -83
  132. data/lib/action_dispatch/testing/assertions/dom.rb +3 -13
  133. data/lib/action_dispatch/testing/assertions/response.rb +32 -40
  134. data/lib/action_dispatch/testing/assertions/routing.rb +42 -41
  135. data/lib/action_dispatch/testing/assertions/selector.rb +17 -22
  136. data/lib/action_dispatch/testing/assertions/tag.rb +20 -23
  137. data/lib/action_dispatch/testing/integration.rb +65 -51
  138. data/lib/action_dispatch/testing/test_process.rb +9 -6
  139. data/lib/action_dispatch/testing/test_request.rb +7 -3
  140. data/lib/action_dispatch.rb +21 -15
  141. data/lib/action_pack/version.rb +7 -6
  142. data/lib/action_pack.rb +1 -1
  143. data/lib/action_view/base.rb +15 -34
  144. data/lib/action_view/buffers.rb +7 -1
  145. data/lib/action_view/context.rb +4 -4
  146. data/lib/action_view/dependency_tracker.rb +93 -0
  147. data/lib/action_view/digestor.rb +85 -0
  148. data/lib/action_view/flows.rb +1 -4
  149. data/lib/action_view/helpers/active_model_helper.rb +3 -4
  150. data/lib/action_view/helpers/asset_tag_helper.rb +215 -352
  151. data/lib/action_view/helpers/asset_url_helper.rb +355 -0
  152. data/lib/action_view/helpers/atom_feed_helper.rb +13 -10
  153. data/lib/action_view/helpers/cache_helper.rb +150 -18
  154. data/lib/action_view/helpers/capture_helper.rb +44 -31
  155. data/lib/action_view/helpers/csrf_helper.rb +0 -2
  156. data/lib/action_view/helpers/date_helper.rb +269 -248
  157. data/lib/action_view/helpers/debug_helper.rb +10 -11
  158. data/lib/action_view/helpers/form_helper.rb +931 -537
  159. data/lib/action_view/helpers/form_options_helper.rb +341 -166
  160. data/lib/action_view/helpers/form_tag_helper.rb +190 -90
  161. data/lib/action_view/helpers/javascript_helper.rb +23 -16
  162. data/lib/action_view/helpers/number_helper.rb +148 -329
  163. data/lib/action_view/helpers/output_safety_helper.rb +3 -3
  164. data/lib/action_view/helpers/record_tag_helper.rb +17 -22
  165. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  166. data/lib/action_view/helpers/sanitize_helper.rb +3 -6
  167. data/lib/action_view/helpers/tag_helper.rb +46 -33
  168. data/lib/action_view/helpers/tags/base.rb +147 -0
  169. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  170. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  171. data/lib/action_view/helpers/tags/collection_check_boxes.rb +43 -0
  172. data/lib/action_view/helpers/tags/collection_helpers.rb +83 -0
  173. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  174. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  175. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  176. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  177. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  178. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  179. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  180. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  181. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  182. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  183. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  184. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  185. data/lib/action_view/helpers/tags/label.rb +65 -0
  186. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  187. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  188. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  189. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  190. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  191. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  192. data/lib/action_view/helpers/tags/select.rb +40 -0
  193. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  194. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  195. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  196. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  197. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  198. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  199. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  200. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  201. data/lib/action_view/helpers/tags.rb +39 -0
  202. data/lib/action_view/helpers/text_helper.rb +130 -114
  203. data/lib/action_view/helpers/translation_helper.rb +32 -16
  204. data/lib/action_view/helpers/url_helper.rb +211 -270
  205. data/lib/action_view/helpers.rb +2 -4
  206. data/lib/action_view/locale/en.yml +1 -105
  207. data/lib/action_view/log_subscriber.rb +6 -4
  208. data/lib/action_view/lookup_context.rb +15 -28
  209. data/lib/action_view/model_naming.rb +12 -0
  210. data/lib/action_view/path_set.rb +8 -20
  211. data/lib/action_view/railtie.rb +6 -22
  212. data/lib/action_view/record_identifier.rb +84 -0
  213. data/lib/action_view/renderer/abstract_renderer.rb +25 -19
  214. data/lib/action_view/renderer/partial_renderer.rb +158 -81
  215. data/lib/action_view/renderer/renderer.rb +8 -12
  216. data/lib/action_view/renderer/streaming_template_renderer.rb +2 -5
  217. data/lib/action_view/renderer/template_renderer.rb +12 -10
  218. data/lib/action_view/routing_url_for.rb +107 -0
  219. data/lib/action_view/template/error.rb +22 -12
  220. data/lib/action_view/template/handlers/builder.rb +1 -1
  221. data/lib/action_view/template/handlers/erb.rb +40 -19
  222. data/lib/action_view/template/handlers/raw.rb +11 -0
  223. data/lib/action_view/template/handlers.rb +12 -9
  224. data/lib/action_view/template/resolver.rb +107 -53
  225. data/lib/action_view/template/text.rb +12 -8
  226. data/lib/action_view/template/types.rb +57 -0
  227. data/lib/action_view/template.rb +25 -23
  228. data/lib/action_view/test_case.rb +67 -42
  229. data/lib/{action_controller → action_view}/vendor/html-scanner/html/document.rb +0 -0
  230. data/lib/{action_controller → action_view}/vendor/html-scanner/html/node.rb +12 -12
  231. data/lib/{action_controller → action_view}/vendor/html-scanner/html/sanitizer.rb +13 -2
  232. data/lib/{action_controller → action_view}/vendor/html-scanner/html/selector.rb +9 -9
  233. data/lib/{action_controller → action_view}/vendor/html-scanner/html/tokenizer.rb +1 -1
  234. data/lib/{action_controller → action_view}/vendor/html-scanner/html/version.rb +0 -0
  235. data/lib/action_view/vendor/html-scanner.rb +20 -0
  236. data/lib/action_view.rb +17 -8
  237. metadata +184 -214
  238. data/lib/action_controller/caching/actions.rb +0 -185
  239. data/lib/action_controller/caching/pages.rb +0 -187
  240. data/lib/action_controller/caching/sweeping.rb +0 -97
  241. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  242. data/lib/action_controller/metal/compatibility.rb +0 -65
  243. data/lib/action_controller/metal/session_management.rb +0 -14
  244. data/lib/action_controller/railties/paths.rb +0 -25
  245. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  246. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  247. data/lib/action_dispatch/middleware/head.rb +0 -18
  248. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  249. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  250. data/lib/action_view/asset_paths.rb +0 -142
  251. data/lib/action_view/helpers/asset_paths.rb +0 -7
  252. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  253. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  254. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  255. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  256. data/lib/sprockets/assets.rake +0 -99
  257. data/lib/sprockets/bootstrap.rb +0 -37
  258. data/lib/sprockets/compressors.rb +0 -83
  259. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  260. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  261. data/lib/sprockets/helpers.rb +0 -6
  262. data/lib/sprockets/railtie.rb +0 -62
  263. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,5 +1,5 @@
1
- require 'active_support/core_ext/object/blank'
2
1
  require 'active_support/core_ext/string/filters'
2
+ require 'active_support/core_ext/array/extract_options'
3
3
 
4
4
  module ActionView
5
5
  # = Action View Text Helpers
@@ -36,7 +36,6 @@ module ActionView
36
36
  # do not operate as expected in an eRuby code block. If you absolutely must
37
37
  # output text within a non-output code block (i.e., <% %>), you can use the concat method.
38
38
  #
39
- # ==== Examples
40
39
  # <%
41
40
  # concat "hello"
42
41
  # # is the equivalent of <%= "hello" %>
@@ -44,7 +43,7 @@ module ActionView
44
43
  # if logged_in
45
44
  # concat "Logged in!"
46
45
  # else
47
- # concat link_to('login', :action => login)
46
+ # concat link_to('login', action: :login)
48
47
  # end
49
48
  # # will either display "Logged in!" or a login link
50
49
  # %>
@@ -62,128 +61,121 @@ module ActionView
62
61
  #
63
62
  # Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
64
63
  #
65
- # The result is not marked as HTML-safe, so will be subject to the default escaping when
66
- # used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags
67
- # or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags).
64
+ # Pass a block if you want to show extra content when the text is truncated.
68
65
  #
69
- # ==== Examples
66
+ # The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is
67
+ # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation
68
+ # may produce invalid HTML (such as unbalanced or incomplete tags).
70
69
  #
71
70
  # truncate("Once upon a time in a world far far away")
72
71
  # # => "Once upon a time in a world..."
73
72
  #
74
- # truncate("Once upon a time in a world far far away", :length => 17)
73
+ # truncate("Once upon a time in a world far far away", length: 17)
75
74
  # # => "Once upon a ti..."
76
75
  #
77
- # truncate("Once upon a time in a world far far away", :length => 17, :separator => ' ')
76
+ # truncate("Once upon a time in a world far far away", length: 17, separator: ' ')
78
77
  # # => "Once upon a..."
79
78
  #
80
- # truncate("And they found that many people were sleeping better.", :length => 25, :omission => '... (continued)')
79
+ # truncate("And they found that many people were sleeping better.", length: 25, omission: '... (continued)')
81
80
  # # => "And they f... (continued)"
82
81
  #
83
82
  # truncate("<p>Once upon a time in a world far far away</p>")
84
83
  # # => "<p>Once upon a time in a wo..."
85
- def truncate(text, options = {})
86
- options.reverse_merge!(:length => 30)
87
- text.truncate(options.delete(:length), options) if text
84
+ #
85
+ # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
86
+ # # => "Once upon a time in a wo...<a href="#">Continue</a>"
87
+ def truncate(text, options = {}, &block)
88
+ if text
89
+ length = options.fetch(:length, 30)
90
+
91
+ content = text.truncate(length, options)
92
+ content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content)
93
+ content << capture(&block) if block_given? && text.length > length
94
+ content
95
+ end
88
96
  end
89
97
 
90
98
  # Highlights one or more +phrases+ everywhere in +text+ by inserting it into
91
99
  # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
92
- # as a single-quoted string with \1 where the phrase is to be inserted (defaults to
93
- # '<strong class="highlight">\1</strong>')
100
+ # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
101
+ # '<mark>\1</mark>')
94
102
  #
95
- # ==== Examples
96
103
  # highlight('You searched for: rails', 'rails')
97
- # # => You searched for: <strong class="highlight">rails</strong>
104
+ # # => You searched for: <mark>rails</mark>
98
105
  #
99
106
  # highlight('You searched for: ruby, rails, dhh', 'actionpack')
100
107
  # # => You searched for: ruby, rails, dhh
101
108
  #
102
- # highlight('You searched for: rails', ['for', 'rails'], :highlighter => '<em>\1</em>')
109
+ # highlight('You searched for: rails', ['for', 'rails'], highlighter: '<em>\1</em>')
103
110
  # # => You searched <em>for</em>: <em>rails</em>
104
111
  #
105
- # highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>')
112
+ # highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
106
113
  # # => You searched for: <a href="search?q=rails">rails</a>
107
- #
108
- # You can still use <tt>highlight</tt> with the old API that accepts the
109
- # +highlighter+ as its optional third parameter:
110
- # highlight('You searched for: rails', 'rails', '<a href="search?q=\1">\1</a>') # => You searched for: <a href="search?q=rails">rails</a>
111
- def highlight(text, phrases, *args)
112
- options = args.extract_options!
113
- unless args.empty?
114
- ActiveSupport::Deprecation.warn "Calling highlight with a highlighter as an argument is deprecated. " \
115
- "Please call with :highlighter => '#{args[0]}' instead.", caller
114
+ def highlight(text, phrases, options = {})
115
+ text = sanitize(text) if options.fetch(:sanitize, true)
116
116
 
117
- options[:highlighter] = args[0] || '<strong class="highlight">\1</strong>'
118
- end
119
- options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
120
-
121
- text = sanitize(text) unless options[:sanitize] == false
122
117
  if text.blank? || phrases.blank?
123
118
  text
124
119
  else
120
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
125
121
  match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
126
- text.gsub(/(#{match})(?![^<]*?>)/i, options[:highlighter])
122
+ text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
127
123
  end.html_safe
128
124
  end
129
125
 
130
126
  # Extracts an excerpt from +text+ that matches the first instance of +phrase+.
131
127
  # The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
132
128
  # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
133
- # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The resulting string
134
- # will be stripped in any case. If the +phrase+ isn't found, nil is returned.
129
+ # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. Use the
130
+ # <tt>:separator</tt> option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+
131
+ # isn't found, nil is returned.
135
132
  #
136
- # ==== Examples
137
- # excerpt('This is an example', 'an', :radius => 5)
133
+ # excerpt('This is an example', 'an', radius: 5)
138
134
  # # => ...s is an exam...
139
135
  #
140
- # excerpt('This is an example', 'is', :radius => 5)
136
+ # excerpt('This is an example', 'is', radius: 5)
141
137
  # # => This is a...
142
138
  #
143
139
  # excerpt('This is an example', 'is')
144
140
  # # => This is an example
145
141
  #
146
- # excerpt('This next thing is an example', 'ex', :radius => 2)
142
+ # excerpt('This next thing is an example', 'ex', radius: 2)
147
143
  # # => ...next...
148
144
  #
149
- # excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ')
145
+ # excerpt('This is also an example', 'an', radius: 8, omission: '<chop> ')
150
146
  # # => <chop> is also an example
151
147
  #
152
- # You can still use <tt>excerpt</tt> with the old API that accepts the
153
- # +radius+ as its optional third and the +ellipsis+ as its
154
- # optional forth parameter:
155
- # excerpt('This is an example', 'an', 5) # => ...s is an exam...
156
- # excerpt('This is also an example', 'an', 8, '<chop> ') # => <chop> is also an example
157
- def excerpt(text, phrase, *args)
148
+ # excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
149
+ # # => ...a very beautiful...
150
+ def excerpt(text, phrase, options = {})
158
151
  return unless text && phrase
159
152
 
160
- options = args.extract_options!
161
- unless args.empty?
162
- ActiveSupport::Deprecation.warn "Calling excerpt with radius and omission as arguments is deprecated. " \
163
- "Please call with :radius => #{args[0]}#{", :omission => '#{args[1]}'" if args[1]} instead.", caller
153
+ separator = options.fetch(:separator, "")
154
+ phrase = Regexp.escape(phrase)
155
+ regex = /#{phrase}/i
164
156
 
165
- options[:radius] = args[0] || 100
166
- options[:omission] = args[1] || "..."
167
- end
168
- options.reverse_merge!(:radius => 100, :omission => "...")
157
+ return unless matches = text.match(regex)
158
+ phrase = matches[0]
169
159
 
170
- phrase = Regexp.escape(phrase)
171
- return unless found_pos = text.mb_chars =~ /(#{phrase})/i
160
+ text.split(separator).each do |value|
161
+ if value.match(regex)
162
+ regex = phrase = value
163
+ break
164
+ end
165
+ end
172
166
 
173
- start_pos = [ found_pos - options[:radius], 0 ].max
174
- end_pos = [ [ found_pos + phrase.mb_chars.length + options[:radius] - 1, 0].max, text.mb_chars.length ].min
167
+ first_part, second_part = text.split(regex, 2)
175
168
 
176
- prefix = start_pos > 0 ? options[:omission] : ""
177
- postfix = end_pos < text.mb_chars.length - 1 ? options[:omission] : ""
169
+ prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
170
+ postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
178
171
 
179
- prefix + text.mb_chars[start_pos..end_pos].strip + postfix
172
+ prefix + (first_part + separator + phrase + separator + second_part).strip + postfix
180
173
  end
181
174
 
182
175
  # Attempts to pluralize the +singular+ word unless +count+ is 1. If
183
176
  # +plural+ is supplied, it will use that when count is > 1, otherwise
184
- # it will use the Inflector to determine the plural form
177
+ # it will use the Inflector to determine the plural form.
185
178
  #
186
- # ==== Examples
187
179
  # pluralize(1, 'person')
188
180
  # # => 1 person
189
181
  #
@@ -196,42 +188,35 @@ module ActionView
196
188
  # pluralize(0, 'person')
197
189
  # # => 0 people
198
190
  def pluralize(count, singular, plural = nil)
199
- "#{count || 0} " + ((count == 1 || count =~ /^1(\.0+)?$/) ? singular : (plural || singular.pluralize))
191
+ word = if (count == 1 || count =~ /^1(\.0+)?$/)
192
+ singular
193
+ else
194
+ plural || singular.pluralize
195
+ end
196
+
197
+ "#{count || 0} #{word}"
200
198
  end
201
199
 
202
200
  # Wraps the +text+ into lines no longer than +line_width+ width. This method
203
201
  # breaks on the first whitespace character that does not exceed +line_width+
204
202
  # (which is 80 by default).
205
203
  #
206
- # ==== Examples
207
- #
208
204
  # word_wrap('Once upon a time')
209
205
  # # => Once upon a time
210
206
  #
211
207
  # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
212
- # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\n a successor to the throne turned out to be more trouble than anyone could have\n imagined...
208
+ # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined...
213
209
  #
214
- # word_wrap('Once upon a time', :line_width => 8)
215
- # # => Once upon\na time
210
+ # word_wrap('Once upon a time', line_width: 8)
211
+ # # => Once\nupon a\ntime
216
212
  #
217
- # word_wrap('Once upon a time', :line_width => 1)
213
+ # word_wrap('Once upon a time', line_width: 1)
218
214
  # # => Once\nupon\na\ntime
219
- #
220
- # You can still use <tt>word_wrap</tt> with the old API that accepts the
221
- # +line_width+ as its optional second parameter:
222
- # word_wrap('Once upon a time', 8) # => Once upon\na time
223
- def word_wrap(text, *args)
224
- options = args.extract_options!
225
- unless args.blank?
226
- ActiveSupport::Deprecation.warn "Calling word_wrap with line_width as an argument is deprecated. " \
227
- "Please call with :line_width => #{args[0]} instead.", caller
228
-
229
- options[:line_width] = args[0] || 80
230
- end
231
- options.reverse_merge!(:line_width => 80)
215
+ def word_wrap(text, options = {})
216
+ line_width = options.fetch(:line_width, 80)
232
217
 
233
218
  text.split("\n").collect do |line|
234
- line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line
219
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
235
220
  end * "\n"
236
221
  end
237
222
 
@@ -246,6 +231,7 @@ module ActionView
246
231
  #
247
232
  # ==== Options
248
233
  # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
234
+ # * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>
249
235
  #
250
236
  # ==== Examples
251
237
  # my_text = "Here is some basic text...\n...with a line break."
@@ -253,27 +239,35 @@ module ActionView
253
239
  # simple_format(my_text)
254
240
  # # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
255
241
  #
242
+ # simple_format(my_text, {}, wrapper_tag: "div")
243
+ # # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
244
+ #
256
245
  # more_text = "We want to put a paragraph...\n\n...right there."
257
246
  #
258
247
  # simple_format(more_text)
259
248
  # # => "<p>We want to put a paragraph...</p>\n\n<p>...right there.</p>"
260
249
  #
261
- # simple_format("Look ma! A class!", :class => 'description')
250
+ # simple_format("Look ma! A class!", class: 'description')
262
251
  # # => "<p class='description'>Look ma! A class!</p>"
263
252
  #
264
- # simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false)
265
- # # => "<p><span>I'm allowed!</span> It's true.</p>"
266
- def simple_format(text, html_options={}, options={})
267
- text = '' if text.nil?
268
- text = text.dup
269
- start_tag = tag('p', html_options, true)
270
- text = sanitize(text) unless options[:sanitize] == false
271
- text = text.to_str
272
- text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
273
- text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph
274
- text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
275
- text.insert 0, start_tag
276
- text.html_safe.safe_concat("</p>")
253
+ # simple_format("<blink>Unblinkable.</blink>")
254
+ # # => "<p>Unblinkable.</p>"
255
+ #
256
+ # simple_format("<blink>Blinkable!</blink> It's true.", {}, sanitize: false)
257
+ # # => "<p><blink>Blinkable!</span> It's true.</p>"
258
+ def simple_format(text, html_options = {}, options = {})
259
+ wrapper_tag = options.fetch(:wrapper_tag, :p)
260
+
261
+ text = sanitize(text) if options.fetch(:sanitize, true)
262
+ paragraphs = split_paragraphs(text)
263
+
264
+ if paragraphs.empty?
265
+ content_tag(wrapper_tag, nil, html_options)
266
+ else
267
+ paragraphs.map { |paragraph|
268
+ content_tag(wrapper_tag, paragraph, html_options, options[:sanitize])
269
+ }.join("\n\n").html_safe
270
+ end
277
271
  end
278
272
 
279
273
  # Creates a Cycle object whose _to_s_ method cycles through elements of an
@@ -285,7 +279,6 @@ module ActionView
285
279
  # and passing the name of the cycle. The current cycle string can be obtained
286
280
  # anytime using the current_cycle method.
287
281
  #
288
- # ==== Examples
289
282
  # # Alternate CSS classes for even and odd numbers...
290
283
  # @items = [1,2,3,4]
291
284
  # <table>
@@ -298,15 +291,15 @@ module ActionView
298
291
  #
299
292
  #
300
293
  # # Cycle CSS classes for rows, and text colors for values within each row
301
- # @items = x = [{:first => 'Robert', :middle => 'Daniel', :last => 'James'},
302
- # {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'},
303
- # {:first => 'June', :middle => 'Dae', :last => 'Jones'}]
294
+ # @items = x = [{first: 'Robert', middle: 'Daniel', last: 'James'},
295
+ # {first: 'Emily', middle: 'Shannon', maiden: 'Pike', last: 'Hicks'},
296
+ # {first: 'June', middle: 'Dae', last: 'Jones'}]
304
297
  # <% @items.each do |item| %>
305
- # <tr class="<%= cycle("odd", "even", :name => "row_class") -%>">
298
+ # <tr class="<%= cycle("odd", "even", name: "row_class") -%>">
306
299
  # <td>
307
300
  # <% item.values.each do |value| %>
308
301
  # <%# Create a named cycle "colors" %>
309
- # <span style="color:<%= cycle("red", "green", "blue", :name => "colors") -%>">
302
+ # <span style="color:<%= cycle("red", "green", "blue", name: "colors") -%>">
310
303
  # <%= value %>
311
304
  # </span>
312
305
  # <% end %>
@@ -315,12 +308,9 @@ module ActionView
315
308
  # </tr>
316
309
  # <% end %>
317
310
  def cycle(first_value, *values)
318
- if (values.last.instance_of? Hash)
319
- params = values.pop
320
- name = params[:name]
321
- else
322
- name = "default"
323
- end
311
+ options = values.extract_options!
312
+ name = options.fetch(:name, 'default')
313
+
324
314
  values.unshift(first_value)
325
315
 
326
316
  cycle = get_cycle(name)
@@ -334,7 +324,6 @@ module ActionView
334
324
  # for complex table highlighting or any other design need which requires
335
325
  # the current cycle string in more than one place.
336
326
  #
337
- # ==== Example
338
327
  # # Alternate background colors
339
328
  # @items = [1,2,3,4]
340
329
  # <% @items.each do |item| %>
@@ -350,14 +339,13 @@ module ActionView
350
339
  # Resets a cycle so that it starts from the first element the next time
351
340
  # it is called. Pass in +name+ to reset a named cycle.
352
341
  #
353
- # ==== Example
354
342
  # # Alternate CSS classes for even and odd numbers...
355
343
  # @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
356
344
  # <table>
357
345
  # <% @items.each do |item| %>
358
346
  # <tr class="<%= cycle("even", "odd") -%>">
359
347
  # <% item.each do |value| %>
360
- # <span style="color:<%= cycle("#333", "#666", "#999", :name => "colors") -%>">
348
+ # <span style="color:<%= cycle("#333", "#666", "#999", name: "colors") -%>">
361
349
  # <%= value %>
362
350
  # </span>
363
351
  # <% end %>
@@ -421,6 +409,34 @@ module ActionView
421
409
  @_cycles = Hash.new unless defined?(@_cycles)
422
410
  @_cycles[name] = cycle_object
423
411
  end
412
+
413
+ def split_paragraphs(text)
414
+ return [] if text.blank?
415
+
416
+ text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t|
417
+ t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
418
+ end
419
+ end
420
+
421
+ def cut_excerpt_part(part_position, part, separator, options)
422
+ return "", "" unless part
423
+
424
+ radius = options.fetch(:radius, 100)
425
+ omission = options.fetch(:omission, "...")
426
+
427
+ part = part.split(separator)
428
+ part.delete("")
429
+ affix = part.size > radius ? omission : ""
430
+
431
+ part = if part_position == :first
432
+ drop_index = [part.length - radius, 0].max
433
+ part.drop(drop_index)
434
+ else
435
+ part.first(radius)
436
+ end
437
+
438
+ return affix, part.join(separator)
439
+ end
424
440
  end
425
441
  end
426
442
  end
@@ -1,14 +1,24 @@
1
1
  require 'action_view/helpers/tag_helper'
2
2
  require 'i18n/exceptions'
3
3
 
4
+ module I18n
5
+ class ExceptionHandler
6
+ include Module.new {
7
+ def call(exception, locale, key, options)
8
+ exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super
9
+ end
10
+ }
11
+ end
12
+ end
13
+
4
14
  module ActionView
5
15
  # = Action View Translation Helpers
6
16
  module Helpers
7
17
  module TranslationHelper
8
18
  # Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
9
19
  #
10
- # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
11
- # into inline spans that:
20
+ # First, it'll pass the <tt>rescue_format: :html</tt> option to I18n so that any
21
+ # thrown +MissingTranslation+ messages will be turned into inline spans that
12
22
  #
13
23
  # * have a "translation-missing" class set,
14
24
  # * contain the missing key as a title attribute and
@@ -34,15 +44,8 @@ module ActionView
34
44
  # naming convention helps to identify translations that include HTML tags so that
35
45
  # you know what kind of output to expect when you call translate in a template.
36
46
  def translate(key, options = {})
37
- # If the user has specified rescue_format then pass it all through, otherwise use
38
- # raise and do the work ourselves
39
- if options.key?(:raise) || options.key?(:rescue_format)
40
- raise_error = options[:raise] || options[:rescue_format]
41
- else
42
- raise_error = false
43
- options[:raise] = true
44
- end
45
-
47
+ options.merge!(:rescue_format => :html) unless options.key?(:rescue_format)
48
+ options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
46
49
  if html_safe_translation_key?(key)
47
50
  html_safe_options = options.dup
48
51
  options.except(*I18n::RESERVED_KEYS).each do |name, value|
@@ -56,15 +59,13 @@ module ActionView
56
59
  else
57
60
  I18n.translate(scope_key_by_partial(key), options)
58
61
  end
59
- rescue I18n::MissingTranslationData => e
60
- raise e if raise_error
61
-
62
- keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
63
- content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
64
62
  end
65
63
  alias :t :translate
66
64
 
67
65
  # Delegates to <tt>I18n.localize</tt> with no additional functionality.
66
+ #
67
+ # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
68
+ # for more information.
68
69
  def localize(*args)
69
70
  I18n.localize(*args)
70
71
  end
@@ -86,6 +87,21 @@ module ActionView
86
87
  def html_safe_translation_key?(key)
87
88
  key.to_s =~ /(\b|_|\.)html$/
88
89
  end
90
+
91
+ def wrap_translate_defaults(defaults)
92
+ new_defaults = []
93
+ defaults = Array(defaults)
94
+ while key = defaults.shift
95
+ if key.is_a?(Symbol)
96
+ new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) }
97
+ break
98
+ else
99
+ new_defaults << key
100
+ end
101
+ end
102
+
103
+ new_defaults
104
+ end
89
105
  end
90
106
  end
91
107
  end