rails_stats 0.0.4

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