motion_blender-support 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
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