motion_blender-support 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +4 -0
  5. data/HACKS.md +7 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +359 -0
  8. data/Rakefile +14 -0
  9. data/app/app_delegate.rb +5 -0
  10. data/examples/Inflector/.gitignore +16 -0
  11. data/examples/Inflector/Gemfile +5 -0
  12. data/examples/Inflector/Rakefile +13 -0
  13. data/examples/Inflector/app/app_delegate.rb +8 -0
  14. data/examples/Inflector/app/inflections.rb +5 -0
  15. data/examples/Inflector/app/inflector_view_controller.rb +109 -0
  16. data/examples/Inflector/app/words_view_controller.rb +101 -0
  17. data/examples/Inflector/resources/Default-568h@2x.png +0 -0
  18. data/examples/Inflector/spec/main_spec.rb +9 -0
  19. data/lib/motion-support/callbacks.rb +8 -0
  20. data/lib/motion-support/concern.rb +4 -0
  21. data/lib/motion-support/core_ext/array.rb +10 -0
  22. data/lib/motion-support/core_ext/class.rb +5 -0
  23. data/lib/motion-support/core_ext/hash.rb +13 -0
  24. data/lib/motion-support/core_ext/integer.rb +6 -0
  25. data/lib/motion-support/core_ext/module.rb +11 -0
  26. data/lib/motion-support/core_ext/numeric.rb +6 -0
  27. data/lib/motion-support/core_ext/object.rb +12 -0
  28. data/lib/motion-support/core_ext/range.rb +5 -0
  29. data/lib/motion-support/core_ext/string.rb +13 -0
  30. data/lib/motion-support/core_ext/time.rb +16 -0
  31. data/lib/motion-support/core_ext.rb +13 -0
  32. data/lib/motion-support/inflector.rb +8 -0
  33. data/lib/motion-support.rb +81 -0
  34. data/motion/_stdlib/array.rb +13 -0
  35. data/motion/_stdlib/cgi.rb +22 -0
  36. data/motion/_stdlib/date.rb +81 -0
  37. data/motion/_stdlib/enumerable.rb +9 -0
  38. data/motion/_stdlib/time.rb +19 -0
  39. data/motion/callbacks.rb +511 -0
  40. data/motion/concern.rb +122 -0
  41. data/motion/core_ext/array/access.rb +28 -0
  42. data/motion/core_ext/array/conversions.rb +86 -0
  43. data/motion/core_ext/array/extract_options.rb +11 -0
  44. data/motion/core_ext/array/grouping.rb +99 -0
  45. data/motion/core_ext/array/prepend_and_append.rb +7 -0
  46. data/motion/core_ext/array/wrap.rb +45 -0
  47. data/motion/core_ext/array.rb +19 -0
  48. data/motion/core_ext/class/attribute.rb +119 -0
  49. data/motion/core_ext/class/attribute_accessors.rb +168 -0
  50. data/motion/core_ext/date/acts_like.rb +8 -0
  51. data/motion/core_ext/date/calculations.rb +117 -0
  52. data/motion/core_ext/date/conversions.rb +56 -0
  53. data/motion/core_ext/date_and_time/calculations.rb +232 -0
  54. data/motion/core_ext/enumerable.rb +90 -0
  55. data/motion/core_ext/hash/deep_delete_if.rb +23 -0
  56. data/motion/core_ext/hash/deep_merge.rb +27 -0
  57. data/motion/core_ext/hash/except.rb +15 -0
  58. data/motion/core_ext/hash/indifferent_access.rb +19 -0
  59. data/motion/core_ext/hash/keys.rb +150 -0
  60. data/motion/core_ext/hash/reverse_merge.rb +22 -0
  61. data/motion/core_ext/hash/slice.rb +40 -0
  62. data/motion/core_ext/integer/inflections.rb +27 -0
  63. data/motion/core_ext/integer/multiple.rb +10 -0
  64. data/motion/core_ext/integer/time.rb +41 -0
  65. data/motion/core_ext/kernel/singleton_class.rb +6 -0
  66. data/motion/core_ext/metaclass.rb +8 -0
  67. data/motion/core_ext/module/aliasing.rb +69 -0
  68. data/motion/core_ext/module/anonymous.rb +19 -0
  69. data/motion/core_ext/module/attr_internal.rb +38 -0
  70. data/motion/core_ext/module/attribute_accessors.rb +64 -0
  71. data/motion/core_ext/module/delegation.rb +175 -0
  72. data/motion/core_ext/module/introspection.rb +60 -0
  73. data/motion/core_ext/module/reachable.rb +5 -0
  74. data/motion/core_ext/module/remove_method.rb +12 -0
  75. data/motion/core_ext/ns_dictionary.rb +14 -0
  76. data/motion/core_ext/ns_string.rb +14 -0
  77. data/motion/core_ext/numeric/bytes.rb +44 -0
  78. data/motion/core_ext/numeric/conversions.rb +49 -0
  79. data/motion/core_ext/numeric/time.rb +75 -0
  80. data/motion/core_ext/object/acts_like.rb +10 -0
  81. data/motion/core_ext/object/blank.rb +105 -0
  82. data/motion/core_ext/object/deep_dup.rb +44 -0
  83. data/motion/core_ext/object/duplicable.rb +87 -0
  84. data/motion/core_ext/object/inclusion.rb +15 -0
  85. data/motion/core_ext/object/instance_variables.rb +28 -0
  86. data/motion/core_ext/object/to_param.rb +58 -0
  87. data/motion/core_ext/object/to_query.rb +26 -0
  88. data/motion/core_ext/object/try.rb +78 -0
  89. data/motion/core_ext/range/include_range.rb +23 -0
  90. data/motion/core_ext/range/overlaps.rb +8 -0
  91. data/motion/core_ext/regexp.rb +5 -0
  92. data/motion/core_ext/string/access.rb +104 -0
  93. data/motion/core_ext/string/behavior.rb +6 -0
  94. data/motion/core_ext/string/exclude.rb +11 -0
  95. data/motion/core_ext/string/filters.rb +55 -0
  96. data/motion/core_ext/string/indent.rb +43 -0
  97. data/motion/core_ext/string/inflections.rb +178 -0
  98. data/motion/core_ext/string/starts_ends_with.rb +4 -0
  99. data/motion/core_ext/string/strip.rb +24 -0
  100. data/motion/core_ext/time/acts_like.rb +8 -0
  101. data/motion/core_ext/time/calculations.rb +215 -0
  102. data/motion/core_ext/time/conversions.rb +52 -0
  103. data/motion/descendants_tracker.rb +50 -0
  104. data/motion/duration.rb +104 -0
  105. data/motion/hash_with_indifferent_access.rb +253 -0
  106. data/motion/inflections.rb +67 -0
  107. data/motion/inflector/inflections.rb +203 -0
  108. data/motion/inflector/methods.rb +321 -0
  109. data/motion/logger.rb +47 -0
  110. data/motion/number_helper.rb +54 -0
  111. data/motion/version.rb +3 -0
  112. data/motion_blender-support.gemspec +21 -0
  113. data/spec/motion-support/_helpers/constantize_test_cases.rb +75 -0
  114. data/spec/motion-support/_helpers/inflector_test_cases.rb +270 -0
  115. data/spec/motion-support/callback_spec.rb +702 -0
  116. data/spec/motion-support/concern_spec.rb +93 -0
  117. data/spec/motion-support/core_ext/array/access_spec.rb +29 -0
  118. data/spec/motion-support/core_ext/array/conversion_spec.rb +60 -0
  119. data/spec/motion-support/core_ext/array/extract_options_spec.rb +15 -0
  120. data/spec/motion-support/core_ext/array/grouping_spec.rb +85 -0
  121. data/spec/motion-support/core_ext/array/prepend_and_append_spec.rb +25 -0
  122. data/spec/motion-support/core_ext/array/wrap_spec.rb +19 -0
  123. data/spec/motion-support/core_ext/array_spec.rb +16 -0
  124. data/spec/motion-support/core_ext/class/attribute_accessor_spec.rb +127 -0
  125. data/spec/motion-support/core_ext/class/attribute_spec.rb +92 -0
  126. data/spec/motion-support/core_ext/date/acts_like_spec.rb +11 -0
  127. data/spec/motion-support/core_ext/date/calculation_spec.rb +186 -0
  128. data/spec/motion-support/core_ext/date/conversion_spec.rb +18 -0
  129. data/spec/motion-support/core_ext/date_and_time/calculation_spec.rb +336 -0
  130. data/spec/motion-support/core_ext/enumerable_spec.rb +130 -0
  131. data/spec/motion-support/core_ext/hash/deep_delete_if_spec.rb +19 -0
  132. data/spec/motion-support/core_ext/hash/deep_merge_spec.rb +32 -0
  133. data/spec/motion-support/core_ext/hash/except_spec.rb +43 -0
  134. data/spec/motion-support/core_ext/hash/key_spec.rb +236 -0
  135. data/spec/motion-support/core_ext/hash/reverse_merge_spec.rb +26 -0
  136. data/spec/motion-support/core_ext/hash/slice_spec.rb +61 -0
  137. data/spec/motion-support/core_ext/integer/inflection_spec.rb +23 -0
  138. data/spec/motion-support/core_ext/integer/multiple_spec.rb +19 -0
  139. data/spec/motion-support/core_ext/kernel/singleton_class_spec.rb +9 -0
  140. data/spec/motion-support/core_ext/metaclass_spec.rb +9 -0
  141. data/spec/motion-support/core_ext/module/aliasing_spec.rb +143 -0
  142. data/spec/motion-support/core_ext/module/anonymous_spec.rb +29 -0
  143. data/spec/motion-support/core_ext/module/attr_internal_spec.rb +104 -0
  144. data/spec/motion-support/core_ext/module/attribute_accessor_spec.rb +86 -0
  145. data/spec/motion-support/core_ext/module/delegation_spec.rb +136 -0
  146. data/spec/motion-support/core_ext/module/introspection_spec.rb +70 -0
  147. data/spec/motion-support/core_ext/module/reachable_spec.rb +61 -0
  148. data/spec/motion-support/core_ext/module/remove_method_spec.rb +25 -0
  149. data/spec/motion-support/core_ext/numeric/bytes_spec.rb +43 -0
  150. data/spec/motion-support/core_ext/numeric/conversions_spec.rb +40 -0
  151. data/spec/motion-support/core_ext/object/acts_like_spec.rb +21 -0
  152. data/spec/motion-support/core_ext/object/blank_spec.rb +54 -0
  153. data/spec/motion-support/core_ext/object/deep_dup_spec.rb +54 -0
  154. data/spec/motion-support/core_ext/object/duplicable_spec.rb +31 -0
  155. data/spec/motion-support/core_ext/object/inclusion_spec.rb +34 -0
  156. data/spec/motion-support/core_ext/object/instance_variable_spec.rb +19 -0
  157. data/spec/motion-support/core_ext/object/to_param_spec.rb +75 -0
  158. data/spec/motion-support/core_ext/object/to_query_spec.rb +37 -0
  159. data/spec/motion-support/core_ext/object/try_spec.rb +92 -0
  160. data/spec/motion-support/core_ext/range/include_range_spec.rb +31 -0
  161. data/spec/motion-support/core_ext/range/overlap_spec.rb +43 -0
  162. data/spec/motion-support/core_ext/regexp_spec.rb +7 -0
  163. data/spec/motion-support/core_ext/string/access_spec.rb +53 -0
  164. data/spec/motion-support/core_ext/string/behavior_spec.rb +7 -0
  165. data/spec/motion-support/core_ext/string/exclude_spec.rb +8 -0
  166. data/spec/motion-support/core_ext/string/filter_spec.rb +49 -0
  167. data/spec/motion-support/core_ext/string/indent_spec.rb +56 -0
  168. data/spec/motion-support/core_ext/string/inflection_spec.rb +142 -0
  169. data/spec/motion-support/core_ext/string/starts_end_with_spec.rb +14 -0
  170. data/spec/motion-support/core_ext/string/strip_spec.rb +34 -0
  171. data/spec/motion-support/core_ext/string_spec.rb +88 -0
  172. data/spec/motion-support/core_ext/time/acts_like_spec.rb +11 -0
  173. data/spec/motion-support/core_ext/time/calculation_spec.rb +201 -0
  174. data/spec/motion-support/core_ext/time/conversion_spec.rb +53 -0
  175. data/spec/motion-support/descendants_tracker_spec.rb +58 -0
  176. data/spec/motion-support/duration_spec.rb +107 -0
  177. data/spec/motion-support/hash_with_indifferent_access_spec.rb +605 -0
  178. data/spec/motion-support/inflector_spec.rb +504 -0
  179. data/spec/motion-support/ns_dictionary_spec.rb +89 -0
  180. data/spec/motion-support/ns_string_spec.rb +182 -0
  181. data/spec/motion-support/number_helper_spec.rb +55 -0
  182. metadata +352 -0
@@ -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