activesupport 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- data/CHANGELOG +70 -0
- data/lib/active_support.rb +31 -0
- data/lib/active_support/binding_of_caller.rb +84 -0
- data/lib/active_support/breakpoint.rb +523 -0
- data/lib/active_support/class_attribute_accessors.rb +57 -0
- data/lib/active_support/class_inheritable_attributes.rb +117 -0
- data/lib/active_support/clean_logger.rb +10 -0
- data/lib/active_support/core_ext.rb +1 -0
- data/lib/active_support/core_ext/date.rb +6 -0
- data/lib/active_support/core_ext/date/conversions.rb +31 -0
- data/lib/active_support/core_ext/hash.rb +7 -0
- data/lib/active_support/core_ext/hash/indifferent_access.rb +38 -0
- data/lib/active_support/core_ext/hash/keys.rb +53 -0
- data/lib/active_support/core_ext/numeric.rb +7 -0
- data/lib/active_support/core_ext/numeric/bytes.rb +33 -0
- data/lib/active_support/core_ext/numeric/time.rb +59 -0
- data/lib/active_support/core_ext/object_and_class.rb +24 -0
- data/lib/active_support/core_ext/string.rb +5 -0
- data/lib/active_support/core_ext/string/inflections.rb +49 -0
- data/lib/active_support/core_ext/time.rb +7 -0
- data/lib/active_support/core_ext/time/calculations.rb +133 -0
- data/lib/active_support/core_ext/time/conversions.rb +34 -0
- data/lib/active_support/dependencies.rb +212 -0
- data/lib/active_support/inflector.rb +93 -0
- data/lib/active_support/misc.rb +8 -0
- data/lib/active_support/module_attribute_accessors.rb +57 -0
- data/lib/active_support/values/time_zone.rb +180 -0
- metadata +71 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../inflector'
|
2
|
+
module ActiveSupport #:nodoc:
|
3
|
+
module CoreExtensions #:nodoc:
|
4
|
+
module String #:nodoc:
|
5
|
+
# Makes it possible to do "posts".singularize that returns "post" and "MegaCoolClass".underscore that returns "mega_cool_class".
|
6
|
+
module Inflections
|
7
|
+
def pluralize
|
8
|
+
Inflector.pluralize(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def singularize
|
12
|
+
Inflector.singularize(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def camelize
|
16
|
+
Inflector.camelize(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def underscore
|
20
|
+
Inflector.underscore(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def demodulize
|
24
|
+
Inflector.demodulize(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def tableize
|
28
|
+
Inflector.tableize(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def classify
|
32
|
+
Inflector.classify(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def humanize
|
36
|
+
Inflector.humanize(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def foreign_key(separate_class_name_and_id_with_underscore = true)
|
40
|
+
Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
|
41
|
+
end
|
42
|
+
|
43
|
+
def constantize
|
44
|
+
Inflector.constantize(self)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module ActiveSupport #:nodoc:
|
2
|
+
module CoreExtensions #:nodoc:
|
3
|
+
module Time #:nodoc:
|
4
|
+
# Enables the use of time calculations within Time itself
|
5
|
+
module Calculations
|
6
|
+
# Seconds since midnight: Time.now.seconds_since_midnight
|
7
|
+
def seconds_since_midnight
|
8
|
+
self.hour.hours + self.min.minutes + self.sec + (self.usec/1.0e+6)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
|
12
|
+
# (hour, minute, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
|
13
|
+
# minute is passed, then sec and usec is set to 0.
|
14
|
+
def change(options)
|
15
|
+
::Time.send(
|
16
|
+
self.utc? ? :utc : :local,
|
17
|
+
options[:year] || self.year,
|
18
|
+
options[:month] || self.month,
|
19
|
+
options[:mday] || self.mday,
|
20
|
+
options[:hour] || self.hour,
|
21
|
+
options[:min] || (options[:hour] ? 0 : self.min),
|
22
|
+
options[:sec] || ((options[:hour] || options[:min]) ? 0 : self.sec),
|
23
|
+
options[:usec] || ((options[:hour] || options[:min] || options[:usec]) ? 0 : self.usec)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
|
28
|
+
# Do not use this method in combination with x.months, use months_ago instead!
|
29
|
+
def ago(seconds)
|
30
|
+
seconds.until(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a new Time representing the time a number of seconds since the instance time, this is basically a wrapper around
|
34
|
+
#the Numeric extension. Do not use this method in combination with x.months, use months_since instead!
|
35
|
+
def since(seconds)
|
36
|
+
seconds.since(self)
|
37
|
+
end
|
38
|
+
alias :in :since
|
39
|
+
|
40
|
+
# Returns a new Time representing the time a number of specified months ago
|
41
|
+
def months_ago(months)
|
42
|
+
if months >= self.month
|
43
|
+
change(:year => self.year - 1, :month => 12).months_ago(months - self.month)
|
44
|
+
else
|
45
|
+
change(:year => self.year, :month => self.month - months)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def months_since(months)
|
50
|
+
if months + self.month > 12
|
51
|
+
change(:year => self.year + 1, :month => 1).months_since(months - (self.month == 1 ? 12 : (self.month + 1)))
|
52
|
+
else
|
53
|
+
change(:year => self.year, :month => self.month + months)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns a new Time representing the time a number of specified years ago
|
58
|
+
def years_ago(years)
|
59
|
+
change(:year => self.year - years)
|
60
|
+
end
|
61
|
+
|
62
|
+
def years_since(years)
|
63
|
+
change(:year => self.year + years)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Short-hand for months_ago(1)
|
67
|
+
def last_year
|
68
|
+
years_since(1)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Short-hand for months_since(1)
|
72
|
+
def next_year
|
73
|
+
years_since(1)
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Short-hand for months_ago(1)
|
78
|
+
def last_month
|
79
|
+
months_ago(1)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Short-hand for months_since(1)
|
83
|
+
def next_month
|
84
|
+
months_since(1)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns a new Time representing the "start" of this week (Monday, 0:00)
|
88
|
+
def beginning_of_week
|
89
|
+
(self - self.wday.days).midnight + 1.day
|
90
|
+
end
|
91
|
+
alias :monday :beginning_of_week
|
92
|
+
alias :at_beginning_of_week :beginning_of_week
|
93
|
+
|
94
|
+
# Returns a new Time representing the start of the given day in next week (default is Monday).
|
95
|
+
def next_week(day = :monday)
|
96
|
+
days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6}
|
97
|
+
since(1.week).beginning_of_week.since(days_into_week[day].day).change(:hour => 0)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns a new Time representing the start of the day (0:00)
|
101
|
+
def beginning_of_day
|
102
|
+
self - self.seconds_since_midnight
|
103
|
+
end
|
104
|
+
alias :midnight :beginning_of_day
|
105
|
+
alias :at_midnight :beginning_of_day
|
106
|
+
alias :at_beginning_of_day :beginning_of_day
|
107
|
+
|
108
|
+
# Returns a new Time representing the start of the month (1st of the month, 0:00)
|
109
|
+
def beginning_of_month
|
110
|
+
#self - ((self.mday-1).days + self.seconds_since_midnight)
|
111
|
+
change(:mday => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
|
112
|
+
end
|
113
|
+
alias :at_beginning_of_month :beginning_of_month
|
114
|
+
|
115
|
+
# Returns a new Time representing the start of the year (1st of january, 0:00)
|
116
|
+
def beginning_of_year
|
117
|
+
change(:month => 1,:mday => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
|
118
|
+
end
|
119
|
+
alias :at_beginning_of_year :beginning_of_year
|
120
|
+
|
121
|
+
# Convenience method which returns a new Time representing the time 1 day ago
|
122
|
+
def yesterday
|
123
|
+
self.ago(1.day)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Convenience method which returns a new Time representing the time 1 day since the instance time
|
127
|
+
def tomorrow
|
128
|
+
self.since(1.day)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module ActiveSupport #:nodoc:
|
4
|
+
module CoreExtensions #:nodoc:
|
5
|
+
module Time #:nodoc:
|
6
|
+
# Getting times in different convenient string representations and other objects
|
7
|
+
module Conversions
|
8
|
+
def self.append_features(klass)
|
9
|
+
super
|
10
|
+
klass.send(:alias_method, :to_default_s, :to_s)
|
11
|
+
klass.send(:alias_method, :to_s, :to_formatted_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_formatted_s(format = :default)
|
15
|
+
case format
|
16
|
+
when :default then to_default_s
|
17
|
+
when :db then strftime("%Y-%m-%d %H:%M:%S")
|
18
|
+
when :short then strftime("%e %b %H:%M").strip
|
19
|
+
when :long then strftime("%B %e, %Y %H:%M").strip
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_date
|
24
|
+
::Date.new(year, month, day)
|
25
|
+
end
|
26
|
+
|
27
|
+
# To be able to keep Dates and Times interchangeable on conversions
|
28
|
+
def to_time
|
29
|
+
self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/module_attribute_accessors'
|
2
|
+
|
3
|
+
module Dependencies
|
4
|
+
extend self
|
5
|
+
|
6
|
+
@@loaded = [ ]
|
7
|
+
mattr_accessor :loaded
|
8
|
+
|
9
|
+
@@mechanism = :load
|
10
|
+
mattr_accessor :mechanism
|
11
|
+
|
12
|
+
def load?
|
13
|
+
mechanism == :load
|
14
|
+
end
|
15
|
+
|
16
|
+
def depend_on(file_name, swallow_load_errors = false)
|
17
|
+
if !loaded.include?(file_name)
|
18
|
+
loaded << file_name
|
19
|
+
|
20
|
+
begin
|
21
|
+
require_or_load(file_name)
|
22
|
+
rescue LoadError
|
23
|
+
raise unless swallow_load_errors
|
24
|
+
rescue Object => e
|
25
|
+
raise ScriptError, "#{e.message}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def associate_with(file_name)
|
31
|
+
depend_on(file_name, true)
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear
|
35
|
+
self.loaded = [ ]
|
36
|
+
end
|
37
|
+
|
38
|
+
def require_or_load(file_name)
|
39
|
+
load? ? load("#{file_name}.rb") : require(file_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove_subclasses_for(*classes)
|
43
|
+
classes.each { |klass| klass.remove_subclasses }
|
44
|
+
end
|
45
|
+
|
46
|
+
# LoadingModules implement namespace-safe dynamic loading.
|
47
|
+
# They support automatic loading via const_missing, allowing contained items to be automatically
|
48
|
+
# loaded when required. No extra syntax is required, as expressions such as Controller::Admin::UserController
|
49
|
+
# load the relavent files automatically.
|
50
|
+
#
|
51
|
+
# Ruby-style modules are supported, as a folder named 'submodule' will load 'submodule.rb' when available.
|
52
|
+
class LoadingModule < Module
|
53
|
+
attr_reader :path
|
54
|
+
attr_reader :root
|
55
|
+
|
56
|
+
def self.root(*load_paths)
|
57
|
+
RootLoadingModule.new(*load_paths)
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(root, path=[])
|
61
|
+
@path = path.clone.freeze
|
62
|
+
@root = root
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_paths() self.root.load_paths end
|
66
|
+
|
67
|
+
# Load missing constants if possible.
|
68
|
+
def const_missing(name)
|
69
|
+
const_load!(name) ? const_get(name) : super(name)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Load the controller class or a parent module.
|
73
|
+
def const_load!(name, file_name = nil)
|
74
|
+
path = self.path + [file_name || name]
|
75
|
+
|
76
|
+
load_paths.each do |load_path|
|
77
|
+
fs_path = load_path.filesystem_path(path)
|
78
|
+
next unless fs_path
|
79
|
+
|
80
|
+
if File.directory?(fs_path)
|
81
|
+
self.const_set name, LoadingModule.new(self.root, self.path + [name])
|
82
|
+
break
|
83
|
+
elsif File.file?(fs_path)
|
84
|
+
self.root.load_file!(fs_path)
|
85
|
+
break
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
return self.const_defined?(name)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Is this name present or loadable?
|
93
|
+
# This method is used by Routes to find valid controllers.
|
94
|
+
def const_available?(name)
|
95
|
+
self.const_defined?(name) || load_paths.any? {|lp| lp.filesystem_path(path + [name])}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class RootLoadingModule < LoadingModule
|
100
|
+
attr_reader :load_paths
|
101
|
+
|
102
|
+
def initialize(*paths)
|
103
|
+
@load_paths = paths.flatten.collect {|p| p.kind_of?(ConstantLoadPath) ? p : ConstantLoadPath.new(p)}
|
104
|
+
end
|
105
|
+
|
106
|
+
def root() self end
|
107
|
+
|
108
|
+
def path() [] end
|
109
|
+
|
110
|
+
# Load the source file at the given file path
|
111
|
+
def load_file!(file_path)
|
112
|
+
begin root.module_eval(IO.read(file_path), file_path, 1)
|
113
|
+
rescue Object => exception
|
114
|
+
exception.blame_file! file_path
|
115
|
+
raise
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Erase all items in this module
|
120
|
+
def clear!
|
121
|
+
constants.each do |name|
|
122
|
+
Object.send(:remove_const, name) if Object.const_defined?(name) && self.path.empty?
|
123
|
+
self.send(:remove_const, name)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# This object defines a path from which Constants can be loaded.
|
129
|
+
class ConstantLoadPath
|
130
|
+
# Create a new load path with the filesystem path
|
131
|
+
def initialize(root) @root = root end
|
132
|
+
|
133
|
+
# Return nil if the path does not exist, or the path to a directory
|
134
|
+
# if the path leads to a module, or the path to a file if it leads to an object.
|
135
|
+
def filesystem_path(path, allow_module=true)
|
136
|
+
fs_path = [@root]
|
137
|
+
fs_path += path[0..-2].collect {|name| const_name_to_module_name name}
|
138
|
+
|
139
|
+
if allow_module
|
140
|
+
result = File.join(fs_path, const_name_to_module_name(path.last))
|
141
|
+
return result if File.directory? result # Return the module path if one exists
|
142
|
+
end
|
143
|
+
|
144
|
+
result = File.join(fs_path, const_name_to_file_name(path.last))
|
145
|
+
|
146
|
+
return File.file?(result) ? result : nil
|
147
|
+
end
|
148
|
+
|
149
|
+
def const_name_to_file_name(name)
|
150
|
+
name.to_s.underscore + '.rb'
|
151
|
+
end
|
152
|
+
|
153
|
+
def const_name_to_module_name(name)
|
154
|
+
name.to_s.underscore
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
Object.send(:define_method, :require_or_load) { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
|
160
|
+
Object.send(:define_method, :require_dependency) { |file_name| Dependencies.depend_on(file_name) } unless Object.respond_to?(:require_dependency)
|
161
|
+
Object.send(:define_method, :require_association) { |file_name| Dependencies.associate_with(file_name) } unless Object.respond_to?(:require_association)
|
162
|
+
|
163
|
+
class Object #:nodoc:
|
164
|
+
class << self
|
165
|
+
# Use const_missing to autoload associations so we don't have to
|
166
|
+
# require_association when using single-table inheritance.
|
167
|
+
def const_missing(class_id)
|
168
|
+
if Object.const_defined?(:Controllers) and Object::Controllers.const_available?(class_id)
|
169
|
+
return Object::Controllers.const_get(class_id)
|
170
|
+
end
|
171
|
+
|
172
|
+
begin
|
173
|
+
require_or_load(class_id.to_s.demodulize.underscore)
|
174
|
+
if Object.const_defined?(class_id) then return Object.const_get(class_id) else raise LoadError end
|
175
|
+
rescue LoadError
|
176
|
+
raise NameError, "uninitialized constant #{class_id}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def load(file, *extras)
|
182
|
+
begin super(file, *extras)
|
183
|
+
rescue Object => exception
|
184
|
+
exception.blame_file! file
|
185
|
+
raise
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def require(file, *extras)
|
190
|
+
begin super(file, *extras)
|
191
|
+
rescue Object => exception
|
192
|
+
exception.blame_file! file
|
193
|
+
raise
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Add file-blaming to exceptions
|
199
|
+
class Exception
|
200
|
+
def blame_file!(file)
|
201
|
+
(@blamed_files ||= []).unshift file
|
202
|
+
end
|
203
|
+
|
204
|
+
def blamed_files
|
205
|
+
@blamed_files ||= []
|
206
|
+
end
|
207
|
+
|
208
|
+
def describe_blame
|
209
|
+
return nil if blamed_files.empty?
|
210
|
+
"This error occured while loading the following files:\n #{blamed_files.join "\n "}"
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
|
2
|
+
# and class names to foreign keys.
|
3
|
+
module Inflector
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def pluralize(word)
|
7
|
+
result = word.to_s.dup
|
8
|
+
plural_rules.each do |(rule, replacement)|
|
9
|
+
break if result.gsub!(rule, replacement)
|
10
|
+
end
|
11
|
+
return result
|
12
|
+
end
|
13
|
+
|
14
|
+
def singularize(word)
|
15
|
+
result = word.to_s.dup
|
16
|
+
singular_rules.each do |(rule, replacement)|
|
17
|
+
break if result.gsub!(rule, replacement)
|
18
|
+
end
|
19
|
+
return result
|
20
|
+
end
|
21
|
+
|
22
|
+
def camelize(lower_case_and_underscored_word)
|
23
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
24
|
+
end
|
25
|
+
|
26
|
+
def underscore(camel_cased_word)
|
27
|
+
camel_cased_word.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
28
|
+
end
|
29
|
+
|
30
|
+
def humanize(lower_case_and_underscored_word)
|
31
|
+
lower_case_and_underscored_word.to_s.gsub(/_/, " ").capitalize
|
32
|
+
end
|
33
|
+
|
34
|
+
def demodulize(class_name_in_module)
|
35
|
+
class_name_in_module.to_s.gsub(/^.*::/, '')
|
36
|
+
end
|
37
|
+
|
38
|
+
def tableize(class_name)
|
39
|
+
pluralize(underscore(class_name))
|
40
|
+
end
|
41
|
+
|
42
|
+
def classify(table_name)
|
43
|
+
camelize(singularize(table_name))
|
44
|
+
end
|
45
|
+
|
46
|
+
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
|
47
|
+
Inflector.underscore(Inflector.demodulize(class_name)) +
|
48
|
+
(separate_class_name_and_id_with_underscore ? "_id" : "id")
|
49
|
+
end
|
50
|
+
|
51
|
+
def constantize(camel_cased_word)
|
52
|
+
camel_cased_word.split("::").inject(Object) do |final_type, part|
|
53
|
+
final_type = final_type.const_get(part)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def plural_rules #:doc:
|
59
|
+
[
|
60
|
+
[/(x|ch|ss|sh)$/, '\1es'], # search, switch, fix, box, process, address
|
61
|
+
[/series$/, '\1series'],
|
62
|
+
[/([^aeiouy]|qu)ies$/, '\1y'],
|
63
|
+
[/([^aeiouy]|qu)y$/, '\1ies'], # query, ability, agency
|
64
|
+
[/(?:([^f])fe|([lr])f)$/, '\1\2ves'], # half, safe, wife
|
65
|
+
[/sis$/, 'ses'], # basis, diagnosis
|
66
|
+
[/([ti])um$/, '\1a'], # datum, medium
|
67
|
+
[/person$/, 'people'], # person, salesperson
|
68
|
+
[/man$/, 'men'], # man, woman, spokesman
|
69
|
+
[/child$/, 'children'], # child
|
70
|
+
[/s$/, 's'], # no change (compatibility)
|
71
|
+
[/$/, 's']
|
72
|
+
]
|
73
|
+
end
|
74
|
+
|
75
|
+
def singular_rules #:doc:
|
76
|
+
[
|
77
|
+
[/(x|ch|ss)es$/, '\1'],
|
78
|
+
[/movies$/, 'movie'],
|
79
|
+
[/series$/, 'series'],
|
80
|
+
[/([^aeiouy]|qu)ies$/, '\1y'],
|
81
|
+
[/([lr])ves$/, '\1f'],
|
82
|
+
[/([^f])ves$/, '\1fe'],
|
83
|
+
[/(analy|ba|diagno|parenthe|progno|synop|the)ses$/, '\1sis'],
|
84
|
+
[/([ti])a$/, '\1um'],
|
85
|
+
[/people$/, 'person'],
|
86
|
+
[/men$/, 'man'],
|
87
|
+
[/status$/, 'status'],
|
88
|
+
[/children$/, 'child'],
|
89
|
+
[/news$/, 'news'],
|
90
|
+
[/s$/, '']
|
91
|
+
]
|
92
|
+
end
|
93
|
+
end
|