motion_blender-support 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/HACKS.md +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +359 -0
- data/Rakefile +14 -0
- data/app/app_delegate.rb +5 -0
- data/examples/Inflector/.gitignore +16 -0
- data/examples/Inflector/Gemfile +5 -0
- data/examples/Inflector/Rakefile +13 -0
- data/examples/Inflector/app/app_delegate.rb +8 -0
- data/examples/Inflector/app/inflections.rb +5 -0
- data/examples/Inflector/app/inflector_view_controller.rb +109 -0
- data/examples/Inflector/app/words_view_controller.rb +101 -0
- data/examples/Inflector/resources/Default-568h@2x.png +0 -0
- data/examples/Inflector/spec/main_spec.rb +9 -0
- data/lib/motion-support/callbacks.rb +8 -0
- data/lib/motion-support/concern.rb +4 -0
- data/lib/motion-support/core_ext/array.rb +10 -0
- data/lib/motion-support/core_ext/class.rb +5 -0
- data/lib/motion-support/core_ext/hash.rb +13 -0
- data/lib/motion-support/core_ext/integer.rb +6 -0
- data/lib/motion-support/core_ext/module.rb +11 -0
- data/lib/motion-support/core_ext/numeric.rb +6 -0
- data/lib/motion-support/core_ext/object.rb +12 -0
- data/lib/motion-support/core_ext/range.rb +5 -0
- data/lib/motion-support/core_ext/string.rb +13 -0
- data/lib/motion-support/core_ext/time.rb +16 -0
- data/lib/motion-support/core_ext.rb +13 -0
- data/lib/motion-support/inflector.rb +8 -0
- data/lib/motion-support.rb +81 -0
- data/motion/_stdlib/array.rb +13 -0
- data/motion/_stdlib/cgi.rb +22 -0
- data/motion/_stdlib/date.rb +81 -0
- data/motion/_stdlib/enumerable.rb +9 -0
- data/motion/_stdlib/time.rb +19 -0
- data/motion/callbacks.rb +511 -0
- data/motion/concern.rb +122 -0
- data/motion/core_ext/array/access.rb +28 -0
- data/motion/core_ext/array/conversions.rb +86 -0
- data/motion/core_ext/array/extract_options.rb +11 -0
- data/motion/core_ext/array/grouping.rb +99 -0
- data/motion/core_ext/array/prepend_and_append.rb +7 -0
- data/motion/core_ext/array/wrap.rb +45 -0
- data/motion/core_ext/array.rb +19 -0
- data/motion/core_ext/class/attribute.rb +119 -0
- data/motion/core_ext/class/attribute_accessors.rb +168 -0
- data/motion/core_ext/date/acts_like.rb +8 -0
- data/motion/core_ext/date/calculations.rb +117 -0
- data/motion/core_ext/date/conversions.rb +56 -0
- data/motion/core_ext/date_and_time/calculations.rb +232 -0
- data/motion/core_ext/enumerable.rb +90 -0
- data/motion/core_ext/hash/deep_delete_if.rb +23 -0
- data/motion/core_ext/hash/deep_merge.rb +27 -0
- data/motion/core_ext/hash/except.rb +15 -0
- data/motion/core_ext/hash/indifferent_access.rb +19 -0
- data/motion/core_ext/hash/keys.rb +150 -0
- data/motion/core_ext/hash/reverse_merge.rb +22 -0
- data/motion/core_ext/hash/slice.rb +40 -0
- data/motion/core_ext/integer/inflections.rb +27 -0
- data/motion/core_ext/integer/multiple.rb +10 -0
- data/motion/core_ext/integer/time.rb +41 -0
- data/motion/core_ext/kernel/singleton_class.rb +6 -0
- data/motion/core_ext/metaclass.rb +8 -0
- data/motion/core_ext/module/aliasing.rb +69 -0
- data/motion/core_ext/module/anonymous.rb +19 -0
- data/motion/core_ext/module/attr_internal.rb +38 -0
- data/motion/core_ext/module/attribute_accessors.rb +64 -0
- data/motion/core_ext/module/delegation.rb +175 -0
- data/motion/core_ext/module/introspection.rb +60 -0
- data/motion/core_ext/module/reachable.rb +5 -0
- data/motion/core_ext/module/remove_method.rb +12 -0
- data/motion/core_ext/ns_dictionary.rb +14 -0
- data/motion/core_ext/ns_string.rb +14 -0
- data/motion/core_ext/numeric/bytes.rb +44 -0
- data/motion/core_ext/numeric/conversions.rb +49 -0
- data/motion/core_ext/numeric/time.rb +75 -0
- data/motion/core_ext/object/acts_like.rb +10 -0
- data/motion/core_ext/object/blank.rb +105 -0
- data/motion/core_ext/object/deep_dup.rb +44 -0
- data/motion/core_ext/object/duplicable.rb +87 -0
- data/motion/core_ext/object/inclusion.rb +15 -0
- data/motion/core_ext/object/instance_variables.rb +28 -0
- data/motion/core_ext/object/to_param.rb +58 -0
- data/motion/core_ext/object/to_query.rb +26 -0
- data/motion/core_ext/object/try.rb +78 -0
- data/motion/core_ext/range/include_range.rb +23 -0
- data/motion/core_ext/range/overlaps.rb +8 -0
- data/motion/core_ext/regexp.rb +5 -0
- data/motion/core_ext/string/access.rb +104 -0
- data/motion/core_ext/string/behavior.rb +6 -0
- data/motion/core_ext/string/exclude.rb +11 -0
- data/motion/core_ext/string/filters.rb +55 -0
- data/motion/core_ext/string/indent.rb +43 -0
- data/motion/core_ext/string/inflections.rb +178 -0
- data/motion/core_ext/string/starts_ends_with.rb +4 -0
- data/motion/core_ext/string/strip.rb +24 -0
- data/motion/core_ext/time/acts_like.rb +8 -0
- data/motion/core_ext/time/calculations.rb +215 -0
- data/motion/core_ext/time/conversions.rb +52 -0
- data/motion/descendants_tracker.rb +50 -0
- data/motion/duration.rb +104 -0
- data/motion/hash_with_indifferent_access.rb +253 -0
- data/motion/inflections.rb +67 -0
- data/motion/inflector/inflections.rb +203 -0
- data/motion/inflector/methods.rb +321 -0
- data/motion/logger.rb +47 -0
- data/motion/number_helper.rb +54 -0
- data/motion/version.rb +3 -0
- data/motion_blender-support.gemspec +21 -0
- data/spec/motion-support/_helpers/constantize_test_cases.rb +75 -0
- data/spec/motion-support/_helpers/inflector_test_cases.rb +270 -0
- data/spec/motion-support/callback_spec.rb +702 -0
- data/spec/motion-support/concern_spec.rb +93 -0
- data/spec/motion-support/core_ext/array/access_spec.rb +29 -0
- data/spec/motion-support/core_ext/array/conversion_spec.rb +60 -0
- data/spec/motion-support/core_ext/array/extract_options_spec.rb +15 -0
- data/spec/motion-support/core_ext/array/grouping_spec.rb +85 -0
- data/spec/motion-support/core_ext/array/prepend_and_append_spec.rb +25 -0
- data/spec/motion-support/core_ext/array/wrap_spec.rb +19 -0
- data/spec/motion-support/core_ext/array_spec.rb +16 -0
- data/spec/motion-support/core_ext/class/attribute_accessor_spec.rb +127 -0
- data/spec/motion-support/core_ext/class/attribute_spec.rb +92 -0
- data/spec/motion-support/core_ext/date/acts_like_spec.rb +11 -0
- data/spec/motion-support/core_ext/date/calculation_spec.rb +186 -0
- data/spec/motion-support/core_ext/date/conversion_spec.rb +18 -0
- data/spec/motion-support/core_ext/date_and_time/calculation_spec.rb +336 -0
- data/spec/motion-support/core_ext/enumerable_spec.rb +130 -0
- data/spec/motion-support/core_ext/hash/deep_delete_if_spec.rb +19 -0
- data/spec/motion-support/core_ext/hash/deep_merge_spec.rb +32 -0
- data/spec/motion-support/core_ext/hash/except_spec.rb +43 -0
- data/spec/motion-support/core_ext/hash/key_spec.rb +236 -0
- data/spec/motion-support/core_ext/hash/reverse_merge_spec.rb +26 -0
- data/spec/motion-support/core_ext/hash/slice_spec.rb +61 -0
- data/spec/motion-support/core_ext/integer/inflection_spec.rb +23 -0
- data/spec/motion-support/core_ext/integer/multiple_spec.rb +19 -0
- data/spec/motion-support/core_ext/kernel/singleton_class_spec.rb +9 -0
- data/spec/motion-support/core_ext/metaclass_spec.rb +9 -0
- data/spec/motion-support/core_ext/module/aliasing_spec.rb +143 -0
- data/spec/motion-support/core_ext/module/anonymous_spec.rb +29 -0
- data/spec/motion-support/core_ext/module/attr_internal_spec.rb +104 -0
- data/spec/motion-support/core_ext/module/attribute_accessor_spec.rb +86 -0
- data/spec/motion-support/core_ext/module/delegation_spec.rb +136 -0
- data/spec/motion-support/core_ext/module/introspection_spec.rb +70 -0
- data/spec/motion-support/core_ext/module/reachable_spec.rb +61 -0
- data/spec/motion-support/core_ext/module/remove_method_spec.rb +25 -0
- data/spec/motion-support/core_ext/numeric/bytes_spec.rb +43 -0
- data/spec/motion-support/core_ext/numeric/conversions_spec.rb +40 -0
- data/spec/motion-support/core_ext/object/acts_like_spec.rb +21 -0
- data/spec/motion-support/core_ext/object/blank_spec.rb +54 -0
- data/spec/motion-support/core_ext/object/deep_dup_spec.rb +54 -0
- data/spec/motion-support/core_ext/object/duplicable_spec.rb +31 -0
- data/spec/motion-support/core_ext/object/inclusion_spec.rb +34 -0
- data/spec/motion-support/core_ext/object/instance_variable_spec.rb +19 -0
- data/spec/motion-support/core_ext/object/to_param_spec.rb +75 -0
- data/spec/motion-support/core_ext/object/to_query_spec.rb +37 -0
- data/spec/motion-support/core_ext/object/try_spec.rb +92 -0
- data/spec/motion-support/core_ext/range/include_range_spec.rb +31 -0
- data/spec/motion-support/core_ext/range/overlap_spec.rb +43 -0
- data/spec/motion-support/core_ext/regexp_spec.rb +7 -0
- data/spec/motion-support/core_ext/string/access_spec.rb +53 -0
- data/spec/motion-support/core_ext/string/behavior_spec.rb +7 -0
- data/spec/motion-support/core_ext/string/exclude_spec.rb +8 -0
- data/spec/motion-support/core_ext/string/filter_spec.rb +49 -0
- data/spec/motion-support/core_ext/string/indent_spec.rb +56 -0
- data/spec/motion-support/core_ext/string/inflection_spec.rb +142 -0
- data/spec/motion-support/core_ext/string/starts_end_with_spec.rb +14 -0
- data/spec/motion-support/core_ext/string/strip_spec.rb +34 -0
- data/spec/motion-support/core_ext/string_spec.rb +88 -0
- data/spec/motion-support/core_ext/time/acts_like_spec.rb +11 -0
- data/spec/motion-support/core_ext/time/calculation_spec.rb +201 -0
- data/spec/motion-support/core_ext/time/conversion_spec.rb +53 -0
- data/spec/motion-support/descendants_tracker_spec.rb +58 -0
- data/spec/motion-support/duration_spec.rb +107 -0
- data/spec/motion-support/hash_with_indifferent_access_spec.rb +605 -0
- data/spec/motion-support/inflector_spec.rb +504 -0
- data/spec/motion-support/ns_dictionary_spec.rb +89 -0
- data/spec/motion-support/ns_string_spec.rb +182 -0
- data/spec/motion-support/number_helper_spec.rb +55 -0
- metadata +352 -0
data/motion/duration.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
module MotionSupport
|
2
|
+
# Provides accurate date and time measurements using Date#advance and
|
3
|
+
# Time#advance, respectively. It mainly supports the methods on Numeric.
|
4
|
+
#
|
5
|
+
# 1.month.ago # equivalent to Time.now.advance(months: -1)
|
6
|
+
class Duration < BasicObject
|
7
|
+
attr_accessor :value, :parts
|
8
|
+
|
9
|
+
def initialize(value, parts) #:nodoc:
|
10
|
+
@value, @parts = value, parts
|
11
|
+
end
|
12
|
+
|
13
|
+
# Adds another Duration or a Numeric to this Duration. Numeric values
|
14
|
+
# are treated as seconds.
|
15
|
+
def +(other)
|
16
|
+
if Duration === other
|
17
|
+
Duration.new(value + other.value, @parts + other.parts)
|
18
|
+
else
|
19
|
+
Duration.new(value + other, @parts + [[:seconds, other]])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Subtracts another Duration or a Numeric from this Duration. Numeric
|
24
|
+
# values are treated as seconds.
|
25
|
+
def -(other)
|
26
|
+
self + (-other)
|
27
|
+
end
|
28
|
+
|
29
|
+
def -@ #:nodoc:
|
30
|
+
Duration.new(-value, parts.map { |type,number| [type, -number] })
|
31
|
+
end
|
32
|
+
|
33
|
+
def is_a?(klass) #:nodoc:
|
34
|
+
Duration == klass || value.is_a?(klass)
|
35
|
+
end
|
36
|
+
alias :kind_of? :is_a?
|
37
|
+
|
38
|
+
# Returns +true+ if +other+ is also a Duration instance with the
|
39
|
+
# same +value+, or if <tt>other == value</tt>.
|
40
|
+
def ==(other)
|
41
|
+
if Duration === other
|
42
|
+
other.value == value
|
43
|
+
else
|
44
|
+
other == value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.===(other) #:nodoc:
|
49
|
+
other.is_a?(Duration)
|
50
|
+
rescue ::NoMethodError
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Calculates a new Time or Date that is as far in the future
|
55
|
+
# as this Duration represents.
|
56
|
+
def since(time = ::Time.now)
|
57
|
+
sum(1, time)
|
58
|
+
end
|
59
|
+
alias :from_now :since
|
60
|
+
|
61
|
+
# Calculates a new Time or Date that is as far in the past
|
62
|
+
# as this Duration represents.
|
63
|
+
def ago(time = ::Time.now)
|
64
|
+
sum(-1, time)
|
65
|
+
end
|
66
|
+
alias :until :ago
|
67
|
+
|
68
|
+
def inspect #:nodoc:
|
69
|
+
consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
|
70
|
+
parts = [:years, :months, :days, :minutes, :seconds].map do |length|
|
71
|
+
n = consolidated[length]
|
72
|
+
"#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
|
73
|
+
end.compact
|
74
|
+
parts = ["0 seconds"] if parts.empty?
|
75
|
+
parts.to_sentence
|
76
|
+
end
|
77
|
+
|
78
|
+
def as_json(options = nil) #:nodoc:
|
79
|
+
to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def sum(sign, time = ::Time.now) #:nodoc:
|
85
|
+
parts.inject(time) do |t,(type,number)|
|
86
|
+
if t.acts_like?(:time) || t.acts_like?(:date)
|
87
|
+
if type == :seconds
|
88
|
+
t.since(sign * number)
|
89
|
+
else
|
90
|
+
t.advance(type => sign * number)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def method_missing(method, *args, &block) #:nodoc:
|
101
|
+
value.send(method, *args, &block)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require_relative 'core_ext/hash/keys'
|
2
|
+
|
3
|
+
module MotionSupport
|
4
|
+
# Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
|
5
|
+
# to be the same.
|
6
|
+
#
|
7
|
+
# rgb = MotionSupport::HashWithIndifferentAccess.new
|
8
|
+
#
|
9
|
+
# rgb[:black] = '#000000'
|
10
|
+
# rgb[:black] # => '#000000'
|
11
|
+
# rgb['black'] # => '#000000'
|
12
|
+
#
|
13
|
+
# rgb['white'] = '#FFFFFF'
|
14
|
+
# rgb[:white] # => '#FFFFFF'
|
15
|
+
# rgb['white'] # => '#FFFFFF'
|
16
|
+
#
|
17
|
+
# Internally symbols are mapped to strings when used as keys in the entire
|
18
|
+
# writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This
|
19
|
+
# mapping belongs to the public interface. For example, given:
|
20
|
+
#
|
21
|
+
# hash = MotionSupport::HashWithIndifferentAccess.new(a: 1)
|
22
|
+
#
|
23
|
+
# You are guaranteed that the key is returned as a string:
|
24
|
+
#
|
25
|
+
# hash.keys # => ["a"]
|
26
|
+
#
|
27
|
+
# Technically other types of keys are accepted:
|
28
|
+
#
|
29
|
+
# hash = MotionSupport::HashWithIndifferentAccess.new(a: 1)
|
30
|
+
# hash[0] = 0
|
31
|
+
# hash # => {"a"=>1, 0=>0}
|
32
|
+
#
|
33
|
+
# but this class is intended for use cases where strings or symbols are the
|
34
|
+
# expected keys and it is convenient to understand both as the same. For
|
35
|
+
# example the +params+ hash in Ruby on Rails.
|
36
|
+
#
|
37
|
+
# Note that core extensions define <tt>Hash#with_indifferent_access</tt>:
|
38
|
+
#
|
39
|
+
# rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access
|
40
|
+
#
|
41
|
+
# which may be handy.
|
42
|
+
class HashWithIndifferentAccess < Hash
|
43
|
+
# Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
|
44
|
+
# this class.
|
45
|
+
def extractable_options?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def with_indifferent_access
|
50
|
+
dup
|
51
|
+
end
|
52
|
+
|
53
|
+
def nested_under_indifferent_access
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(constructor = {})
|
58
|
+
if constructor.is_a?(Hash)
|
59
|
+
super()
|
60
|
+
update(constructor)
|
61
|
+
else
|
62
|
+
super(constructor)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def default(key = nil)
|
67
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
68
|
+
self[key]
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.new_from_hash_copying_default(hash)
|
75
|
+
new(hash).tap do |new_hash|
|
76
|
+
new_hash.default = hash.default
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.[](*args)
|
81
|
+
new.merge!(Hash[*args])
|
82
|
+
end
|
83
|
+
|
84
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
85
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
86
|
+
|
87
|
+
# Assigns a new value to the hash:
|
88
|
+
#
|
89
|
+
# hash = MotionSupport::HashWithIndifferentAccess.new
|
90
|
+
# hash[:key] = 'value'
|
91
|
+
#
|
92
|
+
# This value can be later fetched using either +:key+ or +'key'+.
|
93
|
+
def []=(key, value)
|
94
|
+
regular_writer(convert_key(key), convert_value(value))
|
95
|
+
end
|
96
|
+
|
97
|
+
alias_method :store, :[]=
|
98
|
+
|
99
|
+
# Updates the receiver in-place, merging in the hash passed as argument:
|
100
|
+
#
|
101
|
+
# hash_1 = MotionSupport::HashWithIndifferentAccess.new
|
102
|
+
# hash_1[:key] = 'value'
|
103
|
+
#
|
104
|
+
# hash_2 = MotionSupport::HashWithIndifferentAccess.new
|
105
|
+
# hash_2[:key] = 'New Value!'
|
106
|
+
#
|
107
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
108
|
+
#
|
109
|
+
# The argument can be either an
|
110
|
+
# <tt>MotionSupport::HashWithIndifferentAccess</tt> or a regular +Hash+.
|
111
|
+
# In either case the merge respects the semantics of indifferent access.
|
112
|
+
#
|
113
|
+
# If the argument is a regular hash with keys +:key+ and +"key"+ only one
|
114
|
+
# of the values end up in the receiver, but which one is unspecified.
|
115
|
+
#
|
116
|
+
# When given a block, the value for duplicated keys will be determined
|
117
|
+
# by the result of invoking the block with the duplicated key, the value
|
118
|
+
# in the receiver, and the value in +other_hash+. The rules for duplicated
|
119
|
+
# keys follow the semantics of indifferent access:
|
120
|
+
#
|
121
|
+
# hash_1[:key] = 10
|
122
|
+
# hash_2['key'] = 12
|
123
|
+
# hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22}
|
124
|
+
def update(other_hash)
|
125
|
+
if other_hash.is_a? HashWithIndifferentAccess
|
126
|
+
super(other_hash)
|
127
|
+
else
|
128
|
+
other_hash.each_pair do |key, value|
|
129
|
+
if block_given? && key?(key)
|
130
|
+
value = yield(convert_key(key), self[key], value)
|
131
|
+
end
|
132
|
+
regular_writer(convert_key(key), convert_value(value))
|
133
|
+
end
|
134
|
+
self
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
alias_method :merge!, :update
|
139
|
+
|
140
|
+
# Checks the hash for a key matching the argument passed in:
|
141
|
+
#
|
142
|
+
# hash = MotionSupport::HashWithIndifferentAccess.new
|
143
|
+
# hash['key'] = 'value'
|
144
|
+
# hash.key?(:key) # => true
|
145
|
+
# hash.key?('key') # => true
|
146
|
+
def key?(key)
|
147
|
+
super(convert_key(key))
|
148
|
+
end
|
149
|
+
|
150
|
+
alias_method :include?, :key?
|
151
|
+
alias_method :has_key?, :key?
|
152
|
+
alias_method :member?, :key?
|
153
|
+
|
154
|
+
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
|
155
|
+
# either a string or a symbol:
|
156
|
+
#
|
157
|
+
# counters = MotionSupport::HashWithIndifferentAccess.new
|
158
|
+
# counters[:foo] = 1
|
159
|
+
#
|
160
|
+
# counters.fetch('foo') # => 1
|
161
|
+
# counters.fetch(:bar, 0) # => 0
|
162
|
+
# counters.fetch(:bar) {|key| 0} # => 0
|
163
|
+
# counters.fetch(:zoo) # => KeyError: key not found: "zoo"
|
164
|
+
def fetch(key, *extras)
|
165
|
+
super(convert_key(key), *extras)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns an array of the values at the specified indices:
|
169
|
+
#
|
170
|
+
# hash = MotionSupport::HashWithIndifferentAccess.new
|
171
|
+
# hash[:a] = 'x'
|
172
|
+
# hash[:b] = 'y'
|
173
|
+
# hash.values_at('a', 'b') # => ["x", "y"]
|
174
|
+
def values_at(*indices)
|
175
|
+
indices.collect {|key| self[convert_key(key)]}
|
176
|
+
end
|
177
|
+
|
178
|
+
# Returns an exact copy of the hash.
|
179
|
+
def dup
|
180
|
+
self.class.new(self).tap do |new_hash|
|
181
|
+
new_hash.default = default
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# This method has the same semantics of +update+, except it does not
|
186
|
+
# modify the receiver but rather returns a new hash with indifferent
|
187
|
+
# access with the result of the merge.
|
188
|
+
def merge(hash, &block)
|
189
|
+
self.dup.update(hash, &block)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Like +merge+ but the other way around: Merges the receiver into the
|
193
|
+
# argument and returns a new hash with indifferent access as result:
|
194
|
+
#
|
195
|
+
# hash = MotionSupport::HashWithIndifferentAccess.new
|
196
|
+
# hash['a'] = nil
|
197
|
+
# hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
|
198
|
+
def reverse_merge(other_hash)
|
199
|
+
super(self.class.new_from_hash_copying_default(other_hash))
|
200
|
+
end
|
201
|
+
|
202
|
+
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
|
203
|
+
def reverse_merge!(other_hash)
|
204
|
+
replace(reverse_merge( other_hash ))
|
205
|
+
end
|
206
|
+
|
207
|
+
# Replaces the contents of this hash with other_hash.
|
208
|
+
#
|
209
|
+
# h = { "a" => 100, "b" => 200 }
|
210
|
+
# h.replace({ "c" => 300, "d" => 400 }) #=> {"c"=>300, "d"=>400}
|
211
|
+
def replace(other_hash)
|
212
|
+
super(self.class.new_from_hash_copying_default(other_hash))
|
213
|
+
end
|
214
|
+
|
215
|
+
# Removes the specified key from the hash.
|
216
|
+
def delete(key)
|
217
|
+
super(convert_key(key))
|
218
|
+
end
|
219
|
+
|
220
|
+
def stringify_keys!; self end
|
221
|
+
def deep_stringify_keys!; self end
|
222
|
+
def stringify_keys; dup end
|
223
|
+
def deep_stringify_keys; dup end
|
224
|
+
undef :symbolize_keys!
|
225
|
+
undef :deep_symbolize_keys!
|
226
|
+
def symbolize_keys; to_hash.symbolize_keys end
|
227
|
+
def deep_symbolize_keys; to_hash.deep_symbolize_keys end
|
228
|
+
def to_options!; self end
|
229
|
+
|
230
|
+
# Convert to a regular hash with string keys.
|
231
|
+
def to_hash
|
232
|
+
Hash.new(default).merge!(self)
|
233
|
+
end
|
234
|
+
|
235
|
+
protected
|
236
|
+
def convert_key(key)
|
237
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
238
|
+
end
|
239
|
+
|
240
|
+
def convert_value(value)
|
241
|
+
if value.is_a? Hash
|
242
|
+
value.nested_under_indifferent_access
|
243
|
+
elsif value.is_a?(Array)
|
244
|
+
value = value.dup if value.frozen?
|
245
|
+
value.map! { |e| convert_value(e) }
|
246
|
+
else
|
247
|
+
value
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
HashWithIndifferentAccess = MotionSupport::HashWithIndifferentAccess
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative 'inflector/inflections'
|
2
|
+
|
3
|
+
module MotionSupport
|
4
|
+
Inflector.inflections do |inflect|
|
5
|
+
inflect.plural(/$/, 's')
|
6
|
+
inflect.plural(/s$/i, 's')
|
7
|
+
inflect.plural(/^(ax|test)is$/i, '\1es')
|
8
|
+
inflect.plural(/(octop|vir)us$/i, '\1i')
|
9
|
+
inflect.plural(/(octop|vir)i$/i, '\1i')
|
10
|
+
inflect.plural(/(alias|status)$/i, '\1es')
|
11
|
+
inflect.plural(/(bu)s$/i, '\1ses')
|
12
|
+
inflect.plural(/(buffal|tomat)o$/i, '\1oes')
|
13
|
+
inflect.plural(/([ti])um$/i, '\1a')
|
14
|
+
inflect.plural(/([ti])a$/i, '\1a')
|
15
|
+
inflect.plural(/sis$/i, 'ses')
|
16
|
+
inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
|
17
|
+
inflect.plural(/(hive)$/i, '\1s')
|
18
|
+
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
|
19
|
+
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
|
20
|
+
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
|
21
|
+
inflect.plural(/^(m|l)ouse$/i, '\1ice')
|
22
|
+
inflect.plural(/^(m|l)ice$/i, '\1ice')
|
23
|
+
inflect.plural(/^(ox)$/i, '\1en')
|
24
|
+
inflect.plural(/^(oxen)$/i, '\1')
|
25
|
+
inflect.plural(/(quiz)$/i, '\1zes')
|
26
|
+
|
27
|
+
inflect.singular(/s$/i, '')
|
28
|
+
inflect.singular(/(ss)$/i, '\1')
|
29
|
+
inflect.singular(/(n)ews$/i, '\1ews')
|
30
|
+
inflect.singular(/([ti])a$/i, '\1um')
|
31
|
+
inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
|
32
|
+
inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
|
33
|
+
inflect.singular(/([^f])ves$/i, '\1fe')
|
34
|
+
inflect.singular(/(hive)s$/i, '\1')
|
35
|
+
inflect.singular(/(tive)s$/i, '\1')
|
36
|
+
inflect.singular(/([lr])ves$/i, '\1f')
|
37
|
+
inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
|
38
|
+
inflect.singular(/(s)eries$/i, '\1eries')
|
39
|
+
inflect.singular(/(m)ovies$/i, '\1ovie')
|
40
|
+
inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
|
41
|
+
inflect.singular(/^(m|l)ice$/i, '\1ouse')
|
42
|
+
inflect.singular(/(bus)(es)?$/i, '\1')
|
43
|
+
inflect.singular(/(o)es$/i, '\1')
|
44
|
+
inflect.singular(/(shoe)s$/i, '\1')
|
45
|
+
inflect.singular(/(cris|test)(is|es)$/i, '\1is')
|
46
|
+
inflect.singular(/^(a)x[ie]s$/i, '\1xis')
|
47
|
+
inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
|
48
|
+
inflect.singular(/(alias|status)(es)?$/i, '\1')
|
49
|
+
inflect.singular(/^(ox)en/i, '\1')
|
50
|
+
inflect.singular(/(vert|ind)ices$/i, '\1ex')
|
51
|
+
inflect.singular(/(matr)ices$/i, '\1ix')
|
52
|
+
inflect.singular(/(quiz)zes$/i, '\1')
|
53
|
+
inflect.singular(/(database)s$/i, '\1')
|
54
|
+
|
55
|
+
inflect.irregular('person', 'people')
|
56
|
+
inflect.irregular('man', 'men')
|
57
|
+
inflect.irregular('child', 'children')
|
58
|
+
inflect.irregular('sex', 'sexes')
|
59
|
+
inflect.irregular('move', 'moves')
|
60
|
+
inflect.irregular('cow', 'kine')
|
61
|
+
inflect.irregular('zombie', 'zombies')
|
62
|
+
|
63
|
+
inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
|
64
|
+
|
65
|
+
inflect.acronym('UI')
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require_relative '../core_ext/array/prepend_and_append'
|
2
|
+
|
3
|
+
module MotionSupport
|
4
|
+
module Inflector
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# A singleton instance of this class is yielded by Inflector.inflections,
|
8
|
+
# which can then be used to specify additional inflection rules.
|
9
|
+
#
|
10
|
+
# MotionSupport::Inflector.inflections do |inflect|
|
11
|
+
# inflect.plural /^(ox)$/i, '\1\2en'
|
12
|
+
# inflect.singular /^(ox)en/i, '\1'
|
13
|
+
#
|
14
|
+
# inflect.irregular 'octopus', 'octopi'
|
15
|
+
#
|
16
|
+
# inflect.uncountable 'equipment'
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# New rules are added at the top. So in the example above, the irregular
|
20
|
+
# rule for octopus will now be the first of the pluralization and
|
21
|
+
# singularization rules that is runs. This guarantees that your rules run
|
22
|
+
# before any of the rules that may already have been loaded.
|
23
|
+
class Inflections
|
24
|
+
def self.instance
|
25
|
+
@__instance__ ||= new
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
|
32
|
+
end
|
33
|
+
|
34
|
+
# Private, for the test suite.
|
35
|
+
def initialize_dup(orig) # :nodoc:
|
36
|
+
%w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
|
37
|
+
instance_variable_set("@#{scope}", orig.send(scope).dup)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Specifies a new acronym. An acronym must be specified as it will appear
|
42
|
+
# in a camelized string. An underscore string that contains the acronym
|
43
|
+
# will retain the acronym when passed to +camelize+, +humanize+, or
|
44
|
+
# +titleize+. A camelized string that contains the acronym will maintain
|
45
|
+
# the acronym when titleized or humanized, and will convert the acronym
|
46
|
+
# into a non-delimited single lowercase word when passed to +underscore+.
|
47
|
+
#
|
48
|
+
# acronym 'HTML'
|
49
|
+
# titleize 'html' #=> 'HTML'
|
50
|
+
# camelize 'html' #=> 'HTML'
|
51
|
+
# underscore 'MyHTML' #=> 'my_html'
|
52
|
+
#
|
53
|
+
# The acronym, however, must occur as a delimited unit and not be part of
|
54
|
+
# another word for conversions to recognize it:
|
55
|
+
#
|
56
|
+
# acronym 'HTTP'
|
57
|
+
# camelize 'my_http_delimited' #=> 'MyHTTPDelimited'
|
58
|
+
# camelize 'https' #=> 'Https', not 'HTTPs'
|
59
|
+
# underscore 'HTTPS' #=> 'http_s', not 'https'
|
60
|
+
#
|
61
|
+
# acronym 'HTTPS'
|
62
|
+
# camelize 'https' #=> 'HTTPS'
|
63
|
+
# underscore 'HTTPS' #=> 'https'
|
64
|
+
#
|
65
|
+
# Note: Acronyms that are passed to +pluralize+ will no longer be
|
66
|
+
# recognized, since the acronym will not occur as a delimited unit in the
|
67
|
+
# pluralized result. To work around this, you must specify the pluralized
|
68
|
+
# form as an acronym as well:
|
69
|
+
#
|
70
|
+
# acronym 'API'
|
71
|
+
# camelize(pluralize('api')) #=> 'Apis'
|
72
|
+
#
|
73
|
+
# acronym 'APIs'
|
74
|
+
# camelize(pluralize('api')) #=> 'APIs'
|
75
|
+
#
|
76
|
+
# +acronym+ may be used to specify any word that contains an acronym or
|
77
|
+
# otherwise needs to maintain a non-standard capitalization. The only
|
78
|
+
# restriction is that the word must begin with a capital letter.
|
79
|
+
#
|
80
|
+
# acronym 'RESTful'
|
81
|
+
# underscore 'RESTful' #=> 'restful'
|
82
|
+
# underscore 'RESTfulController' #=> 'restful_controller'
|
83
|
+
# titleize 'RESTfulController' #=> 'RESTful Controller'
|
84
|
+
# camelize 'restful' #=> 'RESTful'
|
85
|
+
# camelize 'restful_controller' #=> 'RESTfulController'
|
86
|
+
#
|
87
|
+
# acronym 'McDonald'
|
88
|
+
# underscore 'McDonald' #=> 'mcdonald'
|
89
|
+
# camelize 'mcdonald' #=> 'McDonald'
|
90
|
+
def acronym(word)
|
91
|
+
@acronyms[word.downcase] = word
|
92
|
+
@acronym_regex = /#{@acronyms.values.join("|")}/
|
93
|
+
end
|
94
|
+
|
95
|
+
# Specifies a new pluralization rule and its replacement. The rule can
|
96
|
+
# either be a string or a regular expression. The replacement should
|
97
|
+
# always be a string that may include references to the matched data from
|
98
|
+
# the rule.
|
99
|
+
def plural(rule, replacement)
|
100
|
+
@uncountables.delete(rule) if rule.is_a?(String)
|
101
|
+
@uncountables.delete(replacement)
|
102
|
+
@plurals.prepend([rule, replacement])
|
103
|
+
end
|
104
|
+
|
105
|
+
# Specifies a new singularization rule and its replacement. The rule can
|
106
|
+
# either be a string or a regular expression. The replacement should
|
107
|
+
# always be a string that may include references to the matched data from
|
108
|
+
# the rule.
|
109
|
+
def singular(rule, replacement)
|
110
|
+
@uncountables.delete(rule) if rule.is_a?(String)
|
111
|
+
@uncountables.delete(replacement)
|
112
|
+
@singulars.prepend([rule, replacement])
|
113
|
+
end
|
114
|
+
|
115
|
+
# Specifies a new irregular that applies to both pluralization and
|
116
|
+
# singularization at the same time. This can only be used for strings, not
|
117
|
+
# regular expressions. You simply pass the irregular in singular and
|
118
|
+
# plural form.
|
119
|
+
#
|
120
|
+
# irregular 'octopus', 'octopi'
|
121
|
+
# irregular 'person', 'people'
|
122
|
+
def irregular(singular, plural)
|
123
|
+
@uncountables.delete(singular)
|
124
|
+
@uncountables.delete(plural)
|
125
|
+
|
126
|
+
s0 = singular[0]
|
127
|
+
srest = singular[1..-1]
|
128
|
+
|
129
|
+
p0 = plural[0]
|
130
|
+
prest = plural[1..-1]
|
131
|
+
|
132
|
+
if s0.upcase == p0.upcase
|
133
|
+
plural(/(#{s0})#{srest}$/i, '\1' + prest)
|
134
|
+
plural(/(#{p0})#{prest}$/i, '\1' + prest)
|
135
|
+
|
136
|
+
singular(/(#{s0})#{srest}$/i, '\1' + srest)
|
137
|
+
singular(/(#{p0})#{prest}$/i, '\1' + srest)
|
138
|
+
else
|
139
|
+
plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest)
|
140
|
+
plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest)
|
141
|
+
plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest)
|
142
|
+
plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest)
|
143
|
+
|
144
|
+
singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest)
|
145
|
+
singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest)
|
146
|
+
singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest)
|
147
|
+
singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Add uncountable words that shouldn't be attempted inflected.
|
152
|
+
#
|
153
|
+
# uncountable 'money'
|
154
|
+
# uncountable 'money', 'information'
|
155
|
+
# uncountable %w( money information rice )
|
156
|
+
def uncountable(*words)
|
157
|
+
(@uncountables << words).flatten!
|
158
|
+
end
|
159
|
+
|
160
|
+
# Specifies a humanized form of a string by a regular expression rule or
|
161
|
+
# by a string mapping. When using a regular expression based replacement,
|
162
|
+
# the normal humanize formatting is called after the replacement. When a
|
163
|
+
# string is used, the human form should be specified as desired (example:
|
164
|
+
# 'The name', not 'the_name').
|
165
|
+
#
|
166
|
+
# human /_cnt$/i, '\1_count'
|
167
|
+
# human 'legacy_col_person_name', 'Name'
|
168
|
+
def human(rule, replacement)
|
169
|
+
@humans.prepend([rule, replacement])
|
170
|
+
end
|
171
|
+
|
172
|
+
# Clears the loaded inflections within a given scope (default is
|
173
|
+
# <tt>:all</tt>). Give the scope as a symbol of the inflection type, the
|
174
|
+
# options are: <tt>:plurals</tt>, <tt>:singulars</tt>, <tt>:uncountables</tt>,
|
175
|
+
# <tt>:humans</tt>.
|
176
|
+
#
|
177
|
+
# clear :all
|
178
|
+
# clear :plurals
|
179
|
+
def clear(scope = :all)
|
180
|
+
case scope
|
181
|
+
when :all
|
182
|
+
@plurals, @singulars, @uncountables, @humans = [], [], [], []
|
183
|
+
else
|
184
|
+
instance_variable_set "@#{scope}", []
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Yields a singleton instance of Inflector::Inflections so you can specify
|
190
|
+
# additional inflector rules.
|
191
|
+
#
|
192
|
+
# MotionSupport::Inflector.inflections do |inflect|
|
193
|
+
# inflect.uncountable 'rails'
|
194
|
+
# end
|
195
|
+
def inflections
|
196
|
+
if block_given?
|
197
|
+
yield Inflections.instance
|
198
|
+
else
|
199
|
+
Inflections.instance
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|