core_ext 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3 -0
  3. data/lib/core_ext/array/access.rb +76 -0
  4. data/lib/core_ext/array/conversions.rb +211 -0
  5. data/lib/core_ext/array/extract_options.rb +29 -0
  6. data/lib/core_ext/array/grouping.rb +116 -0
  7. data/lib/core_ext/array/inquiry.rb +17 -0
  8. data/lib/core_ext/array/prepend_and_append.rb +7 -0
  9. data/lib/core_ext/array/wrap.rb +46 -0
  10. data/lib/core_ext/array.rb +7 -0
  11. data/lib/core_ext/array_inquirer.rb +44 -0
  12. data/lib/core_ext/benchmark.rb +14 -0
  13. data/lib/core_ext/benchmarkable.rb +49 -0
  14. data/lib/core_ext/big_decimal/conversions.rb +14 -0
  15. data/lib/core_ext/big_decimal.rb +1 -0
  16. data/lib/core_ext/builder.rb +6 -0
  17. data/lib/core_ext/callbacks.rb +770 -0
  18. data/lib/core_ext/class/attribute.rb +128 -0
  19. data/lib/core_ext/class/attribute_accessors.rb +4 -0
  20. data/lib/core_ext/class/subclasses.rb +42 -0
  21. data/lib/core_ext/class.rb +2 -0
  22. data/lib/core_ext/concern.rb +142 -0
  23. data/lib/core_ext/configurable.rb +148 -0
  24. data/lib/core_ext/date/acts_like.rb +8 -0
  25. data/lib/core_ext/date/blank.rb +12 -0
  26. data/lib/core_ext/date/calculations.rb +143 -0
  27. data/lib/core_ext/date/conversions.rb +93 -0
  28. data/lib/core_ext/date/zones.rb +6 -0
  29. data/lib/core_ext/date.rb +5 -0
  30. data/lib/core_ext/date_and_time/calculations.rb +328 -0
  31. data/lib/core_ext/date_and_time/zones.rb +40 -0
  32. data/lib/core_ext/date_time/acts_like.rb +14 -0
  33. data/lib/core_ext/date_time/blank.rb +12 -0
  34. data/lib/core_ext/date_time/calculations.rb +177 -0
  35. data/lib/core_ext/date_time/conversions.rb +104 -0
  36. data/lib/core_ext/date_time/zones.rb +6 -0
  37. data/lib/core_ext/date_time.rb +5 -0
  38. data/lib/core_ext/deprecation/behaviors.rb +86 -0
  39. data/lib/core_ext/deprecation/instance_delegator.rb +24 -0
  40. data/lib/core_ext/deprecation/method_wrappers.rb +70 -0
  41. data/lib/core_ext/deprecation/proxy_wrappers.rb +149 -0
  42. data/lib/core_ext/deprecation/reporting.rb +105 -0
  43. data/lib/core_ext/deprecation.rb +43 -0
  44. data/lib/core_ext/digest/uuid.rb +51 -0
  45. data/lib/core_ext/duration.rb +157 -0
  46. data/lib/core_ext/enumerable.rb +106 -0
  47. data/lib/core_ext/file/atomic.rb +68 -0
  48. data/lib/core_ext/file.rb +1 -0
  49. data/lib/core_ext/hash/compact.rb +20 -0
  50. data/lib/core_ext/hash/conversions.rb +261 -0
  51. data/lib/core_ext/hash/deep_merge.rb +38 -0
  52. data/lib/core_ext/hash/except.rb +22 -0
  53. data/lib/core_ext/hash/indifferent_access.rb +23 -0
  54. data/lib/core_ext/hash/keys.rb +170 -0
  55. data/lib/core_ext/hash/reverse_merge.rb +22 -0
  56. data/lib/core_ext/hash/slice.rb +48 -0
  57. data/lib/core_ext/hash/transform_values.rb +29 -0
  58. data/lib/core_ext/hash.rb +9 -0
  59. data/lib/core_ext/hash_with_indifferent_access.rb +298 -0
  60. data/lib/core_ext/inflections.rb +70 -0
  61. data/lib/core_ext/inflector/inflections.rb +244 -0
  62. data/lib/core_ext/inflector/methods.rb +381 -0
  63. data/lib/core_ext/inflector/transliterate.rb +112 -0
  64. data/lib/core_ext/inflector.rb +7 -0
  65. data/lib/core_ext/integer/inflections.rb +29 -0
  66. data/lib/core_ext/integer/multiple.rb +10 -0
  67. data/lib/core_ext/integer/time.rb +29 -0
  68. data/lib/core_ext/integer.rb +3 -0
  69. data/lib/core_ext/json/decoding.rb +67 -0
  70. data/lib/core_ext/json/encoding.rb +127 -0
  71. data/lib/core_ext/json.rb +2 -0
  72. data/lib/core_ext/kernel/agnostics.rb +11 -0
  73. data/lib/core_ext/kernel/concern.rb +10 -0
  74. data/lib/core_ext/kernel/reporting.rb +41 -0
  75. data/lib/core_ext/kernel/singleton_class.rb +6 -0
  76. data/lib/core_ext/kernel.rb +4 -0
  77. data/lib/core_ext/load_error.rb +30 -0
  78. data/lib/core_ext/logger.rb +57 -0
  79. data/lib/core_ext/logger_silence.rb +24 -0
  80. data/lib/core_ext/marshal.rb +19 -0
  81. data/lib/core_ext/module/aliasing.rb +74 -0
  82. data/lib/core_ext/module/anonymous.rb +28 -0
  83. data/lib/core_ext/module/attr_internal.rb +36 -0
  84. data/lib/core_ext/module/attribute_accessors.rb +212 -0
  85. data/lib/core_ext/module/concerning.rb +135 -0
  86. data/lib/core_ext/module/delegation.rb +218 -0
  87. data/lib/core_ext/module/deprecation.rb +23 -0
  88. data/lib/core_ext/module/introspection.rb +62 -0
  89. data/lib/core_ext/module/method_transplanting.rb +3 -0
  90. data/lib/core_ext/module/qualified_const.rb +52 -0
  91. data/lib/core_ext/module/reachable.rb +8 -0
  92. data/lib/core_ext/module/remove_method.rb +35 -0
  93. data/lib/core_ext/module.rb +11 -0
  94. data/lib/core_ext/multibyte/chars.rb +231 -0
  95. data/lib/core_ext/multibyte/unicode.rb +388 -0
  96. data/lib/core_ext/multibyte.rb +21 -0
  97. data/lib/core_ext/name_error.rb +31 -0
  98. data/lib/core_ext/numeric/bytes.rb +64 -0
  99. data/lib/core_ext/numeric/conversions.rb +132 -0
  100. data/lib/core_ext/numeric/inquiry.rb +26 -0
  101. data/lib/core_ext/numeric/time.rb +74 -0
  102. data/lib/core_ext/numeric.rb +4 -0
  103. data/lib/core_ext/object/acts_like.rb +10 -0
  104. data/lib/core_ext/object/blank.rb +140 -0
  105. data/lib/core_ext/object/conversions.rb +4 -0
  106. data/lib/core_ext/object/deep_dup.rb +53 -0
  107. data/lib/core_ext/object/duplicable.rb +98 -0
  108. data/lib/core_ext/object/inclusion.rb +27 -0
  109. data/lib/core_ext/object/instance_variables.rb +28 -0
  110. data/lib/core_ext/object/json.rb +199 -0
  111. data/lib/core_ext/object/to_param.rb +1 -0
  112. data/lib/core_ext/object/to_query.rb +84 -0
  113. data/lib/core_ext/object/try.rb +146 -0
  114. data/lib/core_ext/object/with_options.rb +69 -0
  115. data/lib/core_ext/object.rb +14 -0
  116. data/lib/core_ext/option_merger.rb +25 -0
  117. data/lib/core_ext/ordered_hash.rb +48 -0
  118. data/lib/core_ext/ordered_options.rb +81 -0
  119. data/lib/core_ext/range/conversions.rb +34 -0
  120. data/lib/core_ext/range/each.rb +21 -0
  121. data/lib/core_ext/range/include_range.rb +23 -0
  122. data/lib/core_ext/range/overlaps.rb +8 -0
  123. data/lib/core_ext/range.rb +4 -0
  124. data/lib/core_ext/regexp.rb +5 -0
  125. data/lib/core_ext/rescuable.rb +119 -0
  126. data/lib/core_ext/securerandom.rb +23 -0
  127. data/lib/core_ext/security_utils.rb +20 -0
  128. data/lib/core_ext/string/access.rb +104 -0
  129. data/lib/core_ext/string/behavior.rb +6 -0
  130. data/lib/core_ext/string/conversions.rb +56 -0
  131. data/lib/core_ext/string/exclude.rb +11 -0
  132. data/lib/core_ext/string/filters.rb +102 -0
  133. data/lib/core_ext/string/indent.rb +43 -0
  134. data/lib/core_ext/string/inflections.rb +235 -0
  135. data/lib/core_ext/string/inquiry.rb +13 -0
  136. data/lib/core_ext/string/multibyte.rb +53 -0
  137. data/lib/core_ext/string/output_safety.rb +261 -0
  138. data/lib/core_ext/string/starts_ends_with.rb +4 -0
  139. data/lib/core_ext/string/strip.rb +23 -0
  140. data/lib/core_ext/string/zones.rb +14 -0
  141. data/lib/core_ext/string.rb +13 -0
  142. data/lib/core_ext/string_inquirer.rb +26 -0
  143. data/lib/core_ext/tagged_logging.rb +78 -0
  144. data/lib/core_ext/test_case.rb +88 -0
  145. data/lib/core_ext/testing/assertions.rb +99 -0
  146. data/lib/core_ext/testing/autorun.rb +12 -0
  147. data/lib/core_ext/testing/composite_filter.rb +54 -0
  148. data/lib/core_ext/testing/constant_lookup.rb +50 -0
  149. data/lib/core_ext/testing/declarative.rb +26 -0
  150. data/lib/core_ext/testing/deprecation.rb +36 -0
  151. data/lib/core_ext/testing/file_fixtures.rb +34 -0
  152. data/lib/core_ext/testing/isolation.rb +115 -0
  153. data/lib/core_ext/testing/method_call_assertions.rb +41 -0
  154. data/lib/core_ext/testing/setup_and_teardown.rb +50 -0
  155. data/lib/core_ext/testing/stream.rb +42 -0
  156. data/lib/core_ext/testing/tagged_logging.rb +25 -0
  157. data/lib/core_ext/testing/time_helpers.rb +134 -0
  158. data/lib/core_ext/time/acts_like.rb +8 -0
  159. data/lib/core_ext/time/calculations.rb +284 -0
  160. data/lib/core_ext/time/conversions.rb +66 -0
  161. data/lib/core_ext/time/zones.rb +95 -0
  162. data/lib/core_ext/time.rb +20 -0
  163. data/lib/core_ext/time_with_zone.rb +503 -0
  164. data/lib/core_ext/time_zone.rb +464 -0
  165. data/lib/core_ext/uri.rb +25 -0
  166. data/lib/core_ext/version.rb +3 -0
  167. data/lib/core_ext/xml_mini/jdom.rb +181 -0
  168. data/lib/core_ext/xml_mini/libxml.rb +79 -0
  169. data/lib/core_ext/xml_mini/libxmlsax.rb +85 -0
  170. data/lib/core_ext/xml_mini/nokogiri.rb +83 -0
  171. data/lib/core_ext/xml_mini/nokogirisax.rb +87 -0
  172. data/lib/core_ext/xml_mini/rexml.rb +130 -0
  173. data/lib/core_ext/xml_mini.rb +194 -0
  174. data/lib/core_ext.rb +3 -0
  175. metadata +310 -0
@@ -0,0 +1,244 @@
1
+ require 'core_ext/array/prepend_and_append'
2
+ require 'i18n'
3
+
4
+ module CoreExt
5
+ module Inflector
6
+ extend self
7
+
8
+ # A singleton instance of this class is yielded by Inflector.inflections,
9
+ # which can then be used to specify additional inflection rules. If passed
10
+ # an optional locale, rules for other languages can be specified. The
11
+ # default locale is <tt>:en</tt>. Only rules for English are provided.
12
+ #
13
+ # CoreExt::Inflector.inflections(:en) do |inflect|
14
+ # inflect.plural /^(ox)$/i, '\1\2en'
15
+ # inflect.singular /^(ox)en/i, '\1'
16
+ #
17
+ # inflect.irregular 'octopus', 'octopi'
18
+ #
19
+ # inflect.uncountable 'equipment'
20
+ # end
21
+ #
22
+ # New rules are added at the top. So in the example above, the irregular
23
+ # rule for octopus will now be the first of the pluralization and
24
+ # singularization rules that is runs. This guarantees that your rules run
25
+ # before any of the rules that may already have been loaded.
26
+ class Inflections
27
+
28
+ # TODO Add concurrent-ruby to support thread safe
29
+ # @__instance__ = Concurrent::Map.new
30
+ @__instance__ = {}
31
+
32
+ class Uncountables < Array
33
+ def initialize
34
+ @regex_array = []
35
+ super
36
+ end
37
+
38
+ def delete(entry)
39
+ super entry
40
+ @regex_array.delete(to_regex(entry))
41
+ end
42
+
43
+ def <<(*word)
44
+ add(word)
45
+ end
46
+
47
+ def add(words)
48
+ self.concat(words.flatten.map(&:downcase))
49
+ @regex_array += self.map {|word| to_regex(word) }
50
+ self
51
+ end
52
+
53
+ def uncountable?(str)
54
+ @regex_array.any? { |regex| regex === str }
55
+ end
56
+
57
+ private
58
+ def to_regex(string)
59
+ /\b#{::Regexp.escape(string)}\Z/i
60
+ end
61
+ end
62
+
63
+ def self.instance(locale = :en)
64
+ @__instance__[locale] ||= new
65
+ end
66
+
67
+ attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
68
+
69
+ def initialize
70
+ @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
71
+ end
72
+
73
+ # Private, for the test suite.
74
+ def initialize_dup(orig) # :nodoc:
75
+ %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
76
+ instance_variable_set("@#{scope}", orig.send(scope).dup)
77
+ end
78
+ end
79
+
80
+ # Specifies a new acronym. An acronym must be specified as it will appear
81
+ # in a camelized string. An underscore string that contains the acronym
82
+ # will retain the acronym when passed to +camelize+, +humanize+, or
83
+ # +titleize+. A camelized string that contains the acronym will maintain
84
+ # the acronym when titleized or humanized, and will convert the acronym
85
+ # into a non-delimited single lowercase word when passed to +underscore+.
86
+ #
87
+ # acronym 'HTML'
88
+ # titleize 'html' # => 'HTML'
89
+ # camelize 'html' # => 'HTML'
90
+ # underscore 'MyHTML' # => 'my_html'
91
+ #
92
+ # The acronym, however, must occur as a delimited unit and not be part of
93
+ # another word for conversions to recognize it:
94
+ #
95
+ # acronym 'HTTP'
96
+ # camelize 'my_http_delimited' # => 'MyHTTPDelimited'
97
+ # camelize 'https' # => 'Https', not 'HTTPs'
98
+ # underscore 'HTTPS' # => 'http_s', not 'https'
99
+ #
100
+ # acronym 'HTTPS'
101
+ # camelize 'https' # => 'HTTPS'
102
+ # underscore 'HTTPS' # => 'https'
103
+ #
104
+ # Note: Acronyms that are passed to +pluralize+ will no longer be
105
+ # recognized, since the acronym will not occur as a delimited unit in the
106
+ # pluralized result. To work around this, you must specify the pluralized
107
+ # form as an acronym as well:
108
+ #
109
+ # acronym 'API'
110
+ # camelize(pluralize('api')) # => 'Apis'
111
+ #
112
+ # acronym 'APIs'
113
+ # camelize(pluralize('api')) # => 'APIs'
114
+ #
115
+ # +acronym+ may be used to specify any word that contains an acronym or
116
+ # otherwise needs to maintain a non-standard capitalization. The only
117
+ # restriction is that the word must begin with a capital letter.
118
+ #
119
+ # acronym 'RESTful'
120
+ # underscore 'RESTful' # => 'restful'
121
+ # underscore 'RESTfulController' # => 'restful_controller'
122
+ # titleize 'RESTfulController' # => 'RESTful Controller'
123
+ # camelize 'restful' # => 'RESTful'
124
+ # camelize 'restful_controller' # => 'RESTfulController'
125
+ #
126
+ # acronym 'McDonald'
127
+ # underscore 'McDonald' # => 'mcdonald'
128
+ # camelize 'mcdonald' # => 'McDonald'
129
+ def acronym(word)
130
+ @acronyms[word.downcase] = word
131
+ @acronym_regex = /#{@acronyms.values.join("|")}/
132
+ end
133
+
134
+ # Specifies a new pluralization rule and its replacement. The rule can
135
+ # either be a string or a regular expression. The replacement should
136
+ # always be a string that may include references to the matched data from
137
+ # the rule.
138
+ def plural(rule, replacement)
139
+ @uncountables.delete(rule) if rule.is_a?(String)
140
+ @uncountables.delete(replacement)
141
+ @plurals.prepend([rule, replacement])
142
+ end
143
+
144
+ # Specifies a new singularization rule and its replacement. The rule can
145
+ # either be a string or a regular expression. The replacement should
146
+ # always be a string that may include references to the matched data from
147
+ # the rule.
148
+ def singular(rule, replacement)
149
+ @uncountables.delete(rule) if rule.is_a?(String)
150
+ @uncountables.delete(replacement)
151
+ @singulars.prepend([rule, replacement])
152
+ end
153
+
154
+ # Specifies a new irregular that applies to both pluralization and
155
+ # singularization at the same time. This can only be used for strings, not
156
+ # regular expressions. You simply pass the irregular in singular and
157
+ # plural form.
158
+ #
159
+ # irregular 'octopus', 'octopi'
160
+ # irregular 'person', 'people'
161
+ def irregular(singular, plural)
162
+ @uncountables.delete(singular)
163
+ @uncountables.delete(plural)
164
+
165
+ s0 = singular[0]
166
+ srest = singular[1..-1]
167
+
168
+ p0 = plural[0]
169
+ prest = plural[1..-1]
170
+
171
+ if s0.upcase == p0.upcase
172
+ plural(/(#{s0})#{srest}$/i, '\1' + prest)
173
+ plural(/(#{p0})#{prest}$/i, '\1' + prest)
174
+
175
+ singular(/(#{s0})#{srest}$/i, '\1' + srest)
176
+ singular(/(#{p0})#{prest}$/i, '\1' + srest)
177
+ else
178
+ plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest)
179
+ plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest)
180
+ plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest)
181
+ plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest)
182
+
183
+ singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest)
184
+ singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest)
185
+ singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest)
186
+ singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest)
187
+ end
188
+ end
189
+
190
+ # Specifies words that are uncountable and should not be inflected.
191
+ #
192
+ # uncountable 'money'
193
+ # uncountable 'money', 'information'
194
+ # uncountable %w( money information rice )
195
+ def uncountable(*words)
196
+ @uncountables.add(words)
197
+ end
198
+
199
+ # Specifies a humanized form of a string by a regular expression rule or
200
+ # by a string mapping. When using a regular expression based replacement,
201
+ # the normal humanize formatting is called after the replacement. When a
202
+ # string is used, the human form should be specified as desired (example:
203
+ # 'The name', not 'the_name').
204
+ #
205
+ # human /_cnt$/i, '\1_count'
206
+ # human 'legacy_col_person_name', 'Name'
207
+ def human(rule, replacement)
208
+ @humans.prepend([rule, replacement])
209
+ end
210
+
211
+ # Clears the loaded inflections within a given scope (default is
212
+ # <tt>:all</tt>). Give the scope as a symbol of the inflection type, the
213
+ # options are: <tt>:plurals</tt>, <tt>:singulars</tt>, <tt>:uncountables</tt>,
214
+ # <tt>:humans</tt>.
215
+ #
216
+ # clear :all
217
+ # clear :plurals
218
+ def clear(scope = :all)
219
+ case scope
220
+ when :all
221
+ @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
222
+ else
223
+ instance_variable_set "@#{scope}", []
224
+ end
225
+ end
226
+ end
227
+
228
+ # Yields a singleton instance of Inflector::Inflections so you can specify
229
+ # additional inflector rules. If passed an optional locale, rules for other
230
+ # languages can be specified. If not specified, defaults to <tt>:en</tt>.
231
+ # Only rules for English are provided.
232
+ #
233
+ # CoreExt::Inflector.inflections(:en) do |inflect|
234
+ # inflect.uncountable 'rails'
235
+ # end
236
+ def inflections(locale = :en)
237
+ if block_given?
238
+ yield Inflections.instance(locale)
239
+ else
240
+ Inflections.instance(locale)
241
+ end
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,381 @@
1
+ require 'core_ext/inflections'
2
+
3
+ module CoreExt
4
+ # The Inflector transforms words from singular to plural, class names to table
5
+ # names, modularized class names to ones without, and class names to foreign
6
+ # keys. The default inflections for pluralization, singularization, and
7
+ # uncountable words are kept in inflections.rb.
8
+ #
9
+ # The Rails core team has stated patches for the inflections library will not
10
+ # be accepted in order to avoid breaking legacy applications which may be
11
+ # relying on errant inflections. If you discover an incorrect inflection and
12
+ # require it for your application or wish to define rules for languages other
13
+ # than English, please correct or add them yourself (explained below).
14
+ module Inflector
15
+ extend self
16
+
17
+ # Returns the plural form of the word in the string.
18
+ #
19
+ # If passed an optional +locale+ parameter, the word will be
20
+ # pluralized using rules defined for that language. By default,
21
+ # this parameter is set to <tt>:en</tt>.
22
+ #
23
+ # pluralize('post') # => "posts"
24
+ # pluralize('octopus') # => "octopi"
25
+ # pluralize('sheep') # => "sheep"
26
+ # pluralize('words') # => "words"
27
+ # pluralize('CamelOctopus') # => "CamelOctopi"
28
+ # pluralize('ley', :es) # => "leyes"
29
+ def pluralize(word, locale = :en)
30
+ apply_inflections(word, inflections(locale).plurals)
31
+ end
32
+
33
+ # The reverse of #pluralize, returns the singular form of a word in a
34
+ # string.
35
+ #
36
+ # If passed an optional +locale+ parameter, the word will be
37
+ # singularized using rules defined for that language. By default,
38
+ # this parameter is set to <tt>:en</tt>.
39
+ #
40
+ # singularize('posts') # => "post"
41
+ # singularize('octopi') # => "octopus"
42
+ # singularize('sheep') # => "sheep"
43
+ # singularize('word') # => "word"
44
+ # singularize('CamelOctopi') # => "CamelOctopus"
45
+ # singularize('leyes', :es) # => "ley"
46
+ def singularize(word, locale = :en)
47
+ apply_inflections(word, inflections(locale).singulars)
48
+ end
49
+
50
+ # Converts strings to UpperCamelCase.
51
+ # If the +uppercase_first_letter+ parameter is set to false, then produces
52
+ # lowerCamelCase.
53
+ #
54
+ # Also converts '/' to '::' which is useful for converting
55
+ # paths to namespaces.
56
+ #
57
+ # camelize('active_model') # => "ActiveModel"
58
+ # camelize('active_model', false) # => "activeModel"
59
+ # camelize('active_model/errors') # => "ActiveModel::Errors"
60
+ # camelize('active_model/errors', false) # => "activeModel::Errors"
61
+ #
62
+ # As a rule of thumb you can think of +camelize+ as the inverse of
63
+ # #underscore, though there are cases where that does not hold:
64
+ #
65
+ # camelize(underscore('SSLError')) # => "SslError"
66
+ def camelize(term, uppercase_first_letter = true)
67
+ string = term.to_s
68
+ if uppercase_first_letter
69
+ string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
70
+ else
71
+ string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
72
+ end
73
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
74
+ string.gsub!('/'.freeze, '::'.freeze)
75
+ string
76
+ end
77
+
78
+ # Makes an underscored, lowercase form from the expression in the string.
79
+ #
80
+ # Changes '::' to '/' to convert namespaces to paths.
81
+ #
82
+ # underscore('ActiveModel') # => "active_model"
83
+ # underscore('ActiveModel::Errors') # => "active_model/errors"
84
+ #
85
+ # As a rule of thumb you can think of +underscore+ as the inverse of
86
+ # #camelize, though there are cases where that does not hold:
87
+ #
88
+ # camelize(underscore('SSLError')) # => "SslError"
89
+ def underscore(camel_cased_word)
90
+ return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
91
+ word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
92
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
93
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
94
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
95
+ word.tr!("-".freeze, "_".freeze)
96
+ word.downcase!
97
+ word
98
+ end
99
+
100
+ # Tweaks an attribute name for display to end users.
101
+ #
102
+ # Specifically, performs these transformations:
103
+ #
104
+ # * Applies human inflection rules to the argument.
105
+ # * Deletes leading underscores, if any.
106
+ # * Removes a "_id" suffix if present.
107
+ # * Replaces underscores with spaces, if any.
108
+ # * Downcases all words except acronyms.
109
+ # * Capitalizes the first word.
110
+ #
111
+ # The capitalization of the first word can be turned off by setting the
112
+ # +:capitalize+ option to false (default is true).
113
+ #
114
+ # humanize('employee_salary') # => "Employee salary"
115
+ # humanize('author_id') # => "Author"
116
+ # humanize('author_id', capitalize: false) # => "author"
117
+ # humanize('_id') # => "Id"
118
+ #
119
+ # If "SSL" was defined to be an acronym:
120
+ #
121
+ # humanize('ssl_error') # => "SSL error"
122
+ #
123
+ def humanize(lower_case_and_underscored_word, options = {})
124
+ result = lower_case_and_underscored_word.to_s.dup
125
+
126
+ inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
127
+
128
+ result.sub!(/\A_+/, ''.freeze)
129
+ result.sub!(/_id\z/, ''.freeze)
130
+ result.tr!('_'.freeze, ' '.freeze)
131
+
132
+ result.gsub!(/([a-z\d]*)/i) do |match|
133
+ "#{inflections.acronyms[match] || match.downcase}"
134
+ end
135
+
136
+ if options.fetch(:capitalize, true)
137
+ result.sub!(/\A\w/) { |match| match.upcase }
138
+ end
139
+
140
+ result
141
+ end
142
+
143
+ # Capitalizes all the words and replaces some characters in the string to
144
+ # create a nicer looking title. +titleize+ is meant for creating pretty
145
+ # output. It is not used in the Rails internals.
146
+ #
147
+ # +titleize+ is also aliased as +titlecase+.
148
+ #
149
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
150
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
151
+ # titleize('TheManWithoutAPast') # => "The Man Without A Past"
152
+ # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
153
+ def titleize(word)
154
+ humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }
155
+ end
156
+
157
+ # Creates the name of a table like Rails does for models to table names.
158
+ # This method uses the #pluralize method on the last word in the string.
159
+ #
160
+ # tableize('RawScaledScorer') # => "raw_scaled_scorers"
161
+ # tableize('ham_and_egg') # => "ham_and_eggs"
162
+ # tableize('fancyCategory') # => "fancy_categories"
163
+ def tableize(class_name)
164
+ pluralize(underscore(class_name))
165
+ end
166
+
167
+ # Creates a class name from a plural table name like Rails does for table
168
+ # names to models. Note that this returns a string and not a Class (To
169
+ # convert to an actual class follow +classify+ with #constantize).
170
+ #
171
+ # classify('ham_and_eggs') # => "HamAndEgg"
172
+ # classify('posts') # => "Post"
173
+ #
174
+ # Singular names are not handled correctly:
175
+ #
176
+ # classify('calculus') # => "Calculu"
177
+ def classify(table_name)
178
+ # strip out any leading schema name
179
+ camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
180
+ end
181
+
182
+ # Replaces underscores with dashes in the string.
183
+ #
184
+ # dasherize('puni_puni') # => "puni-puni"
185
+ def dasherize(underscored_word)
186
+ underscored_word.tr('_'.freeze, '-'.freeze)
187
+ end
188
+
189
+ # Removes the module part from the expression in the string.
190
+ #
191
+ # demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections"
192
+ # demodulize('Inflections') # => "Inflections"
193
+ # demodulize('::Inflections') # => "Inflections"
194
+ # demodulize('') # => ""
195
+ #
196
+ # See also #deconstantize.
197
+ def demodulize(path)
198
+ path = path.to_s
199
+ if i = path.rindex('::')
200
+ path[(i+2)..-1]
201
+ else
202
+ path
203
+ end
204
+ end
205
+
206
+ # Removes the rightmost segment from the constant expression in the string.
207
+ #
208
+ # deconstantize('Net::HTTP') # => "Net"
209
+ # deconstantize('::Net::HTTP') # => "::Net"
210
+ # deconstantize('String') # => ""
211
+ # deconstantize('::String') # => ""
212
+ # deconstantize('') # => ""
213
+ #
214
+ # See also #demodulize.
215
+ def deconstantize(path)
216
+ path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
217
+ end
218
+
219
+ # Creates a foreign key name from a class name.
220
+ # +separate_class_name_and_id_with_underscore+ sets whether
221
+ # the method should put '_' between the name and 'id'.
222
+ #
223
+ # foreign_key('Message') # => "message_id"
224
+ # foreign_key('Message', false) # => "messageid"
225
+ # foreign_key('Admin::Post') # => "post_id"
226
+ def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
227
+ underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
228
+ end
229
+
230
+ # Tries to find a constant with the name specified in the argument string.
231
+ #
232
+ # 'Module'.constantize # => Module
233
+ # 'Foo::Bar'.constantize # => Foo::Bar
234
+ #
235
+ # The name is assumed to be the one of a top-level constant, no matter
236
+ # whether it starts with "::" or not. No lexical context is taken into
237
+ # account:
238
+ #
239
+ # C = 'outside'
240
+ # module M
241
+ # C = 'inside'
242
+ # C # => 'inside'
243
+ # 'C'.constantize # => 'outside', same as ::C
244
+ # end
245
+ #
246
+ # NameError is raised when the name is not in CamelCase or the constant is
247
+ # unknown.
248
+ def constantize(camel_cased_word)
249
+ names = camel_cased_word.split('::'.freeze)
250
+
251
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
252
+ Object.const_get(camel_cased_word) if names.empty?
253
+
254
+ # Remove the first blank element in case of '::ClassName' notation.
255
+ names.shift if names.size > 1 && names.first.empty?
256
+
257
+ names.inject(Object) do |constant, name|
258
+ if constant == Object
259
+ constant.const_get(name)
260
+ else
261
+ candidate = constant.const_get(name)
262
+ next candidate if constant.const_defined?(name, false)
263
+ next candidate unless Object.const_defined?(name)
264
+
265
+ # Go down the ancestors to check if it is owned directly. The check
266
+ # stops when we reach Object or the end of ancestors tree.
267
+ constant = constant.ancestors.inject do |const, ancestor|
268
+ break const if ancestor == Object
269
+ break ancestor if ancestor.const_defined?(name, false)
270
+ const
271
+ end
272
+
273
+ # owner is in Object, so raise
274
+ constant.const_get(name, false)
275
+ end
276
+ end
277
+ end
278
+
279
+ # Tries to find a constant with the name specified in the argument string.
280
+ #
281
+ # safe_constantize('Module') # => Module
282
+ # safe_constantize('Foo::Bar') # => Foo::Bar
283
+ #
284
+ # The name is assumed to be the one of a top-level constant, no matter
285
+ # whether it starts with "::" or not. No lexical context is taken into
286
+ # account:
287
+ #
288
+ # C = 'outside'
289
+ # module M
290
+ # C = 'inside'
291
+ # C # => 'inside'
292
+ # safe_constantize('C') # => 'outside', same as ::C
293
+ # end
294
+ #
295
+ # +nil+ is returned when the name is not in CamelCase or the constant (or
296
+ # part of it) is unknown.
297
+ #
298
+ # safe_constantize('blargle') # => nil
299
+ # safe_constantize('UnknownModule') # => nil
300
+ # safe_constantize('UnknownModule::Foo::Bar') # => nil
301
+ def safe_constantize(camel_cased_word)
302
+ constantize(camel_cased_word)
303
+ rescue NameError => e
304
+ raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
305
+ e.name.to_s == camel_cased_word.to_s)
306
+ rescue ArgumentError => e
307
+ raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
308
+ end
309
+
310
+ # Returns the suffix that should be added to a number to denote the position
311
+ # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
312
+ #
313
+ # ordinal(1) # => "st"
314
+ # ordinal(2) # => "nd"
315
+ # ordinal(1002) # => "nd"
316
+ # ordinal(1003) # => "rd"
317
+ # ordinal(-11) # => "th"
318
+ # ordinal(-1021) # => "st"
319
+ def ordinal(number)
320
+ abs_number = number.to_i.abs
321
+
322
+ if (11..13).include?(abs_number % 100)
323
+ "th"
324
+ else
325
+ case abs_number % 10
326
+ when 1; "st"
327
+ when 2; "nd"
328
+ when 3; "rd"
329
+ else "th"
330
+ end
331
+ end
332
+ end
333
+
334
+ # Turns a number into an ordinal string used to denote the position in an
335
+ # ordered sequence such as 1st, 2nd, 3rd, 4th.
336
+ #
337
+ # ordinalize(1) # => "1st"
338
+ # ordinalize(2) # => "2nd"
339
+ # ordinalize(1002) # => "1002nd"
340
+ # ordinalize(1003) # => "1003rd"
341
+ # ordinalize(-11) # => "-11th"
342
+ # ordinalize(-1021) # => "-1021st"
343
+ def ordinalize(number)
344
+ "#{number}#{ordinal(number)}"
345
+ end
346
+
347
+ private
348
+
349
+ # Mounts a regular expression, returned as a string to ease interpolation,
350
+ # that will match part by part the given constant.
351
+ #
352
+ # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
353
+ # const_regexp("::") # => "::"
354
+ def const_regexp(camel_cased_word) #:nodoc:
355
+ parts = camel_cased_word.split("::".freeze)
356
+
357
+ return Regexp.escape(camel_cased_word) if parts.blank?
358
+
359
+ last = parts.pop
360
+
361
+ parts.reverse.inject(last) do |acc, part|
362
+ part.empty? ? acc : "#{part}(::#{acc})?"
363
+ end
364
+ end
365
+
366
+ # Applies inflection rules for +singularize+ and +pluralize+.
367
+ #
368
+ # apply_inflections('post', inflections.plurals) # => "posts"
369
+ # apply_inflections('posts', inflections.singulars) # => "post"
370
+ def apply_inflections(word, rules)
371
+ result = word.to_s.dup
372
+
373
+ if word.empty? || inflections.uncountables.uncountable?(result)
374
+ result
375
+ else
376
+ rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
377
+ result
378
+ end
379
+ end
380
+ end
381
+ end