actionpack 4.0.13 → 4.1.0.beta1

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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +131 -1636
  3. data/README.rdoc +1 -6
  4. data/lib/abstract_controller.rb +1 -2
  5. data/lib/abstract_controller/base.rb +3 -25
  6. data/lib/abstract_controller/callbacks.rb +4 -2
  7. data/lib/abstract_controller/collector.rb +11 -1
  8. data/lib/abstract_controller/helpers.rb +18 -15
  9. data/lib/abstract_controller/rendering.rb +48 -127
  10. data/lib/action_controller.rb +1 -17
  11. data/lib/action_controller/base.rb +14 -6
  12. data/lib/action_controller/caching.rb +1 -11
  13. data/lib/action_controller/log_subscriber.rb +1 -1
  14. data/lib/action_controller/metal.rb +0 -4
  15. data/lib/action_controller/metal/flash.rb +17 -0
  16. data/lib/action_controller/metal/force_ssl.rb +1 -1
  17. data/lib/action_controller/metal/head.rb +1 -3
  18. data/lib/action_controller/metal/helpers.rb +6 -2
  19. data/lib/action_controller/metal/http_authentication.rb +7 -14
  20. data/lib/action_controller/metal/instrumentation.rb +1 -1
  21. data/lib/action_controller/metal/live.rb +74 -0
  22. data/lib/action_controller/metal/mime_responds.rb +93 -16
  23. data/lib/action_controller/metal/params_wrapper.rb +4 -11
  24. data/lib/action_controller/metal/rack_delegation.rb +1 -1
  25. data/lib/action_controller/metal/redirecting.rb +20 -20
  26. data/lib/action_controller/metal/renderers.rb +8 -5
  27. data/lib/action_controller/metal/rendering.rb +14 -11
  28. data/lib/action_controller/metal/request_forgery_protection.rb +67 -13
  29. data/lib/action_controller/metal/responder.rb +12 -2
  30. data/lib/action_controller/metal/streaming.rb +18 -20
  31. data/lib/action_controller/metal/strong_parameters.rb +22 -34
  32. data/lib/action_controller/railtie.rb +0 -1
  33. data/lib/action_controller/test_case.rb +0 -15
  34. data/lib/action_dispatch.rb +1 -0
  35. data/lib/action_dispatch/http/headers.rb +1 -3
  36. data/lib/action_dispatch/http/mime_negotiation.rb +16 -2
  37. data/lib/action_dispatch/http/mime_type.rb +4 -22
  38. data/lib/action_dispatch/http/mime_types.rb +1 -0
  39. data/lib/action_dispatch/http/parameters.rb +18 -19
  40. data/lib/action_dispatch/http/request.rb +16 -25
  41. data/lib/action_dispatch/http/response.rb +21 -8
  42. data/lib/action_dispatch/http/upload.rb +0 -13
  43. data/lib/action_dispatch/http/url.rb +10 -18
  44. data/lib/action_dispatch/journey/formatter.rb +3 -3
  45. data/lib/action_dispatch/journey/gtg/transition_table.rb +3 -5
  46. data/lib/action_dispatch/journey/parser.rb +1 -1
  47. data/lib/action_dispatch/journey/parser.y +1 -0
  48. data/lib/action_dispatch/journey/router.rb +7 -1
  49. data/lib/action_dispatch/journey/router/utils.rb +1 -1
  50. data/lib/action_dispatch/journey/visitors.rb +26 -47
  51. data/lib/action_dispatch/middleware/callbacks.rb +6 -6
  52. data/lib/action_dispatch/middleware/cookies.rb +15 -15
  53. data/lib/action_dispatch/middleware/debug_exceptions.rb +21 -13
  54. data/lib/action_dispatch/middleware/exception_wrapper.rb +1 -1
  55. data/lib/action_dispatch/middleware/flash.rb +5 -11
  56. data/lib/action_dispatch/middleware/params_parser.rb +1 -1
  57. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
  58. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  59. data/lib/action_dispatch/middleware/session/cookie_store.rb +4 -3
  60. data/lib/action_dispatch/middleware/show_exceptions.rb +5 -2
  61. data/lib/action_dispatch/middleware/ssl.rb +1 -1
  62. data/lib/action_dispatch/middleware/static.rb +5 -25
  63. data/lib/action_dispatch/middleware/templates/rescues/{_request_and_response.erb → _request_and_response.html.erb} +0 -0
  64. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  65. data/lib/action_dispatch/middleware/templates/rescues/{_trace.erb → _trace.html.erb} +0 -0
  66. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +15 -0
  67. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +1 -1
  68. data/lib/action_dispatch/middleware/templates/rescues/{missing_template.erb → missing_template.html.erb} +1 -1
  69. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  70. data/lib/action_dispatch/middleware/templates/rescues/{routing_error.erb → routing_error.html.erb} +1 -1
  71. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  72. data/lib/action_dispatch/middleware/templates/rescues/{template_error.erb → template_error.html.erb} +1 -1
  73. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +8 -0
  74. data/lib/action_dispatch/middleware/templates/rescues/{unknown_action.erb → unknown_action.html.erb} +1 -1
  75. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  76. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +3 -3
  77. data/lib/action_dispatch/railtie.rb +1 -2
  78. data/lib/action_dispatch/request/session.rb +12 -0
  79. data/lib/action_dispatch/request/utils.rb +24 -0
  80. data/lib/action_dispatch/routing.rb +7 -6
  81. data/lib/action_dispatch/routing/inspector.rb +4 -4
  82. data/lib/action_dispatch/routing/mapper.rb +81 -138
  83. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -0
  84. data/lib/action_dispatch/routing/redirection.rb +34 -27
  85. data/lib/action_dispatch/routing/route_set.rb +43 -37
  86. data/lib/action_dispatch/routing/url_for.rb +3 -1
  87. data/lib/action_dispatch/testing/assertions/response.rb +8 -15
  88. data/lib/action_dispatch/testing/assertions/selector.rb +4 -4
  89. data/lib/action_dispatch/testing/integration.rb +1 -7
  90. data/lib/action_pack/version.rb +1 -1
  91. metadata +43 -167
  92. data/lib/abstract_controller/layouts.rb +0 -423
  93. data/lib/abstract_controller/view_paths.rb +0 -96
  94. data/lib/action_controller/deprecated.rb +0 -7
  95. data/lib/action_controller/deprecated/integration_test.rb +0 -5
  96. data/lib/action_controller/record_identifier.rb +0 -31
  97. data/lib/action_controller/vendor/html-scanner.rb +0 -5
  98. data/lib/action_view.rb +0 -93
  99. data/lib/action_view/base.rb +0 -205
  100. data/lib/action_view/buffers.rb +0 -49
  101. data/lib/action_view/context.rb +0 -36
  102. data/lib/action_view/dependency_tracker.rb +0 -93
  103. data/lib/action_view/digestor.rb +0 -113
  104. data/lib/action_view/flows.rb +0 -76
  105. data/lib/action_view/helpers.rb +0 -58
  106. data/lib/action_view/helpers/active_model_helper.rb +0 -49
  107. data/lib/action_view/helpers/asset_tag_helper.rb +0 -320
  108. data/lib/action_view/helpers/asset_url_helper.rb +0 -355
  109. data/lib/action_view/helpers/atom_feed_helper.rb +0 -203
  110. data/lib/action_view/helpers/cache_helper.rb +0 -196
  111. data/lib/action_view/helpers/capture_helper.rb +0 -216
  112. data/lib/action_view/helpers/controller_helper.rb +0 -25
  113. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  114. data/lib/action_view/helpers/date_helper.rb +0 -1087
  115. data/lib/action_view/helpers/debug_helper.rb +0 -39
  116. data/lib/action_view/helpers/form_helper.rb +0 -1882
  117. data/lib/action_view/helpers/form_options_helper.rb +0 -838
  118. data/lib/action_view/helpers/form_tag_helper.rb +0 -785
  119. data/lib/action_view/helpers/javascript_helper.rb +0 -117
  120. data/lib/action_view/helpers/number_helper.rb +0 -451
  121. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  122. data/lib/action_view/helpers/record_tag_helper.rb +0 -106
  123. data/lib/action_view/helpers/rendering_helper.rb +0 -90
  124. data/lib/action_view/helpers/sanitize_helper.rb +0 -256
  125. data/lib/action_view/helpers/tag_helper.rb +0 -173
  126. data/lib/action_view/helpers/tags.rb +0 -39
  127. data/lib/action_view/helpers/tags/base.rb +0 -148
  128. data/lib/action_view/helpers/tags/check_box.rb +0 -64
  129. data/lib/action_view/helpers/tags/checkable.rb +0 -16
  130. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -53
  131. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -84
  132. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -36
  133. data/lib/action_view/helpers/tags/collection_select.rb +0 -28
  134. data/lib/action_view/helpers/tags/color_field.rb +0 -25
  135. data/lib/action_view/helpers/tags/date_field.rb +0 -13
  136. data/lib/action_view/helpers/tags/date_select.rb +0 -72
  137. data/lib/action_view/helpers/tags/datetime_field.rb +0 -22
  138. data/lib/action_view/helpers/tags/datetime_local_field.rb +0 -19
  139. data/lib/action_view/helpers/tags/datetime_select.rb +0 -8
  140. data/lib/action_view/helpers/tags/email_field.rb +0 -8
  141. data/lib/action_view/helpers/tags/file_field.rb +0 -8
  142. data/lib/action_view/helpers/tags/grouped_collection_select.rb +0 -29
  143. data/lib/action_view/helpers/tags/hidden_field.rb +0 -8
  144. data/lib/action_view/helpers/tags/label.rb +0 -65
  145. data/lib/action_view/helpers/tags/month_field.rb +0 -13
  146. data/lib/action_view/helpers/tags/number_field.rb +0 -18
  147. data/lib/action_view/helpers/tags/password_field.rb +0 -12
  148. data/lib/action_view/helpers/tags/radio_button.rb +0 -31
  149. data/lib/action_view/helpers/tags/range_field.rb +0 -8
  150. data/lib/action_view/helpers/tags/search_field.rb +0 -22
  151. data/lib/action_view/helpers/tags/select.rb +0 -40
  152. data/lib/action_view/helpers/tags/tel_field.rb +0 -8
  153. data/lib/action_view/helpers/tags/text_area.rb +0 -18
  154. data/lib/action_view/helpers/tags/text_field.rb +0 -30
  155. data/lib/action_view/helpers/tags/time_field.rb +0 -13
  156. data/lib/action_view/helpers/tags/time_select.rb +0 -8
  157. data/lib/action_view/helpers/tags/time_zone_select.rb +0 -20
  158. data/lib/action_view/helpers/tags/url_field.rb +0 -8
  159. data/lib/action_view/helpers/tags/week_field.rb +0 -13
  160. data/lib/action_view/helpers/text_helper.rb +0 -448
  161. data/lib/action_view/helpers/translation_helper.rb +0 -112
  162. data/lib/action_view/helpers/url_helper.rb +0 -635
  163. data/lib/action_view/locale/en.yml +0 -56
  164. data/lib/action_view/log_subscriber.rb +0 -30
  165. data/lib/action_view/lookup_context.rb +0 -248
  166. data/lib/action_view/model_naming.rb +0 -12
  167. data/lib/action_view/path_set.rb +0 -77
  168. data/lib/action_view/railtie.rb +0 -43
  169. data/lib/action_view/record_identifier.rb +0 -84
  170. data/lib/action_view/renderer/abstract_renderer.rb +0 -47
  171. data/lib/action_view/renderer/partial_renderer.rb +0 -500
  172. data/lib/action_view/renderer/renderer.rb +0 -50
  173. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -103
  174. data/lib/action_view/renderer/template_renderer.rb +0 -96
  175. data/lib/action_view/routing_url_for.rb +0 -107
  176. data/lib/action_view/tasks/dependencies.rake +0 -17
  177. data/lib/action_view/template.rb +0 -339
  178. data/lib/action_view/template/error.rb +0 -138
  179. data/lib/action_view/template/handlers.rb +0 -53
  180. data/lib/action_view/template/handlers/builder.rb +0 -26
  181. data/lib/action_view/template/handlers/erb.rb +0 -146
  182. data/lib/action_view/template/handlers/raw.rb +0 -11
  183. data/lib/action_view/template/resolver.rb +0 -340
  184. data/lib/action_view/template/text.rb +0 -34
  185. data/lib/action_view/template/types.rb +0 -57
  186. data/lib/action_view/test_case.rb +0 -270
  187. data/lib/action_view/testing/resolvers.rb +0 -50
  188. data/lib/action_view/vendor/html-scanner.rb +0 -20
  189. data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
  190. data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
  191. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
  192. data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
  193. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
  194. data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
@@ -1,18 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class NumberField < TextField # :nodoc:
5
- def render
6
- options = @options.stringify_keys
7
-
8
- if range = options.delete("in") || options.delete("within")
9
- options.update("min" => range.min, "max" => range.max)
10
- end
11
-
12
- @options = options
13
- super
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,12 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class PasswordField < TextField # :nodoc:
5
- def render
6
- @options = {:value => nil}.merge!(@options)
7
- super
8
- end
9
- end
10
- end
11
- end
12
- end
@@ -1,31 +0,0 @@
1
- require 'action_view/helpers/tags/checkable'
2
-
3
- module ActionView
4
- module Helpers
5
- module Tags # :nodoc:
6
- class RadioButton < Base # :nodoc:
7
- include Checkable
8
-
9
- def initialize(object_name, method_name, template_object, tag_value, options)
10
- @tag_value = tag_value
11
- super(object_name, method_name, template_object, options)
12
- end
13
-
14
- def render
15
- options = @options.stringify_keys
16
- options["type"] = "radio"
17
- options["value"] = @tag_value
18
- options["checked"] = "checked" if input_checked?(object, options)
19
- add_default_name_and_id_for_value(@tag_value, options)
20
- tag("input", options)
21
- end
22
-
23
- private
24
-
25
- def checked?(value)
26
- value.to_s == @tag_value.to_s
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,8 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class RangeField < NumberField # :nodoc:
5
- end
6
- end
7
- end
8
- end
@@ -1,22 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class SearchField < TextField # :nodoc:
5
- def render
6
- super do |options|
7
- if options["autosave"]
8
- if options["autosave"] == true
9
- options["autosave"] = request.host.split(".").reverse.join(".")
10
- end
11
- options["results"] ||= 10
12
- end
13
-
14
- if options["onsearch"]
15
- options["incremental"] = true unless options.has_key?("incremental")
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,40 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class Select < Base # :nodoc:
5
- def initialize(object_name, method_name, template_object, choices, options, html_options)
6
- @choices = choices
7
- @choices = @choices.to_a if @choices.is_a?(Range)
8
- @html_options = html_options
9
-
10
- super(object_name, method_name, template_object, options)
11
- end
12
-
13
- def render
14
- option_tags_options = {
15
- :selected => @options.fetch(:selected) { value(@object) },
16
- :disabled => @options[:disabled]
17
- }
18
-
19
- option_tags = if grouped_choices?
20
- grouped_options_for_select(@choices, option_tags_options)
21
- else
22
- options_for_select(@choices, option_tags_options)
23
- end
24
-
25
- select_content_tag(option_tags, @options, @html_options)
26
- end
27
-
28
- private
29
-
30
- # Grouped choices look like this:
31
- #
32
- # [nil, []]
33
- # { nil => [] }
34
- def grouped_choices?
35
- !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,8 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class TelField < TextField # :nodoc:
5
- end
6
- end
7
- end
8
- end
@@ -1,18 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class TextArea < Base # :nodoc:
5
- def render
6
- options = @options.stringify_keys
7
- add_default_name_and_id(options)
8
-
9
- if size = options.delete("size")
10
- options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
11
- end
12
-
13
- content_tag("textarea", options.delete("value") { value_before_type_cast(object) }, options)
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,30 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class TextField < Base # :nodoc:
5
- def render
6
- options = @options.stringify_keys
7
- options["size"] = options["maxlength"] unless options.key?("size")
8
- options["type"] ||= field_type
9
- options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
10
- options["value"] &&= ERB::Util.html_escape(options["value"])
11
- yield options if block_given?
12
- add_default_name_and_id(options)
13
- tag("input", options)
14
- end
15
-
16
- class << self
17
- def field_type
18
- @field_type ||= self.name.split("::").last.sub("Field", "").downcase
19
- end
20
- end
21
-
22
- private
23
-
24
- def field_type
25
- self.class.field_type
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,13 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class TimeField < DatetimeField # :nodoc:
5
- private
6
-
7
- def format_date(value)
8
- value.try(:strftime, "%T.%L")
9
- end
10
- end
11
- end
12
- end
13
- end
@@ -1,8 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class TimeSelect < DateSelect # :nodoc:
5
- end
6
- end
7
- end
8
- end
@@ -1,20 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class TimeZoneSelect < Base # :nodoc:
5
- def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
6
- @priority_zones = priority_zones
7
- @html_options = html_options
8
-
9
- super(object_name, method_name, template_object, options)
10
- end
11
-
12
- def render
13
- select_content_tag(
14
- time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
15
- )
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,8 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class UrlField < TextField # :nodoc:
5
- end
6
- end
7
- end
8
- end
@@ -1,13 +0,0 @@
1
- module ActionView
2
- module Helpers
3
- module Tags # :nodoc:
4
- class WeekField < DatetimeField # :nodoc:
5
- private
6
-
7
- def format_date(value)
8
- value.try(:strftime, "%Y-W%W")
9
- end
10
- end
11
- end
12
- end
13
- end
@@ -1,448 +0,0 @@
1
- require 'active_support/core_ext/string/filters'
2
- require 'active_support/core_ext/array/extract_options'
3
-
4
- module ActionView
5
- # = Action View Text Helpers
6
- module Helpers #:nodoc:
7
- # The TextHelper module provides a set of methods for filtering, formatting
8
- # and transforming strings, which can reduce the amount of inline Ruby code in
9
- # your views. These helper methods extend Action View making them callable
10
- # within your template files.
11
- #
12
- # ==== Sanitization
13
- #
14
- # Most text helpers by default sanitize the given content, but do not escape it.
15
- # This means HTML tags will appear in the page but all malicious code will be removed.
16
- # Let's look at some examples using the +simple_format+ method:
17
- #
18
- # simple_format('<a href="http://example.com/">Example</a>')
19
- # # => "<p><a href=\"http://example.com/\">Example</a></p>"
20
- #
21
- # simple_format('<a href="javascript:alert(\'no!\')">Example</a>')
22
- # # => "<p><a>Example</a></p>"
23
- #
24
- # If you want to escape all content, you should invoke the +h+ method before
25
- # calling the text helper.
26
- #
27
- # simple_format h('<a href="http://example.com/">Example</a>')
28
- # # => "<p>&lt;a href=\"http://example.com/\"&gt;Example&lt;/a&gt;</p>"
29
- module TextHelper
30
- extend ActiveSupport::Concern
31
-
32
- include SanitizeHelper
33
- include TagHelper
34
- include OutputSafetyHelper
35
-
36
- # The preferred method of outputting text in your views is to use the
37
- # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
38
- # do not operate as expected in an eRuby code block. If you absolutely must
39
- # output text within a non-output code block (i.e., <% %>), you can use the concat method.
40
- #
41
- # <%
42
- # concat "hello"
43
- # # is the equivalent of <%= "hello" %>
44
- #
45
- # if logged_in
46
- # concat "Logged in!"
47
- # else
48
- # concat link_to('login', action: :login)
49
- # end
50
- # # will either display "Logged in!" or a login link
51
- # %>
52
- def concat(string)
53
- output_buffer << string
54
- end
55
-
56
- def safe_concat(string)
57
- output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string)
58
- end
59
-
60
- # Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
61
- # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...")
62
- # for a total length not exceeding <tt>:length</tt>.
63
- #
64
- # Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
65
- #
66
- # Pass a block if you want to show extra content when the text is truncated.
67
- #
68
- # The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is
69
- # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation
70
- # may produce invalid HTML (such as unbalanced or incomplete tags).
71
- #
72
- # truncate("Once upon a time in a world far far away")
73
- # # => "Once upon a time in a world..."
74
- #
75
- # truncate("Once upon a time in a world far far away", length: 17)
76
- # # => "Once upon a ti..."
77
- #
78
- # truncate("Once upon a time in a world far far away", length: 17, separator: ' ')
79
- # # => "Once upon a..."
80
- #
81
- # truncate("And they found that many people were sleeping better.", length: 25, omission: '... (continued)')
82
- # # => "And they f... (continued)"
83
- #
84
- # truncate("<p>Once upon a time in a world far far away</p>")
85
- # # => "&lt;p&gt;Once upon a time in a wo..."
86
- #
87
- # truncate("<p>Once upon a time in a world far far away</p>", escape: false)
88
- # # => "<p>Once upon a time in a wo..."
89
- #
90
- # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
91
- # # => "Once upon a time in a wo...<a href="#">Continue</a>"
92
- def truncate(text, options = {}, &block)
93
- if text
94
- length = options.fetch(:length, 30)
95
-
96
- content = text.truncate(length, options)
97
- content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content)
98
- content << capture(&block) if block_given? && text.length > length
99
- content
100
- end
101
- end
102
-
103
- # Highlights one or more +phrases+ everywhere in +text+ by inserting it into
104
- # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
105
- # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
106
- # '<mark>\1</mark>')
107
- #
108
- # highlight('You searched for: rails', 'rails')
109
- # # => You searched for: <mark>rails</mark>
110
- #
111
- # highlight('You searched for: ruby, rails, dhh', 'actionpack')
112
- # # => You searched for: ruby, rails, dhh
113
- #
114
- # highlight('You searched for: rails', ['for', 'rails'], highlighter: '<em>\1</em>')
115
- # # => You searched <em>for</em>: <em>rails</em>
116
- #
117
- # highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
118
- # # => You searched for: <a href="search?q=rails">rails</a>
119
- def highlight(text, phrases, options = {})
120
- text = sanitize(text) if options.fetch(:sanitize, true)
121
-
122
- if text.blank? || phrases.blank?
123
- text
124
- else
125
- highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
126
- match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
127
- text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
128
- end.html_safe
129
- end
130
-
131
- # Extracts an excerpt from +text+ that matches the first instance of +phrase+.
132
- # The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
133
- # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
134
- # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. Use the
135
- # <tt>:separator</tt> option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+
136
- # isn't found, nil is returned.
137
- #
138
- # excerpt('This is an example', 'an', radius: 5)
139
- # # => ...s is an exam...
140
- #
141
- # excerpt('This is an example', 'is', radius: 5)
142
- # # => This is a...
143
- #
144
- # excerpt('This is an example', 'is')
145
- # # => This is an example
146
- #
147
- # excerpt('This next thing is an example', 'ex', radius: 2)
148
- # # => ...next...
149
- #
150
- # excerpt('This is also an example', 'an', radius: 8, omission: '<chop> ')
151
- # # => <chop> is also an example
152
- #
153
- # excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
154
- # # => ...a very beautiful...
155
- def excerpt(text, phrase, options = {})
156
- return unless text && phrase
157
-
158
- separator = options[:separator] || ''
159
- phrase = Regexp.escape(phrase)
160
- regex = /#{phrase}/i
161
-
162
- return unless matches = text.match(regex)
163
- phrase = matches[0]
164
-
165
- text.split(separator).each do |value|
166
- if value.match(regex)
167
- regex = phrase = value
168
- break
169
- end
170
- end
171
-
172
- first_part, second_part = text.split(regex, 2)
173
-
174
- prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
175
- postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
176
-
177
- affix = [first_part, separator, phrase, separator, second_part].join.strip
178
- [prefix, affix, postfix].join
179
- end
180
-
181
- # Attempts to pluralize the +singular+ word unless +count+ is 1. If
182
- # +plural+ is supplied, it will use that when count is > 1, otherwise
183
- # it will use the Inflector to determine the plural form.
184
- #
185
- # pluralize(1, 'person')
186
- # # => 1 person
187
- #
188
- # pluralize(2, 'person')
189
- # # => 2 people
190
- #
191
- # pluralize(3, 'person', 'users')
192
- # # => 3 users
193
- #
194
- # pluralize(0, 'person')
195
- # # => 0 people
196
- def pluralize(count, singular, plural = nil)
197
- word = if (count == 1 || count =~ /^1(\.0+)?$/)
198
- singular
199
- else
200
- plural || singular.pluralize
201
- end
202
-
203
- "#{count || 0} #{word}"
204
- end
205
-
206
- # Wraps the +text+ into lines no longer than +line_width+ width. This method
207
- # breaks on the first whitespace character that does not exceed +line_width+
208
- # (which is 80 by default).
209
- #
210
- # word_wrap('Once upon a time')
211
- # # => Once upon a time
212
- #
213
- # 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...')
214
- # # => 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...
215
- #
216
- # word_wrap('Once upon a time', line_width: 8)
217
- # # => Once\nupon a\ntime
218
- #
219
- # word_wrap('Once upon a time', line_width: 1)
220
- # # => Once\nupon\na\ntime
221
- def word_wrap(text, options = {})
222
- line_width = options.fetch(:line_width, 80)
223
-
224
- text.split("\n").collect do |line|
225
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
226
- end * "\n"
227
- end
228
-
229
- # Returns +text+ transformed into HTML using simple formatting rules.
230
- # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
231
- # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
232
- # considered as a linebreak and a <tt><br /></tt> tag is appended. This
233
- # method does not remove the newlines from the +text+.
234
- #
235
- # You can pass any HTML attributes into <tt>html_options</tt>. These
236
- # will be added to all created paragraphs.
237
- #
238
- # ==== Options
239
- # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
240
- # * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>
241
- #
242
- # ==== Examples
243
- # my_text = "Here is some basic text...\n...with a line break."
244
- #
245
- # simple_format(my_text)
246
- # # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
247
- #
248
- # simple_format(my_text, {}, wrapper_tag: "div")
249
- # # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
250
- #
251
- # more_text = "We want to put a paragraph...\n\n...right there."
252
- #
253
- # simple_format(more_text)
254
- # # => "<p>We want to put a paragraph...</p>\n\n<p>...right there.</p>"
255
- #
256
- # simple_format("Look ma! A class!", class: 'description')
257
- # # => "<p class='description'>Look ma! A class!</p>"
258
- #
259
- # simple_format("<blink>Unblinkable.</blink>")
260
- # # => "<p>Unblinkable.</p>"
261
- #
262
- # simple_format("<blink>Blinkable!</blink> It's true.", {}, sanitize: false)
263
- # # => "<p><blink>Blinkable!</blink> It's true.</p>"
264
- def simple_format(text, html_options = {}, options = {})
265
- wrapper_tag = options.fetch(:wrapper_tag, :p)
266
-
267
- text = sanitize(text) if options.fetch(:sanitize, true)
268
- paragraphs = split_paragraphs(text)
269
-
270
- if paragraphs.empty?
271
- content_tag(wrapper_tag, nil, html_options)
272
- else
273
- paragraphs.map { |paragraph|
274
- content_tag(wrapper_tag, raw(paragraph), html_options)
275
- }.join("\n\n").html_safe
276
- end
277
- end
278
-
279
- # Creates a Cycle object whose _to_s_ method cycles through elements of an
280
- # array every time it is called. This can be used for example, to alternate
281
- # classes for table rows. You can use named cycles to allow nesting in loops.
282
- # Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
283
- # named cycle. The default name for a cycle without a +:name+ key is
284
- # <tt>"default"</tt>. You can manually reset a cycle by calling reset_cycle
285
- # and passing the name of the cycle. The current cycle string can be obtained
286
- # anytime using the current_cycle method.
287
- #
288
- # # Alternate CSS classes for even and odd numbers...
289
- # @items = [1,2,3,4]
290
- # <table>
291
- # <% @items.each do |item| %>
292
- # <tr class="<%= cycle("odd", "even") -%>">
293
- # <td>item</td>
294
- # </tr>
295
- # <% end %>
296
- # </table>
297
- #
298
- #
299
- # # Cycle CSS classes for rows, and text colors for values within each row
300
- # @items = x = [{first: 'Robert', middle: 'Daniel', last: 'James'},
301
- # {first: 'Emily', middle: 'Shannon', maiden: 'Pike', last: 'Hicks'},
302
- # {first: 'June', middle: 'Dae', last: 'Jones'}]
303
- # <% @items.each do |item| %>
304
- # <tr class="<%= cycle("odd", "even", name: "row_class") -%>">
305
- # <td>
306
- # <% item.values.each do |value| %>
307
- # <%# Create a named cycle "colors" %>
308
- # <span style="color:<%= cycle("red", "green", "blue", name: "colors") -%>">
309
- # <%= value %>
310
- # </span>
311
- # <% end %>
312
- # <% reset_cycle("colors") %>
313
- # </td>
314
- # </tr>
315
- # <% end %>
316
- def cycle(first_value, *values)
317
- options = values.extract_options!
318
- name = options.fetch(:name, 'default')
319
-
320
- values.unshift(first_value)
321
-
322
- cycle = get_cycle(name)
323
- unless cycle && cycle.values == values
324
- cycle = set_cycle(name, Cycle.new(*values))
325
- end
326
- cycle.to_s
327
- end
328
-
329
- # Returns the current cycle string after a cycle has been started. Useful
330
- # for complex table highlighting or any other design need which requires
331
- # the current cycle string in more than one place.
332
- #
333
- # # Alternate background colors
334
- # @items = [1,2,3,4]
335
- # <% @items.each do |item| %>
336
- # <div style="background-color:<%= cycle("red","white","blue") %>">
337
- # <span style="background-color:<%= current_cycle %>"><%= item %></span>
338
- # </div>
339
- # <% end %>
340
- def current_cycle(name = "default")
341
- cycle = get_cycle(name)
342
- cycle.current_value if cycle
343
- end
344
-
345
- # Resets a cycle so that it starts from the first element the next time
346
- # it is called. Pass in +name+ to reset a named cycle.
347
- #
348
- # # Alternate CSS classes for even and odd numbers...
349
- # @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
350
- # <table>
351
- # <% @items.each do |item| %>
352
- # <tr class="<%= cycle("even", "odd") -%>">
353
- # <% item.each do |value| %>
354
- # <span style="color:<%= cycle("#333", "#666", "#999", name: "colors") -%>">
355
- # <%= value %>
356
- # </span>
357
- # <% end %>
358
- #
359
- # <% reset_cycle("colors") %>
360
- # </tr>
361
- # <% end %>
362
- # </table>
363
- def reset_cycle(name = "default")
364
- cycle = get_cycle(name)
365
- cycle.reset if cycle
366
- end
367
-
368
- class Cycle #:nodoc:
369
- attr_reader :values
370
-
371
- def initialize(first_value, *values)
372
- @values = values.unshift(first_value)
373
- reset
374
- end
375
-
376
- def reset
377
- @index = 0
378
- end
379
-
380
- def current_value
381
- @values[previous_index].to_s
382
- end
383
-
384
- def to_s
385
- value = @values[@index].to_s
386
- @index = next_index
387
- return value
388
- end
389
-
390
- private
391
-
392
- def next_index
393
- step_index(1)
394
- end
395
-
396
- def previous_index
397
- step_index(-1)
398
- end
399
-
400
- def step_index(n)
401
- (@index + n) % @values.size
402
- end
403
- end
404
-
405
- private
406
- # The cycle helpers need to store the cycles in a place that is
407
- # guaranteed to be reset every time a page is rendered, so it
408
- # uses an instance variable of ActionView::Base.
409
- def get_cycle(name)
410
- @_cycles = Hash.new unless defined?(@_cycles)
411
- return @_cycles[name]
412
- end
413
-
414
- def set_cycle(name, cycle_object)
415
- @_cycles = Hash.new unless defined?(@_cycles)
416
- @_cycles[name] = cycle_object
417
- end
418
-
419
- def split_paragraphs(text)
420
- return [] if text.blank?
421
-
422
- text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t|
423
- t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
424
- end
425
- end
426
-
427
- def cut_excerpt_part(part_position, part, separator, options)
428
- return "", "" unless part
429
-
430
- radius = options.fetch(:radius, 100)
431
- omission = options.fetch(:omission, "...")
432
-
433
- part = part.split(separator)
434
- part.delete("")
435
- affix = part.size > radius ? omission : ""
436
-
437
- part = if part_position == :first
438
- drop_index = [part.length - radius, 0].max
439
- part.drop(drop_index)
440
- else
441
- part.first(radius)
442
- end
443
-
444
- return affix, part.join(separator)
445
- end
446
- end
447
- end
448
- end