rails_stats 0.0.4

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.
@@ -0,0 +1,605 @@
1
+ # encoding: utf-8
2
+
3
+ module RailsStats
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
+ # A singleton instance of this class is yielded by Inflector.inflections,
18
+ # which can then be used to specify additional inflection rules. If passed
19
+ # an optional locale, rules for other languages can be specified. The
20
+ # default locale is <tt>:en</tt>. Only rules for English are provided.
21
+ #
22
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
23
+ # inflect.plural /^(ox)$/i, '\1\2en'
24
+ # inflect.singular /^(ox)en/i, '\1'
25
+ #
26
+ # inflect.irregular 'octopus', 'octopi'
27
+ #
28
+ # inflect.uncountable 'equipment'
29
+ # end
30
+ #
31
+ # New rules are added at the top. So in the example above, the irregular
32
+ # rule for octopus will now be the first of the pluralization and
33
+ # singularization rules that is runs. This guarantees that your rules run
34
+ # before any of the rules that may already have been loaded.
35
+ class Inflections
36
+
37
+ def self.instance(locale = :en)
38
+ @__instance__ ||= new
39
+ end
40
+
41
+ attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
42
+
43
+ def initialize
44
+ @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
45
+ end
46
+
47
+ # Private, for the test suite.
48
+ def initialize_dup(orig) # :nodoc:
49
+ %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
50
+ instance_variable_set("@#{scope}", orig.send(scope).dup)
51
+ end
52
+ end
53
+
54
+ # Specifies a new acronym. An acronym must be specified as it will appear
55
+ # in a camelized string. An underscore string that contains the acronym
56
+ # will retain the acronym when passed to +camelize+, +humanize+, or
57
+ # +titleize+. A camelized string that contains the acronym will maintain
58
+ # the acronym when titleized or humanized, and will convert the acronym
59
+ # into a non-delimited single lowercase word when passed to +underscore+.
60
+ #
61
+ # acronym 'HTML'
62
+ # titleize 'html' #=> 'HTML'
63
+ # camelize 'html' #=> 'HTML'
64
+ # underscore 'MyHTML' #=> 'my_html'
65
+ #
66
+ # The acronym, however, must occur as a delimited unit and not be part of
67
+ # another word for conversions to recognize it:
68
+ #
69
+ # acronym 'HTTP'
70
+ # camelize 'my_http_delimited' #=> 'MyHTTPDelimited'
71
+ # camelize 'https' #=> 'Https', not 'HTTPs'
72
+ # underscore 'HTTPS' #=> 'http_s', not 'https'
73
+ #
74
+ # acronym 'HTTPS'
75
+ # camelize 'https' #=> 'HTTPS'
76
+ # underscore 'HTTPS' #=> 'https'
77
+ #
78
+ # Note: Acronyms that are passed to +pluralize+ will no longer be
79
+ # recognized, since the acronym will not occur as a delimited unit in the
80
+ # pluralized result. To work around this, you must specify the pluralized
81
+ # form as an acronym as well:
82
+ #
83
+ # acronym 'API'
84
+ # camelize(pluralize('api')) #=> 'Apis'
85
+ #
86
+ # acronym 'APIs'
87
+ # camelize(pluralize('api')) #=> 'APIs'
88
+ #
89
+ # +acronym+ may be used to specify any word that contains an acronym or
90
+ # otherwise needs to maintain a non-standard capitalization. The only
91
+ # restriction is that the word must begin with a capital letter.
92
+ #
93
+ # acronym 'RESTful'
94
+ # underscore 'RESTful' #=> 'restful'
95
+ # underscore 'RESTfulController' #=> 'restful_controller'
96
+ # titleize 'RESTfulController' #=> 'RESTful Controller'
97
+ # camelize 'restful' #=> 'RESTful'
98
+ # camelize 'restful_controller' #=> 'RESTfulController'
99
+ #
100
+ # acronym 'McDonald'
101
+ # underscore 'McDonald' #=> 'mcdonald'
102
+ # camelize 'mcdonald' #=> 'McDonald'
103
+ def acronym(word)
104
+ @acronyms[word.downcase] = word
105
+ @acronym_regex = /#{@acronyms.values.join("|")}/
106
+ end
107
+
108
+ # Specifies a new pluralization rule and its replacement. The rule can
109
+ # either be a string or a regular expression. The replacement should
110
+ # always be a string that may include references to the matched data from
111
+ # the rule.
112
+ def plural(rule, replacement)
113
+ @uncountables.delete(rule) if rule.is_a?(String)
114
+ @uncountables.delete(replacement)
115
+ @plurals.unshift([rule, replacement])
116
+ end
117
+
118
+ # Specifies a new singularization rule and its replacement. The rule can
119
+ # either be a string or a regular expression. The replacement should
120
+ # always be a string that may include references to the matched data from
121
+ # the rule.
122
+ def singular(rule, replacement)
123
+ @uncountables.delete(rule) if rule.is_a?(String)
124
+ @uncountables.delete(replacement)
125
+ @singulars.unshift([rule, replacement])
126
+ end
127
+
128
+ # Specifies a new irregular that applies to both pluralization and
129
+ # singularization at the same time. This can only be used for strings, not
130
+ # regular expressions. You simply pass the irregular in singular and
131
+ # plural form.
132
+ #
133
+ # irregular 'octopus', 'octopi'
134
+ # irregular 'person', 'people'
135
+ def irregular(singular, plural)
136
+ @uncountables.delete(singular)
137
+ @uncountables.delete(plural)
138
+
139
+ s0 = singular[0]
140
+ srest = singular[1..-1]
141
+
142
+ p0 = plural[0]
143
+ prest = plural[1..-1]
144
+
145
+ if s0.upcase == p0.upcase
146
+ plural(/(#{s0})#{srest}$/i, '\1' + prest)
147
+ plural(/(#{p0})#{prest}$/i, '\1' + prest)
148
+
149
+ singular(/(#{s0})#{srest}$/i, '\1' + srest)
150
+ singular(/(#{p0})#{prest}$/i, '\1' + srest)
151
+ else
152
+ plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest)
153
+ plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest)
154
+ plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest)
155
+ plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest)
156
+
157
+ singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest)
158
+ singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest)
159
+ singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest)
160
+ singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest)
161
+ end
162
+ end
163
+
164
+ # Add uncountable words that shouldn't be attempted inflected.
165
+ #
166
+ # uncountable 'money'
167
+ # uncountable 'money', 'information'
168
+ # uncountable %w( money information rice )
169
+ def uncountable(*words)
170
+ (@uncountables << words).flatten!
171
+ end
172
+
173
+ # Specifies a humanized form of a string by a regular expression rule or
174
+ # by a string mapping. When using a regular expression based replacement,
175
+ # the normal humanize formatting is called after the replacement. When a
176
+ # string is used, the human form should be specified as desired (example:
177
+ # 'The name', not 'the_name').
178
+ #
179
+ # human /_cnt$/i, '\1_count'
180
+ # human 'legacy_col_person_name', 'Name'
181
+ def human(rule, replacement)
182
+ @humans.unshift([rule, replacement])
183
+ end
184
+
185
+ # Clears the loaded inflections within a given scope (default is
186
+ # <tt>:all</tt>). Give the scope as a symbol of the inflection type, the
187
+ # options are: <tt>:plurals</tt>, <tt>:singulars</tt>, <tt>:uncountables</tt>,
188
+ # <tt>:humans</tt>.
189
+ #
190
+ # clear :all
191
+ # clear :plurals
192
+ def clear(scope = :all)
193
+ case scope
194
+ when :all
195
+ @plurals, @singulars, @uncountables, @humans = [], [], [], []
196
+ else
197
+ instance_variable_set "@#{scope}", []
198
+ end
199
+ end
200
+ end
201
+
202
+ # Yields a singleton instance of Inflector::Inflections so you can specify
203
+ # additional inflector rules. If passed an optional locale, rules for other
204
+ # languages can be specified. If not specified, defaults to <tt>:en</tt>.
205
+ # Only rules for English are provided.
206
+ #
207
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
208
+ # inflect.uncountable 'rails'
209
+ # end
210
+ def inflections(locale = :en)
211
+ if block_given?
212
+ yield Inflections.instance(locale)
213
+ else
214
+ Inflections.instance(locale)
215
+ end
216
+ end
217
+
218
+
219
+
220
+
221
+ # Returns the plural form of the word in the string.
222
+ #
223
+ # If passed an optional +locale+ parameter, the word will be
224
+ # pluralized using rules defined for that language. By default,
225
+ # this parameter is set to <tt>:en</tt>.
226
+ #
227
+ # 'post'.pluralize # => "posts"
228
+ # 'octopus'.pluralize # => "octopi"
229
+ # 'sheep'.pluralize # => "sheep"
230
+ # 'words'.pluralize # => "words"
231
+ # 'CamelOctopus'.pluralize # => "CamelOctopi"
232
+ # 'ley'.pluralize(:es) # => "leyes"
233
+ def pluralize(word, locale = :en)
234
+ apply_inflections(word, inflections(locale).plurals)
235
+ end
236
+
237
+ # The reverse of +pluralize+, returns the singular form of a word in a
238
+ # string.
239
+ #
240
+ # If passed an optional +locale+ parameter, the word will be
241
+ # pluralized using rules defined for that language. By default,
242
+ # this parameter is set to <tt>:en</tt>.
243
+ #
244
+ # 'posts'.singularize # => "post"
245
+ # 'octopi'.singularize # => "octopus"
246
+ # 'sheep'.singularize # => "sheep"
247
+ # 'word'.singularize # => "word"
248
+ # 'CamelOctopi'.singularize # => "CamelOctopus"
249
+ # 'leyes'.singularize(:es) # => "ley"
250
+ def singularize(word, locale = :en)
251
+ apply_inflections(word, inflections(locale).singulars)
252
+ end
253
+
254
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument
255
+ # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
256
+ # lowerCamelCase.
257
+ #
258
+ # +camelize+ will also convert '/' to '::' which is useful for converting
259
+ # paths to namespaces.
260
+ #
261
+ # 'active_model'.camelize # => "ActiveModel"
262
+ # 'active_model'.camelize(:lower) # => "activeModel"
263
+ # 'active_model/errors'.camelize # => "ActiveModel::Errors"
264
+ # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
265
+ #
266
+ # As a rule of thumb you can think of +camelize+ as the inverse of
267
+ # +underscore+, though there are cases where that does not hold:
268
+ #
269
+ # 'SSLError'.underscore.camelize # => "SslError"
270
+ def camelize(term, uppercase_first_letter = true)
271
+ string = term.to_s
272
+ if uppercase_first_letter
273
+ string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
274
+ else
275
+ string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
276
+ end
277
+ string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::')
278
+ end
279
+
280
+ # Makes an underscored, lowercase form from the expression in the string.
281
+ #
282
+ # Changes '::' to '/' to convert namespaces to paths.
283
+ #
284
+ # 'ActiveModel'.underscore # => "active_model"
285
+ # 'ActiveModel::Errors'.underscore # => "active_model/errors"
286
+ #
287
+ # As a rule of thumb you can think of +underscore+ as the inverse of
288
+ # +camelize+, though there are cases where that does not hold:
289
+ #
290
+ # 'SSLError'.underscore.camelize # => "SslError"
291
+ def underscore(camel_cased_word)
292
+ word = camel_cased_word.to_s.dup
293
+ word.gsub!('::', '/')
294
+ word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
295
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
296
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
297
+ word.tr!("-", "_")
298
+ word.downcase!
299
+ word
300
+ end
301
+
302
+ # Capitalizes the first word and turns underscores into spaces and strips a
303
+ # trailing "_id", if any. Like +titleize+, this is meant for creating pretty
304
+ # output.
305
+ #
306
+ # 'employee_salary'.humanize # => "Employee salary"
307
+ # 'author_id'.humanize # => "Author"
308
+ def humanize(lower_case_and_underscored_word)
309
+ result = lower_case_and_underscored_word.to_s.dup
310
+ inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
311
+ result.gsub!(/_id$/, "")
312
+ result.tr!('_', ' ')
313
+ result.gsub(/([a-z\d]*)/i) { |match|
314
+ "#{inflections.acronyms[match] || match.downcase}"
315
+ }.gsub(/^\w/) { $&.upcase }
316
+ end
317
+
318
+ # Capitalizes all the words and replaces some characters in the string to
319
+ # create a nicer looking title. +titleize+ is meant for creating pretty
320
+ # output. It is not used in the Rails internals.
321
+ #
322
+ # +titleize+ is also aliased as +titlecase+.
323
+ #
324
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
325
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
326
+ # 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
327
+ # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
328
+ def titleize(word)
329
+ humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
330
+ end
331
+
332
+ # Create the name of a table like Rails does for models to table names. This
333
+ # method uses the +pluralize+ method on the last word in the string.
334
+ #
335
+ # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
336
+ # 'egg_and_ham'.tableize # => "egg_and_hams"
337
+ # 'fancyCategory'.tableize # => "fancy_categories"
338
+ def tableize(class_name)
339
+ pluralize(underscore(class_name))
340
+ end
341
+
342
+ # Create a class name from a plural table name like Rails does for table
343
+ # names to models. Note that this returns a string and not a Class (To
344
+ # convert to an actual class follow +classify+ with +constantize+).
345
+ #
346
+ # 'egg_and_hams'.classify # => "EggAndHam"
347
+ # 'posts'.classify # => "Post"
348
+ #
349
+ # Singular names are not handled correctly:
350
+ #
351
+ # 'business'.classify # => "Busines"
352
+ def classify(table_name)
353
+ # strip out any leading schema name
354
+ camelize(singularize(table_name.to_s.sub(/.*\./, '')))
355
+ end
356
+
357
+ # Replaces underscores with dashes in the string.
358
+ #
359
+ # 'puni_puni'.dasherize # => "puni-puni"
360
+ def dasherize(underscored_word)
361
+ underscored_word.tr('_', '-')
362
+ end
363
+
364
+ # Removes the module part from the expression in the string.
365
+ #
366
+ # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
367
+ # 'Inflections'.demodulize # => "Inflections"
368
+ #
369
+ # See also +deconstantize+.
370
+ def demodulize(path)
371
+ path = path.to_s
372
+ if i = path.rindex('::')
373
+ path[(i+2)..-1]
374
+ else
375
+ path
376
+ end
377
+ end
378
+
379
+ # Removes the rightmost segment from the constant expression in the string.
380
+ #
381
+ # 'Net::HTTP'.deconstantize # => "Net"
382
+ # '::Net::HTTP'.deconstantize # => "::Net"
383
+ # 'String'.deconstantize # => ""
384
+ # '::String'.deconstantize # => ""
385
+ # ''.deconstantize # => ""
386
+ #
387
+ # See also +demodulize+.
388
+ def deconstantize(path)
389
+ path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename
390
+ end
391
+
392
+ # Creates a foreign key name from a class name.
393
+ # +separate_class_name_and_id_with_underscore+ sets whether
394
+ # the method should put '_' between the name and 'id'.
395
+ #
396
+ # 'Message'.foreign_key # => "message_id"
397
+ # 'Message'.foreign_key(false) # => "messageid"
398
+ # 'Admin::Post'.foreign_key # => "post_id"
399
+ def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
400
+ underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
401
+ end
402
+
403
+ # Tries to find a constant with the name specified in the argument string.
404
+ #
405
+ # 'Module'.constantize # => Module
406
+ # 'Test::Unit'.constantize # => Test::Unit
407
+ #
408
+ # The name is assumed to be the one of a top-level constant, no matter
409
+ # whether it starts with "::" or not. No lexical context is taken into
410
+ # account:
411
+ #
412
+ # C = 'outside'
413
+ # module M
414
+ # C = 'inside'
415
+ # C # => 'inside'
416
+ # 'C'.constantize # => 'outside', same as ::C
417
+ # end
418
+ #
419
+ # NameError is raised when the name is not in CamelCase or the constant is
420
+ # unknown.
421
+ def constantize(camel_cased_word)
422
+ names = camel_cased_word.split('::')
423
+ names.shift if names.empty? || names.first.empty?
424
+
425
+ names.inject(Object) do |constant, name|
426
+ if constant == Object
427
+ constant.const_get(name)
428
+ else
429
+ candidate = constant.const_get(name)
430
+ next candidate if constant.const_defined?(name, false)
431
+ next candidate unless Object.const_defined?(name)
432
+
433
+ # Go down the ancestors to check it it's owned
434
+ # directly before we reach Object or the end of ancestors.
435
+ constant = constant.ancestors.inject do |const, ancestor|
436
+ break const if ancestor == Object
437
+ break ancestor if ancestor.const_defined?(name, false)
438
+ const
439
+ end
440
+
441
+ # owner is in Object, so raise
442
+ constant.const_get(name, false)
443
+ end
444
+ end
445
+ end
446
+
447
+ # Tries to find a constant with the name specified in the argument string.
448
+ #
449
+ # 'Module'.safe_constantize # => Module
450
+ # 'Test::Unit'.safe_constantize # => Test::Unit
451
+ #
452
+ # The name is assumed to be the one of a top-level constant, no matter
453
+ # whether it starts with "::" or not. No lexical context is taken into
454
+ # account:
455
+ #
456
+ # C = 'outside'
457
+ # module M
458
+ # C = 'inside'
459
+ # C # => 'inside'
460
+ # 'C'.safe_constantize # => 'outside', same as ::C
461
+ # end
462
+ #
463
+ # +nil+ is returned when the name is not in CamelCase or the constant (or
464
+ # part of it) is unknown.
465
+ #
466
+ # 'blargle'.safe_constantize # => nil
467
+ # 'UnknownModule'.safe_constantize # => nil
468
+ # 'UnknownModule::Foo::Bar'.safe_constantize # => nil
469
+ def safe_constantize(camel_cased_word)
470
+ constantize(camel_cased_word)
471
+ rescue NameError => e
472
+ raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
473
+ e.name.to_s == camel_cased_word.to_s
474
+ rescue ArgumentError => e
475
+ raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
476
+ end
477
+
478
+ # Returns the suffix that should be added to a number to denote the position
479
+ # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
480
+ #
481
+ # ordinal(1) # => "st"
482
+ # ordinal(2) # => "nd"
483
+ # ordinal(1002) # => "nd"
484
+ # ordinal(1003) # => "rd"
485
+ # ordinal(-11) # => "th"
486
+ # ordinal(-1021) # => "st"
487
+ def ordinal(number)
488
+ abs_number = number.to_i.abs
489
+
490
+ if (11..13).include?(abs_number % 100)
491
+ "th"
492
+ else
493
+ case abs_number % 10
494
+ when 1; "st"
495
+ when 2; "nd"
496
+ when 3; "rd"
497
+ else "th"
498
+ end
499
+ end
500
+ end
501
+
502
+ # Turns a number into an ordinal string used to denote the position in an
503
+ # ordered sequence such as 1st, 2nd, 3rd, 4th.
504
+ #
505
+ # ordinalize(1) # => "1st"
506
+ # ordinalize(2) # => "2nd"
507
+ # ordinalize(1002) # => "1002nd"
508
+ # ordinalize(1003) # => "1003rd"
509
+ # ordinalize(-11) # => "-11th"
510
+ # ordinalize(-1021) # => "-1021st"
511
+ def ordinalize(number)
512
+ "#{number}#{ordinal(number)}"
513
+ end
514
+
515
+ private
516
+
517
+ # Mount a regular expression that will match part by part of the constant.
518
+ # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)?
519
+ def const_regexp(camel_cased_word) #:nodoc:
520
+ parts = camel_cased_word.split("::")
521
+ last = parts.pop
522
+
523
+ parts.reverse.inject(last) do |acc, part|
524
+ part.empty? ? acc : "#{part}(::#{acc})?"
525
+ end
526
+ end
527
+
528
+ # Applies inflection rules for +singularize+ and +pluralize+.
529
+ #
530
+ # apply_inflections('post', inflections.plurals) # => "posts"
531
+ # apply_inflections('posts', inflections.singulars) # => "post"
532
+ def apply_inflections(word, rules)
533
+ result = word.to_s.dup
534
+
535
+ if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
536
+ result
537
+ else
538
+ rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
539
+ result
540
+ end
541
+ end
542
+ end
543
+ end
544
+
545
+ RailsStats::Inflector.inflections(:en) do |inflect|
546
+ inflect.plural(/$/, 's')
547
+ inflect.plural(/s$/i, 's')
548
+ inflect.plural(/^(ax|test)is$/i, '\1es')
549
+ inflect.plural(/(octop|vir)us$/i, '\1i')
550
+ inflect.plural(/(octop|vir)i$/i, '\1i')
551
+ inflect.plural(/(alias|status)$/i, '\1es')
552
+ inflect.plural(/(bu)s$/i, '\1ses')
553
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
554
+ inflect.plural(/([ti])um$/i, '\1a')
555
+ inflect.plural(/([ti])a$/i, '\1a')
556
+ inflect.plural(/sis$/i, 'ses')
557
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
558
+ inflect.plural(/(hive)$/i, '\1s')
559
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
560
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
561
+ inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
562
+ inflect.plural(/^(m|l)ouse$/i, '\1ice')
563
+ inflect.plural(/^(m|l)ice$/i, '\1ice')
564
+ inflect.plural(/^(ox)$/i, '\1en')
565
+ inflect.plural(/^(oxen)$/i, '\1')
566
+ inflect.plural(/(quiz)$/i, '\1zes')
567
+
568
+ inflect.singular(/s$/i, '')
569
+ inflect.singular(/(ss)$/i, '\1')
570
+ inflect.singular(/(n)ews$/i, '\1ews')
571
+ inflect.singular(/([ti])a$/i, '\1um')
572
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
573
+ inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
574
+ inflect.singular(/([^f])ves$/i, '\1fe')
575
+ inflect.singular(/(hive)s$/i, '\1')
576
+ inflect.singular(/(tive)s$/i, '\1')
577
+ inflect.singular(/([lr])ves$/i, '\1f')
578
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
579
+ inflect.singular(/(s)eries$/i, '\1eries')
580
+ inflect.singular(/(m)ovies$/i, '\1ovie')
581
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
582
+ inflect.singular(/^(m|l)ice$/i, '\1ouse')
583
+ inflect.singular(/(bus)(es)?$/i, '\1')
584
+ inflect.singular(/(o)es$/i, '\1')
585
+ inflect.singular(/(shoe)s$/i, '\1')
586
+ inflect.singular(/(cris|test)(is|es)$/i, '\1is')
587
+ inflect.singular(/^(a)x[ie]s$/i, '\1xis')
588
+ inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
589
+ inflect.singular(/(alias|status)(es)?$/i, '\1')
590
+ inflect.singular(/^(ox)en/i, '\1')
591
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
592
+ inflect.singular(/(matr)ices$/i, '\1ix')
593
+ inflect.singular(/(quiz)zes$/i, '\1')
594
+ inflect.singular(/(database)s$/i, '\1')
595
+
596
+ inflect.irregular('person', 'people')
597
+ inflect.irregular('man', 'men')
598
+ inflect.irregular('child', 'children')
599
+ inflect.irregular('sex', 'sexes')
600
+ inflect.irregular('move', 'moves')
601
+ inflect.irregular('cow', 'kine')
602
+ inflect.irregular('zombie', 'zombies')
603
+
604
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
605
+ end
@@ -0,0 +1,37 @@
1
+ # railties/lib/rails/tasks/statistics.rake
2
+
3
+ module RailsStats
4
+ module Rake
5
+ STATS_DIRECTORIES = [
6
+ %w(Controllers app/controllers),
7
+ %w(Helpers app/helpers),
8
+ %w(Models app/models),
9
+ %w(Mailers app/mailers),
10
+ %w(Observers app/observers),
11
+ %w(Javascripts app/assets/javascripts),
12
+ %w(Libraries lib/),
13
+ %w(APIs app/apis),
14
+ %w(Controller\ tests test/controllers),
15
+ %w(Helper\ tests test/helpers),
16
+ %w(Model\ tests test/models),
17
+ %w(Mailer\ tests test/mailers),
18
+ %w(Integration\ tests test/integration),
19
+ %w(Functional\ tests\ (old) test/functional),
20
+ %w(Unit\ tests \ (old) test/unit),
21
+ %w(Controller\ tests spec/controllers),
22
+ %w(Helper\ tests spec/helpers),
23
+ %w(Model\ tests spec/models),
24
+ %w(Mailer\ tests spec/mailers),
25
+ %w(Integration\ tests spec/integration),
26
+ %w(Integration\ tests spec/integrations),
27
+ %w(Request\ tests spec/requests),
28
+ %w(Library\ tests spec/lib),
29
+ %w(Cucumber\ tests features),
30
+ ]
31
+
32
+ def calculate(root_directory)
33
+ puts "\nDirectory: #{root_directory}\n\n"
34
+ CodeStatistics.new(root_directory).to_s
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ module RailsStats
2
+ class RootStatistics
3
+ attr_reader :statistics, :total, :test
4
+
5
+ ROOT_FOLDERS = {
6
+ "lib" => "Libraries",
7
+ "config" => "Configuration"
8
+ }
9
+
10
+ def initialize(directory)
11
+ @test = false
12
+ @directory = directory
13
+ @statistics = calculate_statistics
14
+ @total = calculate_total
15
+ end
16
+
17
+ private
18
+
19
+ def calculate_total
20
+ out = CodeStatisticsCalculator.new
21
+ @statistics.each do |key, stats|
22
+ out.add(stats)
23
+ end
24
+ out
25
+ end
26
+
27
+ def calculate_statistics
28
+ Util.calculate_statistics(directories) do |folder|
29
+ ROOT_FOLDERS[File.basename(folder)]
30
+ end
31
+ end
32
+
33
+ def directories
34
+ out = []
35
+ ROOT_FOLDERS.each do |folder, name|
36
+ out << File.join(@directory, folder)
37
+ end
38
+ out
39
+ end
40
+ end
41
+
42
+ end