activesupport 3.2.22.5 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +325 -136
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -2
- data/lib/active_support.rb +8 -21
- data/lib/active_support/backtrace_cleaner.rb +33 -25
- data/lib/active_support/basic_object.rb +7 -17
- data/lib/active_support/benchmarkable.rb +19 -15
- data/lib/active_support/buffered_logger.rb +9 -113
- data/lib/active_support/cache.rb +203 -171
- data/lib/active_support/cache/file_store.rb +12 -12
- data/lib/active_support/cache/mem_cache_store.rb +24 -30
- data/lib/active_support/cache/memory_store.rb +2 -0
- data/lib/active_support/callbacks.rb +195 -247
- data/lib/active_support/concern.rb +16 -23
- data/lib/active_support/concurrency/latch.rb +27 -0
- data/lib/active_support/configurable.rb +69 -12
- data/lib/active_support/core_ext.rb +1 -0
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/array/access.rb +17 -9
- data/lib/active_support/core_ext/array/conversions.rb +113 -55
- data/lib/active_support/core_ext/array/extract_options.rb +2 -2
- data/lib/active_support/core_ext/array/grouping.rb +21 -22
- data/lib/active_support/core_ext/array/uniq_by.rb +12 -9
- data/lib/active_support/core_ext/array/wrap.rb +11 -14
- data/lib/active_support/core_ext/big_decimal/conversions.rb +7 -24
- data/lib/active_support/core_ext/class/attribute.rb +12 -8
- data/lib/active_support/core_ext/class/attribute_accessors.rb +14 -12
- data/lib/active_support/core_ext/class/delegating_attributes.rb +15 -19
- data/lib/active_support/core_ext/class/subclasses.rb +11 -5
- data/lib/active_support/core_ext/date.rb +6 -0
- data/lib/active_support/core_ext/date/calculations.rb +34 -188
- data/lib/active_support/core_ext/date/conversions.rb +16 -38
- data/lib/active_support/core_ext/date/infinite_comparable.rb +5 -0
- data/lib/active_support/core_ext/date/zones.rb +25 -2
- data/lib/active_support/core_ext/date_and_time/calculations.rb +232 -0
- data/lib/active_support/core_ext/date_time.rb +5 -0
- data/lib/active_support/core_ext/date_time/acts_like.rb +0 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +73 -65
- data/lib/active_support/core_ext/date_time/conversions.rb +21 -33
- data/lib/active_support/core_ext/date_time/infinite_comparable.rb +5 -0
- data/lib/active_support/core_ext/date_time/zones.rb +11 -8
- data/lib/active_support/core_ext/enumerable.rb +26 -73
- data/lib/active_support/core_ext/file.rb +0 -1
- data/lib/active_support/core_ext/file/atomic.rb +27 -11
- data/lib/active_support/core_ext/hash.rb +0 -1
- data/lib/active_support/core_ext/hash/conversions.rb +145 -79
- data/lib/active_support/core_ext/hash/deep_merge.rb +14 -8
- data/lib/active_support/core_ext/hash/diff.rb +5 -4
- data/lib/active_support/core_ext/hash/except.rb +1 -9
- data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -5
- data/lib/active_support/core_ext/hash/keys.rb +108 -24
- data/lib/active_support/core_ext/hash/reverse_merge.rb +2 -3
- data/lib/active_support/core_ext/hash/slice.rb +12 -12
- data/lib/active_support/core_ext/infinite_comparable.rb +35 -0
- data/lib/active_support/core_ext/integer/inflections.rb +13 -1
- data/lib/active_support/core_ext/integer/time.rb +17 -12
- data/lib/active_support/core_ext/kernel/debugger.rb +2 -2
- data/lib/active_support/core_ext/kernel/reporting.rb +36 -22
- data/lib/active_support/core_ext/kernel/singleton_class.rb +0 -7
- data/lib/active_support/core_ext/load_error.rb +7 -5
- data/lib/active_support/core_ext/logger.rb +7 -23
- data/lib/active_support/core_ext/marshal.rb +19 -0
- data/lib/active_support/core_ext/module.rb +1 -3
- data/lib/active_support/core_ext/module/aliasing.rb +8 -9
- data/lib/active_support/core_ext/module/anonymous.rb +2 -7
- data/lib/active_support/core_ext/module/attr_internal.rb +0 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +12 -10
- data/lib/active_support/core_ext/module/delegation.rb +57 -40
- data/lib/active_support/core_ext/module/deprecation.rb +19 -3
- data/lib/active_support/core_ext/module/introspection.rb +17 -27
- data/lib/active_support/core_ext/module/qualified_const.rb +8 -20
- data/lib/active_support/core_ext/module/remove_method.rb +1 -5
- data/lib/active_support/core_ext/numeric.rb +2 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +135 -0
- data/lib/active_support/core_ext/numeric/infinite_comparable.rb +9 -0
- data/lib/active_support/core_ext/numeric/time.rb +6 -6
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/object/acts_like.rb +4 -4
- data/lib/active_support/core_ext/object/blank.rb +7 -23
- data/lib/active_support/core_ext/object/deep_dup.rb +46 -0
- data/lib/active_support/core_ext/object/duplicable.rb +1 -30
- data/lib/active_support/core_ext/object/inclusion.rb +6 -6
- data/lib/active_support/core_ext/object/instance_variables.rb +7 -12
- data/lib/active_support/core_ext/object/to_json.rb +8 -0
- data/lib/active_support/core_ext/object/to_param.rb +5 -2
- data/lib/active_support/core_ext/object/try.rb +46 -25
- data/lib/active_support/core_ext/object/with_options.rb +7 -8
- data/lib/active_support/core_ext/proc.rb +3 -0
- data/lib/active_support/core_ext/range.rb +0 -2
- data/lib/active_support/core_ext/range/conversions.rb +0 -2
- data/lib/active_support/core_ext/range/include_range.rb +1 -1
- data/lib/active_support/core_ext/range/overlaps.rb +1 -1
- data/lib/active_support/core_ext/string.rb +2 -2
- data/lib/active_support/core_ext/string/access.rb +95 -90
- data/lib/active_support/core_ext/string/conversions.rb +29 -38
- data/lib/active_support/core_ext/string/encoding.rb +6 -9
- data/lib/active_support/core_ext/string/filters.rb +24 -18
- data/lib/active_support/core_ext/string/indent.rb +43 -0
- data/lib/active_support/core_ext/string/inflections.rb +70 -60
- data/lib/active_support/core_ext/string/inquiry.rb +2 -2
- data/lib/active_support/core_ext/string/multibyte.rb +41 -64
- data/lib/active_support/core_ext/string/output_safety.rb +59 -51
- data/lib/active_support/core_ext/string/zones.rb +13 -0
- data/lib/active_support/core_ext/struct.rb +6 -0
- data/lib/active_support/core_ext/thread.rb +74 -0
- data/lib/active_support/core_ext/time.rb +6 -0
- data/lib/active_support/core_ext/time/calculations.rb +105 -193
- data/lib/active_support/core_ext/time/conversions.rb +27 -51
- data/lib/active_support/core_ext/time/infinite_comparable.rb +5 -0
- data/lib/active_support/core_ext/time/marshal.rb +0 -27
- data/lib/active_support/core_ext/time/zones.rb +27 -17
- data/lib/active_support/core_ext/uri.rb +13 -17
- data/lib/active_support/dependencies.rb +160 -141
- data/lib/active_support/dependencies/autoload.rb +47 -20
- data/lib/active_support/deprecation.rb +39 -14
- data/lib/active_support/deprecation/behaviors.rb +44 -30
- data/lib/active_support/deprecation/instance_delegator.rb +24 -0
- data/lib/active_support/deprecation/method_wrappers.rb +33 -18
- data/lib/active_support/deprecation/proxy_wrappers.rb +58 -13
- data/lib/active_support/deprecation/reporting.rb +40 -11
- data/lib/active_support/descendants_tracker.rb +34 -19
- data/lib/active_support/duration.rb +6 -8
- data/lib/active_support/file_update_checker.rb +63 -47
- data/lib/active_support/gzip.rb +11 -5
- data/lib/active_support/hash_with_indifferent_access.rb +112 -37
- data/lib/active_support/i18n.rb +4 -0
- data/lib/active_support/i18n_railtie.rb +5 -22
- data/lib/active_support/inflections.rb +14 -12
- data/lib/active_support/inflector/inflections.rb +108 -71
- data/lib/active_support/inflector/methods.rb +181 -160
- data/lib/active_support/inflector/transliterate.rb +16 -17
- data/lib/active_support/json/decoding.rb +18 -17
- data/lib/active_support/json/encoding.rb +93 -39
- data/lib/active_support/json/variable.rb +10 -1
- data/lib/active_support/key_generator.rb +75 -0
- data/lib/active_support/lazy_load_hooks.rb +21 -19
- data/lib/active_support/locale/en.yml +100 -3
- data/lib/active_support/log_subscriber.rb +56 -36
- data/lib/active_support/log_subscriber/test_helper.rb +18 -15
- data/lib/active_support/logger.rb +57 -0
- data/lib/active_support/logger_silence.rb +24 -0
- data/lib/active_support/message_encryptor.rb +32 -29
- data/lib/active_support/message_verifier.rb +8 -14
- data/lib/active_support/multibyte.rb +5 -28
- data/lib/active_support/multibyte/chars.rb +80 -333
- data/lib/active_support/multibyte/unicode.rb +74 -64
- data/lib/active_support/notifications.rb +57 -25
- data/lib/active_support/notifications/fanout.rb +105 -18
- data/lib/active_support/notifications/instrumenter.rb +32 -13
- data/lib/active_support/number_helper.rb +636 -0
- data/lib/active_support/ordered_hash.rb +8 -190
- data/lib/active_support/ordered_options.rb +21 -23
- data/lib/active_support/proxy_object.rb +13 -0
- data/lib/active_support/rails.rb +27 -0
- data/lib/active_support/railtie.rb +12 -32
- data/lib/active_support/rescuable.rb +9 -4
- data/lib/active_support/string_inquirer.rb +13 -8
- data/lib/active_support/tagged_logging.rb +51 -73
- data/lib/active_support/test_case.rb +46 -17
- data/lib/active_support/testing/assertions.rb +56 -26
- data/lib/active_support/testing/autorun.rb +5 -0
- data/lib/active_support/testing/constant_lookup.rb +52 -0
- data/lib/active_support/testing/declarative.rb +1 -1
- data/lib/active_support/testing/deprecation.rb +0 -19
- data/lib/active_support/testing/isolation.rb +25 -58
- data/lib/active_support/testing/pending.rb +5 -43
- data/lib/active_support/testing/setup_and_teardown.rb +6 -92
- data/lib/active_support/testing/tagged_logging.rb +25 -0
- data/lib/active_support/time.rb +6 -21
- data/lib/active_support/time_with_zone.rb +78 -43
- data/lib/active_support/values/time_zone.rb +77 -58
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/version.rb +4 -4
- data/lib/active_support/xml_mini.rb +35 -17
- data/lib/active_support/xml_mini/jdom.rb +9 -17
- data/lib/active_support/xml_mini/libxml.rb +1 -2
- data/lib/active_support/xml_mini/libxmlsax.rb +1 -2
- data/lib/active_support/xml_mini/nokogiri.rb +1 -2
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -2
- data/lib/active_support/xml_mini/rexml.rb +6 -8
- metadata +107 -77
- data/lib/active_support/base64.rb +0 -54
- data/lib/active_support/core_ext/array/random_access.rb +0 -30
- data/lib/active_support/core_ext/date/freeze.rb +0 -33
- data/lib/active_support/core_ext/exception.rb +0 -3
- data/lib/active_support/core_ext/file/path.rb +0 -5
- data/lib/active_support/core_ext/float.rb +0 -1
- data/lib/active_support/core_ext/float/rounding.rb +0 -19
- data/lib/active_support/core_ext/hash/deep_dup.rb +0 -18
- data/lib/active_support/core_ext/io.rb +0 -15
- data/lib/active_support/core_ext/module/method_names.rb +0 -14
- data/lib/active_support/core_ext/module/synchronization.rb +0 -45
- data/lib/active_support/core_ext/process.rb +0 -1
- data/lib/active_support/core_ext/process/daemon.rb +0 -23
- data/lib/active_support/core_ext/range/blockless_step.rb +0 -29
- data/lib/active_support/core_ext/range/cover.rb +0 -3
- data/lib/active_support/core_ext/rexml.rb +0 -46
- data/lib/active_support/core_ext/string/interpolation.rb +0 -2
- data/lib/active_support/core_ext/time/publicize_conversion_methods.rb +0 -10
- data/lib/active_support/memoizable.rb +0 -116
- data/lib/active_support/multibyte/exceptions.rb +0 -8
- data/lib/active_support/multibyte/utils.rb +0 -60
- data/lib/active_support/ruby/shim.rb +0 -22
- data/lib/active_support/security_utils.rb +0 -27
- data/lib/active_support/testing/mochaing.rb +0 -7
- data/lib/active_support/testing/performance.rb +0 -317
- data/lib/active_support/testing/performance/jruby.rb +0 -115
- data/lib/active_support/testing/performance/rubinius.rb +0 -113
- data/lib/active_support/testing/performance/ruby.rb +0 -152
- data/lib/active_support/testing/performance/ruby/mri.rb +0 -57
- data/lib/active_support/testing/performance/ruby/yarv.rb +0 -57
- data/lib/active_support/time/autoload.rb +0 -5
- data/lib/active_support/whiny_nil.rb +0 -24
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module LoggerSilence
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
cattr_accessor :silencer
|
8
|
+
self.silencer = true
|
9
|
+
end
|
10
|
+
|
11
|
+
# Silences the logger for the duration of the block.
|
12
|
+
def silence(temporary_level = Logger::ERROR)
|
13
|
+
if silencer
|
14
|
+
begin
|
15
|
+
old_logger_level, self.level = level, temporary_level
|
16
|
+
yield self
|
17
|
+
ensure
|
18
|
+
self.level = old_logger_level
|
19
|
+
end
|
20
|
+
else
|
21
|
+
yield self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,14 +1,21 @@
|
|
1
1
|
require 'openssl'
|
2
|
-
require '
|
2
|
+
require 'base64'
|
3
|
+
require 'active_support/core_ext/array/extract_options'
|
3
4
|
|
4
5
|
module ActiveSupport
|
5
|
-
# MessageEncryptor is a simple way to encrypt values which get stored
|
6
|
-
# you don't trust.
|
6
|
+
# MessageEncryptor is a simple way to encrypt values which get stored
|
7
|
+
# somewhere you don't trust.
|
7
8
|
#
|
8
|
-
# The cipher text and initialization vector are base64 encoded and returned
|
9
|
+
# The cipher text and initialization vector are base64 encoded and returned
|
10
|
+
# to you.
|
9
11
|
#
|
10
|
-
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but
|
11
|
-
# want users to be able to determine the value of the payload.
|
12
|
+
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but
|
13
|
+
# where you don't want users to be able to determine the value of the payload.
|
14
|
+
#
|
15
|
+
# key = OpenSSL::Digest::SHA256.new('password').digest # => "\x89\xE0\x156\xAC..."
|
16
|
+
# crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
|
17
|
+
# encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
|
18
|
+
# crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
|
12
19
|
class MessageEncryptor
|
13
20
|
module NullSerializer #:nodoc:
|
14
21
|
def self.load(value)
|
@@ -23,38 +30,34 @@ module ActiveSupport
|
|
23
30
|
class InvalidMessage < StandardError; end
|
24
31
|
OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
|
25
32
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
33
|
+
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
|
34
|
+
# the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
|
35
|
+
# bits. If you are using a user-entered secret, you can generate a suitable
|
36
|
+
# key with <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or
|
37
|
+
# similar.
|
38
|
+
#
|
39
|
+
# Options:
|
40
|
+
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
|
41
|
+
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
|
42
|
+
# * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
|
43
|
+
def initialize(secret, *signature_key_or_options)
|
44
|
+
options = signature_key_or_options.extract_options!
|
45
|
+
sign_secret = signature_key_or_options.first
|
32
46
|
@secret = secret
|
47
|
+
@sign_secret = sign_secret
|
33
48
|
@cipher = options[:cipher] || 'aes-256-cbc'
|
34
|
-
@verifier = MessageVerifier.new(@secret, :serializer => NullSerializer)
|
49
|
+
@verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)
|
35
50
|
@serializer = options[:serializer] || Marshal
|
36
51
|
end
|
37
52
|
|
38
|
-
|
39
|
-
|
40
|
-
"Please use MessageEncryptor#encrypt_and_sign instead."
|
41
|
-
_encrypt(value)
|
42
|
-
end
|
43
|
-
|
44
|
-
def decrypt(value)
|
45
|
-
ActiveSupport::Deprecation.warn "MessageEncryptor#decrypt is deprecated as it is not safe without a signature. " \
|
46
|
-
"Please use MessageEncryptor#decrypt_and_verify instead."
|
47
|
-
_decrypt(value)
|
48
|
-
end
|
49
|
-
|
50
|
-
# Encrypt and sign a message. We need to sign the message in order to avoid padding attacks.
|
51
|
-
# Reference: http://www.limited-entropy.com/padding-oracle-attacks
|
53
|
+
# Encrypt and sign a message. We need to sign the message in order to avoid
|
54
|
+
# padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
|
52
55
|
def encrypt_and_sign(value)
|
53
56
|
verifier.generate(_encrypt(value))
|
54
57
|
end
|
55
58
|
|
56
|
-
# Decrypt and verify a message. We need to verify the message in order to
|
57
|
-
# Reference: http://www.limited-entropy.com/padding-oracle-attacks
|
59
|
+
# Decrypt and verify a message. We need to verify the message in order to
|
60
|
+
# avoid padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
|
58
61
|
def decrypt_and_verify(value)
|
59
62
|
_decrypt(verifier.verify(value))
|
60
63
|
end
|
@@ -1,13 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
require 'active_support/deprecation'
|
1
|
+
require 'base64'
|
3
2
|
require 'active_support/core_ext/object/blank'
|
4
3
|
|
5
4
|
module ActiveSupport
|
6
|
-
# +MessageVerifier+ makes it easy to generate and verify messages which are
|
7
|
-
# to prevent tampering.
|
5
|
+
# +MessageVerifier+ makes it easy to generate and verify messages which are
|
6
|
+
# signed to prevent tampering.
|
8
7
|
#
|
9
|
-
# This is useful for cases like remember-me tokens and auto-unsubscribe links
|
10
|
-
# session store isn't suitable or available.
|
8
|
+
# This is useful for cases like remember-me tokens and auto-unsubscribe links
|
9
|
+
# where the session store isn't suitable or available.
|
11
10
|
#
|
12
11
|
# Remember Me:
|
13
12
|
# cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
|
@@ -19,20 +18,15 @@ module ActiveSupport
|
|
19
18
|
# self.current_user = User.find(id)
|
20
19
|
# end
|
21
20
|
#
|
22
|
-
# By default it uses Marshal to serialize the message. If you want to use
|
23
|
-
# serialization method, you can set the serializer attribute to
|
24
|
-
# to dump and load, e.g.:
|
21
|
+
# By default it uses Marshal to serialize the message. If you want to use
|
22
|
+
# another serialization method, you can set the serializer attribute to
|
23
|
+
# something that responds to dump and load, e.g.:
|
25
24
|
#
|
26
25
|
# @verifier.serializer = YAML
|
27
26
|
class MessageVerifier
|
28
27
|
class InvalidSignature < StandardError; end
|
29
28
|
|
30
29
|
def initialize(secret, options = {})
|
31
|
-
unless options.is_a?(Hash)
|
32
|
-
ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :digest => 'algorithm' to specify the digest algorithm."
|
33
|
-
options = { :digest => options }
|
34
|
-
end
|
35
|
-
|
36
30
|
@secret = secret
|
37
31
|
@digest = options[:digest] || 'SHA1'
|
38
32
|
@serializer = options[:serializer] || Marshal
|
@@ -1,44 +1,21 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'active_support/core_ext/module/attribute_accessors'
|
3
|
-
|
4
1
|
module ActiveSupport #:nodoc:
|
5
2
|
module Multibyte
|
6
|
-
autoload :EncodingError, 'active_support/multibyte/exceptions'
|
7
3
|
autoload :Chars, 'active_support/multibyte/chars'
|
8
4
|
autoload :Unicode, 'active_support/multibyte/unicode'
|
9
5
|
|
10
|
-
# The proxy class returned when calling mb_chars. You can use this accessor
|
11
|
-
# class so you can support other encodings. See
|
12
|
-
# an example how to
|
6
|
+
# The proxy class returned when calling mb_chars. You can use this accessor
|
7
|
+
# to configure your own proxy class so you can support other encodings. See
|
8
|
+
# the ActiveSupport::Multibyte::Chars implementation for an example how to
|
9
|
+
# do this.
|
13
10
|
#
|
14
|
-
# Example:
|
15
11
|
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
|
16
12
|
def self.proxy_class=(klass)
|
17
13
|
@proxy_class = klass
|
18
14
|
end
|
19
15
|
|
20
|
-
# Returns the current proxy class
|
16
|
+
# Returns the current proxy class.
|
21
17
|
def self.proxy_class
|
22
18
|
@proxy_class ||= ActiveSupport::Multibyte::Chars
|
23
19
|
end
|
24
|
-
|
25
|
-
# Regular expressions that describe valid byte sequences for a character
|
26
|
-
VALID_CHARACTER = {
|
27
|
-
# Borrowed from the Kconv library by Shinji KONO - (also as seen on the W3C site)
|
28
|
-
'UTF-8' => /\A(?:
|
29
|
-
[\x00-\x7f] |
|
30
|
-
[\xc2-\xdf] [\x80-\xbf] |
|
31
|
-
\xe0 [\xa0-\xbf] [\x80-\xbf] |
|
32
|
-
[\xe1-\xef] [\x80-\xbf] [\x80-\xbf] |
|
33
|
-
\xf0 [\x90-\xbf] [\x80-\xbf] [\x80-\xbf] |
|
34
|
-
[\xf1-\xf3] [\x80-\xbf] [\x80-\xbf] [\x80-\xbf] |
|
35
|
-
\xf4 [\x80-\x8f] [\x80-\xbf] [\x80-\xbf])\z /xn,
|
36
|
-
# Quick check for valid Shift-JIS characters, disregards the odd-even pairing
|
37
|
-
'Shift_JIS' => /\A(?:
|
38
|
-
[\x00-\x7e\xa1-\xdf] |
|
39
|
-
[\x81-\x9f\xe0-\xef] [\x40-\x7e\x80-\x9e\x9f-\xfc])\z /xn
|
40
|
-
}
|
41
20
|
end
|
42
21
|
end
|
43
|
-
|
44
|
-
require 'active_support/multibyte/utils'
|
@@ -1,25 +1,32 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'active_support/json'
|
2
3
|
require 'active_support/core_ext/string/access'
|
3
4
|
require 'active_support/core_ext/string/behavior'
|
5
|
+
require 'active_support/core_ext/module/delegation'
|
4
6
|
|
5
7
|
module ActiveSupport #:nodoc:
|
6
8
|
module Multibyte #:nodoc:
|
7
|
-
# Chars enables you to work transparently with UTF-8 encoding in the Ruby
|
8
|
-
# knowledge about the encoding. A
|
9
|
-
#
|
9
|
+
# Chars enables you to work transparently with UTF-8 encoding in the Ruby
|
10
|
+
# String class without having extensive knowledge about the encoding. A
|
11
|
+
# Chars object accepts a string upon initialization and proxies String
|
12
|
+
# methods in an encoding safe manner. All the normal String methods are also
|
13
|
+
# implemented on the proxy.
|
10
14
|
#
|
11
|
-
# String methods are proxied through the Chars object, and can be accessed
|
12
|
-
#
|
15
|
+
# String methods are proxied through the Chars object, and can be accessed
|
16
|
+
# through the +mb_chars+ method. Methods which would normally return a
|
17
|
+
# String object now return a Chars object so methods can be chained.
|
13
18
|
#
|
14
|
-
#
|
19
|
+
# 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
|
15
20
|
#
|
16
|
-
# Chars objects are perfectly interchangeable with String objects as long as
|
17
|
-
#
|
21
|
+
# Chars objects are perfectly interchangeable with String objects as long as
|
22
|
+
# no explicit class checks are made. If certain methods do explicitly check
|
23
|
+
# the class, call +to_s+ before you pass chars objects to them.
|
18
24
|
#
|
19
|
-
# bad.explicit_checking_method
|
25
|
+
# bad.explicit_checking_method 'T'.mb_chars.downcase.to_s
|
20
26
|
#
|
21
|
-
# The default Chars implementation assumes that the encoding of the string
|
22
|
-
#
|
27
|
+
# The default Chars implementation assumes that the encoding of the string
|
28
|
+
# is UTF-8, if you want to handle different encodings you can write your own
|
29
|
+
# multibyte string handler and configure it through
|
23
30
|
# ActiveSupport::Multibyte.proxy_class.
|
24
31
|
#
|
25
32
|
# class CharsForUTF32
|
@@ -34,327 +41,97 @@ module ActiveSupport #:nodoc:
|
|
34
41
|
#
|
35
42
|
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
|
36
43
|
class Chars
|
44
|
+
include Comparable
|
37
45
|
attr_reader :wrapped_string
|
38
46
|
alias to_s wrapped_string
|
39
47
|
alias to_str wrapped_string
|
40
48
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
else
|
48
|
-
def initialize(string) #:nodoc:
|
49
|
-
@wrapped_string = string
|
50
|
-
end
|
49
|
+
delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string
|
50
|
+
|
51
|
+
# Creates a new Chars instance by wrapping _string_.
|
52
|
+
def initialize(string)
|
53
|
+
@wrapped_string = string
|
54
|
+
@wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
|
51
55
|
end
|
52
56
|
|
53
57
|
# Forward all undefined methods to the wrapped string.
|
54
58
|
def method_missing(method, *args, &block)
|
55
59
|
if method.to_s =~ /!$/
|
56
|
-
@wrapped_string.__send__(method, *args, &block)
|
57
|
-
self
|
60
|
+
result = @wrapped_string.__send__(method, *args, &block)
|
61
|
+
self if result
|
58
62
|
else
|
59
63
|
result = @wrapped_string.__send__(method, *args, &block)
|
60
64
|
result.kind_of?(String) ? chars(result) : result
|
61
65
|
end
|
62
66
|
end
|
63
67
|
|
64
|
-
# Returns +true+ if _obj_ responds to the given method. Private methods
|
65
|
-
# only if the optional second parameter
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
# Enable more predictable duck-typing on String-like classes. See Object#acts_like?.
|
71
|
-
def acts_like_string?
|
72
|
-
true
|
68
|
+
# Returns +true+ if _obj_ responds to the given method. Private methods
|
69
|
+
# are included in the search only if the optional second parameter
|
70
|
+
# evaluates to +true+.
|
71
|
+
def respond_to_missing?(method, include_private)
|
72
|
+
@wrapped_string.respond_to?(method, include_private)
|
73
73
|
end
|
74
74
|
|
75
|
-
# Returns +true+ when the proxy class can handle the string. Returns
|
75
|
+
# Returns +true+ when the proxy class can handle the string. Returns
|
76
|
+
# +false+ otherwise.
|
76
77
|
def self.consumes?(string)
|
77
|
-
|
78
|
-
string.unpack('U*')
|
79
|
-
true
|
80
|
-
rescue ArgumentError
|
81
|
-
false
|
78
|
+
string.encoding == Encoding::UTF_8
|
82
79
|
end
|
83
80
|
|
84
|
-
|
85
|
-
|
86
|
-
#
|
87
|
-
# equal or after the object on the right side of the operation. It accepts any object
|
88
|
-
# that implements +to_s+:
|
81
|
+
# Works just like <tt>String#split</tt>, with the exception that the items
|
82
|
+
# in the resulting list are Chars instances instead of String. This makes
|
83
|
+
# chaining methods easier.
|
89
84
|
#
|
90
|
-
# 'é'.mb_chars <=> 'ü'.mb_chars # => -1
|
91
|
-
#
|
92
|
-
# See <tt>String#<=></tt> for more details.
|
93
|
-
def <=>(other)
|
94
|
-
@wrapped_string <=> other.to_s
|
95
|
-
end
|
96
|
-
|
97
|
-
if RUBY_VERSION < "1.9"
|
98
|
-
# Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns
|
99
|
-
# +false+ otherwise.
|
100
|
-
def self.wants?(string)
|
101
|
-
$KCODE == 'UTF8' && consumes?(string)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Returns a new Chars object containing the _other_ object concatenated to the string.
|
105
|
-
#
|
106
|
-
# Example:
|
107
|
-
# ('Café'.mb_chars + ' périferôl').to_s # => "Café périferôl"
|
108
|
-
def +(other)
|
109
|
-
chars(@wrapped_string + other)
|
110
|
-
end
|
111
|
-
|
112
|
-
# Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
|
113
|
-
#
|
114
|
-
# Example:
|
115
|
-
# 'Café périferôl'.mb_chars =~ /ô/ # => 12
|
116
|
-
def =~(other)
|
117
|
-
translate_offset(@wrapped_string =~ other)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Inserts the passed string at specified codepoint offsets.
|
121
|
-
#
|
122
|
-
# Example:
|
123
|
-
# 'Café'.mb_chars.insert(4, ' périferôl').to_s # => "Café périferôl"
|
124
|
-
def insert(offset, fragment)
|
125
|
-
unpacked = Unicode.u_unpack(@wrapped_string)
|
126
|
-
unless offset > unpacked.length
|
127
|
-
@wrapped_string.replace(
|
128
|
-
Unicode.u_unpack(@wrapped_string).insert(offset, *Unicode.u_unpack(fragment)).pack('U*')
|
129
|
-
)
|
130
|
-
else
|
131
|
-
raise IndexError, "index #{offset} out of string"
|
132
|
-
end
|
133
|
-
self
|
134
|
-
end
|
135
|
-
|
136
|
-
# Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
|
137
|
-
#
|
138
|
-
# Example:
|
139
|
-
# 'Café'.mb_chars.include?('é') # => true
|
140
|
-
def include?(other)
|
141
|
-
# We have to redefine this method because Enumerable defines it.
|
142
|
-
@wrapped_string.include?(other)
|
143
|
-
end
|
144
|
-
|
145
|
-
# Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
|
146
|
-
#
|
147
|
-
# Example:
|
148
|
-
# 'Café périferôl'.mb_chars.index('ô') # => 12
|
149
|
-
# 'Café périferôl'.mb_chars.index(/\w/u) # => 0
|
150
|
-
def index(needle, offset=0)
|
151
|
-
wrapped_offset = first(offset).wrapped_string.length
|
152
|
-
index = @wrapped_string.index(needle, wrapped_offset)
|
153
|
-
index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
|
154
|
-
end
|
155
|
-
|
156
|
-
# Returns the position _needle_ in the string, counting in
|
157
|
-
# codepoints, searching backward from _offset_ or the end of the
|
158
|
-
# string. Returns +nil+ if _needle_ isn't found.
|
159
|
-
#
|
160
|
-
# Example:
|
161
|
-
# 'Café périferôl'.mb_chars.rindex('é') # => 6
|
162
|
-
# 'Café périferôl'.mb_chars.rindex(/\w/u) # => 13
|
163
|
-
def rindex(needle, offset=nil)
|
164
|
-
offset ||= length
|
165
|
-
wrapped_offset = first(offset).wrapped_string.length
|
166
|
-
index = @wrapped_string.rindex(needle, wrapped_offset)
|
167
|
-
index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
|
168
|
-
end
|
169
|
-
|
170
|
-
# Returns the number of codepoints in the string
|
171
|
-
def size
|
172
|
-
Unicode.u_unpack(@wrapped_string).size
|
173
|
-
end
|
174
|
-
alias_method :length, :size
|
175
|
-
|
176
|
-
# Strips entire range of Unicode whitespace from the right of the string.
|
177
|
-
def rstrip
|
178
|
-
chars(@wrapped_string.gsub(Unicode::TRAILERS_PAT, ''))
|
179
|
-
end
|
180
|
-
|
181
|
-
# Strips entire range of Unicode whitespace from the left of the string.
|
182
|
-
def lstrip
|
183
|
-
chars(@wrapped_string.gsub(Unicode::LEADERS_PAT, ''))
|
184
|
-
end
|
185
|
-
|
186
|
-
# Strips entire range of Unicode whitespace from the right and left of the string.
|
187
|
-
def strip
|
188
|
-
rstrip.lstrip
|
189
|
-
end
|
190
|
-
|
191
|
-
# Returns the codepoint of the first character in the string.
|
192
|
-
#
|
193
|
-
# Example:
|
194
|
-
# 'こんにちは'.mb_chars.ord # => 12371
|
195
|
-
def ord
|
196
|
-
Unicode.u_unpack(@wrapped_string)[0]
|
197
|
-
end
|
198
|
-
|
199
|
-
# Works just like <tt>String#rjust</tt>, only integer specifies characters instead of bytes.
|
200
|
-
#
|
201
|
-
# Example:
|
202
|
-
#
|
203
|
-
# "¾ cup".mb_chars.rjust(8).to_s
|
204
|
-
# # => " ¾ cup"
|
205
|
-
#
|
206
|
-
# "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
|
207
|
-
# # => " ¾ cup"
|
208
|
-
def rjust(integer, padstr=' ')
|
209
|
-
justify(integer, :right, padstr)
|
210
|
-
end
|
211
|
-
|
212
|
-
# Works just like <tt>String#ljust</tt>, only integer specifies characters instead of bytes.
|
213
|
-
#
|
214
|
-
# Example:
|
215
|
-
#
|
216
|
-
# "¾ cup".mb_chars.rjust(8).to_s
|
217
|
-
# # => "¾ cup "
|
218
|
-
#
|
219
|
-
# "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
|
220
|
-
# # => "¾ cup "
|
221
|
-
def ljust(integer, padstr=' ')
|
222
|
-
justify(integer, :left, padstr)
|
223
|
-
end
|
224
|
-
|
225
|
-
# Works just like <tt>String#center</tt>, only integer specifies characters instead of bytes.
|
226
|
-
#
|
227
|
-
# Example:
|
228
|
-
#
|
229
|
-
# "¾ cup".mb_chars.center(8).to_s
|
230
|
-
# # => " ¾ cup "
|
231
|
-
#
|
232
|
-
# "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
|
233
|
-
# # => " ¾ cup "
|
234
|
-
def center(integer, padstr=' ')
|
235
|
-
justify(integer, :center, padstr)
|
236
|
-
end
|
237
|
-
|
238
|
-
else
|
239
|
-
def =~(other)
|
240
|
-
@wrapped_string =~ other
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
# Works just like <tt>String#split</tt>, with the exception that the items in the resulting list are Chars
|
245
|
-
# instances instead of String. This makes chaining methods easier.
|
246
|
-
#
|
247
|
-
# Example:
|
248
85
|
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
|
249
86
|
def split(*args)
|
250
|
-
@wrapped_string.split(*args).map { |i| i
|
87
|
+
@wrapped_string.split(*args).map { |i| self.class.new(i) }
|
251
88
|
end
|
252
89
|
|
253
|
-
#
|
254
|
-
#
|
255
|
-
|
256
|
-
|
257
|
-
# s = "Müller"
|
258
|
-
# s.mb_chars[2] = "e" # Replace character with offset 2
|
259
|
-
# s
|
260
|
-
# # => "Müeler"
|
261
|
-
#
|
262
|
-
# s = "Müller"
|
263
|
-
# s.mb_chars[1, 2] = "ö" # Replace 2 characters at character offset 1
|
264
|
-
# s
|
265
|
-
# # => "Möler"
|
266
|
-
def []=(*args)
|
267
|
-
replace_by = args.pop
|
268
|
-
# Indexed replace with regular expressions already works
|
269
|
-
if args.first.is_a?(Regexp)
|
270
|
-
@wrapped_string[*args] = replace_by
|
271
|
-
else
|
272
|
-
result = Unicode.u_unpack(@wrapped_string)
|
273
|
-
case args.first
|
274
|
-
when Fixnum
|
275
|
-
raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
|
276
|
-
min = args[0]
|
277
|
-
max = args[1].nil? ? min : (min + args[1] - 1)
|
278
|
-
range = Range.new(min, max)
|
279
|
-
replace_by = [replace_by].pack('U') if replace_by.is_a?(Fixnum)
|
280
|
-
when Range
|
281
|
-
raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
|
282
|
-
range = args[0]
|
283
|
-
else
|
284
|
-
needle = args[0].to_s
|
285
|
-
min = index(needle)
|
286
|
-
max = min + Unicode.u_unpack(needle).length - 1
|
287
|
-
range = Range.new(min, max)
|
288
|
-
end
|
289
|
-
result[range] = Unicode.u_unpack(replace_by)
|
290
|
-
@wrapped_string.replace(result.pack('U*'))
|
291
|
-
end
|
90
|
+
# Works like like <tt>String#slice!</tt>, but returns an instance of
|
91
|
+
# Chars, or nil if the string was not modified.
|
92
|
+
def slice!(*args)
|
93
|
+
chars(@wrapped_string.slice!(*args))
|
292
94
|
end
|
293
95
|
|
294
96
|
# Reverses all characters in the string.
|
295
97
|
#
|
296
|
-
# Example:
|
297
98
|
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
|
298
99
|
def reverse
|
299
|
-
chars(Unicode.
|
100
|
+
chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
|
300
101
|
end
|
301
102
|
|
302
|
-
#
|
303
|
-
#
|
103
|
+
# Limits the byte size of the string to a number of bytes without breaking
|
104
|
+
# characters. Usable when the storage for a string is limited for some
|
105
|
+
# reason.
|
304
106
|
#
|
305
|
-
# Example:
|
306
|
-
# 'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"
|
307
|
-
def slice(*args)
|
308
|
-
if args.size > 2
|
309
|
-
raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
|
310
|
-
elsif (args.size == 2 && !(args.first.is_a?(Numeric) || args.first.is_a?(Regexp)))
|
311
|
-
raise TypeError, "cannot convert #{args.first.class} into Integer" # Do as if we were native
|
312
|
-
elsif (args.size == 2 && !args[1].is_a?(Numeric))
|
313
|
-
raise TypeError, "cannot convert #{args[1].class} into Integer" # Do as if we were native
|
314
|
-
elsif args[0].kind_of? Range
|
315
|
-
cps = Unicode.u_unpack(@wrapped_string).slice(*args)
|
316
|
-
result = cps.nil? ? nil : cps.pack('U*')
|
317
|
-
elsif args[0].kind_of? Regexp
|
318
|
-
result = @wrapped_string.slice(*args)
|
319
|
-
elsif args.size == 1 && args[0].kind_of?(Numeric)
|
320
|
-
character = Unicode.u_unpack(@wrapped_string)[args[0]]
|
321
|
-
result = character && [character].pack('U')
|
322
|
-
else
|
323
|
-
cps = Unicode.u_unpack(@wrapped_string).slice(*args)
|
324
|
-
result = cps && cps.pack('U*')
|
325
|
-
end
|
326
|
-
result && chars(result)
|
327
|
-
end
|
328
|
-
alias_method :[], :slice
|
329
|
-
|
330
|
-
# Limit the byte size of the string to a number of bytes without breaking characters. Usable
|
331
|
-
# when the storage for a string is limited for some reason.
|
332
|
-
#
|
333
|
-
# Example:
|
334
107
|
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
|
335
108
|
def limit(limit)
|
336
109
|
slice(0...translate_offset(limit))
|
337
110
|
end
|
338
111
|
|
339
|
-
#
|
112
|
+
# Converts characters in the string to uppercase.
|
340
113
|
#
|
341
|
-
# Example:
|
342
114
|
# 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
|
343
115
|
def upcase
|
344
|
-
chars
|
116
|
+
chars Unicode.upcase(@wrapped_string)
|
345
117
|
end
|
346
118
|
|
347
|
-
#
|
119
|
+
# Converts characters in the string to lowercase.
|
348
120
|
#
|
349
|
-
# Example:
|
350
121
|
# 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
|
351
122
|
def downcase
|
352
|
-
chars
|
123
|
+
chars Unicode.downcase(@wrapped_string)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Converts characters in the string to the opposite case.
|
127
|
+
#
|
128
|
+
# 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
|
129
|
+
def swapcase
|
130
|
+
chars Unicode.swapcase(@wrapped_string)
|
353
131
|
end
|
354
132
|
|
355
133
|
# Converts the first character to uppercase and the remainder to lowercase.
|
356
134
|
#
|
357
|
-
# Example:
|
358
135
|
# 'über'.mb_chars.capitalize.to_s # => "Über"
|
359
136
|
def capitalize
|
360
137
|
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
|
@@ -362,16 +139,16 @@ module ActiveSupport #:nodoc:
|
|
362
139
|
|
363
140
|
# Capitalizes the first letter of every word, when possible.
|
364
141
|
#
|
365
|
-
# Example:
|
366
142
|
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
|
367
143
|
# "日本語".mb_chars.titleize # => "日本語"
|
368
144
|
def titleize
|
369
|
-
chars(downcase.to_s.gsub(/\b('
|
145
|
+
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
|
370
146
|
end
|
371
147
|
alias_method :titlecase, :titleize
|
372
148
|
|
373
|
-
# Returns the KC normalization of the string by default. NFKC is
|
374
|
-
# passing strings to databases
|
149
|
+
# Returns the KC normalization of the string by default. NFKC is
|
150
|
+
# considered the best normalization form for passing strings to databases
|
151
|
+
# and validations.
|
375
152
|
#
|
376
153
|
# * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
|
377
154
|
# <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
|
@@ -382,46 +159,45 @@ module ActiveSupport #:nodoc:
|
|
382
159
|
|
383
160
|
# Performs canonical decomposition on all the characters.
|
384
161
|
#
|
385
|
-
# Example:
|
386
162
|
# 'é'.length # => 2
|
387
163
|
# 'é'.mb_chars.decompose.to_s.length # => 3
|
388
164
|
def decompose
|
389
|
-
chars(Unicode.
|
165
|
+
chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*'))
|
390
166
|
end
|
391
167
|
|
392
168
|
# Performs composition on all the characters.
|
393
169
|
#
|
394
|
-
# Example:
|
395
170
|
# 'é'.length # => 3
|
396
171
|
# 'é'.mb_chars.compose.to_s.length # => 2
|
397
172
|
def compose
|
398
|
-
chars(Unicode.
|
173
|
+
chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*'))
|
399
174
|
end
|
400
175
|
|
401
176
|
# Returns the number of grapheme clusters in the string.
|
402
177
|
#
|
403
|
-
# Example:
|
404
178
|
# 'क्षि'.mb_chars.length # => 4
|
405
|
-
# 'क्षि'.mb_chars.
|
406
|
-
def
|
407
|
-
Unicode.
|
179
|
+
# 'क्षि'.mb_chars.grapheme_length # => 3
|
180
|
+
def grapheme_length
|
181
|
+
Unicode.unpack_graphemes(@wrapped_string).length
|
408
182
|
end
|
409
183
|
|
410
|
-
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
|
184
|
+
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
|
185
|
+
# resulting in a valid UTF-8 string.
|
411
186
|
#
|
412
|
-
# Passing +true+ will forcibly tidy all bytes, assuming that the string's
|
187
|
+
# Passing +true+ will forcibly tidy all bytes, assuming that the string's
|
188
|
+
# encoding is entirely CP1252 or ISO-8859-1.
|
413
189
|
def tidy_bytes(force = false)
|
414
190
|
chars(Unicode.tidy_bytes(@wrapped_string, force))
|
415
191
|
end
|
416
192
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
193
|
+
def as_json(options = nil) #:nodoc:
|
194
|
+
to_s.as_json(options)
|
195
|
+
end
|
196
|
+
|
197
|
+
%w(capitalize downcase reverse tidy_bytes upcase).each do |method|
|
198
|
+
define_method("#{method}!") do |*args|
|
199
|
+
@wrapped_string = send(method, *args).to_s
|
200
|
+
self
|
425
201
|
end
|
426
202
|
end
|
427
203
|
|
@@ -431,43 +207,14 @@ module ActiveSupport #:nodoc:
|
|
431
207
|
return nil if byte_offset.nil?
|
432
208
|
return 0 if @wrapped_string == ''
|
433
209
|
|
434
|
-
if @wrapped_string.respond_to?(:force_encoding)
|
435
|
-
@wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT)
|
436
|
-
end
|
437
|
-
|
438
210
|
begin
|
439
|
-
@wrapped_string
|
211
|
+
@wrapped_string.byteslice(0...byte_offset).unpack('U*').length
|
440
212
|
rescue ArgumentError
|
441
213
|
byte_offset -= 1
|
442
214
|
retry
|
443
215
|
end
|
444
216
|
end
|
445
217
|
|
446
|
-
def justify(integer, way, padstr=' ') #:nodoc:
|
447
|
-
raise ArgumentError, "zero width padding" if padstr.length == 0
|
448
|
-
padsize = integer - size
|
449
|
-
padsize = padsize > 0 ? padsize : 0
|
450
|
-
case way
|
451
|
-
when :right
|
452
|
-
result = @wrapped_string.dup.insert(0, padding(padsize, padstr))
|
453
|
-
when :left
|
454
|
-
result = @wrapped_string.dup.insert(-1, padding(padsize, padstr))
|
455
|
-
when :center
|
456
|
-
lpad = padding((padsize / 2.0).floor, padstr)
|
457
|
-
rpad = padding((padsize / 2.0).ceil, padstr)
|
458
|
-
result = @wrapped_string.dup.insert(0, lpad).insert(-1, rpad)
|
459
|
-
end
|
460
|
-
chars(result)
|
461
|
-
end
|
462
|
-
|
463
|
-
def padding(padsize, padstr=' ') #:nodoc:
|
464
|
-
if padsize != 0
|
465
|
-
chars(padstr * ((padsize / Unicode.u_unpack(padstr).size) + 1)).slice(0, padsize)
|
466
|
-
else
|
467
|
-
''
|
468
|
-
end
|
469
|
-
end
|
470
|
-
|
471
218
|
def chars(string) #:nodoc:
|
472
219
|
self.class.new(string)
|
473
220
|
end
|