omg-activesupport 8.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +86 -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 +163 -0
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +53 -0
- data/lib/active_support/broadcast_logger.rb +251 -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 +290 -0
- data/lib/active_support/cache/memory_store.rb +262 -0
- data/lib/active_support/cache/null_store.rb +62 -0
- data/lib/active_support/cache/redis_cache_store.rb +492 -0
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +201 -0
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
- data/lib/active_support/cache.rb +1104 -0
- data/lib/active_support/callbacks.rb +944 -0
- data/lib/active_support/class_attribute.rb +26 -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 +72 -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/configurable.rb +159 -0
- data/lib/active_support/configuration_file.rb +60 -0
- data/lib/active_support/core_ext/array/access.rb +100 -0
- data/lib/active_support/core_ext/array/conversions.rb +213 -0
- 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 +9 -0
- data/lib/active_support/core_ext/benchmark.rb +13 -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 +122 -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 +98 -0
- data/lib/active_support/core_ext/date/zones.rb +8 -0
- data/lib/active_support/core_ext/date.rb +7 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +374 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +58 -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 +18 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +106 -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 +267 -0
- 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 +42 -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 +24 -0
- data/lib/active_support/core_ext/hash/keys.rb +143 -0
- data/lib/active_support/core_ext/hash/reverse_merge.rb +25 -0
- data/lib/active_support/core_ext/hash/slice.rb +27 -0
- data/lib/active_support/core_ext/hash.rb +10 -0
- data/lib/active_support/core_ext/integer/inflections.rb +31 -0
- 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 +5 -0
- 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 +5 -0
- data/lib/active_support/core_ext/load_error.rb +9 -0
- 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 +49 -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 +62 -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 +75 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
- data/lib/active_support/core_ext/numeric/time.rb +66 -0
- data/lib/active_support/core_ext/numeric.rb +5 -0
- 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 +260 -0
- data/lib/active_support/core_ext/object/to_param.rb +3 -0
- data/lib/active_support/core_ext/object/to_query.rb +87 -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 +62 -0
- data/lib/active_support/core_ext/range/each.rb +24 -0
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range.rb +6 -0
- data/lib/active_support/core_ext/regexp.rb +14 -0
- data/lib/active_support/core_ext/securerandom.rb +41 -0
- data/lib/active_support/core_ext/string/access.rb +95 -0
- data/lib/active_support/core_ext/string/behavior.rb +8 -0
- data/lib/active_support/core_ext/string/conversions.rb +60 -0
- 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 +300 -0
- data/lib/active_support/core_ext/string/inquiry.rb +16 -0
- data/lib/active_support/core_ext/string/multibyte.rb +58 -0
- data/lib/active_support/core_ext/string/output_safety.rb +228 -0
- data/lib/active_support/core_ext/string/starts_ends_with.rb +6 -0
- 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 +15 -0
- 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 +12 -0
- data/lib/active_support/core_ext/time/acts_like.rb +10 -0
- data/lib/active_support/core_ext/time/calculations.rb +386 -0
- data/lib/active_support/core_ext/time/compatibility.rb +32 -0
- data/lib/active_support/core_ext/time/conversions.rb +75 -0
- data/lib/active_support/core_ext/time/zones.rb +97 -0
- data/lib/active_support/core_ext/time.rb +7 -0
- data/lib/active_support/core_ext.rb +5 -0
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +233 -0
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/delegation.rb +202 -0
- data/lib/active_support/dependencies/autoload.rb +72 -0
- data/lib/active_support/dependencies/interlock.rb +49 -0
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +98 -0
- 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 +179 -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 +520 -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 +265 -0
- data/lib/active_support/evented_file_update_checker.rb +182 -0
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +53 -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 +164 -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 +40 -0
- data/lib/active_support/hash_with_indifferent_access.rb +445 -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 +138 -0
- data/lib/active_support/inflections.rb +72 -0
- data/lib/active_support/inflector/inflections.rb +273 -0
- data/lib/active_support/inflector/methods.rb +387 -0
- data/lib/active_support/inflector/transliterate.rb +149 -0
- data/lib/active_support/inflector.rb +9 -0
- data/lib/active_support/isolated_execution_state.rb +75 -0
- data/lib/active_support/json/decoding.rb +76 -0
- data/lib/active_support/json/encoding.rb +120 -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 +192 -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 +47 -0
- data/lib/active_support/message_encryptor.rb +374 -0
- data/lib/active_support/message_encryptors.rb +141 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +305 -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 +368 -0
- data/lib/active_support/message_verifiers.rb +135 -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 +93 -0
- data/lib/active_support/messages/rotator.rb +59 -0
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +178 -0
- data/lib/active_support/multibyte/unicode.rb +42 -0
- data/lib/active_support/multibyte.rb +23 -0
- data/lib/active_support/notifications/fanout.rb +446 -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 +147 -0
- data/lib/active_support/parameter_filter.rb +157 -0
- data/lib/active_support/proxy_object.rb +20 -0
- data/lib/active_support/rails.rb +26 -0
- data/lib/active_support/railtie.rb +161 -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/subscriber.rb +146 -0
- data/lib/active_support/syntax_error_proxy.rb +60 -0
- data/lib/active_support/tagged_logging.rb +152 -0
- data/lib/active_support/test_case.rb +304 -0
- data/lib/active_support/testing/assertions.rb +332 -0
- data/lib/active_support/testing/autorun.rb +5 -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 +107 -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/parallelization/server.rb +85 -0
- data/lib/active_support/testing/parallelization/worker.rb +103 -0
- data/lib/active_support/testing/parallelization.rb +55 -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/strict_warnings.rb +43 -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 +269 -0
- data/lib/active_support/time.rb +20 -0
- data/lib/active_support/time_with_zone.rb +609 -0
- data/lib/active_support/values/time_zone.rb +614 -0
- data/lib/active_support/version.rb +10 -0
- 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 +211 -0
- data/lib/active_support.rb +144 -0
- metadata +526 -0
@@ -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(ActiveSupport::Multibyte::Unicode.tidy_bytes(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
|
+
ActiveSupport::Multibyte::Unicode.tidy_bytes(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
|
+
raise NotImplementedError if source.matched.nil?
|
173
|
+
len = source.pos - source.matched.bytesize - pos
|
174
|
+
|
175
|
+
case source.matched
|
176
|
+
when start_re
|
177
|
+
tokens << [:TEXT, source.string[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[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
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
class File
|
6
|
+
# Write to a file atomically. Useful for situations where you don't
|
7
|
+
# want other processes or threads to see half-written files.
|
8
|
+
#
|
9
|
+
# File.atomic_write('important.file') do |file|
|
10
|
+
# file.write('hello')
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# This method needs to create a temporary file. By default it will create it
|
14
|
+
# in the same directory as the destination file. If you don't like this
|
15
|
+
# behavior you can provide a different directory but it must be on the
|
16
|
+
# same physical filesystem as the file you're trying to write.
|
17
|
+
#
|
18
|
+
# File.atomic_write('/data/something.important', '/data/tmp') do |file|
|
19
|
+
# file.write('hello')
|
20
|
+
# end
|
21
|
+
def self.atomic_write(file_name, temp_dir = dirname(file_name))
|
22
|
+
require "tempfile" unless defined?(Tempfile)
|
23
|
+
|
24
|
+
Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file|
|
25
|
+
temp_file.binmode
|
26
|
+
return_val = yield temp_file
|
27
|
+
temp_file.close
|
28
|
+
|
29
|
+
old_stat = if exist?(file_name)
|
30
|
+
# Get original file permissions
|
31
|
+
stat(file_name)
|
32
|
+
else
|
33
|
+
# If not possible, probe which are the default permissions in the
|
34
|
+
# destination directory.
|
35
|
+
probe_stat_in(dirname(file_name))
|
36
|
+
end
|
37
|
+
|
38
|
+
if old_stat
|
39
|
+
# Set correct permissions on new file
|
40
|
+
begin
|
41
|
+
chown(old_stat.uid, old_stat.gid, temp_file.path)
|
42
|
+
# This operation will affect filesystem ACL's
|
43
|
+
chmod(old_stat.mode, temp_file.path)
|
44
|
+
rescue Errno::EPERM, Errno::EACCES
|
45
|
+
# Changing file ownership failed, moving on.
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Overwrite original file with temp file
|
50
|
+
rename(temp_file.path, file_name)
|
51
|
+
return_val
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Private utility method.
|
56
|
+
def self.probe_stat_in(dir) # :nodoc:
|
57
|
+
basename = [
|
58
|
+
".permissions_check",
|
59
|
+
Thread.current.object_id,
|
60
|
+
Process.pid,
|
61
|
+
rand(1000000)
|
62
|
+
].join(".")
|
63
|
+
|
64
|
+
file_name = join(dir, basename)
|
65
|
+
FileUtils.touch(file_name)
|
66
|
+
stat(file_name)
|
67
|
+
rescue Errno::ENOENT
|
68
|
+
file_name = nil
|
69
|
+
ensure
|
70
|
+
FileUtils.rm_f(file_name) if file_name
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/blank"
|
4
|
+
require "active_support/core_ext/object/to_param"
|
5
|
+
require "active_support/core_ext/object/to_query"
|
6
|
+
require "active_support/core_ext/object/try"
|
7
|
+
require "active_support/core_ext/array/wrap"
|
8
|
+
require "active_support/core_ext/hash/reverse_merge"
|
9
|
+
require "active_support/core_ext/string/inflections"
|
10
|
+
|
11
|
+
class Hash
|
12
|
+
# Returns a string containing an XML representation of its receiver:
|
13
|
+
#
|
14
|
+
# { foo: 1, bar: 2 }.to_xml
|
15
|
+
# # =>
|
16
|
+
# # <?xml version="1.0" encoding="UTF-8"?>
|
17
|
+
# # <hash>
|
18
|
+
# # <foo type="integer">1</foo>
|
19
|
+
# # <bar type="integer">2</bar>
|
20
|
+
# # </hash>
|
21
|
+
#
|
22
|
+
# To do so, the method loops over the pairs and builds nodes that depend on
|
23
|
+
# the _values_. Given a pair +key+, +value+:
|
24
|
+
#
|
25
|
+
# * If +value+ is a hash there's a recursive call with +key+ as <tt>:root</tt>.
|
26
|
+
#
|
27
|
+
# * If +value+ is an array there's a recursive call with +key+ as <tt>:root</tt>,
|
28
|
+
# and +key+ singularized as <tt>:children</tt>.
|
29
|
+
#
|
30
|
+
# * If +value+ is a callable object it must expect one or two arguments. Depending
|
31
|
+
# on the arity, the callable is invoked with the +options+ hash as first argument
|
32
|
+
# with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
|
33
|
+
# callable can add nodes by using <tt>options[:builder]</tt>.
|
34
|
+
#
|
35
|
+
# {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml
|
36
|
+
# # => "<b>foo</b>"
|
37
|
+
#
|
38
|
+
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
|
39
|
+
#
|
40
|
+
# class Foo
|
41
|
+
# def to_xml(options)
|
42
|
+
# options[:builder].bar 'fooing!'
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# { foo: Foo.new }.to_xml(skip_instruct: true)
|
47
|
+
# # =>
|
48
|
+
# # <hash>
|
49
|
+
# # <bar>fooing!</bar>
|
50
|
+
# # </hash>
|
51
|
+
#
|
52
|
+
# * Otherwise, a node with +key+ as tag is created with a string representation of
|
53
|
+
# +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added.
|
54
|
+
# Unless the option <tt>:skip_types</tt> exists and is true, an attribute "type" is
|
55
|
+
# added as well according to the following mapping:
|
56
|
+
#
|
57
|
+
# XML_TYPE_NAMES = {
|
58
|
+
# "Symbol" => "symbol",
|
59
|
+
# "Integer" => "integer",
|
60
|
+
# "BigDecimal" => "decimal",
|
61
|
+
# "Float" => "float",
|
62
|
+
# "TrueClass" => "boolean",
|
63
|
+
# "FalseClass" => "boolean",
|
64
|
+
# "Date" => "date",
|
65
|
+
# "DateTime" => "dateTime",
|
66
|
+
# "Time" => "dateTime"
|
67
|
+
# }
|
68
|
+
#
|
69
|
+
# By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
|
70
|
+
#
|
71
|
+
# The default XML builder is a fresh instance of +Builder::XmlMarkup+. You can
|
72
|
+
# configure your own builder with the <tt>:builder</tt> option. The method also accepts
|
73
|
+
# options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
|
74
|
+
def to_xml(options = {})
|
75
|
+
require "active_support/builder" unless defined?(Builder::XmlMarkup)
|
76
|
+
|
77
|
+
options = options.dup
|
78
|
+
options[:indent] ||= 2
|
79
|
+
options[:root] ||= "hash"
|
80
|
+
options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
|
81
|
+
|
82
|
+
builder = options[:builder]
|
83
|
+
builder.instruct! unless options.delete(:skip_instruct)
|
84
|
+
|
85
|
+
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
|
86
|
+
|
87
|
+
builder.tag!(root) do
|
88
|
+
each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) }
|
89
|
+
yield builder if block_given?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class << self
|
94
|
+
# Returns a Hash containing a collection of pairs when the key is the node name and the value is
|
95
|
+
# its content
|
96
|
+
#
|
97
|
+
# xml = <<-XML
|
98
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
99
|
+
# <hash>
|
100
|
+
# <foo type="integer">1</foo>
|
101
|
+
# <bar type="integer">2</bar>
|
102
|
+
# </hash>
|
103
|
+
# XML
|
104
|
+
#
|
105
|
+
# hash = Hash.from_xml(xml)
|
106
|
+
# # => {"hash"=>{"foo"=>1, "bar"=>2}}
|
107
|
+
#
|
108
|
+
# +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or
|
109
|
+
# <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to
|
110
|
+
# parse this XML.
|
111
|
+
#
|
112
|
+
# Custom +disallowed_types+ can also be passed in the form of an
|
113
|
+
# array.
|
114
|
+
#
|
115
|
+
# xml = <<-XML
|
116
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
117
|
+
# <hash>
|
118
|
+
# <foo type="integer">1</foo>
|
119
|
+
# <bar type="string">"David"</bar>
|
120
|
+
# </hash>
|
121
|
+
# XML
|
122
|
+
#
|
123
|
+
# hash = Hash.from_xml(xml, ['integer'])
|
124
|
+
# # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer"
|
125
|
+
#
|
126
|
+
# Note that passing custom disallowed types will override the default types,
|
127
|
+
# which are Symbol and YAML.
|
128
|
+
def from_xml(xml, disallowed_types = nil)
|
129
|
+
ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
|
130
|
+
end
|
131
|
+
|
132
|
+
# Builds a Hash from XML just like <tt>Hash.from_xml</tt>, but also allows Symbol and YAML.
|
133
|
+
def from_trusted_xml(xml)
|
134
|
+
from_xml xml, []
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
module ActiveSupport
|
140
|
+
class XMLConverter # :nodoc:
|
141
|
+
# Raised if the XML contains attributes with type="yaml" or
|
142
|
+
# type="symbol". Read Hash#from_xml for more details.
|
143
|
+
class DisallowedType < StandardError
|
144
|
+
def initialize(type)
|
145
|
+
super "Disallowed type attribute: #{type.inspect}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
DISALLOWED_TYPES = %w(symbol yaml)
|
150
|
+
|
151
|
+
def initialize(xml, disallowed_types = nil)
|
152
|
+
@xml = normalize_keys(XmlMini.parse(xml))
|
153
|
+
@disallowed_types = disallowed_types || DISALLOWED_TYPES
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_h
|
157
|
+
deep_to_h(@xml)
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
def normalize_keys(params)
|
162
|
+
case params
|
163
|
+
when Hash
|
164
|
+
Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ]
|
165
|
+
when Array
|
166
|
+
params.map { |v| normalize_keys(v) }
|
167
|
+
else
|
168
|
+
params
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def deep_to_h(value)
|
173
|
+
case value
|
174
|
+
when Hash
|
175
|
+
process_hash(value)
|
176
|
+
when Array
|
177
|
+
process_array(value)
|
178
|
+
when String
|
179
|
+
value
|
180
|
+
else
|
181
|
+
raise "can't typecast #{value.class.name} - #{value.inspect}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def process_hash(value)
|
186
|
+
if value.include?("type") && !value["type"].is_a?(Hash) && @disallowed_types.include?(value["type"])
|
187
|
+
raise DisallowedType, value["type"]
|
188
|
+
end
|
189
|
+
|
190
|
+
if become_array?(value)
|
191
|
+
_, entries = Array.wrap(value.detect { |k, v| not v.is_a?(String) })
|
192
|
+
if entries.nil? || value["__content__"].try(:empty?)
|
193
|
+
[]
|
194
|
+
else
|
195
|
+
case entries
|
196
|
+
when Array
|
197
|
+
entries.collect { |v| deep_to_h(v) }
|
198
|
+
when Hash
|
199
|
+
[deep_to_h(entries)]
|
200
|
+
else
|
201
|
+
raise "can't typecast #{entries.inspect}"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
elsif become_content?(value)
|
205
|
+
process_content(value)
|
206
|
+
|
207
|
+
elsif become_empty_string?(value)
|
208
|
+
""
|
209
|
+
elsif become_hash?(value)
|
210
|
+
xml_value = value.transform_values { |v| deep_to_h(v) }
|
211
|
+
|
212
|
+
# Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
|
213
|
+
# how multipart uploaded files from HTML appear
|
214
|
+
xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def become_content?(value)
|
219
|
+
value["type"] == "file" || (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
|
220
|
+
end
|
221
|
+
|
222
|
+
def become_array?(value)
|
223
|
+
value["type"] == "array"
|
224
|
+
end
|
225
|
+
|
226
|
+
def become_empty_string?(value)
|
227
|
+
# { "string" => true }
|
228
|
+
# No tests fail when the second term is removed.
|
229
|
+
value["type"] == "string" && value["nil"] != "true"
|
230
|
+
end
|
231
|
+
|
232
|
+
def become_hash?(value)
|
233
|
+
!nothing?(value) && !garbage?(value)
|
234
|
+
end
|
235
|
+
|
236
|
+
def nothing?(value)
|
237
|
+
# blank or nil parsed values are represented by nil
|
238
|
+
value.blank? || value["nil"] == "true"
|
239
|
+
end
|
240
|
+
|
241
|
+
def garbage?(value)
|
242
|
+
# If the type is the only element which makes it then
|
243
|
+
# this still makes the value nil, except if type is
|
244
|
+
# an XML node(where type['value'] is a Hash)
|
245
|
+
value["type"] && !value["type"].is_a?(::Hash) && value.size == 1
|
246
|
+
end
|
247
|
+
|
248
|
+
def process_content(value)
|
249
|
+
content = value["__content__"]
|
250
|
+
if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
|
251
|
+
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
|
252
|
+
else
|
253
|
+
content
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def process_array(value)
|
258
|
+
value.map! { |i| deep_to_h(i) }
|
259
|
+
value.length > 1 ? value : value.first
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/deep_mergeable"
|
4
|
+
|
5
|
+
class Hash
|
6
|
+
include ActiveSupport::DeepMergeable
|
7
|
+
|
8
|
+
##
|
9
|
+
# :method: deep_merge
|
10
|
+
# :call-seq: deep_merge(other_hash, &block)
|
11
|
+
#
|
12
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
13
|
+
#
|
14
|
+
# h1 = { a: true, b: { c: [1, 2, 3] } }
|
15
|
+
# h2 = { a: false, b: { x: [3, 4, 5] } }
|
16
|
+
#
|
17
|
+
# h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
|
18
|
+
#
|
19
|
+
# Like with Hash#merge in the standard library, a block can be provided
|
20
|
+
# to merge values:
|
21
|
+
#
|
22
|
+
# h1 = { a: 100, b: 200, c: { c1: 100 } }
|
23
|
+
# h2 = { b: 250, c: { c1: 200 } }
|
24
|
+
# h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
|
25
|
+
# # => { a: 100, b: 450, c: { c1: 300 } }
|
26
|
+
#
|
27
|
+
#--
|
28
|
+
# Implemented by ActiveSupport::DeepMergeable#deep_merge.
|
29
|
+
|
30
|
+
##
|
31
|
+
# :method: deep_merge!
|
32
|
+
# :call-seq: deep_merge!(other_hash, &block)
|
33
|
+
#
|
34
|
+
# Same as #deep_merge, but modifies +self+.
|
35
|
+
#
|
36
|
+
#--
|
37
|
+
# Implemented by ActiveSupport::DeepMergeable#deep_merge!.
|
38
|
+
|
39
|
+
def deep_merge?(other) # :nodoc:
|
40
|
+
other.is_a?(Hash)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Returns a new hash with all values converted by the block operation.
|
5
|
+
# This includes the values from the root hash and from all
|
6
|
+
# nested hashes and arrays.
|
7
|
+
#
|
8
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
9
|
+
#
|
10
|
+
# hash.deep_transform_values{ |value| value.to_s.upcase }
|
11
|
+
# # => {person: {name: "ROB", age: "28"}}
|
12
|
+
def deep_transform_values(&block)
|
13
|
+
_deep_transform_values_in_object(self, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Destructively converts all values by using the block operation.
|
17
|
+
# This includes the values from the root hash and from all
|
18
|
+
# nested hashes and arrays.
|
19
|
+
def deep_transform_values!(&block)
|
20
|
+
_deep_transform_values_in_object!(self, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
# Support methods for deep transforming nested hashes and arrays.
|
25
|
+
def _deep_transform_values_in_object(object, &block)
|
26
|
+
case object
|
27
|
+
when Hash
|
28
|
+
object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
|
29
|
+
when Array
|
30
|
+
object.map { |e| _deep_transform_values_in_object(e, &block) }
|
31
|
+
else
|
32
|
+
yield(object)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def _deep_transform_values_in_object!(object, &block)
|
37
|
+
case object
|
38
|
+
when Hash
|
39
|
+
object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) }
|
40
|
+
when Array
|
41
|
+
object.map! { |e| _deep_transform_values_in_object!(e, &block) }
|
42
|
+
else
|
43
|
+
yield(object)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Removes the given keys from hash and returns it.
|
5
|
+
# hash = { a: true, b: false, c: nil }
|
6
|
+
# hash.except!(:c) # => { a: true, b: false }
|
7
|
+
# hash # => { a: true, b: false }
|
8
|
+
def except!(*keys)
|
9
|
+
keys.each { |key| delete(key) }
|
10
|
+
self
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/hash_with_indifferent_access"
|
4
|
+
|
5
|
+
class Hash
|
6
|
+
# Returns an ActiveSupport::HashWithIndifferentAccess out of its receiver:
|
7
|
+
#
|
8
|
+
# { a: 1 }.with_indifferent_access['a'] # => 1
|
9
|
+
def with_indifferent_access
|
10
|
+
ActiveSupport::HashWithIndifferentAccess.new(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Called when object is nested under an object that receives
|
14
|
+
# #with_indifferent_access. This method will be called on the current object
|
15
|
+
# by the enclosing object and is aliased to #with_indifferent_access by
|
16
|
+
# default. Subclasses of Hash may override this method to return +self+ if
|
17
|
+
# converting to an ActiveSupport::HashWithIndifferentAccess would not be
|
18
|
+
# desirable.
|
19
|
+
#
|
20
|
+
# b = { b: 1 }
|
21
|
+
# { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
|
22
|
+
# # => {"b"=>1}
|
23
|
+
alias nested_under_indifferent_access with_indifferent_access
|
24
|
+
end
|