motion-support 0.1.0 → 0.2.0

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 (171) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +1 -1
  3. data/README.md +231 -2
  4. data/Rakefile +3 -3
  5. data/app/app_delegate.rb +0 -2
  6. data/examples/Inflector/.gitignore +16 -0
  7. data/examples/Inflector/Gemfile +5 -0
  8. data/examples/Inflector/Rakefile +13 -0
  9. data/examples/Inflector/app/app_delegate.rb +8 -0
  10. data/examples/Inflector/app/inflections.rb +5 -0
  11. data/examples/Inflector/app/inflector_view_controller.rb +120 -0
  12. data/examples/Inflector/app/words_view_controller.rb +101 -0
  13. data/examples/Inflector/resources/Default-568h@2x.png +0 -0
  14. data/examples/Inflector/spec/main_spec.rb +9 -0
  15. data/lib/motion-support/core_ext/array.rb +13 -0
  16. data/lib/motion-support/core_ext/class.rb +8 -0
  17. data/lib/motion-support/core_ext/hash.rb +16 -0
  18. data/lib/motion-support/core_ext/integer.rb +9 -0
  19. data/lib/motion-support/core_ext/module.rb +14 -0
  20. data/lib/motion-support/core_ext/numeric.rb +8 -0
  21. data/lib/motion-support/core_ext/object.rb +14 -0
  22. data/lib/motion-support/core_ext/range.rb +8 -0
  23. data/lib/motion-support/core_ext/string.rb +14 -0
  24. data/lib/motion-support/core_ext/time.rb +19 -0
  25. data/lib/motion-support/core_ext.rb +3 -0
  26. data/lib/motion-support/inflector.rb +12 -156
  27. data/lib/motion-support.rb +5 -5
  28. data/motion/_stdlib/cgi.rb +22 -0
  29. data/motion/_stdlib/date.rb +77 -0
  30. data/motion/_stdlib/time.rb +19 -0
  31. data/motion/core_ext/array/access.rb +28 -0
  32. data/motion/core_ext/array/conversions.rb +86 -0
  33. data/motion/core_ext/array/extract_options.rb +11 -0
  34. data/motion/core_ext/array/grouping.rb +99 -0
  35. data/motion/core_ext/array/prepend_and_append.rb +7 -0
  36. data/motion/core_ext/array/wrap.rb +45 -0
  37. data/{lib/motion-support → motion/core_ext}/array.rb +0 -4
  38. data/motion/core_ext/class/attribute.rb +119 -0
  39. data/motion/core_ext/class/attribute_accessors.rb +168 -0
  40. data/motion/core_ext/date/acts_like.rb +8 -0
  41. data/motion/core_ext/date/calculations.rb +117 -0
  42. data/motion/core_ext/date/conversions.rb +56 -0
  43. data/motion/core_ext/date_and_time/calculations.rb +232 -0
  44. data/motion/core_ext/enumerable.rb +90 -0
  45. data/motion/core_ext/hash/deep_merge.rb +27 -0
  46. data/motion/core_ext/hash/except.rb +15 -0
  47. data/motion/core_ext/hash/indifferent_access.rb +19 -0
  48. data/motion/core_ext/hash/keys.rb +138 -0
  49. data/motion/core_ext/hash/reverse_merge.rb +22 -0
  50. data/motion/core_ext/hash/slice.rb +40 -0
  51. data/motion/core_ext/integer/inflections.rb +27 -0
  52. data/motion/core_ext/integer/multiple.rb +10 -0
  53. data/motion/core_ext/integer/time.rb +41 -0
  54. data/{lib/motion-support → motion/core_ext}/metaclass.rb +0 -0
  55. data/motion/core_ext/module/aliasing.rb +69 -0
  56. data/motion/core_ext/module/anonymous.rb +19 -0
  57. data/motion/core_ext/module/attr_internal.rb +38 -0
  58. data/motion/core_ext/module/attribute_accessors.rb +64 -0
  59. data/motion/core_ext/module/delegation.rb +175 -0
  60. data/motion/core_ext/module/introspection.rb +60 -0
  61. data/motion/core_ext/module/reachable.rb +5 -0
  62. data/motion/core_ext/module/remove_method.rb +12 -0
  63. data/motion/core_ext/ns_dictionary.rb +11 -0
  64. data/motion/core_ext/numeric/bytes.rb +44 -0
  65. data/motion/core_ext/numeric/conversions.rb +7 -0
  66. data/motion/core_ext/numeric/time.rb +75 -0
  67. data/motion/core_ext/object/acts_like.rb +10 -0
  68. data/motion/core_ext/object/blank.rb +105 -0
  69. data/motion/core_ext/object/deep_dup.rb +44 -0
  70. data/motion/core_ext/object/duplicable.rb +83 -0
  71. data/motion/core_ext/object/instance_variables.rb +28 -0
  72. data/motion/core_ext/object/to_param.rb +58 -0
  73. data/motion/core_ext/object/to_query.rb +26 -0
  74. data/motion/core_ext/object/try.rb +78 -0
  75. data/motion/core_ext/range/include_range.rb +23 -0
  76. data/motion/core_ext/range/overlaps.rb +8 -0
  77. data/motion/core_ext/regexp.rb +5 -0
  78. data/motion/core_ext/string/access.rb +104 -0
  79. data/motion/core_ext/string/behavior.rb +6 -0
  80. data/motion/core_ext/string/exclude.rb +11 -0
  81. data/motion/core_ext/string/filters.rb +55 -0
  82. data/motion/core_ext/string/indent.rb +43 -0
  83. data/motion/core_ext/string/inflections.rb +195 -0
  84. data/motion/core_ext/string/starts_ends_with.rb +4 -0
  85. data/motion/core_ext/string/strip.rb +24 -0
  86. data/motion/core_ext/time/acts_like.rb +8 -0
  87. data/motion/core_ext/time/calculations.rb +215 -0
  88. data/motion/core_ext/time/conversions.rb +52 -0
  89. data/motion/duration.rb +104 -0
  90. data/motion/hash_with_indifferent_access.rb +251 -0
  91. data/motion/inflections.rb +67 -0
  92. data/motion/inflector/inflections.rb +203 -0
  93. data/motion/inflector/methods.rb +321 -0
  94. data/{lib/motion-support → motion}/logger.rb +0 -0
  95. data/{lib/motion-support → motion}/version.rb +1 -1
  96. data/motion-support.gemspec +2 -2
  97. data/spec/motion-support/_helpers/constantize_test_cases.rb +75 -0
  98. data/spec/motion-support/_helpers/inflector_test_cases.rb +313 -0
  99. data/spec/motion-support/core_ext/array/access_spec.rb +29 -0
  100. data/spec/motion-support/core_ext/array/conversion_spec.rb +60 -0
  101. data/spec/motion-support/core_ext/array/extract_options_spec.rb +15 -0
  102. data/spec/motion-support/core_ext/array/grouping_spec.rb +85 -0
  103. data/spec/motion-support/core_ext/array/prepend_and_append_spec.rb +25 -0
  104. data/spec/motion-support/core_ext/array/wrap_spec.rb +19 -0
  105. data/spec/motion-support/{array_spec.rb → core_ext/array_spec.rb} +0 -5
  106. data/spec/motion-support/core_ext/class/attribute_accessor_spec.rb +127 -0
  107. data/spec/motion-support/core_ext/class/attribute_spec.rb +92 -0
  108. data/spec/motion-support/core_ext/date/acts_like_spec.rb +11 -0
  109. data/spec/motion-support/core_ext/date/calculation_spec.rb +186 -0
  110. data/spec/motion-support/core_ext/date/conversion_spec.rb +18 -0
  111. data/spec/motion-support/core_ext/date_and_time/calculation_spec.rb +336 -0
  112. data/spec/motion-support/core_ext/enumerable_spec.rb +130 -0
  113. data/spec/motion-support/core_ext/hash/deep_merge_spec.rb +32 -0
  114. data/spec/motion-support/core_ext/hash/except_spec.rb +43 -0
  115. data/spec/motion-support/core_ext/hash/key_spec.rb +230 -0
  116. data/spec/motion-support/core_ext/hash/reverse_merge_spec.rb +26 -0
  117. data/spec/motion-support/core_ext/hash/slice_spec.rb +61 -0
  118. data/spec/motion-support/core_ext/integer/inflection_spec.rb +23 -0
  119. data/spec/motion-support/core_ext/integer/multiple_spec.rb +19 -0
  120. data/spec/motion-support/{metaclass_spec.rb → core_ext/metaclass_spec.rb} +0 -0
  121. data/spec/motion-support/core_ext/module/aliasing_spec.rb +143 -0
  122. data/spec/motion-support/core_ext/module/anonymous_spec.rb +29 -0
  123. data/spec/motion-support/core_ext/module/attr_internal_spec.rb +104 -0
  124. data/spec/motion-support/core_ext/module/attribute_accessor_spec.rb +86 -0
  125. data/spec/motion-support/core_ext/module/delegation_spec.rb +136 -0
  126. data/spec/motion-support/core_ext/module/introspection_spec.rb +70 -0
  127. data/spec/motion-support/core_ext/module/reachable_spec.rb +61 -0
  128. data/spec/motion-support/core_ext/module/remove_method_spec.rb +25 -0
  129. data/spec/motion-support/core_ext/numeric/bytes_spec.rb +43 -0
  130. data/spec/motion-support/core_ext/object/acts_like_spec.rb +21 -0
  131. data/spec/motion-support/core_ext/object/blank_spec.rb +54 -0
  132. data/spec/motion-support/core_ext/object/deep_dup_spec.rb +54 -0
  133. data/spec/motion-support/core_ext/object/duplicable_spec.rb +31 -0
  134. data/spec/motion-support/core_ext/object/instance_variable_spec.rb +19 -0
  135. data/spec/motion-support/core_ext/object/to_param_spec.rb +75 -0
  136. data/spec/motion-support/core_ext/object/to_query_spec.rb +37 -0
  137. data/spec/motion-support/core_ext/object/try_spec.rb +92 -0
  138. data/spec/motion-support/core_ext/range/include_range_spec.rb +31 -0
  139. data/spec/motion-support/core_ext/range/overlap_spec.rb +43 -0
  140. data/spec/motion-support/core_ext/regexp_spec.rb +7 -0
  141. data/spec/motion-support/core_ext/string/access_spec.rb +53 -0
  142. data/spec/motion-support/core_ext/string/behavior_spec.rb +7 -0
  143. data/spec/motion-support/core_ext/string/exclude_spec.rb +8 -0
  144. data/spec/motion-support/core_ext/string/filter_spec.rb +48 -0
  145. data/spec/motion-support/core_ext/string/indent_spec.rb +56 -0
  146. data/spec/motion-support/core_ext/string/inflection_spec.rb +142 -0
  147. data/spec/motion-support/core_ext/string/starts_end_with_spec.rb +14 -0
  148. data/spec/motion-support/core_ext/string/strip_spec.rb +34 -0
  149. data/spec/motion-support/core_ext/string_spec.rb +88 -0
  150. data/spec/motion-support/core_ext/time/acts_like_spec.rb +11 -0
  151. data/spec/motion-support/core_ext/time/calculation_spec.rb +201 -0
  152. data/spec/motion-support/core_ext/time/conversion_spec.rb +54 -0
  153. data/spec/motion-support/duration_spec.rb +107 -0
  154. data/spec/motion-support/hash_with_indifferent_access_spec.rb +605 -0
  155. data/spec/motion-support/inflector_spec.rb +474 -35
  156. data/spec/motion-support/ns_dictionary_spec.rb +29 -0
  157. metadata +212 -35
  158. data/lib/motion-support/cattr_accessor.rb +0 -19
  159. data/lib/motion-support/class_inheritable_accessor.rb +0 -23
  160. data/lib/motion-support/class_inheritable_array.rb +0 -29
  161. data/lib/motion-support/hash.rb +0 -31
  162. data/lib/motion-support/nilclass.rb +0 -5
  163. data/lib/motion-support/object.rb +0 -17
  164. data/lib/motion-support/string.rb +0 -71
  165. data/spec/motion-support/cattr_accessor_spec.rb +0 -49
  166. data/spec/motion-support/class_inheritable_accessor_spec.rb +0 -49
  167. data/spec/motion-support/class_inheritable_array_spec.rb +0 -61
  168. data/spec/motion-support/hash_spec.rb +0 -31
  169. data/spec/motion-support/nilclass_spec.rb +0 -5
  170. data/spec/motion-support/object_spec.rb +0 -43
  171. data/spec/motion-support/string_spec.rb +0 -145
@@ -0,0 +1,215 @@
1
+ motion_require "../date_and_time/calculations"
2
+
3
+ class Time
4
+ include DateAndTime::Calculations
5
+
6
+ COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
7
+
8
+ class << self
9
+ # Return the number of days in the given month.
10
+ # If no year is specified, it will use the current year.
11
+ def days_in_month(month, year = now.year)
12
+ if month == 2 && ::Date.gregorian_leap?(year)
13
+ 29
14
+ else
15
+ COMMON_YEAR_DAYS_IN_MONTH[month]
16
+ end
17
+ end
18
+
19
+ # Alias for <tt>Time.now</tt>.
20
+ def current
21
+ ::Time.now
22
+ end
23
+ end
24
+
25
+ # Seconds since midnight: Time.now.seconds_since_midnight
26
+ def seconds_since_midnight
27
+ to_i - change(:hour => 0).to_i + (usec / 1.0e+6)
28
+ end
29
+
30
+ # Returns the number of seconds until 23:59:59.
31
+ #
32
+ # Time.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399
33
+ # Time.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103
34
+ # Time.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0
35
+ def seconds_until_end_of_day
36
+ end_of_day.to_i - to_i
37
+ end
38
+
39
+ # Returns a new Time where one or more of the elements have been changed according
40
+ # to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
41
+ # <tt>:sec</tt>, <tt>:usec</tt>) reset cascadingly, so if only the hour is passed,
42
+ # then minute, sec, and usec is set to 0. If the hour and minute is passed, then
43
+ # sec and usec is set to 0. The +options+ parameter takes a hash with any of these
44
+ # keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>,
45
+ # <tt>:sec</tt>, <tt>:usec</tt>.
46
+ #
47
+ # Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
48
+ # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
49
+ # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
50
+ def change(options)
51
+ new_year = options.fetch(:year, year)
52
+ new_month = options.fetch(:month, month)
53
+ new_day = options.fetch(:day, day)
54
+ new_hour = options.fetch(:hour, hour)
55
+ new_min = options.fetch(:min, options[:hour] ? 0 : min)
56
+ new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
57
+ new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
58
+
59
+ if utc?
60
+ ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
61
+ elsif zone
62
+ ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
63
+ else
64
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
65
+ end
66
+ end
67
+
68
+ # Uses Date to provide precise Time calculations for years, months, and days.
69
+ # The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>,
70
+ # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
71
+ # <tt>:minutes</tt>, <tt>:seconds</tt>.
72
+ def advance(options)
73
+ unless options[:weeks].nil?
74
+ options[:weeks], partial_weeks = options[:weeks].divmod(1)
75
+ options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
76
+ end
77
+
78
+ unless options[:days].nil?
79
+ options[:days], partial_days = options[:days].divmod(1)
80
+ options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
81
+ end
82
+
83
+ d = to_date.advance(options)
84
+ time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
85
+ seconds_to_advance = \
86
+ options.fetch(:seconds, 0) +
87
+ options.fetch(:minutes, 0) * 60 +
88
+ options.fetch(:hours, 0) * 3600
89
+
90
+ if seconds_to_advance.zero?
91
+ time_advanced_by_date
92
+ else
93
+ time_advanced_by_date.since(seconds_to_advance)
94
+ end
95
+ end
96
+
97
+ # Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
98
+ def ago(seconds)
99
+ since(-seconds)
100
+ end
101
+
102
+ # Returns a new Time representing the time a number of seconds since the instance time
103
+ def since(seconds)
104
+ self + seconds
105
+ rescue
106
+ to_datetime.since(seconds)
107
+ end
108
+ alias :in :since
109
+
110
+ # Returns a new Time representing the start of the day (0:00)
111
+ def beginning_of_day
112
+ #(self - seconds_since_midnight).change(usec: 0)
113
+ change(:hour => 0)
114
+ end
115
+ alias :midnight :beginning_of_day
116
+ alias :at_midnight :beginning_of_day
117
+ alias :at_beginning_of_day :beginning_of_day
118
+
119
+ # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
120
+ def end_of_day
121
+ change(
122
+ :hour => 23,
123
+ :min => 59,
124
+ :sec => 59,
125
+ :usec => Rational(999999999, 1000)
126
+ )
127
+ end
128
+ alias :at_end_of_day :end_of_day
129
+
130
+ # Returns a new Time representing the start of the hour (x:00)
131
+ def beginning_of_hour
132
+ change(:min => 0)
133
+ end
134
+ alias :at_beginning_of_hour :beginning_of_hour
135
+
136
+ # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
137
+ def end_of_hour
138
+ change(
139
+ :min => 59,
140
+ :sec => 59,
141
+ :usec => Rational(999999999, 1000)
142
+ )
143
+ end
144
+ alias :at_end_of_hour :end_of_hour
145
+
146
+ # Returns a new Time representing the start of the minute (x:xx:00)
147
+ def beginning_of_minute
148
+ change(:sec => 0)
149
+ end
150
+ alias :at_beginning_of_minute :beginning_of_minute
151
+
152
+ # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9)
153
+ def end_of_minute
154
+ change(
155
+ :sec => 59,
156
+ :usec => Rational(999999999, 1000)
157
+ )
158
+ end
159
+ alias :at_end_of_minute :end_of_minute
160
+
161
+ # Returns a Range representing the whole day of the current time.
162
+ def all_day
163
+ beginning_of_day..end_of_day
164
+ end
165
+
166
+ # Returns a Range representing the whole week of the current time.
167
+ # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
168
+ def all_week(start_day = Date.beginning_of_week)
169
+ beginning_of_week(start_day)..end_of_week(start_day)
170
+ end
171
+
172
+ # Returns a Range representing the whole month of the current time.
173
+ def all_month
174
+ beginning_of_month..end_of_month
175
+ end
176
+
177
+ # Returns a Range representing the whole quarter of the current time.
178
+ def all_quarter
179
+ beginning_of_quarter..end_of_quarter
180
+ end
181
+
182
+ # Returns a Range representing the whole year of the current time.
183
+ def all_year
184
+ beginning_of_year..end_of_year
185
+ end
186
+
187
+ def plus_with_duration(other) #:nodoc:
188
+ if MotionSupport::Duration === other
189
+ other.since(self)
190
+ else
191
+ plus_without_duration(other)
192
+ end
193
+ end
194
+ alias_method :plus_without_duration, :+
195
+ alias_method :+, :plus_with_duration
196
+
197
+ def minus_with_duration(other) #:nodoc:
198
+ if MotionSupport::Duration === other
199
+ other.until(self)
200
+ else
201
+ minus_without_duration(other)
202
+ end
203
+ end
204
+ alias_method :minus_without_duration, :-
205
+ alias_method :-, :minus_with_duration
206
+
207
+ # Layers additional behavior on Time#<=> so that Date instances
208
+ # can be chronologically compared with a Time
209
+ def compare_with_coercion(other)
210
+ compare_without_coercion(other.to_time)
211
+ end
212
+ alias_method :compare_without_coercion, :<=>
213
+ alias_method :<=>, :compare_with_coercion
214
+
215
+ end
@@ -0,0 +1,52 @@
1
+ class Time
2
+ DATE_FORMATS = {
3
+ :db => '%Y-%m-%d %H:%M:%S',
4
+ :number => '%Y%m%d%H%M%S',
5
+ :nsec => '%Y%m%d%H%M%S%9N',
6
+ :time => '%H:%M',
7
+ :short => '%d %b %H:%M',
8
+ :long => '%B %d, %Y %H:%M',
9
+ :long_ordinal => lambda { |time|
10
+ day_format = MotionSupport::Inflector.ordinalize(time.day)
11
+ time.strftime("%B #{day_format}, %Y %H:%M")
12
+ },
13
+ :rfc822 => lambda { |time|
14
+ offset_format = time.formatted_offset(false)
15
+ time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
16
+ }
17
+ }
18
+
19
+ # Converts to a formatted string. See DATE_FORMATS for builtin formats.
20
+ #
21
+ # This method is aliased to <tt>to_s</tt>.
22
+ #
23
+ # time = Time.now # => Thu Jan 18 06:10:17 CST 2007
24
+ #
25
+ # time.to_formatted_s(:time) # => "06:10"
26
+ # time.to_s(:time) # => "06:10"
27
+ #
28
+ # time.to_formatted_s(:db) # => "2007-01-18 06:10:17"
29
+ # time.to_formatted_s(:number) # => "20070118061017"
30
+ # time.to_formatted_s(:short) # => "18 Jan 06:10"
31
+ # time.to_formatted_s(:long) # => "January 18, 2007 06:10"
32
+ # time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10"
33
+ # time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
34
+ #
35
+ # == Adding your own time formats to +to_formatted_s+
36
+ # You can add your own formats to the Time::DATE_FORMATS hash.
37
+ # Use the format name as the hash key and either a strftime string
38
+ # or Proc instance that takes a time argument as the value.
39
+ #
40
+ # # config/initializers/time_formats.rb
41
+ # Time::DATE_FORMATS[:month_and_year] = '%B %Y'
42
+ # Time::DATE_FORMATS[:short_ordinal] = ->(time) { time.strftime("%B #{time.day.ordinalize}") }
43
+ def to_formatted_s(format = :default)
44
+ if formatter = DATE_FORMATS[format]
45
+ formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
46
+ else
47
+ to_default_s
48
+ end
49
+ end
50
+ alias_method :to_default_s, :to_s
51
+ alias_method :to_s, :to_formatted_s
52
+ end
@@ -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,251 @@
1
+ module MotionSupport
2
+ # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
3
+ # to be the same.
4
+ #
5
+ # rgb = MotionSupport::HashWithIndifferentAccess.new
6
+ #
7
+ # rgb[:black] = '#000000'
8
+ # rgb[:black] # => '#000000'
9
+ # rgb['black'] # => '#000000'
10
+ #
11
+ # rgb['white'] = '#FFFFFF'
12
+ # rgb[:white] # => '#FFFFFF'
13
+ # rgb['white'] # => '#FFFFFF'
14
+ #
15
+ # Internally symbols are mapped to strings when used as keys in the entire
16
+ # writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This
17
+ # mapping belongs to the public interface. For example, given:
18
+ #
19
+ # hash = MotionSupport::HashWithIndifferentAccess.new(a: 1)
20
+ #
21
+ # You are guaranteed that the key is returned as a string:
22
+ #
23
+ # hash.keys # => ["a"]
24
+ #
25
+ # Technically other types of keys are accepted:
26
+ #
27
+ # hash = MotionSupport::HashWithIndifferentAccess.new(a: 1)
28
+ # hash[0] = 0
29
+ # hash # => {"a"=>1, 0=>0}
30
+ #
31
+ # but this class is intended for use cases where strings or symbols are the
32
+ # expected keys and it is convenient to understand both as the same. For
33
+ # example the +params+ hash in Ruby on Rails.
34
+ #
35
+ # Note that core extensions define <tt>Hash#with_indifferent_access</tt>:
36
+ #
37
+ # rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access
38
+ #
39
+ # which may be handy.
40
+ class HashWithIndifferentAccess < Hash
41
+ # Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
42
+ # this class.
43
+ def extractable_options?
44
+ true
45
+ end
46
+
47
+ def with_indifferent_access
48
+ dup
49
+ end
50
+
51
+ def nested_under_indifferent_access
52
+ self
53
+ end
54
+
55
+ def initialize(constructor = {})
56
+ if constructor.is_a?(Hash)
57
+ super()
58
+ update(constructor)
59
+ else
60
+ super(constructor)
61
+ end
62
+ end
63
+
64
+ def default(key = nil)
65
+ if key.is_a?(Symbol) && include?(key = key.to_s)
66
+ self[key]
67
+ else
68
+ super
69
+ end
70
+ end
71
+
72
+ def self.new_from_hash_copying_default(hash)
73
+ new(hash).tap do |new_hash|
74
+ new_hash.default = hash.default
75
+ end
76
+ end
77
+
78
+ def self.[](*args)
79
+ new.merge!(Hash[*args])
80
+ end
81
+
82
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
83
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
84
+
85
+ # Assigns a new value to the hash:
86
+ #
87
+ # hash = MotionSupport::HashWithIndifferentAccess.new
88
+ # hash[:key] = 'value'
89
+ #
90
+ # This value can be later fetched using either +:key+ or +'key'+.
91
+ def []=(key, value)
92
+ regular_writer(convert_key(key), convert_value(value))
93
+ end
94
+
95
+ alias_method :store, :[]=
96
+
97
+ # Updates the receiver in-place, merging in the hash passed as argument:
98
+ #
99
+ # hash_1 = MotionSupport::HashWithIndifferentAccess.new
100
+ # hash_1[:key] = 'value'
101
+ #
102
+ # hash_2 = MotionSupport::HashWithIndifferentAccess.new
103
+ # hash_2[:key] = 'New Value!'
104
+ #
105
+ # hash_1.update(hash_2) # => {"key"=>"New Value!"}
106
+ #
107
+ # The argument can be either an
108
+ # <tt>MotionSupport::HashWithIndifferentAccess</tt> or a regular +Hash+.
109
+ # In either case the merge respects the semantics of indifferent access.
110
+ #
111
+ # If the argument is a regular hash with keys +:key+ and +"key"+ only one
112
+ # of the values end up in the receiver, but which one is unspecified.
113
+ #
114
+ # When given a block, the value for duplicated keys will be determined
115
+ # by the result of invoking the block with the duplicated key, the value
116
+ # in the receiver, and the value in +other_hash+. The rules for duplicated
117
+ # keys follow the semantics of indifferent access:
118
+ #
119
+ # hash_1[:key] = 10
120
+ # hash_2['key'] = 12
121
+ # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22}
122
+ def update(other_hash)
123
+ if other_hash.is_a? HashWithIndifferentAccess
124
+ super(other_hash)
125
+ else
126
+ other_hash.each_pair do |key, value|
127
+ if block_given? && key?(key)
128
+ value = yield(convert_key(key), self[key], value)
129
+ end
130
+ regular_writer(convert_key(key), convert_value(value))
131
+ end
132
+ self
133
+ end
134
+ end
135
+
136
+ alias_method :merge!, :update
137
+
138
+ # Checks the hash for a key matching the argument passed in:
139
+ #
140
+ # hash = MotionSupport::HashWithIndifferentAccess.new
141
+ # hash['key'] = 'value'
142
+ # hash.key?(:key) # => true
143
+ # hash.key?('key') # => true
144
+ def key?(key)
145
+ super(convert_key(key))
146
+ end
147
+
148
+ alias_method :include?, :key?
149
+ alias_method :has_key?, :key?
150
+ alias_method :member?, :key?
151
+
152
+ # Same as <tt>Hash#fetch</tt> where the key passed as argument can be
153
+ # either a string or a symbol:
154
+ #
155
+ # counters = MotionSupport::HashWithIndifferentAccess.new
156
+ # counters[:foo] = 1
157
+ #
158
+ # counters.fetch('foo') # => 1
159
+ # counters.fetch(:bar, 0) # => 0
160
+ # counters.fetch(:bar) {|key| 0} # => 0
161
+ # counters.fetch(:zoo) # => KeyError: key not found: "zoo"
162
+ def fetch(key, *extras)
163
+ super(convert_key(key), *extras)
164
+ end
165
+
166
+ # Returns an array of the values at the specified indices:
167
+ #
168
+ # hash = MotionSupport::HashWithIndifferentAccess.new
169
+ # hash[:a] = 'x'
170
+ # hash[:b] = 'y'
171
+ # hash.values_at('a', 'b') # => ["x", "y"]
172
+ def values_at(*indices)
173
+ indices.collect {|key| self[convert_key(key)]}
174
+ end
175
+
176
+ # Returns an exact copy of the hash.
177
+ def dup
178
+ self.class.new(self).tap do |new_hash|
179
+ new_hash.default = default
180
+ end
181
+ end
182
+
183
+ # This method has the same semantics of +update+, except it does not
184
+ # modify the receiver but rather returns a new hash with indifferent
185
+ # access with the result of the merge.
186
+ def merge(hash, &block)
187
+ self.dup.update(hash, &block)
188
+ end
189
+
190
+ # Like +merge+ but the other way around: Merges the receiver into the
191
+ # argument and returns a new hash with indifferent access as result:
192
+ #
193
+ # hash = MotionSupport::HashWithIndifferentAccess.new
194
+ # hash['a'] = nil
195
+ # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
196
+ def reverse_merge(other_hash)
197
+ super(self.class.new_from_hash_copying_default(other_hash))
198
+ end
199
+
200
+ # Same semantics as +reverse_merge+ but modifies the receiver in-place.
201
+ def reverse_merge!(other_hash)
202
+ replace(reverse_merge( other_hash ))
203
+ end
204
+
205
+ # Replaces the contents of this hash with other_hash.
206
+ #
207
+ # h = { "a" => 100, "b" => 200 }
208
+ # h.replace({ "c" => 300, "d" => 400 }) #=> {"c"=>300, "d"=>400}
209
+ def replace(other_hash)
210
+ super(self.class.new_from_hash_copying_default(other_hash))
211
+ end
212
+
213
+ # Removes the specified key from the hash.
214
+ def delete(key)
215
+ super(convert_key(key))
216
+ end
217
+
218
+ def stringify_keys!; self end
219
+ def deep_stringify_keys!; self end
220
+ def stringify_keys; dup end
221
+ def deep_stringify_keys; dup end
222
+ undef :symbolize_keys!
223
+ undef :deep_symbolize_keys!
224
+ def symbolize_keys; to_hash.symbolize_keys end
225
+ def deep_symbolize_keys; to_hash.deep_symbolize_keys end
226
+ def to_options!; self end
227
+
228
+ # Convert to a regular hash with string keys.
229
+ def to_hash
230
+ Hash.new(default).merge!(self)
231
+ end
232
+
233
+ protected
234
+ def convert_key(key)
235
+ key.kind_of?(Symbol) ? key.to_s : key
236
+ end
237
+
238
+ def convert_value(value)
239
+ if value.is_a? Hash
240
+ value.nested_under_indifferent_access
241
+ elsif value.is_a?(Array)
242
+ value = value.dup if value.frozen?
243
+ value.map! { |e| convert_value(e) }
244
+ else
245
+ value
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ HashWithIndifferentAccess = MotionSupport::HashWithIndifferentAccess
@@ -0,0 +1,67 @@
1
+ motion_require '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