dyoder-waves 0.7.3 → 0.7.6
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.
- data/app/bin/waves-console +1 -0
- data/app/bin/waves-server +1 -0
- data/app/configurations/development.rb.erb +5 -6
- data/app/configurations/mapping.rb.erb +1 -2
- data/app/configurations/production.rb.erb +2 -2
- data/app/controllers/.gitignore +0 -0
- data/app/doc/.gitignore +0 -0
- data/app/helpers/.gitignore +0 -0
- data/app/lib/application.rb.erb +4 -2
- data/app/lib/tasks/.gitignore +0 -0
- data/app/log/.gitignore +0 -0
- data/app/models/.gitignore +0 -0
- data/app/public/css/.gitignore +0 -0
- data/app/public/flash/.gitignore +0 -0
- data/app/public/images/.gitignore +0 -0
- data/app/public/javascript/.gitignore +0 -0
- data/app/schema/migrations/.gitignore +0 -0
- data/app/tmp/sessions/.gitignore +0 -0
- data/app/views/.gitignore +0 -0
- data/bin/waves +29 -46
- data/bin/waves-console +1 -1
- data/bin/waves-server +1 -1
- data/lib/commands/waves-console.rb +0 -3
- data/lib/controllers/base.rb +11 -0
- data/lib/controllers/mixin.rb +39 -32
- data/lib/dispatchers/base.rb +37 -22
- data/lib/dispatchers/default.rb +25 -11
- data/lib/foundations/default.rb +6 -8
- data/lib/foundations/simple.rb +13 -0
- data/lib/helpers/asset_helper.rb +67 -0
- data/lib/helpers/common.rb +4 -0
- data/lib/helpers/default.rb +13 -0
- data/lib/helpers/form.rb +1 -0
- data/lib/helpers/number_helper.rb +25 -0
- data/lib/helpers/tag_helper.rb +58 -0
- data/lib/helpers/url_helper.rb +77 -0
- data/lib/layers/default_errors.rb +7 -5
- data/lib/layers/mvc.rb +54 -0
- data/lib/layers/orm/active_record.rb +93 -0
- data/lib/layers/orm/active_record/migrations/empty.rb.erb +9 -0
- data/lib/layers/orm/active_record/tasks/generate.rb +28 -0
- data/lib/layers/orm/active_record/tasks/schema.rb +22 -0
- data/lib/layers/orm/data_mapper.rb +38 -0
- data/lib/layers/orm/filebase.rb +22 -0
- data/lib/layers/orm/migration.rb +79 -0
- data/lib/layers/orm/sequel.rb +86 -0
- data/lib/layers/orm/sequel/migrations/empty.rb.erb +9 -0
- data/lib/layers/orm/sequel/tasks/generate.rb +28 -0
- data/lib/layers/orm/sequel/tasks/schema.rb +16 -0
- data/lib/layers/simple.rb +35 -0
- data/lib/layers/simple_errors.rb +11 -5
- data/lib/mapping/mapping.rb +61 -24
- data/lib/mapping/pretty_urls.rb +5 -3
- data/lib/renderers/erubis.rb +3 -1
- data/lib/renderers/mixin.rb +1 -4
- data/lib/runtime/application.rb +12 -8
- data/lib/runtime/blackboard.rb +57 -0
- data/lib/runtime/configuration.rb +60 -55
- data/lib/runtime/logger.rb +8 -1
- data/lib/runtime/request.rb +5 -4
- data/lib/runtime/response.rb +3 -3
- data/lib/runtime/response_mixin.rb +3 -0
- data/lib/runtime/response_proxy.rb +4 -1
- data/lib/runtime/server.rb +18 -5
- data/lib/runtime/session.rb +20 -10
- data/lib/tasks/cluster.rb +2 -1
- data/lib/tasks/generate.rb +76 -11
- data/lib/utilities/hash.rb +31 -0
- data/lib/utilities/inflect.rb +86 -170
- data/lib/utilities/inflect/english.rb +84 -0
- data/lib/utilities/integer.rb +23 -16
- data/lib/utilities/module.rb +19 -15
- data/lib/utilities/object.rb +22 -14
- data/lib/utilities/proc.rb +13 -6
- data/lib/utilities/string.rb +58 -44
- data/lib/utilities/symbol.rb +4 -1
- data/lib/views/base.rb +9 -0
- data/lib/views/mixin.rb +3 -1
- data/lib/waves.rb +15 -13
- metadata +50 -25
- data/lib/utilities/kernel.rb +0 -34
- data/lib/verify/mapping.rb +0 -29
- data/lib/verify/request.rb +0 -40
@@ -1,12 +1,15 @@
|
|
1
1
|
module Waves
|
2
2
|
|
3
|
+
# Mapping actions are evaluated in the context of a ResponseProxy.
|
3
4
|
class ResponseProxy
|
4
5
|
|
5
6
|
attr_reader :request
|
6
7
|
|
7
8
|
include ResponseMixin
|
8
9
|
|
9
|
-
def initialize(request)
|
10
|
+
def initialize(request)
|
11
|
+
@request = request
|
12
|
+
end
|
10
13
|
|
11
14
|
def resource( resource, &block )
|
12
15
|
@resource = resource; yield.call
|
data/lib/runtime/server.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Waves
|
2
|
-
# You can run the Waves::Server via the +waves-server+ command or via <tt>rake cluster:start</tt>. Run <tt>waves-server --help</tt> for options on the <tt>waves-server</tt> command. The <tt>cluster
|
2
|
+
# You can run the Waves::Server via the +waves-server+ command or via <tt>rake cluster:start</tt>. Run <tt>waves-server --help</tt> for options on the <tt>waves-server</tt> command. The <tt>cluster:start</tt> task uses the +mode+ environment variable to determine the active configuration. You can define +port+ to run on a single port, or +ports+ (taking an array) to run on multiple ports.
|
3
3
|
#
|
4
4
|
# *Example*
|
5
5
|
#
|
@@ -17,6 +17,8 @@ module Waves
|
|
17
17
|
# waves-server -c development -p 2021 -d
|
18
18
|
# waves-server -c development -p 2022 -d
|
19
19
|
#
|
20
|
+
# The +cluster:stop+ task stops all of the instances.
|
21
|
+
#
|
20
22
|
class Server < Application
|
21
23
|
|
22
24
|
# Access the server thread.
|
@@ -36,8 +38,15 @@ module Waves
|
|
36
38
|
File.write( :log / "#{port}.pid", $$ )
|
37
39
|
end
|
38
40
|
|
41
|
+
def trap(signal)
|
42
|
+
Kernel::trap(signal) { yield }
|
43
|
+
Thread.new { loop {sleep 1} } if RUBY_PLATFORM =~ /mswin32/
|
44
|
+
end
|
45
|
+
|
39
46
|
# Start and / or access the Waves::Logger instance.
|
40
|
-
def log
|
47
|
+
def log
|
48
|
+
@log ||= Waves::Logger.start
|
49
|
+
end
|
41
50
|
|
42
51
|
# Start the server.
|
43
52
|
def start
|
@@ -47,7 +56,7 @@ module Waves
|
|
47
56
|
handler, options = config.handler
|
48
57
|
handler.run( config.application.to_app, options ) do |server|
|
49
58
|
@server = server
|
50
|
-
trap('INT') { puts; stop } if @server.respond_to? :stop
|
59
|
+
self.trap('INT') { puts; stop } if @server.respond_to? :stop
|
51
60
|
end
|
52
61
|
end
|
53
62
|
|
@@ -70,9 +79,13 @@ module Waves
|
|
70
79
|
def run( options={} )
|
71
80
|
@server.stop if @server; @server = new( options ); @server.start
|
72
81
|
end
|
82
|
+
|
73
83
|
# Allows us to access the Waves::Server instance.
|
74
|
-
def method_missing(*args)
|
75
|
-
|
84
|
+
def method_missing(*args)
|
85
|
+
@server.send(*args)
|
86
|
+
end
|
87
|
+
|
88
|
+
#-- Probably wouldn't need this if I added a block parameter to method_missing.
|
76
89
|
def synchronize(&block) ; @server.synchronize(&block) ; end
|
77
90
|
end
|
78
91
|
|
data/lib/runtime/session.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
module Waves
|
2
2
|
|
3
3
|
# Encapsulates the session associated with a given request. A session has an expiration
|
4
|
-
# and path
|
5
|
-
#
|
6
|
-
#
|
4
|
+
# and path, which must be provided in a Waves::Configuration. Sensible defaults are defined
|
5
|
+
# in Waves::Configurations::Default
|
7
6
|
class Session
|
7
|
+
|
8
|
+
# Concoct a (probably) unique session id
|
9
|
+
def self.generate_session_id
|
10
|
+
# from Camping ...
|
11
|
+
chars = [*'A'..'Z'] + [*'0'..'9'] + [*'a'..'z']
|
12
|
+
(0..48).inject(''){|s,x| s+=chars[ rand(chars.length) ] }
|
13
|
+
end
|
8
14
|
|
9
15
|
# Create a new session object using the given request. This is not necessarily the
|
10
16
|
# same as constructing a new session. The session_id cookie for the request domain
|
@@ -14,6 +20,15 @@ module Waves
|
|
14
20
|
@request = request
|
15
21
|
@data ||= ( File.exist?( session_path ) ? load_session : {} )
|
16
22
|
end
|
23
|
+
|
24
|
+
# Return the session data as a hash
|
25
|
+
def to_hash
|
26
|
+
@data.to_hash
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.base_path
|
30
|
+
Waves::Application.instance.config.session[:path]
|
31
|
+
end
|
17
32
|
|
18
33
|
# Save the session data. You shouldn't typically have to call this directly, since it
|
19
34
|
# is called by Waves::Response#finish.
|
@@ -34,16 +49,11 @@ module Waves
|
|
34
49
|
private
|
35
50
|
|
36
51
|
def session_id
|
37
|
-
@session_id ||= ( @request.cookies['session_id'] || generate_session_id )
|
38
|
-
end
|
39
|
-
|
40
|
-
def generate_session_id # from Camping ...
|
41
|
-
chars = [*'A'..'Z'] + [*'0'..'9'] + [*'a'..'z']
|
42
|
-
(0..48).inject(''){|s,x| s+=chars[ rand(chars.length) ] }
|
52
|
+
@session_id ||= ( @request.cookies['session_id'] || Waves::Session.generate_session_id )
|
43
53
|
end
|
44
54
|
|
45
55
|
def session_path
|
46
|
-
Waves::
|
56
|
+
Waves::Session.base_path / session_id
|
47
57
|
end
|
48
58
|
|
49
59
|
def load_session
|
data/lib/tasks/cluster.rb
CHANGED
@@ -2,7 +2,8 @@ namespace :cluster do
|
|
2
2
|
|
3
3
|
desc 'Start a cluster of waves applications.'
|
4
4
|
task :start do |task|
|
5
|
-
|
5
|
+
using_waves_src = defined?(WAVES) || ENV['WAVES'] || File.exist( File.dirname(__FILE__) / :waves )
|
6
|
+
script = using_waves_src ? :bin / 'waves-server' : 'waves-server'
|
6
7
|
( Waves::Console.config.ports || [ Waves::Console.config.port ] ).each do |port|
|
7
8
|
cmd = "#{script} -p #{port} -c #{ENV['mode']||'development'} -d"
|
8
9
|
puts cmd ; `#{cmd}`
|
data/lib/tasks/generate.rb
CHANGED
@@ -1,15 +1,80 @@
|
|
1
1
|
namespace :generate do
|
2
|
-
|
3
|
-
|
4
|
-
template = File.read :models / 'default.rb'
|
5
|
-
File.write( :models / ENV['name'] + '.rb',
|
6
|
-
template.gsub('class Default < Sequel::Model',
|
7
|
-
"class #{ENV['name'].camel_case} < Sequel::Model(:#{ENV['name'].plural})") )
|
8
|
-
end
|
9
|
-
desc 'Generate a new controller'
|
2
|
+
|
3
|
+
desc 'Generate a new controller, with name=<name>'
|
10
4
|
task :controller do |task|
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
name = ENV['name']
|
6
|
+
controller_name = name.camel_case
|
7
|
+
raise "Cannot generate Default yet" if controller_name == 'Default'
|
8
|
+
|
9
|
+
filename = File.expand_path "controllers/#{name}.rb"
|
10
|
+
if File.exist?(filename)
|
11
|
+
$stderr.puts "#{filename} already exists"
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
|
15
|
+
controller = <<TEXT
|
16
|
+
module #{Waves.application.name}
|
17
|
+
module Controllers
|
18
|
+
class #{controller_name} < Default
|
19
|
+
|
20
|
+
end
|
14
21
|
end
|
15
22
|
end
|
23
|
+
TEXT
|
24
|
+
|
25
|
+
File.write( filename, controller )
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'Generate new view, with name=<name>'
|
29
|
+
task :view do |task|
|
30
|
+
name = ENV['name']
|
31
|
+
view_name = name.camel_case
|
32
|
+
raise "Cannot generate Default yet" if view_name == 'Default'
|
33
|
+
|
34
|
+
filename = File.expand_path "views/#{name}.rb"
|
35
|
+
if File.exist?(filename)
|
36
|
+
$stderr.puts "#{filename} already exists"
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
view = <<TEXT
|
41
|
+
module #{Waves.application.name}
|
42
|
+
module Views
|
43
|
+
class #{view_name} < Default
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
TEXT
|
49
|
+
|
50
|
+
File.write( filename, view )
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'Generate a new helper, with name=<name>'
|
54
|
+
task :helper do |task|
|
55
|
+
name = ENV['name']
|
56
|
+
helper_name = name.camel_case
|
57
|
+
raise "Cannot generate Default yet" if helper_name == 'Default'
|
58
|
+
|
59
|
+
filename = File.expand_path "helpers/#{name}.rb"
|
60
|
+
if File.exist?(filename)
|
61
|
+
$stderr.puts "#{filename} already exists"
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
|
65
|
+
helper = <<TEXT
|
66
|
+
module #{Waves.application.name}
|
67
|
+
module Helpers
|
68
|
+
module #{helper_name}
|
69
|
+
include Waves::Helpers::Default
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
TEXT
|
75
|
+
|
76
|
+
File.write( filename, helper )
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Waves
|
2
|
+
module Utilities # :nodoc:
|
3
|
+
|
4
|
+
# Utility methods mixed into Hash.
|
5
|
+
module Hash
|
6
|
+
|
7
|
+
# Return a copy of the hash where all keys have been converted to strings.
|
8
|
+
def stringify_keys
|
9
|
+
inject({}) do |options, (key, value)|
|
10
|
+
options[key.to_s] = value
|
11
|
+
options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Destructively convert all keys to symbols.
|
16
|
+
def symbolize_keys!
|
17
|
+
keys.each do |key|
|
18
|
+
unless key.is_a?(Symbol)
|
19
|
+
self[key.to_sym] = self[key]
|
20
|
+
delete(key)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Hash # :nodoc:
|
30
|
+
include Waves::Utilities::Hash
|
31
|
+
end
|
data/lib/utilities/inflect.rb
CHANGED
@@ -4,191 +4,107 @@
|
|
4
4
|
# of rules for different languages
|
5
5
|
# NOTE: this is NOT implemented yet.
|
6
6
|
# plural and singular work directly with the English class
|
7
|
+
module Waves
|
8
|
+
module Inflect # :nodoc:
|
9
|
+
module InflectorMethods
|
10
|
+
|
11
|
+
# Define a general exception.
|
12
|
+
def word(singular, plural=nil)
|
13
|
+
plural = singular unless plural
|
14
|
+
singular_word(singular, plural)
|
15
|
+
plural_word(singular, plural)
|
16
|
+
end
|
7
17
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def word(singular, plural=nil)
|
13
|
-
plural = singular unless plural
|
14
|
-
singular_word(singular, plural)
|
15
|
-
plural_word(singular, plural)
|
16
|
-
end
|
17
|
-
|
18
|
-
# Define a singularization exception.
|
19
|
-
def singular_word(singular, plural)
|
20
|
-
@singular_of[plural] = singular
|
21
|
-
end
|
22
|
-
|
23
|
-
# Define a pluralization exception.
|
24
|
-
def plural_word(singular, plural)
|
25
|
-
@plural_of[singular] = plural
|
26
|
-
end
|
27
|
-
|
28
|
-
# Define a general rule.
|
29
|
-
def rule(singular, plural)
|
30
|
-
singular_rule(singular, plural)
|
31
|
-
plural_rule(singular, plural)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Define a singularization rule.
|
35
|
-
def singular_rule(singular, plural)
|
36
|
-
@singular_rules << [singular, plural]
|
37
|
-
end
|
38
|
-
|
39
|
-
# Define a plurualization rule.
|
40
|
-
def plural_rule(singular, plural)
|
41
|
-
@plural_rules << [singular, plural]
|
42
|
-
end
|
43
|
-
|
44
|
-
# Read prepared singularization rules.
|
45
|
-
def singularization_rules
|
46
|
-
return @singularization_rules if @singularization_rules
|
47
|
-
sorted = @singular_rules.sort_by{ |s, p| "#{p}".size }.reverse
|
48
|
-
@singularization_rules = sorted.collect do |s, p|
|
49
|
-
[ /#{p}$/, "#{s}" ]
|
18
|
+
# Define a singularization exception.
|
19
|
+
def singular_word(singular, plural)
|
20
|
+
@singular_of ||= {}
|
21
|
+
@singular_of[plural] = singular
|
50
22
|
end
|
51
|
-
end
|
52
23
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@pluralization_rules = sorted.collect do |s, p|
|
58
|
-
[ /#{s}$/, "#{p}" ]
|
24
|
+
# Define a pluralization exception.
|
25
|
+
def plural_word(singular, plural)
|
26
|
+
@plural_of ||= {}
|
27
|
+
@plural_of[singular] = plural
|
59
28
|
end
|
60
|
-
end
|
61
29
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
30
|
+
# Define a general rule.
|
31
|
+
def rule(singular, plural)
|
32
|
+
singular_rule(singular, plural)
|
33
|
+
plural_rule(singular, plural)
|
34
|
+
end
|
66
35
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
36
|
+
# Define a singularization rule.
|
37
|
+
def singular_rule(singular, plural)
|
38
|
+
(@singular_rules ||= []) << [singular, plural]
|
39
|
+
end
|
71
40
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
# "tomatoes".singular #=> tomato
|
76
|
-
#
|
77
|
-
def singular(word)
|
78
|
-
if result = singular_of[word]
|
79
|
-
return result.dup
|
41
|
+
# Define a plurualization rule.
|
42
|
+
def plural_rule(singular, plural)
|
43
|
+
(@plural_rules ||= []) << [singular, plural]
|
80
44
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
45
|
+
|
46
|
+
# Read prepared singularization rules.
|
47
|
+
def singularization_rules
|
48
|
+
@singular_rules ||= []
|
49
|
+
return @singularization_rules if @singularization_rules
|
50
|
+
sorted = @singular_rules.sort_by{ |s, p| "#{p}".size }.reverse
|
51
|
+
@singularization_rules = sorted.collect do |s, p|
|
52
|
+
[ /#{p}$/, "#{s}" ]
|
53
|
+
end
|
84
54
|
end
|
85
|
-
return result
|
86
|
-
end
|
87
55
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
56
|
+
# Read prepared pluralization rules.
|
57
|
+
def pluralization_rules
|
58
|
+
@plural_rules ||= []
|
59
|
+
return @pluralization_rules if @pluralization_rules
|
60
|
+
sorted = @plural_rules.sort_by{ |s, p| "#{s}".size }.reverse
|
61
|
+
@pluralization_rules = sorted.collect do |s, p|
|
62
|
+
[ /#{s}$/, "#{p}" ]
|
63
|
+
end
|
96
64
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
65
|
+
|
66
|
+
#
|
67
|
+
def plural_of
|
68
|
+
@plural_of ||= {}
|
101
69
|
end
|
102
|
-
return result
|
103
|
-
end
|
104
|
-
end
|
105
70
|
|
106
|
-
|
107
|
-
|
108
|
-
|
71
|
+
#
|
72
|
+
def singular_of
|
73
|
+
@singular_of ||= {}
|
74
|
+
end
|
109
75
|
|
110
|
-
|
111
|
-
|
76
|
+
# Convert an English word from plural to singular.
|
77
|
+
#
|
78
|
+
# "boys".singular #=> boy
|
79
|
+
# "tomatoes".singular #=> tomato
|
80
|
+
#
|
81
|
+
def singular(word)
|
82
|
+
if result = singular_of[word]
|
83
|
+
return result.dup
|
84
|
+
end
|
85
|
+
result = word.dup
|
86
|
+
singularization_rules.each do |(match, replacement)|
|
87
|
+
break if result.gsub!(match, replacement)
|
88
|
+
end
|
89
|
+
return result
|
90
|
+
end
|
112
91
|
|
113
|
-
|
114
|
-
|
92
|
+
# Convert an English word from singular to plurel.
|
93
|
+
#
|
94
|
+
# "boy".plural #=> boys
|
95
|
+
# "tomato".plural #=> tomatoes
|
96
|
+
#
|
97
|
+
def plural(word)
|
98
|
+
if result = plural_of[word]
|
99
|
+
return result.dup
|
100
|
+
end
|
101
|
+
#return self.dup if /s$/ =~ self # ???
|
102
|
+
result = word.dup
|
103
|
+
pluralization_rules.each do |(match, replacement)|
|
104
|
+
break if result.gsub!(match, replacement)
|
105
|
+
end
|
106
|
+
return result
|
107
|
+
end
|
115
108
|
end
|
116
|
-
|
117
|
-
# One argument means singular and plural are the same.
|
118
|
-
word 'equipment'
|
119
|
-
word 'information'
|
120
|
-
word 'money'
|
121
|
-
word 'species'
|
122
|
-
word 'series'
|
123
|
-
word 'fish'
|
124
|
-
word 'sheep'
|
125
|
-
word 'moose'
|
126
|
-
word 'hovercraft'
|
127
|
-
|
128
|
-
# Two arguments defines a singular and plural exception.
|
129
|
-
word 'Swiss' , 'Swiss'
|
130
|
-
word 'life' , 'lives'
|
131
|
-
word 'wife' , 'wives'
|
132
|
-
word 'virus' , 'viri'
|
133
|
-
word 'octopus' , 'octopi'
|
134
|
-
word 'cactus' , 'cacti'
|
135
|
-
word 'goose' , 'geese'
|
136
|
-
word 'criterion' , 'criteria'
|
137
|
-
word 'alias' , 'aliases'
|
138
|
-
word 'status' , 'statuses'
|
139
|
-
word 'axis' , 'axes'
|
140
|
-
word 'crisis' , 'crises'
|
141
|
-
word 'testis' , 'testes'
|
142
|
-
word 'child' , 'children'
|
143
|
-
word 'person' , 'people'
|
144
|
-
word 'potato' , 'potatoes'
|
145
|
-
word 'tomato' , 'tomatoes'
|
146
|
-
word 'buffalo' , 'buffaloes'
|
147
|
-
word 'torpedo' , 'torpedoes'
|
148
|
-
word 'quiz' , 'quizes'
|
149
|
-
word 'matrix' , 'matrices'
|
150
|
-
word 'vertex' , 'vetices'
|
151
|
-
word 'index' , 'indices'
|
152
|
-
word 'ox' , 'oxen'
|
153
|
-
word 'mouse' , 'mice'
|
154
|
-
word 'louse' , 'lice'
|
155
|
-
word 'thesis' , 'theses'
|
156
|
-
word 'thief' , 'thieves'
|
157
|
-
word 'analysis' , 'analyses'
|
158
|
-
|
159
|
-
# One-way singularization exception (convert plural to singular).
|
160
|
-
singular_word 'cactus', 'cacti'
|
161
|
-
|
162
|
-
# General rules.
|
163
|
-
rule 'hive' , 'hives'
|
164
|
-
rule 'rf' , 'rves'
|
165
|
-
rule 'af' , 'aves'
|
166
|
-
rule 'ero' , 'eroes'
|
167
|
-
rule 'man' , 'men'
|
168
|
-
rule 'ch' , 'ches'
|
169
|
-
rule 'sh' , 'shes'
|
170
|
-
rule 'ss' , 'sses'
|
171
|
-
rule 'ta' , 'tum'
|
172
|
-
rule 'ia' , 'ium'
|
173
|
-
rule 'ra' , 'rum'
|
174
|
-
rule 'ay' , 'ays'
|
175
|
-
rule 'ey' , 'eys'
|
176
|
-
rule 'oy' , 'oys'
|
177
|
-
rule 'uy' , 'uys'
|
178
|
-
rule 'y' , 'ies'
|
179
|
-
rule 'x' , 'xes'
|
180
|
-
rule 'lf' , 'lves'
|
181
|
-
rule 'us' , 'uses'
|
182
|
-
rule '' , 's'
|
183
|
-
|
184
|
-
# One-way singular rules.
|
185
|
-
singular_rule 'of' , 'ofs' # proof
|
186
|
-
singular_rule 'o' , 'oes' # hero, heroes
|
187
|
-
singular_rule 'f' , 'ves'
|
188
|
-
|
189
|
-
# One-way plural rules.
|
190
|
-
plural_rule 'fe' , 'ves' # safe, wife
|
191
|
-
plural_rule 's' , 'ses'
|
192
109
|
end
|
193
110
|
end
|
194
|
-
|