activesupport 1.2.4 → 8.1.2
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +505 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +40 -0
- data/lib/active_support/actionable_error.rb +50 -0
- data/lib/active_support/all.rb +5 -0
- data/lib/active_support/array_inquirer.rb +50 -0
- data/lib/active_support/backtrace_cleaner.rb +234 -0
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +53 -0
- data/lib/active_support/broadcast_logger.rb +238 -0
- data/lib/active_support/builder.rb +8 -0
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +244 -0
- data/lib/active_support/cache/mem_cache_store.rb +288 -0
- data/lib/active_support/cache/memory_store.rb +264 -0
- data/lib/active_support/cache/null_store.rb +62 -0
- data/lib/active_support/cache/redis_cache_store.rb +498 -0
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +246 -0
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
- data/lib/active_support/cache.rb +1170 -0
- data/lib/active_support/callbacks.rb +960 -0
- data/lib/active_support/class_attribute.rb +33 -0
- data/lib/active_support/code_generator.rb +79 -0
- data/lib/active_support/concern.rb +217 -0
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/concurrency/share_lock.rb +225 -0
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +193 -0
- data/lib/active_support/configuration_file.rb +60 -0
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array/access.rb +100 -0
- data/lib/active_support/core_ext/array/conversions.rb +209 -26
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/extract_options.rb +31 -0
- data/lib/active_support/core_ext/array/grouping.rb +109 -0
- data/lib/active_support/core_ext/array/inquiry.rb +19 -0
- data/lib/active_support/core_ext/array/wrap.rb +48 -0
- data/lib/active_support/core_ext/array.rb +8 -4
- data/lib/active_support/core_ext/benchmark.rb +6 -0
- data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
- data/lib/active_support/core_ext/big_decimal.rb +3 -0
- data/lib/active_support/core_ext/class/attribute.rb +137 -0
- data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/class/subclasses.rb +24 -0
- data/lib/active_support/core_ext/class.rb +4 -0
- data/lib/active_support/core_ext/date/acts_like.rb +10 -0
- data/lib/active_support/core_ext/date/blank.rb +18 -0
- data/lib/active_support/core_ext/date/calculations.rb +161 -0
- data/lib/active_support/core_ext/date/conversions.rb +95 -28
- data/lib/active_support/core_ext/date/zones.rb +8 -0
- data/lib/active_support/core_ext/date.rb +6 -5
- data/lib/active_support/core_ext/date_and_time/calculations.rb +374 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +23 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
- data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
- data/lib/active_support/core_ext/date_time/blank.rb +18 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +215 -0
- data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +108 -0
- data/lib/active_support/core_ext/date_time.rb +7 -0
- data/lib/active_support/core_ext/digest/uuid.rb +76 -0
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +277 -7
- data/lib/active_support/core_ext/erb/util.rb +201 -0
- data/lib/active_support/core_ext/file/atomic.rb +72 -0
- data/lib/active_support/core_ext/file.rb +3 -0
- data/lib/active_support/core_ext/hash/conversions.rb +262 -0
- data/lib/active_support/core_ext/hash/deep_merge.rb +43 -0
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +12 -0
- data/lib/active_support/core_ext/hash/indifferent_access.rb +19 -55
- data/lib/active_support/core_ext/hash/keys.rb +134 -44
- data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -22
- data/lib/active_support/core_ext/hash/slice.rb +27 -0
- data/lib/active_support/core_ext/hash.rb +9 -8
- data/lib/active_support/core_ext/integer/inflections.rb +29 -13
- data/lib/active_support/core_ext/integer/multiple.rb +12 -0
- data/lib/active_support/core_ext/integer/time.rb +22 -0
- data/lib/active_support/core_ext/integer.rb +4 -6
- data/lib/active_support/core_ext/kernel/concern.rb +14 -0
- data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
- data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
- data/lib/active_support/core_ext/kernel.rb +4 -78
- data/lib/active_support/core_ext/load_error.rb +6 -35
- data/lib/active_support/core_ext/module/aliasing.rb +31 -0
- data/lib/active_support/core_ext/module/anonymous.rb +30 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +48 -0
- data/lib/active_support/core_ext/module/attribute_accessors.rb +214 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +175 -0
- data/lib/active_support/core_ext/module/concerning.rb +140 -0
- data/lib/active_support/core_ext/module/delegation.rb +225 -0
- data/lib/active_support/core_ext/module/deprecation.rb +25 -0
- data/lib/active_support/core_ext/module/introspection.rb +65 -0
- data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
- data/lib/active_support/core_ext/module/remove_method.rb +17 -0
- data/lib/active_support/core_ext/module.rb +13 -0
- data/lib/active_support/core_ext/name_error.rb +59 -0
- data/lib/active_support/core_ext/numeric/bytes.rb +73 -42
- data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
- data/lib/active_support/core_ext/numeric/time.rb +64 -57
- data/lib/active_support/core_ext/numeric.rb +4 -6
- data/lib/active_support/core_ext/object/acts_like.rb +45 -0
- data/lib/active_support/core_ext/object/blank.rb +199 -0
- data/lib/active_support/core_ext/object/conversions.rb +6 -0
- data/lib/active_support/core_ext/object/deep_dup.rb +71 -0
- data/lib/active_support/core_ext/object/duplicable.rb +69 -0
- data/lib/active_support/core_ext/object/inclusion.rb +37 -0
- data/lib/active_support/core_ext/object/instance_variables.rb +32 -0
- data/lib/active_support/core_ext/object/json.rb +267 -0
- data/lib/active_support/core_ext/object/to_param.rb +3 -0
- data/lib/active_support/core_ext/object/to_query.rb +93 -0
- data/lib/active_support/core_ext/object/try.rb +158 -0
- data/lib/active_support/core_ext/object/with.rb +46 -0
- data/lib/active_support/core_ext/object/with_options.rb +101 -0
- data/lib/active_support/core_ext/object.rb +17 -0
- data/lib/active_support/core_ext/pathname/blank.rb +20 -0
- data/lib/active_support/core_ext/pathname/existence.rb +23 -0
- data/lib/active_support/core_ext/pathname.rb +4 -0
- data/lib/active_support/core_ext/range/compare_range.rb +57 -0
- data/lib/active_support/core_ext/range/conversions.rb +58 -17
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +5 -4
- data/lib/active_support/core_ext/regexp.rb +14 -0
- data/lib/active_support/core_ext/securerandom.rb +57 -0
- data/lib/active_support/core_ext/string/access.rb +93 -56
- data/lib/active_support/core_ext/string/behavior.rb +8 -0
- data/lib/active_support/core_ext/string/conversions.rb +57 -16
- data/lib/active_support/core_ext/string/exclude.rb +13 -0
- data/lib/active_support/core_ext/string/filters.rb +151 -0
- data/lib/active_support/core_ext/string/indent.rb +45 -0
- data/lib/active_support/core_ext/string/inflections.rb +297 -54
- data/lib/active_support/core_ext/string/inquiry.rb +16 -0
- data/lib/active_support/core_ext/string/multibyte.rb +67 -0
- data/lib/active_support/core_ext/string/output_safety.rb +235 -0
- data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -18
- data/lib/active_support/core_ext/string/strip.rb +27 -0
- data/lib/active_support/core_ext/string/zones.rb +16 -0
- data/lib/active_support/core_ext/string.rb +14 -10
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/thread/backtrace/location.rb +7 -0
- data/lib/active_support/core_ext/time/acts_like.rb +10 -0
- data/lib/active_support/core_ext/time/calculations.rb +358 -153
- data/lib/active_support/core_ext/time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/time/conversions.rb +69 -30
- data/lib/active_support/core_ext/time/zones.rb +97 -0
- data/lib/active_support/core_ext/time.rb +6 -6
- data/lib/active_support/core_ext.rb +5 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +243 -0
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/delegation.rb +183 -0
- data/lib/active_support/dependencies/autoload.rb +72 -0
- data/lib/active_support/dependencies/interlock.rb +55 -0
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +84 -222
- data/lib/active_support/deprecation/behaviors.rb +148 -0
- data/lib/active_support/deprecation/constant_accessor.rb +74 -0
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +54 -0
- data/lib/active_support/deprecation/method_wrappers.rb +68 -0
- data/lib/active_support/deprecation/proxy_wrappers.rb +189 -0
- data/lib/active_support/deprecation/reporting.rb +162 -0
- data/lib/active_support/deprecation.rb +81 -0
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +112 -0
- data/lib/active_support/digest.rb +22 -0
- data/lib/active_support/duration/iso8601_parser.rb +123 -0
- data/lib/active_support/duration/iso8601_serializer.rb +64 -0
- data/lib/active_support/duration.rb +524 -0
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/encrypted_configuration.rb +126 -0
- data/lib/active_support/encrypted_file.rb +133 -0
- data/lib/active_support/environment_inquirer.rb +40 -0
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +318 -0
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +592 -0
- data/lib/active_support/evented_file_update_checker.rb +185 -0
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +110 -0
- data/lib/active_support/execution_wrapper.rb +150 -0
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/executor.rb +8 -0
- data/lib/active_support/file_update_checker.rb +166 -0
- data/lib/active_support/fork_tracker.rb +43 -0
- data/lib/active_support/gem_version.rb +17 -0
- data/lib/active_support/gzip.rb +41 -0
- data/lib/active_support/hash_with_indifferent_access.rb +464 -0
- data/lib/active_support/html_safe_translation.rb +56 -0
- data/lib/active_support/i18n.rb +17 -0
- data/lib/active_support/i18n_railtie.rb +140 -0
- data/lib/active_support/inflections.rb +68 -49
- data/lib/active_support/inflector/inflections.rb +290 -0
- data/lib/active_support/inflector/methods.rb +387 -0
- data/lib/active_support/inflector/transliterate.rb +147 -0
- data/lib/active_support/inflector.rb +7 -164
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +78 -0
- data/lib/active_support/json/encoding.rb +256 -0
- data/lib/active_support/json.rb +4 -0
- data/lib/active_support/key_generator.rb +66 -0
- data/lib/active_support/lazy_load_hooks.rb +107 -0
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/locale/en.yml +141 -0
- data/lib/active_support/log_subscriber/test_helper.rb +106 -0
- data/lib/active_support/log_subscriber.rb +188 -0
- data/lib/active_support/logger.rb +55 -0
- data/lib/active_support/logger_silence.rb +21 -0
- data/lib/active_support/logger_thread_safe_level.rb +50 -0
- data/lib/active_support/message_encryptor.rb +374 -0
- data/lib/active_support/message_encryptors.rb +193 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +310 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +377 -0
- data/lib/active_support/message_verifiers.rb +189 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +146 -0
- data/lib/active_support/messages/rotation_configuration.rb +23 -0
- data/lib/active_support/messages/rotation_coordinator.rb +102 -0
- data/lib/active_support/messages/rotator.rb +69 -0
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +188 -0
- data/lib/active_support/multibyte/unicode.rb +42 -0
- data/lib/active_support/multibyte.rb +27 -0
- data/lib/active_support/notifications/fanout.rb +467 -0
- data/lib/active_support/notifications/instrumenter.rb +240 -0
- data/lib/active_support/notifications.rb +281 -0
- data/lib/active_support/number_helper/number_converter.rb +190 -0
- data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
- data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
- data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
- data/lib/active_support/number_helper/rounding_helper.rb +46 -0
- data/lib/active_support/number_helper.rb +479 -0
- data/lib/active_support/option_merger.rb +38 -0
- data/lib/active_support/ordered_hash.rb +50 -0
- data/lib/active_support/ordered_options.rb +141 -25
- data/lib/active_support/parameter_filter.rb +157 -0
- data/lib/active_support/rails.rb +26 -0
- data/lib/active_support/railtie.rb +180 -0
- data/lib/active_support/reloader.rb +138 -0
- data/lib/active_support/rescuable.rb +176 -0
- data/lib/active_support/secure_compare_rotator.rb +58 -0
- data/lib/active_support/security_utils.rb +38 -0
- data/lib/active_support/string_inquirer.rb +35 -0
- data/lib/active_support/structured_event_subscriber.rb +99 -0
- data/lib/active_support/subscriber.rb +141 -0
- data/lib/active_support/syntax_error_proxy.rb +67 -0
- data/lib/active_support/tagged_logging.rb +157 -0
- data/lib/active_support/test_case.rb +365 -0
- data/lib/active_support/testing/assertions.rb +369 -0
- data/lib/active_support/testing/autorun.rb +10 -0
- data/lib/active_support/testing/constant_lookup.rb +51 -0
- data/lib/active_support/testing/constant_stubbing.rb +54 -0
- data/lib/active_support/testing/declarative.rb +28 -0
- data/lib/active_support/testing/deprecation.rb +82 -0
- data/lib/active_support/testing/error_reporter_assertions.rb +124 -0
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/file_fixtures.rb +38 -0
- data/lib/active_support/testing/isolation.rb +121 -0
- data/lib/active_support/testing/method_call_assertions.rb +69 -0
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +98 -0
- data/lib/active_support/testing/parallelization/worker.rb +107 -0
- data/lib/active_support/testing/parallelization.rb +79 -0
- data/lib/active_support/testing/parallelize_executor.rb +81 -0
- data/lib/active_support/testing/setup_and_teardown.rb +57 -0
- data/lib/active_support/testing/stream.rb +41 -0
- data/lib/active_support/testing/tagged_logging.rb +27 -0
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +273 -0
- data/lib/active_support/time.rb +20 -0
- data/lib/active_support/time_with_zone.rb +613 -0
- data/lib/active_support/values/time_zone.rb +599 -158
- data/lib/active_support/version.rb +7 -6
- data/lib/active_support/xml_mini/jdom.rb +175 -0
- data/lib/active_support/xml_mini/libxml.rb +80 -0
- data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
- data/lib/active_support/xml_mini/nokogiri.rb +83 -0
- data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
- data/lib/active_support/xml_mini/rexml.rb +137 -0
- data/lib/active_support/xml_mini.rb +212 -0
- data/lib/active_support.rb +122 -10
- metadata +524 -93
- data/CHANGELOG +0 -283
- data/lib/active_support/binding_of_caller.rb +0 -84
- data/lib/active_support/breakpoint.rb +0 -523
- data/lib/active_support/class_attribute_accessors.rb +0 -57
- data/lib/active_support/class_inheritable_attributes.rb +0 -117
- data/lib/active_support/clean_logger.rb +0 -36
- data/lib/active_support/core_ext/blank.rb +0 -38
- data/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb +0 -14
- data/lib/active_support/core_ext/cgi.rb +0 -5
- data/lib/active_support/core_ext/exception.rb +0 -29
- data/lib/active_support/core_ext/integer/even_odd.rb +0 -24
- data/lib/active_support/core_ext/object_and_class.rb +0 -44
- data/lib/active_support/module_attribute_accessors.rb +0 -57
- data/lib/active_support/whiny_nil.rb +0 -38
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "active_support/inflector/methods"
|
|
5
|
+
require "active_support/core_ext/time/conversions"
|
|
6
|
+
require "active_support/core_ext/date_time/calculations"
|
|
7
|
+
require "active_support/values/time_zone"
|
|
8
|
+
|
|
9
|
+
class DateTime
|
|
10
|
+
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
|
|
11
|
+
#
|
|
12
|
+
# This method is aliased to <tt>to_formatted_s</tt>.
|
|
13
|
+
#
|
|
14
|
+
# ==== Examples
|
|
15
|
+
#
|
|
16
|
+
# datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
|
|
17
|
+
#
|
|
18
|
+
# datetime.to_fs(:db) # => "2007-12-04 00:00:00"
|
|
19
|
+
# datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
|
|
20
|
+
# datetime.to_fs(:number) # => "20071204000000"
|
|
21
|
+
# datetime.to_fs(:short) # => "04 Dec 00:00"
|
|
22
|
+
# datetime.to_fs(:long) # => "December 04, 2007 00:00"
|
|
23
|
+
# datetime.to_fs(:long_ordinal) # => "December 4th, 2007 00:00"
|
|
24
|
+
# datetime.to_fs(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000"
|
|
25
|
+
# datetime.to_fs(:iso8601) # => "2007-12-04T00:00:00+00:00"
|
|
26
|
+
#
|
|
27
|
+
# ==== Adding your own datetime formats to +to_fs+
|
|
28
|
+
#
|
|
29
|
+
# DateTime formats are shared with Time. You can add your own to the
|
|
30
|
+
# Time::DATE_FORMATS hash. Use the format name as the hash key and
|
|
31
|
+
# either a strftime string or Proc instance that takes a time or
|
|
32
|
+
# datetime argument as the value.
|
|
33
|
+
#
|
|
34
|
+
# # config/initializers/time_formats.rb
|
|
35
|
+
# Time::DATE_FORMATS[:month_and_year] = '%B %Y'
|
|
36
|
+
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
|
|
37
|
+
def to_fs(format = :default)
|
|
38
|
+
if formatter = ::Time::DATE_FORMATS[format]
|
|
39
|
+
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
|
|
40
|
+
else
|
|
41
|
+
to_s
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
alias_method :to_formatted_s, :to_fs
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Returns a formatted string of the offset from UTC, or an alternative
|
|
48
|
+
# string if the time zone is already UTC.
|
|
49
|
+
#
|
|
50
|
+
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
|
|
51
|
+
# datetime.formatted_offset # => "-06:00"
|
|
52
|
+
# datetime.formatted_offset(false) # => "-0600"
|
|
53
|
+
def formatted_offset(colon = true, alternate_utc_string = nil)
|
|
54
|
+
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000".
|
|
58
|
+
def readable_inspect
|
|
59
|
+
to_fs(:rfc822)
|
|
60
|
+
end
|
|
61
|
+
alias_method :default_inspect, :inspect
|
|
62
|
+
alias_method :inspect, :readable_inspect
|
|
63
|
+
|
|
64
|
+
# Returns DateTime with local offset for given year if format is local else
|
|
65
|
+
# offset is zero.
|
|
66
|
+
#
|
|
67
|
+
# DateTime.civil_from_format :local, 2012
|
|
68
|
+
# # => Sun, 01 Jan 2012 00:00:00 +0300
|
|
69
|
+
# DateTime.civil_from_format :local, 2012, 12, 17
|
|
70
|
+
# # => Mon, 17 Dec 2012 00:00:00 +0000
|
|
71
|
+
def self.civil_from_format(utc_or_local, year, month = 1, day = 1, hour = 0, min = 0, sec = 0)
|
|
72
|
+
if utc_or_local.to_sym == :local
|
|
73
|
+
offset = ::Time.local(year, month, day).utc_offset.to_r / 86400
|
|
74
|
+
else
|
|
75
|
+
offset = 0
|
|
76
|
+
end
|
|
77
|
+
civil(year, month, day, hour, min, sec, offset)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Converts +self+ to a floating-point number of seconds, including fractional microseconds, since the Unix epoch.
|
|
81
|
+
def to_f
|
|
82
|
+
seconds_since_unix_epoch.to_f + sec_fraction
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Converts +self+ to an integer number of seconds since the Unix epoch.
|
|
86
|
+
def to_i
|
|
87
|
+
seconds_since_unix_epoch.to_i
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns the fraction of a second as microseconds
|
|
91
|
+
def usec
|
|
92
|
+
(sec_fraction * 1_000_000).to_i
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns the fraction of a second as nanoseconds
|
|
96
|
+
def nsec
|
|
97
|
+
(sec_fraction * 1_000_000_000).to_i
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
def offset_in_seconds
|
|
102
|
+
(offset * 86400).to_i
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def seconds_since_unix_epoch
|
|
106
|
+
(jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "openssl"
|
|
5
|
+
|
|
6
|
+
module Digest
|
|
7
|
+
module UUID
|
|
8
|
+
DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
|
|
9
|
+
URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
|
|
10
|
+
OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
|
|
11
|
+
X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
|
|
12
|
+
|
|
13
|
+
# Generates a v5 non-random UUID (Universally Unique IDentifier).
|
|
14
|
+
#
|
|
15
|
+
# Using OpenSSL::Digest::MD5 generates version 3 UUIDs; OpenSSL::Digest::SHA1 generates version 5 UUIDs.
|
|
16
|
+
# uuid_from_hash always generates the same UUID for a given name and namespace combination.
|
|
17
|
+
#
|
|
18
|
+
# See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
|
|
19
|
+
def self.uuid_from_hash(hash_class, namespace, name)
|
|
20
|
+
if hash_class == Digest::MD5 || hash_class == OpenSSL::Digest::MD5
|
|
21
|
+
version = 3
|
|
22
|
+
elsif hash_class == Digest::SHA1 || hash_class == OpenSSL::Digest::SHA1
|
|
23
|
+
version = 5
|
|
24
|
+
else
|
|
25
|
+
raise ArgumentError, "Expected OpenSSL::Digest::SHA1 or OpenSSL::Digest::MD5, got #{hash_class.name}."
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
uuid_namespace = pack_uuid_namespace(namespace)
|
|
29
|
+
|
|
30
|
+
hash = hash_class.new
|
|
31
|
+
hash.update(uuid_namespace)
|
|
32
|
+
hash.update(name)
|
|
33
|
+
|
|
34
|
+
ary = hash.digest.unpack("NnnnnN")
|
|
35
|
+
ary[2] = (ary[2] & 0x0FFF) | (version << 12)
|
|
36
|
+
ary[3] = (ary[3] & 0x3FFF) | 0x8000
|
|
37
|
+
|
|
38
|
+
"%08x-%04x-%04x-%04x-%04x%08x" % ary
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Convenience method for uuid_from_hash using OpenSSL::Digest::MD5.
|
|
42
|
+
def self.uuid_v3(uuid_namespace, name)
|
|
43
|
+
uuid_from_hash(OpenSSL::Digest::MD5, uuid_namespace, name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Convenience method for uuid_from_hash using OpenSSL::Digest::SHA1.
|
|
47
|
+
def self.uuid_v5(uuid_namespace, name)
|
|
48
|
+
uuid_from_hash(OpenSSL::Digest::SHA1, uuid_namespace, name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Convenience method for SecureRandom.uuid.
|
|
52
|
+
def self.uuid_v4
|
|
53
|
+
SecureRandom.uuid
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the nil UUID. This is a special form of UUID that is specified to
|
|
57
|
+
# have all 128 bits set to zero.
|
|
58
|
+
def self.nil_uuid
|
|
59
|
+
"00000000-0000-0000-0000-000000000000"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.pack_uuid_namespace(namespace)
|
|
63
|
+
if [DNS_NAMESPACE, OID_NAMESPACE, URL_NAMESPACE, X500_NAMESPACE].include?(namespace)
|
|
64
|
+
namespace
|
|
65
|
+
else
|
|
66
|
+
match_data = namespace.match(/\A(\h{8})-(\h{4})-(\h{4})-(\h{4})-(\h{4})(\h{8})\z/)
|
|
67
|
+
|
|
68
|
+
raise ArgumentError, "Only UUIDs are valid namespace identifiers" unless match_data.present?
|
|
69
|
+
|
|
70
|
+
match_data.captures.map { |s| s.to_i(16) }.pack("NnnnnN")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private_class_method :pack_uuid_namespace
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -1,9 +1,279 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module EnumerableCoreExt # :nodoc:
|
|
5
|
+
module Constants
|
|
6
|
+
private
|
|
7
|
+
def const_missing(name)
|
|
8
|
+
if name == :SoleItemExpectedError
|
|
9
|
+
::ActiveSupport::EnumerableCoreExt::SoleItemExpectedError
|
|
10
|
+
else
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
end
|
|
6
14
|
end
|
|
7
|
-
match
|
|
8
15
|
end
|
|
9
|
-
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module Enumerable
|
|
19
|
+
# Error generated by +sole+ when called on an enumerable that doesn't have
|
|
20
|
+
# exactly one item.
|
|
21
|
+
class SoleItemExpectedError < StandardError; end
|
|
22
|
+
|
|
23
|
+
# HACK: For performance reasons, Enumerable shouldn't have any constants of its own.
|
|
24
|
+
# So we move SoleItemExpectedError into ActiveSupport::EnumerableCoreExt.
|
|
25
|
+
ActiveSupport::EnumerableCoreExt::SoleItemExpectedError = remove_const(:SoleItemExpectedError)
|
|
26
|
+
singleton_class.prepend(ActiveSupport::EnumerableCoreExt::Constants)
|
|
27
|
+
|
|
28
|
+
# Calculates the minimum from the extracted elements.
|
|
29
|
+
#
|
|
30
|
+
# payments = [Payment.new(5), Payment.new(15), Payment.new(10)]
|
|
31
|
+
# payments.minimum(:price) # => 5
|
|
32
|
+
def minimum(key)
|
|
33
|
+
map(&key).min
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Calculates the maximum from the extracted elements.
|
|
37
|
+
#
|
|
38
|
+
# payments = [Payment.new(5), Payment.new(15), Payment.new(10)]
|
|
39
|
+
# payments.maximum(:price) # => 15
|
|
40
|
+
def maximum(key)
|
|
41
|
+
map(&key).max
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convert an enumerable to a hash, using the block result as the key and the
|
|
45
|
+
# element as the value.
|
|
46
|
+
#
|
|
47
|
+
# people.index_by(&:login)
|
|
48
|
+
# # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
|
|
49
|
+
#
|
|
50
|
+
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
|
|
51
|
+
# # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
|
|
52
|
+
def index_by
|
|
53
|
+
if block_given?
|
|
54
|
+
result = {}
|
|
55
|
+
each { |elem| result[yield(elem)] = elem }
|
|
56
|
+
result
|
|
57
|
+
else
|
|
58
|
+
to_enum(:index_by) { size if respond_to?(:size) }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Convert an enumerable to a hash, using the element as the key and the block
|
|
63
|
+
# result as the value.
|
|
64
|
+
#
|
|
65
|
+
# post = Post.new(title: "hey there", body: "what's up?")
|
|
66
|
+
#
|
|
67
|
+
# %i( title body ).index_with { |attr_name| post.public_send(attr_name) }
|
|
68
|
+
# # => { title: "hey there", body: "what's up?" }
|
|
69
|
+
#
|
|
70
|
+
# If an argument is passed instead of a block, it will be used as the value
|
|
71
|
+
# for all elements:
|
|
72
|
+
#
|
|
73
|
+
# %i( created_at updated_at ).index_with(Time.now)
|
|
74
|
+
# # => { created_at: 2020-03-09 22:31:47, updated_at: 2020-03-09 22:31:47 }
|
|
75
|
+
def index_with(default = (no_default = true))
|
|
76
|
+
if block_given?
|
|
77
|
+
result = {}
|
|
78
|
+
each { |elem| result[elem] = yield(elem) }
|
|
79
|
+
result
|
|
80
|
+
elsif no_default
|
|
81
|
+
to_enum(:index_with) { size if respond_to?(:size) }
|
|
82
|
+
else
|
|
83
|
+
result = {}
|
|
84
|
+
each { |elem| result[elem] = default }
|
|
85
|
+
result
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns +true+ if the enumerable has more than 1 element. Functionally
|
|
90
|
+
# equivalent to <tt>enum.to_a.size > 1</tt>. Can be called with a block too,
|
|
91
|
+
# much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns +true+
|
|
92
|
+
# if more than one person is over 26.
|
|
93
|
+
def many?
|
|
94
|
+
cnt = 0
|
|
95
|
+
if block_given?
|
|
96
|
+
any? do |*args|
|
|
97
|
+
cnt += 1 if yield(*args)
|
|
98
|
+
cnt > 1
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
any? { (cnt += 1) > 1 }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Returns a new array that includes the passed elements.
|
|
106
|
+
#
|
|
107
|
+
# [ 1, 2, 3 ].including(4, 5)
|
|
108
|
+
# # => [ 1, 2, 3, 4, 5 ]
|
|
109
|
+
#
|
|
110
|
+
# ["David", "Rafael"].including %w[ Aaron Todd ]
|
|
111
|
+
# # => ["David", "Rafael", "Aaron", "Todd"]
|
|
112
|
+
def including(*elements)
|
|
113
|
+
to_a.including(*elements)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
|
|
117
|
+
# collection does not include the object.
|
|
118
|
+
def exclude?(object)
|
|
119
|
+
!include?(object)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Returns a copy of the enumerable excluding the specified elements.
|
|
123
|
+
#
|
|
124
|
+
# ["David", "Rafael", "Aaron", "Todd"].excluding "Aaron", "Todd"
|
|
125
|
+
# # => ["David", "Rafael"]
|
|
126
|
+
#
|
|
127
|
+
# ["David", "Rafael", "Aaron", "Todd"].excluding %w[ Aaron Todd ]
|
|
128
|
+
# # => ["David", "Rafael"]
|
|
129
|
+
#
|
|
130
|
+
# {foo: 1, bar: 2, baz: 3}.excluding :bar
|
|
131
|
+
# # => {foo: 1, baz: 3}
|
|
132
|
+
def excluding(*elements)
|
|
133
|
+
elements.flatten!(1)
|
|
134
|
+
reject { |element| elements.include?(element) }
|
|
135
|
+
end
|
|
136
|
+
alias :without :excluding
|
|
137
|
+
|
|
138
|
+
# Extract the given key from each element in the enumerable.
|
|
139
|
+
#
|
|
140
|
+
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
|
|
141
|
+
# # => ["David", "Rafael", "Aaron"]
|
|
142
|
+
#
|
|
143
|
+
# [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
|
|
144
|
+
# # => [[1, "David"], [2, "Rafael"]]
|
|
145
|
+
def pluck(*keys)
|
|
146
|
+
if keys.many?
|
|
147
|
+
map { |element| keys.map { |key| element[key] } }
|
|
148
|
+
else
|
|
149
|
+
key = keys.first
|
|
150
|
+
map { |element| element[key] }
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Extract the given key from the first element in the enumerable.
|
|
155
|
+
#
|
|
156
|
+
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name)
|
|
157
|
+
# # => "David"
|
|
158
|
+
#
|
|
159
|
+
# [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name)
|
|
160
|
+
# # => [1, "David"]
|
|
161
|
+
def pick(*keys)
|
|
162
|
+
return if none?
|
|
163
|
+
|
|
164
|
+
if keys.many?
|
|
165
|
+
keys.map { |key| first[key] }
|
|
166
|
+
else
|
|
167
|
+
first[keys.first]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Returns a new +Array+ without the blank items.
|
|
172
|
+
# Uses Object#blank? for determining if an item is blank.
|
|
173
|
+
#
|
|
174
|
+
# [1, "", nil, 2, " ", [], {}, false, true].compact_blank
|
|
175
|
+
# # => [1, 2, true]
|
|
176
|
+
#
|
|
177
|
+
# Set.new([nil, "", 1, false]).compact_blank
|
|
178
|
+
# # => [1]
|
|
179
|
+
#
|
|
180
|
+
# When called on a +Hash+, returns a new +Hash+ without the blank values.
|
|
181
|
+
#
|
|
182
|
+
# { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
|
|
183
|
+
# # => { b: 1, f: true }
|
|
184
|
+
def compact_blank
|
|
185
|
+
reject(&:blank?)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Returns a new +Array+ where the order has been set to that provided in the +series+, based on the +key+ of the
|
|
189
|
+
# objects in the original enumerable.
|
|
190
|
+
#
|
|
191
|
+
# [ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ])
|
|
192
|
+
# # => [ Person.find(1), Person.find(5), Person.find(3) ]
|
|
193
|
+
#
|
|
194
|
+
# If the +series+ include keys that have no corresponding element in the Enumerable, these are ignored.
|
|
195
|
+
# If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result, unless
|
|
196
|
+
# the +filter+ option is set to +false+.
|
|
197
|
+
def in_order_of(key, series, filter: true)
|
|
198
|
+
if filter
|
|
199
|
+
group_by(&key).values_at(*series).flatten(1).compact
|
|
200
|
+
else
|
|
201
|
+
sort_by { |v| series.index(v.public_send(key)) || series.size }.compact
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Returns the sole item in the enumerable. If there are no items, or more
|
|
206
|
+
# than one item, raises Enumerable::SoleItemExpectedError.
|
|
207
|
+
#
|
|
208
|
+
# ["x"].sole # => "x"
|
|
209
|
+
# Set.new.sole # => Enumerable::SoleItemExpectedError: no item found
|
|
210
|
+
# { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found
|
|
211
|
+
def sole
|
|
212
|
+
result = nil
|
|
213
|
+
found = false
|
|
214
|
+
|
|
215
|
+
each do |*element|
|
|
216
|
+
if found
|
|
217
|
+
raise SoleItemExpectedError, "multiple items found"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
result = element.size == 1 ? element[0] : element
|
|
221
|
+
found = true
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if found
|
|
225
|
+
result
|
|
226
|
+
else
|
|
227
|
+
raise SoleItemExpectedError, "no item found"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
class Hash
|
|
233
|
+
# Hash#reject has its own definition, so this needs one too.
|
|
234
|
+
def compact_blank # :nodoc:
|
|
235
|
+
reject { |_k, v| v.blank? }
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Removes all blank values from the +Hash+ in place and returns self.
|
|
239
|
+
# Uses Object#blank? for determining if a value is blank.
|
|
240
|
+
#
|
|
241
|
+
# h = { a: "", b: 1, c: nil, d: [], e: false, f: true }
|
|
242
|
+
# h.compact_blank!
|
|
243
|
+
# # => { b: 1, f: true }
|
|
244
|
+
def compact_blank!
|
|
245
|
+
# use delete_if rather than reject! because it always returns self even if nothing changed
|
|
246
|
+
delete_if { |_k, v| v.blank? }
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
class Range # :nodoc:
|
|
251
|
+
# Optimize range sum to use arithmetic progression if a block is not given and
|
|
252
|
+
# we have a range of numeric values.
|
|
253
|
+
def sum(initial_value = 0)
|
|
254
|
+
if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
|
|
255
|
+
super
|
|
256
|
+
else
|
|
257
|
+
actual_last = exclude_end? ? (last - 1) : last
|
|
258
|
+
if actual_last >= first
|
|
259
|
+
sum = initial_value || 0
|
|
260
|
+
sum + (actual_last - first + 1) * (actual_last + first) / 2
|
|
261
|
+
else
|
|
262
|
+
initial_value || 0
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
class Array # :nodoc:
|
|
269
|
+
# Removes all blank elements from the +Array+ in place and returns self.
|
|
270
|
+
# Uses Object#blank? for determining if an item is blank.
|
|
271
|
+
#
|
|
272
|
+
# a = [1, "", nil, 2, " ", [], {}, false, true]
|
|
273
|
+
# a.compact_blank!
|
|
274
|
+
# # => [1, 2, true]
|
|
275
|
+
def compact_blank!
|
|
276
|
+
# use delete_if rather than reject! because it always returns self even if nothing changed
|
|
277
|
+
delete_if(&:blank?)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module ActiveSupport
|
|
6
|
+
module CoreExt
|
|
7
|
+
module ERBUtil
|
|
8
|
+
# HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
|
|
9
|
+
# This method is not for public consumption! Seriously!
|
|
10
|
+
def html_escape(s) # :nodoc:
|
|
11
|
+
s = s.to_s
|
|
12
|
+
if s.html_safe?
|
|
13
|
+
s
|
|
14
|
+
else
|
|
15
|
+
super(s)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
alias :unwrapped_html_escape :html_escape # :nodoc:
|
|
19
|
+
|
|
20
|
+
# A utility method for escaping HTML tag characters.
|
|
21
|
+
# This method is also aliased as <tt>h</tt>.
|
|
22
|
+
#
|
|
23
|
+
# puts html_escape('is a > 0 & a < 10?')
|
|
24
|
+
# # => is a > 0 & a < 10?
|
|
25
|
+
def html_escape(s) # rubocop:disable Lint/DuplicateMethods
|
|
26
|
+
unwrapped_html_escape(s).html_safe
|
|
27
|
+
end
|
|
28
|
+
alias h html_escape
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module ERBUtilPrivate
|
|
32
|
+
include ERBUtil
|
|
33
|
+
private :unwrapped_html_escape, :html_escape, :h
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class ERB
|
|
39
|
+
module Util
|
|
40
|
+
HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" }
|
|
41
|
+
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
|
|
42
|
+
|
|
43
|
+
# Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name
|
|
44
|
+
TAG_NAME_START_CODEPOINTS = "@:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \
|
|
45
|
+
"\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \
|
|
46
|
+
"\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}"
|
|
47
|
+
INVALID_TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_CODEPOINTS}]/
|
|
48
|
+
TAG_NAME_FOLLOWING_CODEPOINTS = "#{TAG_NAME_START_CODEPOINTS}\\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}"
|
|
49
|
+
INVALID_TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_FOLLOWING_CODEPOINTS}]/
|
|
50
|
+
SAFE_XML_TAG_NAME_REGEXP = /\A[#{TAG_NAME_START_CODEPOINTS}][#{TAG_NAME_FOLLOWING_CODEPOINTS}]*\z/
|
|
51
|
+
TAG_NAME_REPLACEMENT_CHAR = "_"
|
|
52
|
+
|
|
53
|
+
prepend ActiveSupport::CoreExt::ERBUtilPrivate
|
|
54
|
+
singleton_class.prepend ActiveSupport::CoreExt::ERBUtil
|
|
55
|
+
|
|
56
|
+
# A utility method for escaping HTML without affecting existing escaped entities.
|
|
57
|
+
#
|
|
58
|
+
# html_escape_once('1 < 2 & 3')
|
|
59
|
+
# # => "1 < 2 & 3"
|
|
60
|
+
#
|
|
61
|
+
# html_escape_once('<< Accept & Checkout')
|
|
62
|
+
# # => "<< Accept & Checkout"
|
|
63
|
+
def html_escape_once(s)
|
|
64
|
+
s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE).html_safe
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
module_function :html_escape_once
|
|
68
|
+
|
|
69
|
+
# A utility method for escaping HTML entities in JSON strings. Specifically, the
|
|
70
|
+
# &, > and < characters are replaced with their equivalent unicode escaped form -
|
|
71
|
+
# \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
|
|
72
|
+
# escaped as they are treated as newline characters in some JavaScript engines.
|
|
73
|
+
# These sequences have identical meaning as the original characters inside the
|
|
74
|
+
# context of a JSON string, so assuming the input is a valid and well-formed
|
|
75
|
+
# JSON value, the output will have equivalent meaning when parsed:
|
|
76
|
+
#
|
|
77
|
+
# json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
|
|
78
|
+
# # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
|
|
79
|
+
#
|
|
80
|
+
# json_escape(json)
|
|
81
|
+
# # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
|
|
82
|
+
#
|
|
83
|
+
# JSON.parse(json) == JSON.parse(json_escape(json))
|
|
84
|
+
# # => true
|
|
85
|
+
#
|
|
86
|
+
# The intended use case for this method is to escape JSON strings before including
|
|
87
|
+
# them inside a script tag to avoid XSS vulnerability:
|
|
88
|
+
#
|
|
89
|
+
# <script>
|
|
90
|
+
# var currentUser = <%= raw json_escape(current_user.to_json) %>;
|
|
91
|
+
# </script>
|
|
92
|
+
#
|
|
93
|
+
# It is necessary to +raw+ the result of +json_escape+, so that quotation marks
|
|
94
|
+
# don't get converted to <tt>"</tt> entities. +json_escape+ doesn't
|
|
95
|
+
# automatically flag the result as HTML safe, since the raw value is unsafe to
|
|
96
|
+
# use inside HTML attributes.
|
|
97
|
+
#
|
|
98
|
+
# If your JSON is being used downstream for insertion into the DOM, be aware of
|
|
99
|
+
# whether or not it is being inserted via <tt>html()</tt>. Most jQuery plugins do this.
|
|
100
|
+
# If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
|
|
101
|
+
# content returned by your JSON.
|
|
102
|
+
#
|
|
103
|
+
# If you need to output JSON elsewhere in your HTML, you can just do something
|
|
104
|
+
# like this, as any unsafe characters (including quotation marks) will be
|
|
105
|
+
# automatically escaped for you:
|
|
106
|
+
#
|
|
107
|
+
# <div data-user-info="<%= current_user.to_json %>">...</div>
|
|
108
|
+
#
|
|
109
|
+
# WARNING: this helper only works with valid JSON. Using this on non-JSON values
|
|
110
|
+
# will open up serious XSS vulnerabilities. For example, if you replace the
|
|
111
|
+
# +current_user.to_json+ in the example above with user input instead, the browser
|
|
112
|
+
# will happily <tt>eval()</tt> that string as JavaScript.
|
|
113
|
+
#
|
|
114
|
+
# The escaping performed in this method is identical to those performed in the
|
|
115
|
+
# Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
|
|
116
|
+
# set to true. Because this transformation is idempotent, this helper can be
|
|
117
|
+
# applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
|
|
118
|
+
#
|
|
119
|
+
# Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
|
|
120
|
+
# is enabled, or if you are unsure where your JSON string originated from, it
|
|
121
|
+
# is recommended that you always apply this helper (other libraries, such as the
|
|
122
|
+
# JSON gem, do not provide this kind of protection by default; also some gems
|
|
123
|
+
# might override +to_json+ to bypass Active Support's encoder).
|
|
124
|
+
def json_escape(s)
|
|
125
|
+
result = s.to_s.dup
|
|
126
|
+
result.gsub!(">", '\u003e')
|
|
127
|
+
result.gsub!("<", '\u003c')
|
|
128
|
+
result.gsub!("&", '\u0026')
|
|
129
|
+
result.gsub!("\u2028", '\u2028')
|
|
130
|
+
result.gsub!("\u2029", '\u2029')
|
|
131
|
+
s.html_safe? ? result.html_safe : result
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
module_function :json_escape
|
|
135
|
+
|
|
136
|
+
# A utility method for escaping XML names of tags and names of attributes.
|
|
137
|
+
#
|
|
138
|
+
# xml_name_escape('1 < 2 & 3')
|
|
139
|
+
# # => "1___2___3"
|
|
140
|
+
#
|
|
141
|
+
# It follows the requirements of the specification: https://www.w3.org/TR/REC-xml/#NT-Name
|
|
142
|
+
def xml_name_escape(name)
|
|
143
|
+
name = name.to_s
|
|
144
|
+
return "" if name.blank?
|
|
145
|
+
return name if name.match?(SAFE_XML_TAG_NAME_REGEXP)
|
|
146
|
+
|
|
147
|
+
starting_char = name[0]
|
|
148
|
+
starting_char.gsub!(INVALID_TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
|
|
149
|
+
|
|
150
|
+
return starting_char if name.size == 1
|
|
151
|
+
|
|
152
|
+
following_chars = name[1..-1]
|
|
153
|
+
following_chars.gsub!(INVALID_TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
|
|
154
|
+
|
|
155
|
+
starting_char << following_chars
|
|
156
|
+
end
|
|
157
|
+
module_function :xml_name_escape
|
|
158
|
+
|
|
159
|
+
# Tokenizes a line of ERB. This is really just for error reporting and
|
|
160
|
+
# nobody should use it.
|
|
161
|
+
def self.tokenize(source) # :nodoc:
|
|
162
|
+
require "strscan"
|
|
163
|
+
source = StringScanner.new(source.chomp)
|
|
164
|
+
tokens = []
|
|
165
|
+
|
|
166
|
+
start_re = /<%(?:={1,2}|-|\#|%)?/m
|
|
167
|
+
finish_re = /(?:[-=])?%>/m
|
|
168
|
+
|
|
169
|
+
while !source.eos?
|
|
170
|
+
pos = source.pos
|
|
171
|
+
source.scan_until(/(?:#{start_re}|#{finish_re})/)
|
|
172
|
+
return [[:PLAIN, source.string]] unless source.matched?
|
|
173
|
+
len = source.pos - source.matched.bytesize - pos
|
|
174
|
+
|
|
175
|
+
case source.matched
|
|
176
|
+
when start_re
|
|
177
|
+
tokens << [:TEXT, source.string.byteslice(pos, len)] if len > 0
|
|
178
|
+
tokens << [:OPEN, source.matched]
|
|
179
|
+
if source.scan(/(.*?)(?=#{finish_re}|\z)/m)
|
|
180
|
+
tokens << [:CODE, source.matched] unless source.matched.empty?
|
|
181
|
+
tokens << [:CLOSE, source.scan(finish_re)] unless source.eos?
|
|
182
|
+
else
|
|
183
|
+
raise NotImplementedError
|
|
184
|
+
end
|
|
185
|
+
when finish_re
|
|
186
|
+
tokens << [:CODE, source.string.byteslice(pos, len)] if len > 0
|
|
187
|
+
tokens << [:CLOSE, source.matched]
|
|
188
|
+
else
|
|
189
|
+
raise NotImplementedError, source.matched
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
unless source.eos? || source.exist?(start_re) || source.exist?(finish_re)
|
|
193
|
+
tokens << [:TEXT, source.rest]
|
|
194
|
+
source.terminate
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
tokens
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|