actionview 4.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

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