actionview 5.2.3

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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +142 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +38 -0
  5. data/lib/action_view.rb +97 -0
  6. data/lib/action_view/base.rb +215 -0
  7. data/lib/action_view/buffers.rb +52 -0
  8. data/lib/action_view/context.rb +36 -0
  9. data/lib/action_view/dependency_tracker.rb +175 -0
  10. data/lib/action_view/digestor.rb +134 -0
  11. data/lib/action_view/flows.rb +76 -0
  12. data/lib/action_view/gem_version.rb +17 -0
  13. data/lib/action_view/helpers.rb +68 -0
  14. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  15. data/lib/action_view/helpers/asset_tag_helper.rb +511 -0
  16. data/lib/action_view/helpers/asset_url_helper.rb +469 -0
  17. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  18. data/lib/action_view/helpers/cache_helper.rb +263 -0
  19. data/lib/action_view/helpers/capture_helper.rb +212 -0
  20. data/lib/action_view/helpers/controller_helper.rb +36 -0
  21. data/lib/action_view/helpers/csp_helper.rb +24 -0
  22. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  23. data/lib/action_view/helpers/date_helper.rb +1156 -0
  24. data/lib/action_view/helpers/debug_helper.rb +36 -0
  25. data/lib/action_view/helpers/form_helper.rb +2337 -0
  26. data/lib/action_view/helpers/form_options_helper.rb +887 -0
  27. data/lib/action_view/helpers/form_tag_helper.rb +917 -0
  28. data/lib/action_view/helpers/javascript_helper.rb +94 -0
  29. data/lib/action_view/helpers/number_helper.rb +451 -0
  30. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  31. data/lib/action_view/helpers/record_tag_helper.rb +23 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +99 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +177 -0
  34. data/lib/action_view/helpers/tag_helper.rb +313 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +192 -0
  37. data/lib/action_view/helpers/tags/check_box.rb +66 -0
  38. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  42. data/lib/action_view/helpers/tags/collection_select.rb +30 -0
  43. data/lib/action_view/helpers/tags/color_field.rb +27 -0
  44. data/lib/action_view/helpers/tags/date_field.rb +15 -0
  45. data/lib/action_view/helpers/tags/date_select.rb +74 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
  48. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +10 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
  52. data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
  53. data/lib/action_view/helpers/tags/label.rb +81 -0
  54. data/lib/action_view/helpers/tags/month_field.rb +15 -0
  55. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +33 -0
  59. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  61. data/lib/action_view/helpers/tags/select.rb +43 -0
  62. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  64. data/lib/action_view/helpers/tags/text_field.rb +34 -0
  65. data/lib/action_view/helpers/tags/time_field.rb +15 -0
  66. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
  68. data/lib/action_view/helpers/tags/translator.rb +44 -0
  69. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +15 -0
  71. data/lib/action_view/helpers/text_helper.rb +486 -0
  72. data/lib/action_view/helpers/translation_helper.rb +141 -0
  73. data/lib/action_view/helpers/url_helper.rb +676 -0
  74. data/lib/action_view/layouts.rb +433 -0
  75. data/lib/action_view/locale/en.yml +56 -0
  76. data/lib/action_view/log_subscriber.rb +96 -0
  77. data/lib/action_view/lookup_context.rb +274 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +100 -0
  80. data/lib/action_view/railtie.rb +82 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +55 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +552 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +57 -0
  85. data/lib/action_view/renderer/renderer.rb +56 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +102 -0
  88. data/lib/action_view/rendering.rb +151 -0
  89. data/lib/action_view/routing_url_for.rb +145 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +361 -0
  92. data/lib/action_view/template/error.rb +141 -0
  93. data/lib/action_view/template/handlers.rb +66 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +74 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +83 -0
  97. data/lib/action_view/template/handlers/html.rb +11 -0
  98. data/lib/action_view/template/handlers/raw.rb +11 -0
  99. data/lib/action_view/template/html.rb +34 -0
  100. data/lib/action_view/template/resolver.rb +391 -0
  101. data/lib/action_view/template/text.rb +33 -0
  102. data/lib/action_view/template/types.rb +57 -0
  103. data/lib/action_view/test_case.rb +300 -0
  104. data/lib/action_view/testing/resolvers.rb +54 -0
  105. data/lib/action_view/version.rb +10 -0
  106. data/lib/action_view/view_paths.rb +105 -0
  107. data/lib/assets/compiled/rails-ujs.js +720 -0
  108. metadata +255 -0
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tag_helper"
4
+
5
+ module ActionView
6
+ module Helpers #:nodoc:
7
+ module JavaScriptHelper
8
+ JS_ESCAPE_MAP = {
9
+ '\\' => '\\\\',
10
+ "</" => '<\/',
11
+ "\r\n" => '\n',
12
+ "\n" => '\n',
13
+ "\r" => '\n',
14
+ '"' => '\\"',
15
+ "'" => "\\'"
16
+ }
17
+
18
+ JS_ESCAPE_MAP["\342\200\250".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
19
+ JS_ESCAPE_MAP["\342\200\251".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
20
+
21
+ # Escapes carriage returns and single and double quotes for JavaScript segments.
22
+ #
23
+ # Also available through the alias j(). This is particularly helpful in JavaScript
24
+ # responses, like:
25
+ #
26
+ # $('some_element').replaceWith('<%= j render 'some/element_template' %>');
27
+ def escape_javascript(javascript)
28
+ if javascript
29
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] }
30
+ javascript.html_safe? ? result.html_safe : result
31
+ else
32
+ ""
33
+ end
34
+ end
35
+
36
+ alias_method :j, :escape_javascript
37
+
38
+ # Returns a JavaScript tag with the +content+ inside. Example:
39
+ # javascript_tag "alert('All is good')"
40
+ #
41
+ # Returns:
42
+ # <script>
43
+ # //<![CDATA[
44
+ # alert('All is good')
45
+ # //]]>
46
+ # </script>
47
+ #
48
+ # +html_options+ may be a hash of attributes for the <tt>\<script></tt>
49
+ # tag.
50
+ #
51
+ # javascript_tag "alert('All is good')", defer: 'defer'
52
+ #
53
+ # Returns:
54
+ # <script defer="defer">
55
+ # //<![CDATA[
56
+ # alert('All is good')
57
+ # //]]>
58
+ # </script>
59
+ #
60
+ # Instead of passing the content as an argument, you can also use a block
61
+ # in which case, you pass your +html_options+ as the first parameter.
62
+ #
63
+ # <%= javascript_tag defer: 'defer' do -%>
64
+ # alert('All is good')
65
+ # <% end -%>
66
+ #
67
+ # If you have a content security policy enabled then you can add an automatic
68
+ # nonce value by passing +nonce: true+ as part of +html_options+. Example:
69
+ #
70
+ # <%= javascript_tag nonce: true do -%>
71
+ # alert('All is good')
72
+ # <% end -%>
73
+ def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
74
+ content =
75
+ if block_given?
76
+ html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
77
+ capture(&block)
78
+ else
79
+ content_or_options_with_block
80
+ end
81
+
82
+ if html_options[:nonce] == true
83
+ html_options[:nonce] = content_security_policy_nonce
84
+ end
85
+
86
+ content_tag("script".freeze, javascript_cdata_section(content), html_options)
87
+ end
88
+
89
+ def javascript_cdata_section(content) #:nodoc:
90
+ "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,451 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+ require "active_support/core_ext/string/output_safety"
5
+ require "active_support/number_helper"
6
+
7
+ module ActionView
8
+ # = Action View Number Helpers
9
+ module Helpers #:nodoc:
10
+ # Provides methods for converting numbers into formatted strings.
11
+ # Methods are provided for phone numbers, currency, percentage,
12
+ # precision, positional notation, file size and pretty printing.
13
+ #
14
+ # Most methods expect a +number+ argument, and will return it
15
+ # unchanged if can't be converted into a valid number.
16
+ module NumberHelper
17
+ # Raised when argument +number+ param given to the helpers is invalid and
18
+ # the option :raise is set to +true+.
19
+ class InvalidNumberError < StandardError
20
+ attr_accessor :number
21
+ def initialize(number)
22
+ @number = number
23
+ end
24
+ end
25
+
26
+ # Formats a +number+ into a phone number (US by default e.g., (555)
27
+ # 123-9876). You can customize the format in the +options+ hash.
28
+ #
29
+ # ==== Options
30
+ #
31
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
32
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use
33
+ # (defaults to "-").
34
+ # * <tt>:extension</tt> - Specifies an extension to add to the
35
+ # end of the generated number.
36
+ # * <tt>:country_code</tt> - Sets the country code for the phone
37
+ # number.
38
+ # * <tt>:pattern</tt> - Specifies how the number is divided into three
39
+ # groups with the custom regexp to override the default format.
40
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
41
+ # the argument is invalid.
42
+ #
43
+ # ==== Examples
44
+ #
45
+ # number_to_phone(5551234) # => 555-1234
46
+ # number_to_phone("5551234") # => 555-1234
47
+ # number_to_phone(1235551234) # => 123-555-1234
48
+ # number_to_phone(1235551234, area_code: true) # => (123) 555-1234
49
+ # number_to_phone(1235551234, delimiter: " ") # => 123 555 1234
50
+ # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
51
+ # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
52
+ # number_to_phone("123a456") # => 123a456
53
+ # number_to_phone("1234a567", raise: true) # => InvalidNumberError
54
+ #
55
+ # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".")
56
+ # # => +1.123.555.1234 x 1343
57
+ #
58
+ # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
59
+ # # => "(755) 6123-4567"
60
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
61
+ # # => "133-1234-5678"
62
+ def number_to_phone(number, options = {})
63
+ return unless number
64
+ options = options.symbolize_keys
65
+
66
+ parse_float(number, true) if options.delete(:raise)
67
+ ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
68
+ end
69
+
70
+ # Formats a +number+ into a currency string (e.g., $13.65). You
71
+ # can customize the format in the +options+ hash.
72
+ #
73
+ # The currency unit and number formatting of the current locale will be used
74
+ # unless otherwise specified in the provided options. No currency conversion
75
+ # is performed. If the user is given a way to change their locale, they will
76
+ # also be able to change the relative value of the currency displayed with
77
+ # this helper. If your application will ever support multiple locales, you
78
+ # may want to specify a constant <tt>:locale</tt> option or consider
79
+ # using a library capable of currency conversion.
80
+ #
81
+ # ==== Options
82
+ #
83
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
84
+ # (defaults to current locale).
85
+ # * <tt>:precision</tt> - Sets the level of precision (defaults
86
+ # to 2).
87
+ # * <tt>:unit</tt> - Sets the denomination of the currency
88
+ # (defaults to "$").
89
+ # * <tt>:separator</tt> - Sets the separator between the units
90
+ # (defaults to ".").
91
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
92
+ # to ",").
93
+ # * <tt>:format</tt> - Sets the format for non-negative numbers
94
+ # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
95
+ # currency, and <tt>%n</tt> for the number.
96
+ # * <tt>:negative_format</tt> - Sets the format for negative
97
+ # numbers (defaults to prepending a hyphen to the formatted
98
+ # number given by <tt>:format</tt>). Accepts the same fields
99
+ # than <tt>:format</tt>, except <tt>%n</tt> is here the
100
+ # absolute value of the number.
101
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
102
+ # the argument is invalid.
103
+ #
104
+ # ==== Examples
105
+ #
106
+ # number_to_currency(1234567890.50) # => $1,234,567,890.50
107
+ # number_to_currency(1234567890.506) # => $1,234,567,890.51
108
+ # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
109
+ # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
110
+ # number_to_currency("123a456") # => $123a456
111
+ #
112
+ # number_to_currency("123a456", raise: true) # => InvalidNumberError
113
+ #
114
+ # number_to_currency(-1234567890.50, negative_format: "(%u%n)")
115
+ # # => ($1,234,567,890.50)
116
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
117
+ # # => R$1234567890,50
118
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
119
+ # # => 1234567890,50 R$
120
+ def number_to_currency(number, options = {})
121
+ delegate_number_helper_method(:number_to_currency, number, options)
122
+ end
123
+
124
+ # Formats a +number+ as a percentage string (e.g., 65%). You can
125
+ # customize the format in the +options+ hash.
126
+ #
127
+ # ==== Options
128
+ #
129
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
130
+ # (defaults to current locale).
131
+ # * <tt>:precision</tt> - Sets the precision of the number
132
+ # (defaults to 3).
133
+ # * <tt>:significant</tt> - If +true+, precision will be the number
134
+ # of significant_digits. If +false+, the number of fractional
135
+ # digits (defaults to +false+).
136
+ # * <tt>:separator</tt> - Sets the separator between the
137
+ # fractional and integer digits (defaults to ".").
138
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
139
+ # to "").
140
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
141
+ # insignificant zeros after the decimal separator (defaults to
142
+ # +false+).
143
+ # * <tt>:format</tt> - Specifies the format of the percentage
144
+ # string The number field is <tt>%n</tt> (defaults to "%n%").
145
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
146
+ # the argument is invalid.
147
+ #
148
+ # ==== Examples
149
+ #
150
+ # number_to_percentage(100) # => 100.000%
151
+ # number_to_percentage("98") # => 98.000%
152
+ # number_to_percentage(100, precision: 0) # => 100%
153
+ # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
154
+ # number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
155
+ # number_to_percentage(1000, locale: :fr) # => 1 000,000%
156
+ # number_to_percentage("98a") # => 98a%
157
+ # number_to_percentage(100, format: "%n %") # => 100.000 %
158
+ #
159
+ # number_to_percentage("98a", raise: true) # => InvalidNumberError
160
+ def number_to_percentage(number, options = {})
161
+ delegate_number_helper_method(:number_to_percentage, number, options)
162
+ end
163
+
164
+ # Formats a +number+ with grouped thousands using +delimiter+
165
+ # (e.g., 12,324). You can customize the format in the +options+
166
+ # hash.
167
+ #
168
+ # ==== Options
169
+ #
170
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
171
+ # (defaults to current locale).
172
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
173
+ # to ",").
174
+ # * <tt>:separator</tt> - Sets the separator between the
175
+ # fractional and integer digits (defaults to ".").
176
+ # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for
177
+ # deriving the placement of delimiter. Helpful when using currency formats
178
+ # like INR.
179
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
180
+ # the argument is invalid.
181
+ #
182
+ # ==== Examples
183
+ #
184
+ # number_with_delimiter(12345678) # => 12,345,678
185
+ # number_with_delimiter("123456") # => 123,456
186
+ # number_with_delimiter(12345678.05) # => 12,345,678.05
187
+ # number_with_delimiter(12345678, delimiter: ".") # => 12.345.678
188
+ # number_with_delimiter(12345678, delimiter: ",") # => 12,345,678
189
+ # number_with_delimiter(12345678.05, separator: " ") # => 12,345,678 05
190
+ # number_with_delimiter(12345678.05, locale: :fr) # => 12 345 678,05
191
+ # number_with_delimiter("112a") # => 112a
192
+ # number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
193
+ # # => 98 765 432,98
194
+ #
195
+ # number_with_delimiter("123456.78",
196
+ # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) # => "1,23,456.78"
197
+ #
198
+ # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
199
+ def number_with_delimiter(number, options = {})
200
+ delegate_number_helper_method(:number_to_delimited, number, options)
201
+ end
202
+
203
+ # Formats a +number+ with the specified level of
204
+ # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
205
+ # +:significant+ is +false+, and 5 if +:significant+ is +true+).
206
+ # You can customize the format in the +options+ hash.
207
+ #
208
+ # ==== Options
209
+ #
210
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
211
+ # (defaults to current locale).
212
+ # * <tt>:precision</tt> - Sets the precision of the number
213
+ # (defaults to 3).
214
+ # * <tt>:significant</tt> - If +true+, precision will be the number
215
+ # of significant_digits. If +false+, the number of fractional
216
+ # digits (defaults to +false+).
217
+ # * <tt>:separator</tt> - Sets the separator between the
218
+ # fractional and integer digits (defaults to ".").
219
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
220
+ # to "").
221
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
222
+ # insignificant zeros after the decimal separator (defaults to
223
+ # +false+).
224
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
225
+ # the argument is invalid.
226
+ #
227
+ # ==== Examples
228
+ #
229
+ # number_with_precision(111.2345) # => 111.235
230
+ # number_with_precision(111.2345, precision: 2) # => 111.23
231
+ # number_with_precision(13, precision: 5) # => 13.00000
232
+ # number_with_precision(389.32314, precision: 0) # => 389
233
+ # number_with_precision(111.2345, significant: true) # => 111
234
+ # number_with_precision(111.2345, precision: 1, significant: true) # => 100
235
+ # number_with_precision(13, precision: 5, significant: true) # => 13.000
236
+ # number_with_precision(111.234, locale: :fr) # => 111,234
237
+ #
238
+ # number_with_precision(13, precision: 5, significant: true, strip_insignificant_zeros: true)
239
+ # # => 13
240
+ #
241
+ # number_with_precision(389.32314, precision: 4, significant: true) # => 389.3
242
+ # number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.')
243
+ # # => 1.111,23
244
+ def number_with_precision(number, options = {})
245
+ delegate_number_helper_method(:number_to_rounded, number, options)
246
+ end
247
+
248
+ # Formats the bytes in +number+ into a more understandable
249
+ # representation (e.g., giving it 1500 yields 1.5 KB). This
250
+ # method is useful for reporting file sizes to users. You can
251
+ # customize the format in the +options+ hash.
252
+ #
253
+ # See <tt>number_to_human</tt> if you want to pretty-print a
254
+ # generic number.
255
+ #
256
+ # ==== Options
257
+ #
258
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
259
+ # (defaults to current locale).
260
+ # * <tt>:precision</tt> - Sets the precision of the number
261
+ # (defaults to 3).
262
+ # * <tt>:significant</tt> - If +true+, precision will be the number
263
+ # of significant_digits. If +false+, the number of fractional
264
+ # digits (defaults to +true+)
265
+ # * <tt>:separator</tt> - Sets the separator between the
266
+ # fractional and integer digits (defaults to ".").
267
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
268
+ # to "").
269
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
270
+ # insignificant zeros after the decimal separator (defaults to
271
+ # +true+)
272
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
273
+ # the argument is invalid.
274
+ #
275
+ # ==== Examples
276
+ #
277
+ # number_to_human_size(123) # => 123 Bytes
278
+ # number_to_human_size(1234) # => 1.21 KB
279
+ # number_to_human_size(12345) # => 12.1 KB
280
+ # number_to_human_size(1234567) # => 1.18 MB
281
+ # number_to_human_size(1234567890) # => 1.15 GB
282
+ # number_to_human_size(1234567890123) # => 1.12 TB
283
+ # number_to_human_size(1234567890123456) # => 1.1 PB
284
+ # number_to_human_size(1234567890123456789) # => 1.07 EB
285
+ # number_to_human_size(1234567, precision: 2) # => 1.2 MB
286
+ # number_to_human_size(483989, precision: 2) # => 470 KB
287
+ # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
288
+ # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
289
+ # number_to_human_size(524288000, precision: 5) # => "500 MB"
290
+ def number_to_human_size(number, options = {})
291
+ delegate_number_helper_method(:number_to_human_size, number, options)
292
+ end
293
+
294
+ # Pretty prints (formats and approximates) a number in a way it
295
+ # is more readable by humans (eg.: 1200000000 becomes "1.2
296
+ # Billion"). This is useful for numbers that can get very large
297
+ # (and too hard to read).
298
+ #
299
+ # See <tt>number_to_human_size</tt> if you want to print a file
300
+ # size.
301
+ #
302
+ # You can also define your own unit-quantifier names if you want
303
+ # to use other decimal units (eg.: 1500 becomes "1.5
304
+ # kilometers", 0.150 becomes "150 milliliters", etc). You may
305
+ # define a wide range of unit quantifiers, even fractional ones
306
+ # (centi, deci, mili, etc).
307
+ #
308
+ # ==== Options
309
+ #
310
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
311
+ # (defaults to current locale).
312
+ # * <tt>:precision</tt> - Sets the precision of the number
313
+ # (defaults to 3).
314
+ # * <tt>:significant</tt> - If +true+, precision will be the number
315
+ # of significant_digits. If +false+, the number of fractional
316
+ # digits (defaults to +true+)
317
+ # * <tt>:separator</tt> - Sets the separator between the
318
+ # fractional and integer digits (defaults to ".").
319
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
320
+ # to "").
321
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
322
+ # insignificant zeros after the decimal separator (defaults to
323
+ # +true+)
324
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a
325
+ # string containing an i18n scope where to find this hash. It
326
+ # might have the following keys:
327
+ # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
328
+ # <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
329
+ # <tt>:billion</tt>, <tt>:trillion</tt>,
330
+ # <tt>:quadrillion</tt>
331
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
332
+ # <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
333
+ # <tt>:pico</tt>, <tt>:femto</tt>
334
+ # * <tt>:format</tt> - Sets the format of the output string
335
+ # (defaults to "%n %u"). The field types are:
336
+ # * %u - The quantifier (ex.: 'thousand')
337
+ # * %n - The number
338
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
339
+ # the argument is invalid.
340
+ #
341
+ # ==== Examples
342
+ #
343
+ # number_to_human(123) # => "123"
344
+ # number_to_human(1234) # => "1.23 Thousand"
345
+ # number_to_human(12345) # => "12.3 Thousand"
346
+ # number_to_human(1234567) # => "1.23 Million"
347
+ # number_to_human(1234567890) # => "1.23 Billion"
348
+ # number_to_human(1234567890123) # => "1.23 Trillion"
349
+ # number_to_human(1234567890123456) # => "1.23 Quadrillion"
350
+ # number_to_human(1234567890123456789) # => "1230 Quadrillion"
351
+ # number_to_human(489939, precision: 2) # => "490 Thousand"
352
+ # number_to_human(489939, precision: 4) # => "489.9 Thousand"
353
+ # number_to_human(1234567, precision: 4,
354
+ # significant: false) # => "1.2346 Million"
355
+ # number_to_human(1234567, precision: 1,
356
+ # separator: ',',
357
+ # significant: false) # => "1,2 Million"
358
+ #
359
+ # number_to_human(500000000, precision: 5) # => "500 Million"
360
+ # number_to_human(12345012345, significant: false) # => "12.345 Billion"
361
+ #
362
+ # Non-significant zeros after the decimal separator are stripped
363
+ # out by default (set <tt>:strip_insignificant_zeros</tt> to
364
+ # +false+ to change that):
365
+ #
366
+ # number_to_human(12.00001) # => "12"
367
+ # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
368
+ #
369
+ # ==== Custom Unit Quantifiers
370
+ #
371
+ # You can also use your own custom unit quantifiers:
372
+ # number_to_human(500000, units: {unit: "ml", thousand: "lt"}) # => "500 lt"
373
+ #
374
+ # If in your I18n locale you have:
375
+ # distance:
376
+ # centi:
377
+ # one: "centimeter"
378
+ # other: "centimeters"
379
+ # unit:
380
+ # one: "meter"
381
+ # other: "meters"
382
+ # thousand:
383
+ # one: "kilometer"
384
+ # other: "kilometers"
385
+ # billion: "gazillion-distance"
386
+ #
387
+ # Then you could do:
388
+ #
389
+ # number_to_human(543934, units: :distance) # => "544 kilometers"
390
+ # number_to_human(54393498, units: :distance) # => "54400 kilometers"
391
+ # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
392
+ # number_to_human(343, units: :distance, precision: 1) # => "300 meters"
393
+ # number_to_human(1, units: :distance) # => "1 meter"
394
+ # number_to_human(0.34, units: :distance) # => "34 centimeters"
395
+ #
396
+ def number_to_human(number, options = {})
397
+ delegate_number_helper_method(:number_to_human, number, options)
398
+ end
399
+
400
+ private
401
+
402
+ def delegate_number_helper_method(method, number, options)
403
+ return unless number
404
+ options = escape_unsafe_options(options.symbolize_keys)
405
+
406
+ wrap_with_output_safety_handling(number, options.delete(:raise)) {
407
+ ActiveSupport::NumberHelper.public_send(method, number, options)
408
+ }
409
+ end
410
+
411
+ def escape_unsafe_options(options)
412
+ options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
413
+ options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format]
414
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator]
415
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter]
416
+ options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
417
+ options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units]
418
+ options
419
+ end
420
+
421
+ def escape_units(units)
422
+ Hash[units.map do |k, v|
423
+ [k, ERB::Util.html_escape(v)]
424
+ end]
425
+ end
426
+
427
+ def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
428
+ valid_float = valid_float?(number)
429
+ raise InvalidNumberError, number if raise_on_invalid && !valid_float
430
+
431
+ formatted_number = yield
432
+
433
+ if valid_float || number.html_safe?
434
+ formatted_number.html_safe
435
+ else
436
+ formatted_number
437
+ end
438
+ end
439
+
440
+ def valid_float?(number)
441
+ !parse_float(number, false).nil?
442
+ end
443
+
444
+ def parse_float(number, raise_error)
445
+ Float(number)
446
+ rescue ArgumentError, TypeError
447
+ raise InvalidNumberError, number if raise_error
448
+ end
449
+ end
450
+ end
451
+ end