motion-support 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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,58 @@
1
+ class Object
2
+ # Alias of <tt>to_s</tt>.
3
+ def to_param
4
+ to_s
5
+ end
6
+ end
7
+
8
+ class NilClass
9
+ # Returns +self+.
10
+ def to_param
11
+ self
12
+ end
13
+ end
14
+
15
+ class TrueClass
16
+ # Returns +self+.
17
+ def to_param
18
+ self
19
+ end
20
+ end
21
+
22
+ class FalseClass
23
+ # Returns +self+.
24
+ def to_param
25
+ self
26
+ end
27
+ end
28
+
29
+ class Array
30
+ # Calls <tt>to_param</tt> on all its elements and joins the result with
31
+ # slashes. This is used by <tt>url_for</tt> in Action Pack.
32
+ def to_param
33
+ collect { |e| e.to_param }.join '/'
34
+ end
35
+ end
36
+
37
+ class Hash
38
+ # Returns a string representation of the receiver suitable for use as a URL
39
+ # query string:
40
+ #
41
+ # {name: 'David', nationality: 'Danish'}.to_param
42
+ # # => "name=David&nationality=Danish"
43
+ #
44
+ # An optional namespace can be passed to enclose the param names:
45
+ #
46
+ # {name: 'David', nationality: 'Danish'}.to_param('user')
47
+ # # => "user[name]=David&user[nationality]=Danish"
48
+ #
49
+ # The string pairs "key=value" that conform the query string
50
+ # are sorted lexicographically in ascending order.
51
+ #
52
+ # This method is also aliased as +to_query+.
53
+ def to_param(namespace = nil)
54
+ collect do |key, value|
55
+ value.to_query(namespace ? "#{namespace}[#{key}]" : key)
56
+ end.sort * '&'
57
+ end
58
+ end
@@ -0,0 +1,26 @@
1
+ motion_require 'to_param'
2
+
3
+ class Object
4
+ # Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the
5
+ # param name.
6
+ #
7
+ # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work.
8
+ def to_query(key)
9
+ "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
10
+ end
11
+ end
12
+
13
+ class Array
14
+ # Converts an array into a string suitable for use as a URL query string,
15
+ # using the given +key+ as the param name.
16
+ #
17
+ # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
18
+ def to_query(key)
19
+ prefix = "#{key}[]"
20
+ collect { |value| value.to_query(prefix) }.join '&'
21
+ end
22
+ end
23
+
24
+ class Hash
25
+ alias_method :to_query, :to_param
26
+ end
@@ -0,0 +1,78 @@
1
+ class Object
2
+ # Invokes the public method whose name goes as first argument just like
3
+ # +public_send+ does, except that if the receiver does not respond to it the
4
+ # call returns +nil+ rather than raising an exception.
5
+ #
6
+ # This method is defined to be able to write
7
+ #
8
+ # @person.try(:name)
9
+ #
10
+ # instead of
11
+ #
12
+ # @person ? @person.name : nil
13
+ #
14
+ # +try+ returns +nil+ when called on +nil+ regardless of whether it responds
15
+ # to the method:
16
+ #
17
+ # nil.try(:to_i) # => nil, rather than 0
18
+ #
19
+ # Arguments and blocks are forwarded to the method if invoked:
20
+ #
21
+ # @posts.try(:each_slice, 2) do |a, b|
22
+ # ...
23
+ # end
24
+ #
25
+ # The number of arguments in the signature must match. If the object responds
26
+ # to the method the call is attempted and +ArgumentError+ is still raised
27
+ # otherwise.
28
+ #
29
+ # If +try+ is called without arguments it yields the receiver to a given
30
+ # block unless it is +nil+:
31
+ #
32
+ # @person.try do |p|
33
+ # ...
34
+ # end
35
+ #
36
+ # Please also note that +try+ is defined on +Object+, therefore it won't work
37
+ # with instances of classes that do not have +Object+ among their ancestors,
38
+ # like direct subclasses of +BasicObject+. For example, using +try+ with
39
+ # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
40
+ # delegator itself.
41
+ def try(*a, &b)
42
+ if a.empty? && block_given?
43
+ yield self
44
+ else
45
+ public_send(*a, &b) if respond_to?(a.first)
46
+ end
47
+ end
48
+
49
+ # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and
50
+ # does not implemented the tried method.
51
+ def try!(*a, &b)
52
+ if a.empty? && block_given?
53
+ yield self
54
+ else
55
+ public_send(*a, &b)
56
+ end
57
+ end
58
+ end
59
+
60
+ class NilClass
61
+ # Calling +try+ on +nil+ always returns +nil+.
62
+ # It becomes specially helpful when navigating through associations that may return +nil+.
63
+ #
64
+ # nil.try(:name) # => nil
65
+ #
66
+ # Without +try+
67
+ # @person && !@person.children.blank? && @person.children.first.name
68
+ #
69
+ # With +try+
70
+ # @person.try(:children).try(:first).try(:name)
71
+ def try(*args)
72
+ nil
73
+ end
74
+
75
+ def try!(*args)
76
+ nil
77
+ end
78
+ end
@@ -0,0 +1,23 @@
1
+ motion_require "../module/aliasing"
2
+
3
+ class Range
4
+ # Extends the default Range#include? to support range comparisons.
5
+ # (1..5).include?(1..5) # => true
6
+ # (1..5).include?(2..3) # => true
7
+ # (1..5).include?(2..6) # => false
8
+ #
9
+ # The native Range#include? behavior is untouched.
10
+ # ('a'..'f').include?('c') # => true
11
+ # (5..9).include?(11) # => false
12
+ def include_with_range?(value)
13
+ if value.is_a?(::Range)
14
+ # 1...10 includes 1..9 but it does not include 1..10.
15
+ operator = exclude_end? && !value.exclude_end? ? :< : :<=
16
+ include_without_range?(value.first) && value.last.send(operator, last)
17
+ else
18
+ include_without_range?(value)
19
+ end
20
+ end
21
+
22
+ alias_method_chain :include?, :range
23
+ end
@@ -0,0 +1,8 @@
1
+ class Range
2
+ # Compare two ranges and see if they overlap each other
3
+ # (1..5).overlaps?(4..6) # => true
4
+ # (1..5).overlaps?(7..9) # => false
5
+ def overlaps?(other)
6
+ cover?(other.first) || other.cover?(first)
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class Regexp #:nodoc:
2
+ def multiline?
3
+ options & MULTILINE == MULTILINE
4
+ end
5
+ end
@@ -0,0 +1,104 @@
1
+ class String
2
+ # If you pass a single Fixnum, returns a substring of one character at that
3
+ # position. The first character of the string is at position 0, the next at
4
+ # position 1, and so on. If a range is supplied, a substring containing
5
+ # characters at offsets given by the range is returned. In both cases, if an
6
+ # offset is negative, it is counted from the end of the string. Returns nil
7
+ # if the initial offset falls outside the string. Returns an empty string if
8
+ # the beginning of the range is greater than the end of the string.
9
+ #
10
+ # str = "hello"
11
+ # str.at(0) #=> "h"
12
+ # str.at(1..3) #=> "ell"
13
+ # str.at(-2) #=> "l"
14
+ # str.at(-2..-1) #=> "lo"
15
+ # str.at(5) #=> nil
16
+ # str.at(5..-1) #=> ""
17
+ #
18
+ # If a Regexp is given, the matching portion of the string is returned.
19
+ # If a String is given, that given string is returned if it occurs in
20
+ # the string. In both cases, nil is returned if there is no match.
21
+ #
22
+ # str = "hello"
23
+ # str.at(/lo/) #=> "lo"
24
+ # str.at(/ol/) #=> nil
25
+ # str.at("lo") #=> "lo"
26
+ # str.at("ol") #=> nil
27
+ def at(position)
28
+ self[position]
29
+ end
30
+
31
+ # Returns a substring from the given position to the end of the string.
32
+ # If the position is negative, it is counted from the end of the string.
33
+ #
34
+ # str = "hello"
35
+ # str.from(0) #=> "hello"
36
+ # str.from(3) #=> "lo"
37
+ # str.from(-2) #=> "lo"
38
+ #
39
+ # You can mix it with +to+ method and do fun things like:
40
+ #
41
+ # str = "hello"
42
+ # str.from(0).to(-1) #=> "hello"
43
+ # str.from(1).to(-2) #=> "ell"
44
+ def from(position)
45
+ self[position..-1]
46
+ end
47
+
48
+ # Returns a substring from the beginning of the string to the given position.
49
+ # If the position is negative, it is counted from the end of the string.
50
+ #
51
+ # str = "hello"
52
+ # str.to(0) #=> "h"
53
+ # str.to(3) #=> "hell"
54
+ # str.to(-2) #=> "hell"
55
+ #
56
+ # You can mix it with +from+ method and do fun things like:
57
+ #
58
+ # str = "hello"
59
+ # str.from(0).to(-1) #=> "hello"
60
+ # str.from(1).to(-2) #=> "ell"
61
+ def to(position)
62
+ self[0..position]
63
+ end
64
+
65
+ # Returns the first character. If a limit is supplied, returns a substring
66
+ # from the beginning of the string until it reaches the limit value. If the
67
+ # given limit is greater than or equal to the string length, returns self.
68
+ #
69
+ # str = "hello"
70
+ # str.first #=> "h"
71
+ # str.first(1) #=> "h"
72
+ # str.first(2) #=> "he"
73
+ # str.first(0) #=> ""
74
+ # str.first(6) #=> "hello"
75
+ def first(limit = 1)
76
+ if limit == 0
77
+ ''
78
+ elsif limit >= size
79
+ self
80
+ else
81
+ to(limit - 1)
82
+ end
83
+ end
84
+
85
+ # Returns the last character of the string. If a limit is supplied, returns a substring
86
+ # from the end of the string until it reaches the limit value (counting backwards). If
87
+ # the given limit is greater than or equal to the string length, returns self.
88
+ #
89
+ # str = "hello"
90
+ # str.last #=> "o"
91
+ # str.last(1) #=> "o"
92
+ # str.last(2) #=> "lo"
93
+ # str.last(0) #=> ""
94
+ # str.last(6) #=> "hello"
95
+ def last(limit = 1)
96
+ if limit == 0
97
+ ''
98
+ elsif limit >= size
99
+ self
100
+ else
101
+ from(-limit)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,6 @@
1
+ class String
2
+ # Enable more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
3
+ def acts_like_string?
4
+ true
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ class String
2
+ # The inverse of <tt>String#include?</tt>. Returns true if the string
3
+ # does not include the other string.
4
+ #
5
+ # "hello".exclude? "lo" #=> false
6
+ # "hello".exclude? "ol" #=> true
7
+ # "hello".exclude? ?h #=> false
8
+ def exclude?(string)
9
+ !include?(string)
10
+ end
11
+ end
@@ -0,0 +1,55 @@
1
+ class String
2
+ # Returns the string, first removing all whitespace on both ends of
3
+ # the string, and then changing remaining consecutive whitespace
4
+ # groups into one space each.
5
+ #
6
+ # Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E).
7
+ #
8
+ # %{ Multi-line
9
+ # string }.squish # => "Multi-line string"
10
+ # " foo bar \n \t boo".squish # => "foo bar boo"
11
+ def squish
12
+ dup.squish!
13
+ end
14
+
15
+ # Performs a destructive squish. See String#squish.
16
+ def squish!
17
+ gsub!(/\A[[:space:]]+/, '')
18
+ gsub!(/[[:space:]]+\z/, '')
19
+ gsub!(/[[:space:]]+/, ' ')
20
+ self
21
+ end
22
+
23
+ # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
24
+ #
25
+ # 'Once upon a time in a world far far away'.truncate(27)
26
+ # # => "Once upon a time in a wo..."
27
+ #
28
+ # Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break:
29
+ #
30
+ # 'Once upon a time in a world far far away'.truncate(27, separator: ' ')
31
+ # # => "Once upon a time in a..."
32
+ #
33
+ # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
34
+ # # => "Once upon a time in a..."
35
+ #
36
+ # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
37
+ # for a total length not exceeding <tt>length</tt>:
38
+ #
39
+ # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
40
+ # # => "And they f... (continued)"
41
+ def truncate(truncate_at, options = {})
42
+ return dup unless length > truncate_at
43
+
44
+ options[:omission] ||= '...'
45
+ length_with_room_for_omission = truncate_at - options[:omission].length
46
+ stop = \
47
+ if options[:separator]
48
+ rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
49
+ else
50
+ length_with_room_for_omission
51
+ end
52
+
53
+ self[0...stop] + options[:omission]
54
+ end
55
+ end
@@ -0,0 +1,43 @@
1
+ class String
2
+ # Same as +indent+, except it indents the receiver in-place.
3
+ #
4
+ # Returns the indented string, or +nil+ if there was nothing to indent.
5
+ def indent!(amount, indent_string=nil, indent_empty_lines=false)
6
+ indent_string = indent_string || self[/^[ \t]/] || ' '
7
+ re = indent_empty_lines ? /^/ : /^(?!$)/
8
+ gsub!(re, indent_string * amount)
9
+ end
10
+
11
+ # Indents the lines in the receiver:
12
+ #
13
+ # <<EOS.indent(2)
14
+ # def some_method
15
+ # some_code
16
+ # end
17
+ # EOS
18
+ # # =>
19
+ # def some_method
20
+ # some_code
21
+ # end
22
+ #
23
+ # The second argument, +indent_string+, specifies which indent string to
24
+ # use. The default is +nil+, which tells the method to make a guess by
25
+ # peeking at the first indented line, and fallback to a space if there is
26
+ # none.
27
+ #
28
+ # " foo".indent(2) # => " foo"
29
+ # "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
30
+ # "foo".indent(2, "\t") # => "\t\tfoo"
31
+ #
32
+ # While +indent_string+ is typically one space or tab, it may be any string.
33
+ #
34
+ # The third argument, +indent_empty_lines+, is a flag that says whether
35
+ # empty lines should be indented. Default is false.
36
+ #
37
+ # "foo\n\nbar".indent(2) # => " foo\n\n bar"
38
+ # "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar"
39
+ #
40
+ def indent(amount, indent_string=nil, indent_empty_lines=false)
41
+ dup.tap {|_| _.indent!(amount, indent_string, indent_empty_lines)}
42
+ end
43
+ end
@@ -0,0 +1,195 @@
1
+ # String inflections define new methods on the String class to transform names for different purposes.
2
+ # For instance, you can figure out the name of a table from the name of a class.
3
+ #
4
+ # 'ScaleScore'.tableize # => "scale_scores"
5
+ #
6
+ class String
7
+ # Returns the plural form of the word in the string.
8
+ #
9
+ # If the optional parameter +count+ is specified,
10
+ # the singular form will be returned if <tt>count == 1</tt>.
11
+ # For any other value of +count+ the plural will be returned.
12
+ #
13
+ # 'post'.pluralize # => "posts"
14
+ # 'octopus'.pluralize # => "octopi"
15
+ # 'sheep'.pluralize # => "sheep"
16
+ # 'words'.pluralize # => "words"
17
+ # 'the blue mailman'.pluralize # => "the blue mailmen"
18
+ # 'CamelOctopus'.pluralize # => "CamelOctopi"
19
+ # 'apple'.pluralize(1) # => "apple"
20
+ # 'apple'.pluralize(2) # => "apples"
21
+ def pluralize(count = nil)
22
+ if count == 1
23
+ self
24
+ else
25
+ MotionSupport::Inflector.pluralize(self)
26
+ end
27
+ end
28
+
29
+ # The reverse of +pluralize+, returns the singular form of a word in a string.
30
+ #
31
+ # 'posts'.singularize # => "post"
32
+ # 'octopi'.singularize # => "octopus"
33
+ # 'sheep'.singularize # => "sheep"
34
+ # 'word'.singularize # => "word"
35
+ # 'the blue mailmen'.singularize # => "the blue mailman"
36
+ # 'CamelOctopi'.singularize # => "CamelOctopus"
37
+ def singularize
38
+ MotionSupport::Inflector.singularize(self)
39
+ end
40
+
41
+ # +constantize+ tries to find a declared constant with the name specified
42
+ # in the string. It raises a NameError when the name is not in CamelCase
43
+ # or is not initialized. See MotionSupport::Inflector.constantize
44
+ #
45
+ # 'Module'.constantize # => Module
46
+ # 'Class'.constantize # => Class
47
+ # 'blargle'.constantize # => NameError: wrong constant name blargle
48
+ def constantize
49
+ MotionSupport::Inflector.constantize(self)
50
+ end
51
+
52
+ # +safe_constantize+ tries to find a declared constant with the name specified
53
+ # in the string. It returns nil when the name is not in CamelCase
54
+ # or is not initialized. See MotionSupport::Inflector.safe_constantize
55
+ #
56
+ # 'Module'.safe_constantize # => Module
57
+ # 'Class'.safe_constantize # => Class
58
+ # 'blargle'.safe_constantize # => nil
59
+ def safe_constantize
60
+ MotionSupport::Inflector.safe_constantize(self)
61
+ end
62
+
63
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
64
+ # is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
65
+ #
66
+ # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
67
+ #
68
+ # 'active_record'.camelize # => "ActiveRecord"
69
+ # 'active_record'.camelize(:lower) # => "activeRecord"
70
+ # 'active_record/errors'.camelize # => "ActiveRecord::Errors"
71
+ # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"
72
+ def camelize(first_letter = :upper)
73
+ case first_letter
74
+ when :upper
75
+ MotionSupport::Inflector.camelize(self, true)
76
+ when :lower
77
+ MotionSupport::Inflector.camelize(self, false)
78
+ end
79
+ end
80
+ alias_method :camelcase, :camelize
81
+
82
+ # Capitalizes all the words and replaces some characters in the string to create
83
+ # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
84
+ # used in the Rails internals.
85
+ #
86
+ # +titleize+ is also aliased as +titlecase+.
87
+ #
88
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
89
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
90
+ def titleize
91
+ MotionSupport::Inflector.titleize(self)
92
+ end
93
+ alias_method :titlecase, :titleize
94
+
95
+ # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
96
+ #
97
+ # +underscore+ will also change '::' to '/' to convert namespaces to paths.
98
+ #
99
+ # 'ActiveModel'.underscore # => "active_model"
100
+ # 'ActiveModel::Errors'.underscore # => "active_model/errors"
101
+ def underscore
102
+ MotionSupport::Inflector.underscore(self)
103
+ end
104
+
105
+ # Replaces underscores with dashes in the string.
106
+ #
107
+ # 'puni_puni'.dasherize # => "puni-puni"
108
+ def dasherize
109
+ MotionSupport::Inflector.dasherize(self)
110
+ end
111
+
112
+ # Removes the module part from the constant expression in the string.
113
+ #
114
+ # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
115
+ # 'Inflections'.demodulize # => "Inflections"
116
+ #
117
+ # See also +deconstantize+.
118
+ def demodulize
119
+ MotionSupport::Inflector.demodulize(self)
120
+ end
121
+
122
+ # Removes the rightmost segment from the constant expression in the string.
123
+ #
124
+ # 'Net::HTTP'.deconstantize # => "Net"
125
+ # '::Net::HTTP'.deconstantize # => "::Net"
126
+ # 'String'.deconstantize # => ""
127
+ # '::String'.deconstantize # => ""
128
+ # ''.deconstantize # => ""
129
+ #
130
+ # See also +demodulize+.
131
+ def deconstantize
132
+ MotionSupport::Inflector.deconstantize(self)
133
+ end
134
+
135
+ # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
136
+ #
137
+ # class Person
138
+ # def to_param
139
+ # "#{id}-#{name.parameterize}"
140
+ # end
141
+ # end
142
+ #
143
+ # @person = Person.find(1)
144
+ # # => #<Person id: 1, name: "Donald E. Knuth">
145
+ #
146
+ # <%= link_to(@person.name, person_path) %>
147
+ # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
148
+ def parameterize(sep = '-')
149
+ MotionSupport::Inflector.parameterize(self, sep)
150
+ end
151
+
152
+ # Creates the name of a table like Rails does for models to table names. This method
153
+ # uses the +pluralize+ method on the last word in the string.
154
+ #
155
+ # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
156
+ # 'egg_and_ham'.tableize # => "egg_and_hams"
157
+ # 'fancyCategory'.tableize # => "fancy_categories"
158
+ def tableize
159
+ MotionSupport::Inflector.tableize(self)
160
+ end
161
+
162
+ # Create a class name from a plural table name like Rails does for table names to models.
163
+ # Note that this returns a string and not a class. (To convert to an actual class
164
+ # follow +classify+ with +constantize+.)
165
+ #
166
+ # 'egg_and_hams'.classify # => "EggAndHam"
167
+ # 'posts'.classify # => "Post"
168
+ #
169
+ # Singular names are not handled correctly.
170
+ #
171
+ # 'business'.classify # => "Busines"
172
+ def classify
173
+ MotionSupport::Inflector.classify(self)
174
+ end
175
+
176
+ # Capitalizes the first word, turns underscores into spaces, and strips '_id'.
177
+ # Like +titleize+, this is meant for creating pretty output.
178
+ #
179
+ # 'employee_salary'.humanize # => "Employee salary"
180
+ # 'author_id'.humanize # => "Author"
181
+ def humanize
182
+ MotionSupport::Inflector.humanize(self)
183
+ end
184
+
185
+ # Creates a foreign key name from a class name.
186
+ # +separate_class_name_and_id_with_underscore+ sets whether
187
+ # the method should put '_' between the name and 'id'.
188
+ #
189
+ # 'Message'.foreign_key # => "message_id"
190
+ # 'Message'.foreign_key(false) # => "messageid"
191
+ # 'Admin::Post'.foreign_key # => "post_id"
192
+ def foreign_key(separate_class_name_and_id_with_underscore = true)
193
+ MotionSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
194
+ end
195
+ end
@@ -0,0 +1,4 @@
1
+ class String
2
+ alias_method :starts_with?, :start_with?
3
+ alias_method :ends_with?, :end_with?
4
+ end
@@ -0,0 +1,24 @@
1
+ class String
2
+ # Strips indentation in heredocs.
3
+ #
4
+ # For example in
5
+ #
6
+ # if options[:usage]
7
+ # puts <<-USAGE.strip_heredoc
8
+ # This command does such and such.
9
+ #
10
+ # Supported options are:
11
+ # -h This message
12
+ # ...
13
+ # USAGE
14
+ # end
15
+ #
16
+ # the user would see the usage message aligned against the left margin.
17
+ #
18
+ # Technically, it looks for the least indented line in the whole string, and removes
19
+ # that amount of leading whitespace.
20
+ def strip_heredoc
21
+ indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
22
+ gsub(/^[ \t]{#{indent}}/, '')
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ motion_require '../object/acts_like'
2
+
3
+ class Time
4
+ # Duck-types as a Time-like class. See Object#acts_like?.
5
+ def acts_like_time?
6
+ true
7
+ end
8
+ end