actionview 6.0.0

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