rwdeliza 0.03

Sign up to get free protection for your applications and to get access to all the features.
Files changed (222) hide show
  1. data/ElizaData/database/db.1 +7015 -0
  2. data/ElizaData/database/db.10 +7001 -0
  3. data/ElizaData/database/db.11 +7001 -0
  4. data/ElizaData/database/db.12 +7003 -0
  5. data/ElizaData/database/db.13 +7003 -0
  6. data/ElizaData/database/db.14 +7008 -0
  7. data/ElizaData/database/db.15 +7001 -0
  8. data/ElizaData/database/db.16 +7001 -0
  9. data/ElizaData/database/db.17 +7001 -0
  10. data/ElizaData/database/db.18 +7001 -0
  11. data/ElizaData/database/db.19 +7001 -0
  12. data/ElizaData/database/db.2 +7001 -0
  13. data/ElizaData/database/db.20 +5467 -0
  14. data/ElizaData/database/db.3 +7001 -0
  15. data/ElizaData/database/db.4 +7001 -0
  16. data/ElizaData/database/db.5 +7001 -0
  17. data/ElizaData/database/db.6 +7001 -0
  18. data/ElizaData/database/db.7 +7001 -0
  19. data/ElizaData/database/db.8 +7001 -0
  20. data/ElizaData/database/db.9 +7001 -0
  21. data/ElizaData/responses/hello.res +1 -0
  22. data/ElizaData/responses/i_agree.res +1 -0
  23. data/ElizaData/responses/i_am_listening_to_you.res +1 -0
  24. data/ElizaData/responses/please_explain.res +1 -0
  25. data/ElizaData/responses/thank_you.res +1 -0
  26. data/ElizaData/tiny.dict +211 -0
  27. data/ElizaData/words/adjectives1.words +906 -0
  28. data/ElizaData/words/adjectives2w.words +1 -0
  29. data/ElizaData/words/noun0.words +15 -0
  30. data/ElizaData/words/noun1.words +1391 -0
  31. data/ElizaData/words/noun2.words +1924 -0
  32. data/ElizaData/words/noun4.words +330 -0
  33. data/ElizaData/words/pronoun1.words +6 -0
  34. data/ElizaData/words/verb4.words +350 -0
  35. data/ElizaData/words/verb42.words +391 -0
  36. data/ElizaData/words/verb42w.words +1 -0
  37. data/ElizaData/words/verb43.words +402 -0
  38. data/ElizaData/words/verb43w.words +1 -0
  39. data/ElizaData/words/verb45.words +452 -0
  40. data/ElizaData/words/verb4w.words +1 -0
  41. data/ElizaData/words/verb5.words +13 -0
  42. data/ElizaData/words/verb61.words +35 -0
  43. data/ElizaData/words/verb62.words +41 -0
  44. data/ElizaData/words/verb83.words +17 -0
  45. data/Readme.txt +462 -0
  46. data/bin/rwdeliza +19 -0
  47. data/code/01rwdcore/01rwdcore.rb +29 -0
  48. data/code/01rwdcore/02helptexthashbegin.rb +4 -0
  49. data/code/01rwdcore/03helptexthash.rb +23 -0
  50. data/code/01rwdcore/04helptextend.rb +6 -0
  51. data/code/01rwdcore/jumplinkcommand.rb +26 -0
  52. data/code/01rwdcore/openhelpwindow.rb +31 -0
  53. data/code/01rwdcore/returntomain.rb +10 -0
  54. data/code/01rwdcore/rundocuments.rb +10 -0
  55. data/code/01rwdcore/runeditconfiguration.rb +10 -0
  56. data/code/01rwdcore/runhelpabout.rb +10 -0
  57. data/code/01rwdcore/runopentinkerdocument.rb +7 -0
  58. data/code/01rwdcore/rwdtinkerversion.rb +22 -0
  59. data/code/01rwdcore/rwdwindowreturn.rb +9 -0
  60. data/code/01rwdcore/selectiontab.rb +9 -0
  61. data/code/01rwdcore/setuphelpaboutoptions.rb +13 -0
  62. data/code/01rwdcore/setuptinkerdocuments.rb +6 -0
  63. data/code/01rwdcore/test_cases.rb +109 -0
  64. data/code/01rwdcore/test_harness.rb +13 -0
  65. data/code/01rwdcore/uploadreturns.rb +62 -0
  66. data/code/dd0viewphoto/dd0viewphoto.rb +3 -0
  67. data/code/superant.com.rwdeliza/0uninstallapplet.rb +10 -0
  68. data/code/superant.com.rwdeliza/eliza01.rb +45 -0
  69. data/code/superant.com.rwdeliza/helptexthashrwdeliza.rb +39 -0
  70. data/code/superant.com.rwdeliza/openhelpwindowrwdeliza.rb +23 -0
  71. data/code/superant.com.rwdeliza/runrwdshellwindow.rb +12 -0
  72. data/code/superant.com.rwdeliza/rwdtinkerversion.rb +10 -0
  73. data/code/superant.com.rwdeliza/tagsentence.rb +39 -0
  74. data/code/superant.com.rwdtinkerbackwindow/diagnostictab.rb +19 -0
  75. data/code/superant.com.rwdtinkerbackwindow/helptexthashtinkerwin2.rb +61 -0
  76. data/code/superant.com.rwdtinkerbackwindow/initiateapplets.rb +240 -0
  77. data/code/superant.com.rwdtinkerbackwindow/installgemapplet.rb +34 -0
  78. data/code/superant.com.rwdtinkerbackwindow/installremotegem.rb +20 -0
  79. data/code/superant.com.rwdtinkerbackwindow/listgemdirs.rb +12 -0
  80. data/code/superant.com.rwdtinkerbackwindow/listgemzips.rb +53 -0
  81. data/code/superant.com.rwdtinkerbackwindow/listinstalledfiles.rb +12 -0
  82. data/code/superant.com.rwdtinkerbackwindow/listzips.rb +27 -0
  83. data/code/superant.com.rwdtinkerbackwindow/loadconfigurationrecord.rb +22 -0
  84. data/code/superant.com.rwdtinkerbackwindow/loadconfigurationvariables.rb +14 -0
  85. data/code/superant.com.rwdtinkerbackwindow/network.rb +87 -0
  86. data/code/superant.com.rwdtinkerbackwindow/openappletname.rb +19 -0
  87. data/code/superant.com.rwdtinkerbackwindow/openhelpwindowtinkerwin2.rb +38 -0
  88. data/code/superant.com.rwdtinkerbackwindow/remotegemlist.rb +24 -0
  89. data/code/superant.com.rwdtinkerbackwindow/removeapplet.rb +46 -0
  90. data/code/superant.com.rwdtinkerbackwindow/removeappletvariables.rb +52 -0
  91. data/code/superant.com.rwdtinkerbackwindow/runremoteinstall.rb +11 -0
  92. data/code/superant.com.rwdtinkerbackwindow/runrwdtinkerbackwindow.rb +15 -0
  93. data/code/superant.com.rwdtinkerbackwindow/rwdtinkerwin2version.rb +13 -0
  94. data/code/superant.com.rwdtinkerbackwindow/saveconfigurationrecord.rb +19 -0
  95. data/code/superant.com.rwdtinkerbackwindow/viewappletcontents.rb +22 -0
  96. data/code/superant.com.rwdtinkerbackwindow/viewgemappletcontents.rb +24 -0
  97. data/code/superant.com.words/dictlookup.rb +20 -0
  98. data/code/superant.com.words/runfortunewindow.rb +14 -0
  99. data/code/superant.com.words/runrwdwordsbackwindow.rb +10 -0
  100. data/code/superant.com.words/runrwdwordsversion.rb +14 -0
  101. data/code/superant.com.words/rwdtinkerversion.rb +10 -0
  102. data/code/zz0applicationend/zz0end.rb +4 -0
  103. data/configuration/language.dist +8 -0
  104. data/configuration/rwdapplicationidentity.dist +3 -0
  105. data/configuration/rwdtinker.dist +18 -0
  106. data/configuration/rwdweliza-0.03.dist +31 -0
  107. data/configuration/tinkerwin2variables.dist +17 -0
  108. data/gui/00coreguibegin/applicationguitop.rwd +4 -0
  109. data/gui/frontwindow0/cc0openphoto.rwd +22 -0
  110. data/gui/frontwindowselections/00selectiontabbegin.rwd +11 -0
  111. data/gui/frontwindowselections/jumplinkcommands.rwd +15 -0
  112. data/gui/frontwindowselections/wwselectionend.rwd +3 -0
  113. data/gui/frontwindowtdocuments/00documentbegin.rwd +6 -0
  114. data/gui/frontwindowtdocuments/tinkerdocuments.rwd +14 -0
  115. data/gui/frontwindowtdocuments/zzdocumentend.rwd +8 -0
  116. data/gui/helpaboutbegin/zzzrwdlasttab.rwd +6 -0
  117. data/gui/helpaboutbegin/zzzzhelpscreenstart.rwd +3 -0
  118. data/gui/helpaboutbegin/zzzzzzhelpabouttab.rwd +15 -0
  119. data/gui/helpaboutzend/helpscreenend.rwd +3 -0
  120. data/gui/helpaboutzend/zhelpscreenstart2.rwd +3 -0
  121. data/gui/helpaboutzend/zzzzhelpabout2.rwd +15 -0
  122. data/gui/helpaboutzend/zzzzhelpscreen2end.rwd +3 -0
  123. data/gui/tinkerbackwindows/superant.com.refreshwindow/fortunerefreshwindowtwo.rwd +9 -0
  124. data/gui/tinkerbackwindows/superant.com.rwdeliza/1appname.rwd +5 -0
  125. data/gui/tinkerbackwindows/superant.com.rwdeliza/1eliza.rwd +21 -0
  126. data/gui/tinkerbackwindows/superant.com.rwdeliza/4sentance.rwd +21 -0
  127. data/gui/tinkerbackwindows/superant.com.rwdeliza/98jumplinkcommands.rwd +17 -0
  128. data/gui/tinkerbackwindows/superant.com.rwdeliza/zbackend.rwd +6 -0
  129. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/1appname.rwd +5 -0
  130. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/20downloadftp.rwd +45 -0
  131. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/67viewconfiguration.rwd +29 -0
  132. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/70rwddiagnostics.rwd +16 -0
  133. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/m01menubegin.rwd +18 -0
  134. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/zvbackend.rwd +6 -0
  135. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/1appname.rwd +5 -0
  136. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/40rwdlistzips.rwd +41 -0
  137. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/45installremotezip.rwd +44 -0
  138. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/50rwdlistapplets.rwd +44 -0
  139. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/60editconfiguration.rwd +30 -0
  140. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/70rwddiagnostics.rwd +29 -0
  141. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/81jumplinkcommands.rwd +17 -0
  142. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/9backend.rwd +6 -0
  143. data/gui/tinkerbackwindows/superant.com.tinkerhelpwindow/1appname.rwd +31 -0
  144. data/gui/tinkerbackwindows/superant.com.tinkerhelpwindow/9end.rwd +4 -0
  145. data/gui/tinkerbackwindows/superant.com.versionwindow/1appname.rwd +19 -0
  146. data/gui/tinkerbackwindows/superant.com.versionwindow/helpaboutwindow.rwd +17 -0
  147. data/gui/tinkerbackwindows/superant.com.words/1appname.rwd +4 -0
  148. data/gui/tinkerbackwindows/superant.com.words/1dictionary.rwd +19 -0
  149. data/gui/tinkerbackwindows/superant.com.words/3rwdfortune.rwd +14 -0
  150. data/gui/tinkerbackwindows/superant.com.words/77jumplinkcommands.rwd +17 -0
  151. data/gui/tinkerbackwindows/superant.com.words/z9end.rwd +6 -0
  152. data/gui/zzcoreguiend/yy9rwdend.rwd +4 -0
  153. data/init.rb +277 -0
  154. data/installed/rwdweliza-0.03.inf +24 -0
  155. data/installed/temp.rb +1 -0
  156. data/lang/en/rwdcore/languagefile.rb +58 -0
  157. data/lang/es/rwdcore/languagefile-es.rb +62 -0
  158. data/lang/fr/rwdcore/languagefile.rb +64 -0
  159. data/lang/jp/rwdcore/languagefile.rb +72 -0
  160. data/lang/nl/rwdcore/languagefile.rb +75 -0
  161. data/lib/dict.rb +438 -0
  162. data/lib/druida.rb +499 -0
  163. data/lib/hashslice.rb +71 -0
  164. data/lib/linguistics.rb +360 -0
  165. data/lib/linguistics/en.rb +1601 -0
  166. data/lib/linguistics/en/infinitive.rb +1148 -0
  167. data/lib/linguistics/en/linkparser.rb +142 -0
  168. data/lib/linguistics/en/wordnet.rb +253 -0
  169. data/lib/linguistics/iso639.rb +456 -0
  170. data/lib/linkparser.rb +461 -0
  171. data/lib/linkparser/connection.rb +81 -0
  172. data/lib/linkparser/connector.rb +201 -0
  173. data/lib/linkparser/definition.rb +225 -0
  174. data/lib/linkparser/dictionary.rb +208 -0
  175. data/lib/linkparser/linkage.rb +185 -0
  176. data/lib/linkparser/log.rb +39 -0
  177. data/lib/linkparser/sentence.rb +79 -0
  178. data/lib/linkparser/utils.rb +540 -0
  179. data/lib/linkparser/word.rb +92 -0
  180. data/lib/rconftool.rb +380 -0
  181. data/lib/rwd/browser.rb +123 -0
  182. data/lib/rwd/ftools.rb +174 -0
  183. data/lib/rwd/mime.rb +328 -0
  184. data/lib/rwd/net.rb +866 -0
  185. data/lib/rwd/ruby.rb +889 -0
  186. data/lib/rwd/rwd.rb +1942 -0
  187. data/lib/rwd/sgml.rb +236 -0
  188. data/lib/rwd/thread.rb +63 -0
  189. data/lib/rwd/tree.rb +371 -0
  190. data/lib/rwd/xml.rb +101 -0
  191. data/lib/zip/ioextras.rb +114 -0
  192. data/lib/zip/stdrubyext.rb +111 -0
  193. data/lib/zip/tempfile_bugfixed.rb +195 -0
  194. data/lib/zip/zip.rb +1378 -0
  195. data/lib/zip/zipfilesystem.rb +558 -0
  196. data/lib/zip/ziprequire.rb +61 -0
  197. data/rwd_files/HowTo_Eliza.txt +195 -0
  198. data/rwd_files/HowTo_Tinker.txt +471 -0
  199. data/rwd_files/HowTo_TinkerWin2.txt +202 -0
  200. data/rwd_files/Readme.txt +57 -0
  201. data/rwd_files/RubyWebDialogs.html +6 -0
  202. data/rwd_files/favicon.ico +0 -0
  203. data/rwd_files/rdoc-style.css +175 -0
  204. data/rwd_files/rwdapplications.html +54 -0
  205. data/rwd_files/tinker.png +0 -0
  206. data/rwdconfig.dist +21 -0
  207. data/rwdeliza.rb +1 -0
  208. data/tests/RubyGauge.rb +179 -0
  209. data/tests/checkdepends.sh +4 -0
  210. data/tests/cleancnf.sh +6 -0
  211. data/tests/makedist-rwdweliza.rb +56 -0
  212. data/tests/makedist.rb +66 -0
  213. data/tests/rdep.rb +354 -0
  214. data/tests/totranslate.lang +93 -0
  215. data/zips/rwdwaddresses-1.05.zip +0 -0
  216. data/zips/rwdwcalc-0.61.zip +0 -0
  217. data/zips/rwdwgutenberg-0.09.zip +0 -0
  218. data/zips/rwdwschedule-1.04.zip +0 -0
  219. data/zips/rwdwshell-1.04.zip +0 -0
  220. data/zips/temp.rb +1 -0
  221. data/zips/wrubyslippers-1.06.zip +0 -0
  222. metadata +282 -0
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/ruby
2
+ ###########################################################################
3
+ =begin
4
+
5
+ =hashslice.rb
6
+
7
+ == Name
8
+
9
+ hashslice - Slicing for Ruby Hashes
10
+
11
+ == Synopsis
12
+
13
+ require "hashslice"
14
+
15
+ hash = {
16
+ "this" => "is",
17
+ "a" => "test",
18
+ "hash" => "for",
19
+ "the" => "synopsis"
20
+ }
21
+ hash[ "this", "hash" ]
22
+ # ==>["is", "for"]
23
+
24
+ hash[ "a", "hash" ] = "something", "else"
25
+
26
+ == Description
27
+
28
+ This module adds slicing to Ruby hashes, similar to Perl^s hash slices. If the
29
+ argument to (({Hash#[]})) is an (({Array})) object with one or more keys, the
30
+ return value will be an array of the corresponding values.
31
+
32
+ == Author
33
+
34
+ Michael Granger <((<ged@FaerieMUD.org|URL:mailto:ged@FaerieMUD.org>))>
35
+
36
+ Copyright (c) 2001 The FaerieMUD Consortium. All rights reserved.
37
+
38
+ This module is free software. You may use, modify, and/or redistribute this
39
+ software under the terms of the Perl Artistic License. (See
40
+ http://language.perl.com/misc/Artistic.html)
41
+
42
+ =end
43
+ ###########################################################################
44
+
45
+ class Hash
46
+
47
+ ### Alias the regular methods out of the way so we can override 'em.
48
+ alias :__bracketBracket__ :[]
49
+ alias :__bracketBracketEq__ :[]=
50
+
51
+ ### Add slicing to element reference operator
52
+ def []( *sliceKeys )
53
+ if sliceKeys.length == 1
54
+ return __bracketBracket__( sliceKeys[0] )
55
+ end
56
+ return sliceKeys.collect {|k| __bracketBracket__( k )}
57
+ end
58
+
59
+ ### Add slicing to element assignment operator
60
+ def []=( *args )
61
+ if args.length <= 2
62
+ return __bracketBracketEq__( *args )
63
+ end
64
+ aVals = args.pop
65
+ aVals = [aVals] unless aVals.kind_of?( Array )
66
+ args.each_index {|i| __bracketBracketEq__( args[i], aVals[i] )}
67
+ end
68
+
69
+ end
70
+
71
+
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # linguistics.rb -- provides an interface for extending core Ruby classes with
4
+ # linguistic methods.
5
+ #
6
+ # == Synopsis
7
+ #
8
+ # require 'linguistics'
9
+ # Linguistics::use( :en )
10
+ # MyClass::extend( Linguistics )
11
+ #
12
+ # == Authors
13
+ #
14
+ # * Michael Granger <ged@FaerieMUD.org>
15
+ #
16
+ # == Copyright
17
+ #
18
+ # Copyright (c) 2003 The FaerieMUD Consortium. All rights reserved.
19
+ #
20
+ # This module is free software. You may use, modify, and/or redistribute this
21
+ # software under the terms of the Perl Artistic License. (See
22
+ # http://language.perl.com/misc/Artistic.html)
23
+ #
24
+ # == Version
25
+ #
26
+ # $Id: linguistics.rb,v 1.6 2003/09/11 04:55:11 deveiant Exp $
27
+ #
28
+
29
+ require 'linguistics/iso639'
30
+
31
+ ### A language-independent framework for adding linguistics functions to Ruby
32
+ ### classes.
33
+ module Linguistics
34
+
35
+ ### Class constants
36
+ Version = /([\d\.]+)/.match( %q{$Revision: 1.6 $} )[1]
37
+ Rcsid = %q$Id: linguistics.rb,v 1.6 2003/09/11 04:55:11 deveiant Exp $
38
+
39
+ # Language module implementors should do something like:
40
+ # Linguistics::DefaultLanguages.push( :ja ) # or whatever
41
+ # so that direct requiring of a language module sets the default.
42
+ DefaultLanguages = []
43
+
44
+ # The list of Classes to add linguistic behaviours to.
45
+ DefaultExtClasses = [String, Numeric, Array]
46
+
47
+
48
+ #################################################################
49
+ ### I N F L E C T O R C L A S S F A C T O R Y
50
+ #################################################################
51
+
52
+ ### Template class -- is cloned and
53
+ class LanguageProxyClass
54
+
55
+ ### Class instance variable + accessor. Contains the module which knows
56
+ ### the specifics of the language the languageProxy class is providing
57
+ ### methods for.
58
+ @langmod = nil
59
+ class << self
60
+ attr_accessor :langmod
61
+ end
62
+
63
+
64
+ ### Create a new LanguageProxy for the given +receiver+.
65
+ def initialize( receiver )
66
+ @receiver = receiver
67
+ end
68
+
69
+
70
+ ######
71
+ public
72
+ ######
73
+
74
+ ### Overloaded to take into account the proxy method.
75
+ def respond_to?( sym )
76
+ self.class.langmod.respond_to?( sym ) || super
77
+ end
78
+
79
+
80
+ ### Autoload linguistic methods defined in the module this object's
81
+ ### class uses for inflection.
82
+ def method_missing( sym, *args )
83
+ return super unless self.class.langmod.respond_to?( sym )
84
+
85
+ self.class.module_eval %{
86
+ def #{sym}( *args, &block )
87
+ self.class.langmod.#{sym}( @receiver, *args, &block )
88
+ end
89
+ }, "{Autoloaded: " + __FILE__ + "}", __LINE__
90
+
91
+ self.method( sym ).call( *args )
92
+ end
93
+
94
+
95
+ ### Returns a human-readable representation of the languageProxy for
96
+ ### debugging, logging, etc.
97
+ def inspect
98
+ "<%s languageProxy for %s object %s>" % [
99
+ self.class.langmod.language,
100
+ @receiver.class.name,
101
+ @receiver.inspect,
102
+ ]
103
+ end
104
+
105
+ end
106
+
107
+
108
+ ### Extend the specified target object with one or more language proxy
109
+ ### methods, each of which provides access to one or more linguistic methods
110
+ ### for that language.
111
+ def self::extend_object( obj )
112
+ case obj
113
+ when Class
114
+ # $stderr.puts "Extending %p" % obj if $DEBUG
115
+ self::installLanguageProxy( obj )
116
+ else
117
+ sclass = (class << obj; self; end)
118
+ # $stderr.puts "Extending a object's metaclass: %p" % obj if $DEBUG
119
+ self::installLanguageProxy( sclass )
120
+ end
121
+
122
+ super
123
+ end
124
+
125
+
126
+ ### Extend the including class with linguistics proxy methods.
127
+ def self::included( mod )
128
+ # $stderr.puts "Including Linguistics in %p" % mod if $DEBUG
129
+ mod.extend( self ) unless mod == Linguistics
130
+ end
131
+
132
+
133
+ ### Make an languageProxy class that encapsulates all of the inflect operations
134
+ ### using the given language module.
135
+ def self::makeLanguageProxy( mod )
136
+ Class::new( LanguageProxyClass ) {
137
+ @langmod = mod
138
+ }
139
+ end
140
+
141
+
142
+ ### Install the language proxy
143
+ def self::installLanguageProxy( klass, languages=DefaultLanguages )
144
+ languages.replace( DefaultLanguages ) if languages.empty?
145
+
146
+ # Create an languageProxy class for each language specified
147
+ languages.each {|lang|
148
+ # $stderr.puts "Extending the %p class with %p" %
149
+ # [ klass, lang ] if $DEBUG
150
+
151
+ # Load the language module (skipping to the next if it's already
152
+ # loaded), make an languageProxy class that delegates to it, and figure
153
+ # out what the languageProxy method will be called.
154
+ mod = loadLanguage( lang.to_s.downcase )
155
+ ifaceMeth = mod.name.downcase.sub( /.*:/, '' )
156
+ languageProxyClass = makeLanguageProxy( mod )
157
+
158
+ # Install a hash for languageProxy classes and an accessor for the
159
+ # hash if it's not already present.
160
+ if !klass.class_variables.include?( "@@__languageProxy_class" )
161
+ klass.module_eval %{
162
+ @@__languageProxy_class = {}
163
+ def self::__languageProxy_class; @@__languageProxy_class; end
164
+ }, __FILE__, __LINE__
165
+ end
166
+
167
+ # Merge the current languageProxy into the hash
168
+ klass.__languageProxy_class.merge!( ifaceMeth => languageProxyClass )
169
+
170
+ # Set the language-code proxy method for the class unless it has one
171
+ # already
172
+ unless klass.instance_methods(true).include?( ifaceMeth )
173
+ klass.module_eval %{
174
+ def #{ifaceMeth}
175
+ @__#{ifaceMeth}_languageProxy ||=
176
+ self.class.__languageProxy_class["#{ifaceMeth}"].
177
+ new( self )
178
+ end
179
+ }, __FILE__, __LINE__
180
+ end
181
+ }
182
+ end
183
+
184
+
185
+
186
+ ### Install a regular proxy method in the given klass that will delegate
187
+ ### calls to missing method to the languageProxy for the given +language+.
188
+ def self::installDelegatorProxy( klass, langcode )
189
+
190
+ # Alias any currently-extant
191
+ if klass.instance_methods( false ).include?( "method_missing" )
192
+ klass.module_eval %{
193
+ alias_method :__orig_method_missing, :method_missing
194
+ }
195
+ end
196
+
197
+ # Add the #method_missing method that auto-installs delegator methods
198
+ # for methods supported by the linguistic proxy objects.
199
+ klass.module_eval {
200
+ define_method( :method_missing ) do |sym, *args|
201
+
202
+ if self.send( langcode ).respond_to?( sym )
203
+
204
+ # $stderr.puts "Installing linguistic delegator method #{sym} " \
205
+ # "for the '#{langcode}' proxy"
206
+ self.class.module_eval %{
207
+ def #{sym}( *args )
208
+ self.#{langcode}.#{sym}( *args )
209
+ end
210
+ }
211
+ self.method( sym ).call( *args )
212
+
213
+ # Otherwise either call the overridden proxy method if there is
214
+ # one, or just let our parent deal with it.
215
+ else
216
+ if self.respond_to?( :__orig_method_missing )
217
+ return self.__orig_method_missing( sym, *args )
218
+ else
219
+ super( sym, *args )
220
+ end
221
+ end
222
+ end
223
+ }
224
+ end
225
+
226
+
227
+
228
+ #################################################################
229
+ ### L A N G U A G E - I N D E P E N D E N T F U N C T I O N S
230
+ #################################################################
231
+
232
+ ###############
233
+ module_function
234
+ ###############
235
+
236
+ ### Add linguistics functions for the specified languages to Ruby's core
237
+ ### classes. The interface to all linguistic functions for a given language
238
+ ### is through a method which is the same the language's international 2- or
239
+ ### 3-letter code (ISO 639). You can also specify a Hash of configuration
240
+ ### options which control which classes are extended:
241
+ ###
242
+ ### [<b>:classes</b>]
243
+ ### Specify the classes which are to be extended. If this is not specified,
244
+ ### the Class objects in Linguistics::DefaultExtClasses (an Array) are
245
+ ### extended.
246
+ ### [<b>:installProxy</b>]
247
+ ### Install a proxy method in each of the classes which are to be extended
248
+ ### which will search for missing methods in the languageProxy for the
249
+ ### language code specified as the value. This allows linguistics methods
250
+ ### to be called directly on extended objects directly (e.g.,
251
+ ### 12.en.ordinal becomes 12.ordinal). Obviously, methods which would
252
+ ### collide with the object's builtin methods will need to be invoked
253
+ ### through the languageProxy. Any existing proxy methods in the extended
254
+ ### classes will be preserved.
255
+ def use( *languages )
256
+ config = {}
257
+ config = languages.pop if languages.last.is_a?( Hash )
258
+
259
+ classes = config.key?( :classes ) ? config[:classes] : DefaultExtClasses
260
+ classes = [ classes ] unless classes.is_a?( Array )
261
+
262
+ # Install the languageProxy in each class.
263
+ classes.each {|klass|
264
+
265
+ # Create an languageProxy class for each installed language
266
+ installLanguageProxy( klass, languages )
267
+
268
+ # Install the delegator proxy if configured
269
+ if config[:installProxy]
270
+ case config[:installProxy]
271
+ when Symbol
272
+ langcode = config[:installProxy]
273
+ when String
274
+ langcode = config[:installProxy].intern
275
+ when TrueClass
276
+ langcode = DefaultLanguages[0]
277
+ else
278
+ raise ArgumentError,
279
+ "Unexpected value %p for :installProxy" %
280
+ config[:installProxy]
281
+ end
282
+
283
+ installDelegatorProxy( klass, langcode )
284
+ end
285
+ }
286
+ end
287
+
288
+
289
+
290
+ ### Support Lingua::EN::Inflect-style globals in a threadsafe way by using
291
+ ### Thread-local variables.
292
+
293
+ ### Set the default count for all unspecified plurals to +val+. Setting is
294
+ ### local to calling thread.
295
+ def num=( val )
296
+ Thread.current[:persistent_count] = val
297
+ end
298
+ alias_method :NUM=, :num=
299
+
300
+ ### Get the default count for all unspecified plurals. Setting is local to
301
+ ### calling thread.
302
+ def num
303
+ Thread.current[:persistent_count]
304
+ end
305
+ alias_method :NUM, :num
306
+
307
+
308
+ ### Set the 'classical pluralizations' flag to +val+. Setting is local to
309
+ ### calling thread.
310
+ def classical=( val )
311
+ Thread.current[:classical_plurals] = val
312
+ end
313
+
314
+ ### Return the value of the 'classical pluralizations' flag. Setting is
315
+ ### local to calling thread.
316
+ def classical?
317
+ Thread.current[:classical_plurals] ? true : false
318
+ end
319
+
320
+
321
+ #######
322
+ private
323
+ #######
324
+
325
+ ### Try to load the module that implements the given language, returning
326
+ ### the Module object if successful.
327
+ def self::loadLanguage( lang )
328
+ raise "Unknown language code '#{lang}'" unless
329
+ LanguageCodes.key?( lang )
330
+
331
+ # Sort all the codes for the specified language, trying the 2-letter
332
+ # versions first in alphabetical order, then the 3-letter ones
333
+ msgs = []
334
+ mod = LanguageCodes[ lang ][:codes].sort {|a,b|
335
+ (a.length <=> b.length).nonzero? ||
336
+ (a <=> b)
337
+ }.each {|code|
338
+ unless Linguistics::const_defined?( code.upcase )
339
+ begin
340
+ require "linguistics/#{code}"
341
+ rescue LoadError => err
342
+ msgs << "Tried 'linguistics/#{code}': #{err.message}\n"
343
+ next
344
+ end
345
+ end
346
+
347
+ break Linguistics::const_get( code.upcase ) if
348
+ Linguistics::const_defined?( code.upcase )
349
+ }
350
+
351
+ if mod.is_a?( Array )
352
+ raise LoadError,
353
+ "Failed to load language extension %s:\n%s" %
354
+ [ lang, msgs.join ]
355
+ end
356
+ return mod
357
+ end
358
+
359
+ end # class linguistics
360
+
@@ -0,0 +1,1601 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # = Linguistics::EN
4
+ #
5
+ # This module contains English-language linguistic functions for the Linguistics
6
+ # module. It can be either loaded directly, or by passing some variant of 'en'
7
+ # or 'eng' to the Linguistics::use method.
8
+ #
9
+ # The functions contained by the module provide:
10
+ #
11
+ # == Plural Inflections
12
+ #
13
+ # Plural forms of all nouns, most verbs, and some adjectives are provided. Where
14
+ # appropriate, "classical" variants (for example: "brother" -> "brethren",
15
+ # "dogma" -> "dogmata", etc.) are also provided.
16
+ #
17
+ # These can be accessed via the #plural, #plural_noun, #plural_verb, and
18
+ # #plural_adjective methods.
19
+ #
20
+ # == Indefinite Articles
21
+ #
22
+ # Pronunciation-based "a"/"an" selection is provided for all English words, and
23
+ # most initialisms.
24
+ #
25
+ # See: #a, #an, and #no.
26
+ #
27
+ # == Numbers to Words
28
+ #
29
+ # Conversion from Numeric values to words are supported using the American
30
+ # "thousands" system. E.g., 2561 => "two thousand, five hundred and sixty-one".
31
+ #
32
+ # See the #numwords method.
33
+ #
34
+ # == Ordinals
35
+ #
36
+ # It is also possible to inflect numerals (1,2,3) and number words ("one",
37
+ # "two", "three") to ordinals (1st, 2nd, 3rd) and ordinates ("first", "second",
38
+ # "third").
39
+ #
40
+ # == Conjunctions
41
+ #
42
+ # This module also supports the creation of English conjunctions from Arrays of
43
+ # Strings or objects which respond to the #to_s message. Eg.,
44
+ #
45
+ # %w{cow pig chicken cow dog cow duck duck moose}.en.conjunction
46
+ # ==> "three cows, two ducks, a pig, a chicken, a dog, and a moose"
47
+ #
48
+ # == Infinitives
49
+ #
50
+ # Returns the infinitive form of English verbs:
51
+ #
52
+ # "dodging".en.infinitive
53
+ # ==> "dodge"
54
+ #
55
+ #
56
+ # == Authors
57
+ #
58
+ # * Michael Granger <ged@FaerieMUD.org>
59
+ #
60
+ # == Copyright
61
+ #
62
+ # This module is copyright (c) 2003 The FaerieMUD Consortium. All rights
63
+ # reserved.
64
+ #
65
+ # This module is free software. You may use, modify, and/or redistribute this
66
+ # software under the terms of the Perl Artistic License. (See
67
+ # http://language.perl.com/misc/Artistic.html)
68
+ #
69
+ # The inflection functions of this module were adapted from Damien Conway's
70
+ # Lingua::EN::Inflect Perl module:
71
+ #
72
+ # Copyright (c) 1997-2000, Damian Conway. All Rights Reserved.
73
+ # This module is free software. It may be used, redistributed
74
+ # and/or modified under the same terms as Perl itself.
75
+ #
76
+ # The conjunctions code was adapted from the Lingua::Conjunction Perl module
77
+ # written by Robert Rothenberg and Damian Conway, which has no copyright
78
+ # statement included.
79
+ #
80
+ # == Version
81
+ #
82
+ # $Id: en.rb,v 1.8 2003/09/14 10:47:12 deveiant Exp $
83
+ #
84
+
85
+ require 'hashslice'
86
+
87
+
88
+ module Linguistics
89
+
90
+ ### This module contains English-language linguistics functions accessible from
91
+ ### the Linguistics module, or as a standalone function library.
92
+ module EN
93
+
94
+ begin
95
+ require 'crosscase'
96
+ rescue LoadError
97
+ else
98
+ include CrossCase
99
+ end
100
+
101
+ # Load in the secondary modules and add them to Linguistics::EN.
102
+ require 'linguistics/en/infinitive'
103
+ require 'linguistics/en/wordnet'
104
+ require 'linguistics/en/linkparser'
105
+
106
+ # CVS version tag
107
+ Version = /([\d\.]+)/.match( %q{$Revision: 1.8 $} )[1]
108
+
109
+ # CVS revision tag
110
+ Rcsid = %q$Id: en.rb,v 1.8 2003/09/14 10:47:12 deveiant Exp $
111
+
112
+ # Add 'english' to the list of default languages
113
+ Linguistics::DefaultLanguages.push( :en )
114
+
115
+
116
+ #################################################################
117
+ ### U T I L I T Y F U N C T I O N S
118
+ #################################################################
119
+
120
+ ### Wrap one or more parts in a non-capturing alteration Regexp
121
+ def self::matchgroup( *parts )
122
+ re = parts.flatten.join("|")
123
+ "(?:#{re})"
124
+ end
125
+
126
+
127
+ #################################################################
128
+ ### C O N S T A N T S
129
+ #################################################################
130
+
131
+ #
132
+ # Plurals
133
+ #
134
+
135
+ PL_sb_irregular_s = {
136
+ "ephemeris" => "ephemerides",
137
+ "iris" => "irises|irides",
138
+ "clitoris" => "clitorises|clitorides",
139
+ "corpus" => "corpuses|corpora",
140
+ "opus" => "opuses|opera",
141
+ "genus" => "genera",
142
+ "mythos" => "mythoi",
143
+ "penis" => "penises|penes",
144
+ "testis" => "testes",
145
+ }
146
+
147
+ PL_sb_irregular_h = {
148
+ "child" => "children",
149
+ "brother" => "brothers|brethren",
150
+ "loaf" => "loaves",
151
+ "hoof" => "hoofs|hooves",
152
+ "beef" => "beefs|beeves",
153
+ "money" => "monies",
154
+ "mongoose" => "mongooses",
155
+ "ox" => "oxen",
156
+ "cow" => "cows|kine",
157
+ "soliloquy" => "soliloquies",
158
+ "graffito" => "graffiti",
159
+ "prima donna" => "prima donnas|prime donne",
160
+ "octopus" => "octopuses|octopodes",
161
+ "genie" => "genies|genii",
162
+ "ganglion" => "ganglions|ganglia",
163
+ "trilby" => "trilbys",
164
+ "turf" => "turfs|turves",
165
+ }.update( PL_sb_irregular_s )
166
+ PL_sb_irregular = matchgroup PL_sb_irregular_h.keys
167
+
168
+
169
+ # Classical "..a" -> "..ata"
170
+ PL_sb_C_a_ata = matchgroup %w[
171
+ anathema bema carcinoma charisma diploma
172
+ dogma drama edema enema enigma lemma
173
+ lymphoma magma melisma miasma oedema
174
+ sarcoma schema soma stigma stoma trauma
175
+ gumma pragma
176
+ ].collect {|word| word[0...-1]}
177
+
178
+ # Unconditional "..a" -> "..ae"
179
+ PL_sb_U_a_ae = matchgroup %w[
180
+ alumna alga vertebra persona
181
+ ]
182
+
183
+ # Classical "..a" -> "..ae"
184
+ PL_sb_C_a_ae = matchgroup %w[
185
+ amoeba antenna formula hyperbola
186
+ medusa nebula parabola abscissa
187
+ hydra nova lacuna aurora .*umbra
188
+ flora fauna
189
+ ]
190
+
191
+ # Classical "..en" -> "..ina"
192
+ PL_sb_C_en_ina = matchgroup %w[
193
+ stamen foramen lumen
194
+ ].collect {|word| word[0...-2] }
195
+
196
+ # Unconditional "..um" -> "..a"
197
+ PL_sb_U_um_a = matchgroup %w[
198
+ bacterium agendum desideratum erratum
199
+ stratum datum ovum extremum
200
+ candelabrum
201
+ ].collect {|word| word[0...-2] }
202
+
203
+ # Classical "..um" -> "..a"
204
+ PL_sb_C_um_a = matchgroup %w[
205
+ maximum minimum momentum optimum
206
+ quantum cranium curriculum dictum
207
+ phylum aquarium compendium emporium
208
+ enconium gymnasium honorarium interregnum
209
+ lustrum memorandum millenium rostrum
210
+ spectrum speculum stadium trapezium
211
+ ultimatum medium vacuum velum
212
+ consortium
213
+ ].collect {|word| word[0...-2]}
214
+
215
+ # Unconditional "..us" -> "i"
216
+ PL_sb_U_us_i = matchgroup %w[
217
+ alumnus alveolus bacillus bronchus
218
+ locus nucleus stimulus meniscus
219
+ ].collect {|word| word[0...-2]}
220
+
221
+ # Classical "..us" -> "..i"
222
+ PL_sb_C_us_i = matchgroup %w[
223
+ focus radius genius
224
+ incubus succubus nimbus
225
+ fungus nucleolus stylus
226
+ torus umbilicus uterus
227
+ hippopotamus
228
+ ].collect {|word| word[0...-2]}
229
+
230
+ # Classical "..us" -> "..us" (assimilated 4th declension latin nouns)
231
+ PL_sb_C_us_us = matchgroup %w[
232
+ status apparatus prospectus sinus
233
+ hiatus impetus plexus
234
+ ]
235
+
236
+ # Unconditional "..on" -> "a"
237
+ PL_sb_U_on_a = matchgroup %w[
238
+ criterion perihelion aphelion
239
+ phenomenon prolegomenon noumenon
240
+ organon asyndeton hyperbaton
241
+ ].collect {|word| word[0...-2]}
242
+
243
+ # Classical "..on" -> "..a"
244
+ PL_sb_C_on_a = matchgroup %w[
245
+ oxymoron
246
+ ].collect {|word| word[0...-2]}
247
+
248
+ # Classical "..o" -> "..i" (but normally -> "..os")
249
+ PL_sb_C_o_i_a = %w[
250
+ solo soprano basso alto
251
+ contralto tempo piano
252
+ ]
253
+ PL_sb_C_o_i = matchgroup PL_sb_C_o_i_a.collect{|word| word[0...-1]}
254
+
255
+ # Always "..o" -> "..os"
256
+ PL_sb_U_o_os = matchgroup( %w[
257
+ albino archipelago armadillo
258
+ commando crescendo fiasco
259
+ ditto dynamo embryo
260
+ ghetto guano inferno
261
+ jumbo lumbago magneto
262
+ manifesto medico octavo
263
+ photo pro quarto
264
+ canto lingo generalissimo
265
+ stylo rhino
266
+ ] | PL_sb_C_o_i_a )
267
+
268
+
269
+ # Unconditional "..[ei]x" -> "..ices"
270
+ PL_sb_U_ex_ices = matchgroup %w[
271
+ codex murex silex
272
+ ].collect {|word| word[0...-2]}
273
+ PL_sb_U_ix_ices = matchgroup %w[
274
+ radix helix
275
+ ].collect {|word| word[0...-2]}
276
+
277
+ # Classical "..[ei]x" -> "..ices"
278
+ PL_sb_C_ex_ices = matchgroup %w[
279
+ vortex vertex cortex latex
280
+ pontifex apex index simplex
281
+ ].collect {|word| word[0...-2]}
282
+ PL_sb_C_ix_ices = matchgroup %w[
283
+ appendix
284
+ ].collect {|word| word[0...-2]}
285
+
286
+
287
+ # Arabic: ".." -> "..i"
288
+ PL_sb_C_i = matchgroup %w[
289
+ afrit afreet efreet
290
+ ]
291
+
292
+
293
+ # Hebrew: ".." -> "..im"
294
+ PL_sb_C_im = matchgroup %w[
295
+ goy seraph cherub
296
+ ]
297
+
298
+ # Unconditional "..man" -> "..mans"
299
+ PL_sb_U_man_mans = matchgroup %w[
300
+ human
301
+ Alabaman Bahaman Burman German
302
+ Hiroshiman Liman Nakayaman Oklahoman
303
+ Panaman Selman Sonaman Tacoman Yakiman
304
+ Yokohaman Yuman
305
+ ]
306
+
307
+
308
+ PL_sb_uninflected_s = [
309
+ # Pairs or groups subsumed to a singular...
310
+ "breeches", "britches", "clippers", "gallows", "hijinks",
311
+ "headquarters", "pliers", "scissors", "testes", "herpes",
312
+ "pincers", "shears", "proceedings", "trousers",
313
+
314
+ # Unassimilated Latin 4th declension
315
+ "cantus", "coitus", "nexus",
316
+
317
+ # Recent imports...
318
+ "contretemps", "corps", "debris",
319
+ ".*ois",
320
+
321
+ # Diseases
322
+ ".*measles", "mumps",
323
+
324
+ # Miscellaneous others...
325
+ "diabetes", "jackanapes", "series", "species", "rabies",
326
+ "chassis", "innings", "news", "mews",
327
+ ]
328
+
329
+
330
+ # Don't inflect in classical mode, otherwise normal inflection
331
+ PL_sb_uninflected_herd = matchgroup %w[
332
+ wildebeest swine eland bison buffalo
333
+ elk moose rhinoceros
334
+ ]
335
+
336
+ PL_sb_uninflected = matchgroup [
337
+
338
+ # Some fish and herd animals
339
+ ".*fish", "tuna", "salmon", "mackerel", "trout",
340
+ "bream", "sea[- ]bass", "carp", "cod", "flounder", "whiting",
341
+
342
+ ".*deer", ".*sheep",
343
+
344
+ # All nationals ending in -ese
345
+ "Portuguese", "Amoyese", "Borghese", "Congoese", "Faroese",
346
+ "Foochowese", "Genevese", "Genoese", "Gilbertese", "Hottentotese",
347
+ "Kiplingese", "Kongoese", "Lucchese", "Maltese", "Nankingese",
348
+ "Niasese", "Pekingese", "Piedmontese", "Pistoiese", "Sarawakese",
349
+ "Shavese", "Vermontese", "Wenchowese", "Yengeese",
350
+ ".*[nrlm]ese",
351
+
352
+ # Some words ending in ...s (often pairs taken as a whole)
353
+ PL_sb_uninflected_s,
354
+
355
+ # Diseases
356
+ ".*pox",
357
+
358
+ # Other oddities
359
+ "graffiti", "djinn"
360
+ ]
361
+
362
+
363
+ # Singular words ending in ...s (all inflect with ...es)
364
+ PL_sb_singular_s = matchgroup %w[
365
+ .*ss
366
+ acropolis aegis alias arthritis asbestos atlas
367
+ bathos bias bronchitis bursitis caddis cannabis
368
+ canvas chaos cosmos dais digitalis encephalitis
369
+ epidermis ethos eyas gas glottis hepatitis
370
+ hubris ibis lens mantis marquis metropolis
371
+ neuritis pathos pelvis polis rhinoceros
372
+ sassafras tonsillitis trellis .*us
373
+ ]
374
+
375
+ PL_v_special_s = matchgroup [
376
+ PL_sb_singular_s,
377
+ PL_sb_uninflected_s,
378
+ PL_sb_irregular_s.keys,
379
+ '(.*[csx])is',
380
+ '(.*)ceps',
381
+ '[A-Z].*s',
382
+ ]
383
+
384
+ PL_sb_postfix_adj = '(' + {
385
+
386
+ 'general' => ['(?!major|lieutenant|brigadier|adjutant)\S+'],
387
+ 'martial' => ["court"],
388
+
389
+ }.collect {|key,val|
390
+ matchgroup( matchgroup(val) + "(?=(?:-|\\s+)#{key})" )
391
+ }.join("|") + ")(.*)"
392
+
393
+
394
+ PL_sb_military = %r'major|lieutenant|brigadier|adjutant|quartermaster'
395
+ PL_sb_general = %r'((?!#{PL_sb_military.source}).*?)((-|\s+)general)'
396
+
397
+ PL_prep = matchgroup %w[
398
+ about above across after among around at athwart before behind
399
+ below beneath beside besides between betwixt beyond but by
400
+ during except for from in into near of off on onto out over
401
+ since till to under until unto upon with
402
+ ]
403
+
404
+ PL_sb_prep_dual_compound = %r'(.*?)((?:-|\s+)(?:#{PL_prep}|d[eu])(?:-|\s+))a(?:-|\s+)(.*)'
405
+ PL_sb_prep_compound = %r'(.*?)((-|\s+)(#{PL_prep}|d[eu])((-|\s+)(.*))?)'
406
+
407
+
408
+ PL_pron_nom_h = {
409
+ # Nominative Reflexive
410
+ "i" => "we", "myself" => "ourselves",
411
+ "you" => "you", "yourself" => "yourselves",
412
+ "she" => "they", "herself" => "themselves",
413
+ "he" => "they", "himself" => "themselves",
414
+ "it" => "they", "itself" => "themselves",
415
+ "they" => "they", "themself" => "themselves",
416
+
417
+ # Possessive
418
+ "mine" => "ours",
419
+ "yours" => "yours",
420
+ "hers" => "theirs",
421
+ "his" => "theirs",
422
+ "its" => "theirs",
423
+ "theirs" => "theirs",
424
+ }
425
+ PL_pron_nom = matchgroup PL_pron_nom_h.keys
426
+
427
+ PL_pron_acc_h = {
428
+ # Accusative Reflexive
429
+ "me" => "us", "myself" => "ourselves",
430
+ "you" => "you", "yourself" => "yourselves",
431
+ "her" => "them", "herself" => "themselves",
432
+ "him" => "them", "himself" => "themselves",
433
+ "it" => "them", "itself" => "themselves",
434
+ "them" => "them", "themself" => "themselves",
435
+ }
436
+ PL_pron_acc = matchgroup PL_pron_acc_h.keys
437
+
438
+ PL_v_irregular_pres_h = {
439
+ # 1St pers. sing. 2nd pers. sing. 3rd pers. singular
440
+ # 3rd pers. (indet.)
441
+ "am" => "are", "are" => "are", "is" => "are",
442
+ "was" => "were", "were" => "were", "was" => "were",
443
+ "have" => "have", "have" => "have", "has" => "have",
444
+ }
445
+ PL_v_irregular_pres = matchgroup PL_v_irregular_pres_h.keys
446
+
447
+ PL_v_ambiguous_pres_h = {
448
+ # 1st pers. sing. 2nd pers. sing. 3rd pers. singular
449
+ # 3rd pers. (indet.)
450
+ "act" => "act", "act" => "act", "acts" => "act",
451
+ "blame" => "blame", "blame" => "blame", "blames" => "blame",
452
+ "can" => "can", "can" => "can", "can" => "can",
453
+ "must" => "must", "must" => "must", "must" => "must",
454
+ "fly" => "fly", "fly" => "fly", "flies" => "fly",
455
+ "copy" => "copy", "copy" => "copy", "copies" => "copy",
456
+ "drink" => "drink", "drink" => "drink", "drinks" => "drink",
457
+ "fight" => "fight", "fight" => "fight", "fights" => "fight",
458
+ "fire" => "fire", "fire" => "fire", "fires" => "fire",
459
+ "like" => "like", "like" => "like", "likes" => "like",
460
+ "look" => "look", "look" => "look", "looks" => "look",
461
+ "make" => "make", "make" => "make", "makes" => "make",
462
+ "reach" => "reach", "reach" => "reach", "reaches" => "reach",
463
+ "run" => "run", "run" => "run", "runs" => "run",
464
+ "sink" => "sink", "sink" => "sink", "sinks" => "sink",
465
+ "sleep" => "sleep", "sleep" => "sleep", "sleeps" => "sleep",
466
+ "view" => "view", "view" => "view", "views" => "view",
467
+ }
468
+ PL_v_ambiguous_pres = matchgroup PL_v_ambiguous_pres_h.keys
469
+
470
+ PL_v_irregular_non_pres = matchgroup %w[
471
+ did had ate made put
472
+ spent fought sank gave sought
473
+ shall could ought should
474
+ ]
475
+
476
+ PL_v_ambiguous_non_pres = matchgroup %w[
477
+ thought saw bent will might cut
478
+ ]
479
+
480
+ PL_count_zero = matchgroup %w[
481
+ 0 no zero nil
482
+ ]
483
+
484
+ PL_count_one = matchgroup %w[
485
+ 1 a an one each every this that
486
+ ]
487
+
488
+ PL_adj_special_h = {
489
+ "a" => "some", "an" => "some",
490
+ "this" => "these", "that" => "those",
491
+ }
492
+ PL_adj_special = matchgroup PL_adj_special_h.keys
493
+
494
+ PL_adj_poss_h = {
495
+ "my" => "our",
496
+ "your" => "your",
497
+ "its" => "their",
498
+ "her" => "their",
499
+ "his" => "their",
500
+ "their" => "their",
501
+ }
502
+ PL_adj_poss = matchgroup PL_adj_poss_h.keys
503
+
504
+
505
+ #
506
+ # Numerals, ordinals, and numbers-to-words
507
+ #
508
+
509
+ # Numerical inflections
510
+ Nth = {
511
+ 0 => 'th',
512
+ 1 => 'st',
513
+ 2 => 'nd',
514
+ 3 => 'rd',
515
+ 4 => 'th',
516
+ 5 => 'th',
517
+ 6 => 'th',
518
+ 7 => 'th',
519
+ 8 => 'th',
520
+ 9 => 'th',
521
+ 11 => 'th',
522
+ 12 => 'th',
523
+ 13 => 'th',
524
+ }
525
+
526
+ # Ordinal word parts
527
+ Ordinals = {}
528
+ Ordinals[ *(%w(ty one two three five eight nine twelve )) ] =
529
+ %w[tieth first second third fifth eighth ninth twelfth]
530
+ OrdinalSuffixes = Ordinals.keys.join("|") + "|"
531
+ Ordinals[""] = 'th'
532
+
533
+ # Numeral names
534
+ Units = [''] + %w[one two three four five six seven eight nine]
535
+ Teens = %w[ten eleven twelve thirteen fourteen
536
+ fifteen sixteen seventeen eighteen nineteen]
537
+ Tens = ['',''] + %w[twenty thirty forty fifty sixty seventy eighty ninety]
538
+ Thousands = [' ', ' thousand'] + %w[
539
+ m b tr quadr quint sext sept oct non dec undec duodec tredec
540
+ quattuordec quindec sexdec septemdec octodec novemdec vigint
541
+ ].collect {|prefix| ' ' + prefix + 'illion'}
542
+
543
+ # A collection of functions for transforming digits into word
544
+ # phrases. Indexed by the number of digits being transformed; e.g.,
545
+ # <tt>NumberToWordsFunctions[2]</tt> is the function for transforming
546
+ # double-digit numbers.
547
+ NumberToWordsFunctions = [
548
+ proc {|*args| raise "No digits (#{args.inspect})"},
549
+
550
+ # Single-digits
551
+ proc {|zero,x|
552
+ (x.nonzero? ? to_units(x) : "#{zero} ")
553
+ },
554
+
555
+ # Double-digits
556
+ proc {|zero,x,y|
557
+ if x.nonzero?
558
+ to_tens( x, y )
559
+ elsif y.nonzero?
560
+ "#{zero} " + NumberToWordsFunctions[1].call( zero, y )
561
+ else
562
+ ([zero] * 2).join(" ")
563
+ end
564
+ },
565
+
566
+ # Triple-digits
567
+ proc {|zero,x,y,z|
568
+ NumberToWordsFunctions[1].call(zero,x) +
569
+ NumberToWordsFunctions[2].call(zero,y,z)
570
+ }
571
+ ]
572
+
573
+
574
+ #
575
+ # Indefinite Articles
576
+ #
577
+
578
+ # This pattern matches strings of capitals starting with a "vowel-sound"
579
+ # consonant followed by another consonant, and which are not likely
580
+ # to be real words (oh, all right then, it's just magic!)
581
+ A_abbrev = %{
582
+ (?! FJO | [HLMNS]Y. | RY[EO] | SQU
583
+ | ( F[LR]? | [HL] | MN? | N | RH? | S[CHKLMNPTVW]? | X(YL)?) [AEIOU])
584
+ [FHLMNRSX][A-Z]
585
+ }
586
+
587
+ # This pattern codes the beginnings of all english words begining with a
588
+ # 'y' followed by a consonant. Any other y-consonant prefix therefore
589
+ # implies an abbreviation.
590
+ A_y_cons = 'y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)'
591
+
592
+ # Exceptions to exceptions
593
+ A_explicit_an = matchgroup( "euler", "hour(?!i)", "heir", "honest", "hono" )
594
+
595
+
596
+ #
597
+ # Configuration defaults
598
+ #
599
+
600
+ # Default configuration arguments for the #numwords function
601
+ NumwordDefaults = {
602
+ :group => 0,
603
+ :comma => ', ',
604
+ :and => ' and ',
605
+ :zero => 'zero',
606
+ :decimal => 'point',
607
+ :asArray => false,
608
+ }
609
+
610
+ # Default ranges for #quantify
611
+ SeveralRange = 2..5
612
+ NumberRange = 6..19
613
+ NumerousRange = 20..45
614
+ ManyRange = 46..99
615
+
616
+ # Default configuration arguments for the #quantify function
617
+ QuantifyDefaults = {
618
+ :joinword => " of ",
619
+ }
620
+
621
+ # Default configuration arguments for the #conjunction (junction, what's
622
+ # your) function.
623
+ ConjunctionDefaults = {
624
+ :separator => ', ',
625
+ :altsep => '; ',
626
+ :penultimate => true,
627
+ :conjunctive => 'and',
628
+ :combine => true,
629
+ :casefold => true,
630
+ :generalize => false,
631
+ :quantsort => true,
632
+ }
633
+
634
+
635
+
636
+ #################################################################
637
+ ### " B A C K E N D " F U N C T I O N S
638
+ #################################################################
639
+
640
+ ###############
641
+ module_function
642
+ ###############
643
+
644
+ ### Debugging output
645
+ def debugMsg( *msgs ) # :nodoc:
646
+ $stderr.puts msgs.join(" ") if $DEBUG
647
+ end
648
+
649
+
650
+ ### Normalize a count to either 1 or 2 (singular or plural)
651
+ def normalizeCount( count, default=2 )
652
+ return default if count.nil? # Default to plural
653
+ if /^(#{PL_count_one})$/i =~ count.to_s ||
654
+ Linguistics::classical? &&
655
+ /^(#{PL_count_zero})$/ =~ count.to_s
656
+ return 1
657
+ else
658
+ return default
659
+ end
660
+ end
661
+
662
+
663
+ ### Do normal/classical switching and match capitalization in <tt>inflected</tt> by
664
+ ### examining the <tt>original</tt> input.
665
+ def postprocess( original, inflected )
666
+ inflected.sub!( /([^|]+)\|(.+)/ ) {
667
+ Linguistics::classical? ? $2 : $1
668
+ }
669
+
670
+ case original
671
+ when "I"
672
+ return inflected
673
+ when /^[A-Z]+$/
674
+ return inflected.upcase
675
+ when /^[A-Z]/
676
+ # Can't use #capitalize, as it will downcase the rest of the string,
677
+ # too.
678
+ inflected[0,1] = inflected[0,1].upcase
679
+ return inflected
680
+ else
681
+ return inflected
682
+ end
683
+ end
684
+
685
+
686
+ ### Pluralize nouns
687
+ def pluralize_noun( word, count=nil )
688
+ value = nil
689
+ count ||= Linguistics::num
690
+ count = normalizeCount( count )
691
+
692
+ return word if count == 1
693
+
694
+ # Handle user-defined nouns
695
+ #if value = ud_match( word, PL_sb_user_defined )
696
+ # return value
697
+ #end
698
+
699
+ # Handle empty word, singular count and uninflected plurals
700
+ case word
701
+ when ''
702
+ return word
703
+ when /^(#{PL_sb_uninflected})$/i
704
+ return word
705
+ else
706
+ if Linguistics::classical? &&
707
+ /^(#{PL_sb_uninflected_herd})$/i =~ word
708
+ return word
709
+ end
710
+ end
711
+
712
+ # Handle compounds ("Governor General", "mother-in-law", "aide-de-camp", etc.)
713
+ case word
714
+ when /^(?:#{PL_sb_postfix_adj})$/i
715
+ value = $2
716
+ return pluralize_noun( $1, 2 ) + value
717
+
718
+ when /^(?:#{PL_sb_prep_dual_compound})$/i
719
+ value = [ $2, $3 ]
720
+ return pluralize_noun( $1, 2 ) + value[0] + pluralize_noun( value[1] )
721
+
722
+ when /^(?:#{PL_sb_prep_compound})$/i
723
+ value = $2
724
+ return pluralize_noun( $1, 2 ) + value
725
+
726
+ # Handle pronouns
727
+ when /^((?:#{PL_prep})\s+)(#{PL_pron_acc})$/i
728
+ return $1 + PL_pron_acc_h[ $2.downcase ]
729
+
730
+ when /^(#{PL_pron_nom})$/i
731
+ return PL_pron_nom_h[ word.downcase ]
732
+
733
+ when /^(#{PL_pron_acc})$/i
734
+ return PL_pron_acc_h[ $1.downcase ]
735
+
736
+ # Handle isolated irregular plurals
737
+ when /(.*)\b(#{PL_sb_irregular})$/i
738
+ return $1 + PL_sb_irregular_h[ $2.downcase ]
739
+
740
+ when /(#{PL_sb_U_man_mans})$/i
741
+ return "#{$1}s"
742
+
743
+ # Handle families of irregular plurals
744
+ when /(.*)man$/i ; return "#{$1}men"
745
+ when /(.*[ml])ouse$/i ; return "#{$1}ice"
746
+ when /(.*)goose$/i ; return "#{$1}geese"
747
+ when /(.*)tooth$/i ; return "#{$1}teeth"
748
+ when /(.*)foot$/i ; return "#{$1}feet"
749
+
750
+ # Handle unassimilated imports
751
+ when /(.*)ceps$/i ; return word
752
+ when /(.*)zoon$/i ; return "#{$1}zoa"
753
+ when /(.*[csx])is$/i ; return "#{$1}es"
754
+ when /(#{PL_sb_U_ex_ices})ex$/i; return "#{$1}ices"
755
+ when /(#{PL_sb_U_ix_ices})ix$/i; return "#{$1}ices"
756
+ when /(#{PL_sb_U_um_a})um$/i ; return "#{$1}a"
757
+ when /(#{PL_sb_U_us_i})us$/i ; return "#{$1}i"
758
+ when /(#{PL_sb_U_on_a})on$/i ; return "#{$1}a"
759
+ when /(#{PL_sb_U_a_ae})$/i ; return "#{$1}e"
760
+ end
761
+
762
+ # Handle incompletely assimilated imports
763
+ if Linguistics::classical?
764
+ case word
765
+ when /(.*)trix$/i ; return "#{$1}trices"
766
+ when /(.*)eau$/i ; return "#{$1}eaux"
767
+ when /(.*)ieu$/i ; return "#{$1}ieux"
768
+ when /(.{2,}[yia])nx$/i ; return "#{$1}nges"
769
+ when /(#{PL_sb_C_en_ina})en$/i; return "#{$1}ina"
770
+ when /(#{PL_sb_C_ex_ices})ex$/i; return "#{$1}ices"
771
+ when /(#{PL_sb_C_ix_ices})ix$/i; return "#{$1}ices"
772
+ when /(#{PL_sb_C_um_a})um$/i ; return "#{$1}a"
773
+ when /(#{PL_sb_C_us_i})us$/i ; return "#{$1}i"
774
+ when /(#{PL_sb_C_us_us})$/i ; return "#{$1}"
775
+ when /(#{PL_sb_C_a_ae})$/i ; return "#{$1}e"
776
+ when /(#{PL_sb_C_a_ata})a$/i ; return "#{$1}ata"
777
+ when /(#{PL_sb_C_o_i})o$/i ; return "#{$1}i"
778
+ when /(#{PL_sb_C_on_a})on$/i ; return "#{$1}a"
779
+ when /#{PL_sb_C_im}$/i ; return "#{word}im"
780
+ when /#{PL_sb_C_i}$/i ; return "#{word}i"
781
+ end
782
+ end
783
+
784
+
785
+ # Handle singular nouns ending in ...s or other silibants
786
+ case word
787
+ when /^(#{PL_sb_singular_s})$/i; return "#{$1}es"
788
+ when /^([A-Z].*s)$/; return "#{$1}es"
789
+ when /(.*)([cs]h|[zx])$/i ; return "#{$1}#{$2}es"
790
+ # when /(.*)(us)$/i ; return "#{$1}#{$2}es"
791
+
792
+ # Handle ...f -> ...ves
793
+ when /(.*[eao])lf$/i ; return "#{$1}lves";
794
+ when /(.*[^d])eaf$/i ; return "#{$1}eaves"
795
+ when /(.*[nlw])ife$/i ; return "#{$1}ives"
796
+ when /(.*)arf$/i ; return "#{$1}arves"
797
+
798
+ # Handle ...y
799
+ when /(.*[aeiou])y$/i ; return "#{$1}ys"
800
+ when /([A-Z].*y)$/ ; return "#{$1}s"
801
+ when /(.*)y$/i ; return "#{$1}ies"
802
+
803
+ # Handle ...o
804
+ when /#{PL_sb_U_o_os}$/i ; return "#{word}s"
805
+ when /[aeiou]o$/i ; return "#{word}s"
806
+ when /o$/i ; return "#{word}es"
807
+
808
+ # Otherwise just add ...s
809
+ else
810
+ return "#{word}s"
811
+ end
812
+ end # def pluralize_noun
813
+
814
+
815
+
816
+ ### Pluralize special verbs
817
+ def pluralize_special_verb( word, count )
818
+ count ||= Linguistics::num
819
+ count = normalizeCount( count )
820
+
821
+ return nil if /^(#{PL_count_one})$/i =~ count.to_s
822
+
823
+ # Handle user-defined verbs
824
+ #if value = ud_match( word, PL_v_user_defined )
825
+ # return value
826
+ #end
827
+
828
+ case word
829
+
830
+ # Handle irregular present tense (simple and compound)
831
+ when /^(#{PL_v_irregular_pres})((\s.*)?)$/i
832
+ return PL_v_irregular_pres_h[ $1.downcase ] + $2
833
+
834
+ # Handle irregular future, preterite and perfect tenses
835
+ when /^(#{PL_v_irregular_non_pres})((\s.*)?)$/i
836
+ return word
837
+
838
+ # Handle special cases
839
+ when /^(#{PL_v_special_s})$/, /\s/
840
+ return nil
841
+
842
+ # Handle standard 3rd person (chop the ...(e)s off single words)
843
+ when /^(.*)([cs]h|[x]|zz|ss)es$/i
844
+ return $1 + $2
845
+ when /^(..+)ies$/i
846
+ return "#{$1}y"
847
+ when /^(.+)oes$/i
848
+ return "#{$1}o"
849
+ when /^(.*[^s])s$/i
850
+ return $1
851
+
852
+ # Otherwise, a regular verb (handle elsewhere)
853
+ else
854
+ return nil
855
+ end
856
+ end
857
+
858
+
859
+ ### Pluralize regular verbs
860
+ def pluralize_general_verb( word, count )
861
+ count ||= Linguistics::num
862
+ count = normalizeCount( count )
863
+
864
+ return word if /^(#{PL_count_one})$/i =~ count.to_s
865
+
866
+ case word
867
+
868
+ # Handle ambiguous present tenses (simple and compound)
869
+ when /^(#{PL_v_ambiguous_pres})((\s.*)?)$/i
870
+ return PL_v_ambiguous_pres_h[ $1.downcase ] + $2
871
+
872
+ # Handle ambiguous preterite and perfect tenses
873
+ when /^(#{PL_v_ambiguous_non_pres})((\s.*)?)$/i
874
+ return word
875
+
876
+ # Otherwise, 1st or 2nd person is uninflected
877
+ else
878
+ return word
879
+ end
880
+ end
881
+
882
+
883
+ ### Handle special adjectives
884
+ def pluralize_special_adjective( word, count )
885
+ count ||= Linguistics::num
886
+ count = normalizeCount( count )
887
+
888
+ return word if /^(#{PL_count_one})$/i =~ count.to_s
889
+
890
+ # Handle user-defined verbs
891
+ #if value = ud_match( word, PL_adj_user_defined )
892
+ # return value
893
+ #end
894
+
895
+ case word
896
+
897
+ # Handle known cases
898
+ when /^(#{PL_adj_special})$/i
899
+ return PL_adj_special_h[ $1.downcase ]
900
+
901
+ # Handle possessives
902
+ when /^(#{PL_adj_poss})$/i
903
+ return PL_adj_poss_h[ $1.downcase ]
904
+
905
+ when /^(.*)'s?$/
906
+ pl = plural_noun( $1 )
907
+ if /s$/ =~ pl
908
+ return "#{pl}'"
909
+ else
910
+ return "#{pl}'s"
911
+ end
912
+
913
+ # Otherwise, no idea
914
+ else
915
+ return nil
916
+ end
917
+ end
918
+
919
+
920
+ ### Returns the given word with a prepended indefinite article, unless
921
+ ### +count+ is non-nil and not singular.
922
+ def indef_article( word, count )
923
+ count ||= Linguistics::num
924
+ return "#{count} #{word}" if
925
+ count && /^(#{PL_count_one})$/i !~ count.to_s
926
+
927
+ # Handle user-defined variants
928
+ # return value if value = ud_match( word, A_a_user_defined )
929
+
930
+ case word
931
+
932
+ # Handle special cases
933
+ when /^(#{A_explicit_an})/i
934
+ return "an #{word}"
935
+
936
+ # Handle abbreviations
937
+ when /^(#{A_abbrev})/x
938
+ return "an #{word}"
939
+ when /^[aefhilmnorsx][.-]/i
940
+ return "an #{word}"
941
+ when /^[a-z][.-]/i
942
+ return "a #{word}"
943
+
944
+ # Handle consonants
945
+ when /^[^aeiouy]/i
946
+ return "a #{word}"
947
+
948
+ # Handle special vowel-forms
949
+ when /^e[uw]/i
950
+ return "a #{word}"
951
+ when /^onc?e\b/i
952
+ return "a #{word}"
953
+ when /^uni([^nmd]|mo)/i
954
+ return "a #{word}"
955
+ when /^u[bcfhjkqrst][aeiou]/i
956
+ return "a #{word}"
957
+
958
+ # Handle vowels
959
+ when /^[aeiou]/i
960
+ return "an #{word}"
961
+
962
+ # Handle y... (before certain consonants implies (unnaturalized) "i.." sound)
963
+ when /^(#{A_y_cons})/i
964
+ return "an #{word}"
965
+
966
+ # Otherwise, guess "a"
967
+ else
968
+ return "a #{word}"
969
+ end
970
+ end
971
+
972
+
973
+ ### Transform the specified number of units-place numerals into a
974
+ ### word-phrase at the given number of +thousands+ places.
975
+ def to_units( units, thousands=0 )
976
+ return Units[ units ] + to_thousands( thousands )
977
+ end
978
+
979
+
980
+ ### Transform the specified number of tens- and units-place numerals into a
981
+ ### word-phrase at the given number of +thousands+ places.
982
+ def to_tens( tens, units, thousands=0 )
983
+ unless tens == 1
984
+ return Tens[ tens ] + ( tens.nonzero? && units.nonzero? ? '-' : '' ) +
985
+ to_units( units, thousands )
986
+ else
987
+ return Teens[ units ] + to_thousands( thousands )
988
+ end
989
+ end
990
+
991
+
992
+ ### Transform the specified number of hundreds-, tens-, and units-place
993
+ ### numerals into a word phrase. If the number of thousands (+thousands+) is
994
+ ### greater than 0, it will be used to determine where the decimal point is
995
+ ### in relation to the hundreds-place number.
996
+ def to_hundreds( hundreds, tens=0, units=0, thousands=0, joinword=" and " )
997
+ joinword = ' ' if joinword.empty?
998
+ if hundreds.nonzero?
999
+ return to_units( hundreds ) + " hundred" +
1000
+ (tens.nonzero? || units.nonzero? ? joinword : '') +
1001
+ to_tens( tens, units ) +
1002
+ to_thousands( thousands )
1003
+ elsif tens.nonzero? || units.nonzero?
1004
+ return to_tens( tens, units ) + to_thousands( thousands )
1005
+ else
1006
+ return nil
1007
+ end
1008
+ end
1009
+
1010
+ ### Transform the specified number into one or more words like 'thousand',
1011
+ ### 'million', etc. Uses the thousands (American) system.
1012
+ def to_thousands( thousands=0 )
1013
+ parts = []
1014
+ (0..thousands).step( Thousands.length - 1 ) {|i|
1015
+ if i.zero?
1016
+ parts.push Thousands[ thousands % (Thousands.length - 1) ]
1017
+ else
1018
+ parts.push Thousands.last
1019
+ end
1020
+ }
1021
+
1022
+ return parts.join(" ")
1023
+ end
1024
+
1025
+
1026
+ ### Return the specified number +num+ as an array of number phrases.
1027
+ def number_to_words( num, config )
1028
+ return [config[:zero]] if num.to_i.zero?
1029
+ chunks = []
1030
+
1031
+ # Break into word-groups if groups is set
1032
+ if config[:group].nonzero?
1033
+
1034
+ # Build a Regexp with <config[:group]> number of digits. Any past
1035
+ # the first are optional.
1036
+ re = Regexp::new( "(\\d)" + ("(\\d)?" * (config[:group] - 1)) )
1037
+
1038
+ # Scan the string, and call the word-chunk function that deals with
1039
+ # chunks of the found number of digits.
1040
+ num.to_s.scan( re ) {|digits|
1041
+ debugMsg " digits = #{digits.inspect}"
1042
+ fn = NumberToWordsFunctions[ digits.nitems ]
1043
+ numerals = digits.flatten.compact.collect {|i| i.to_i}
1044
+ debugMsg " numerals = #{numerals.inspect}"
1045
+ chunks.push fn.call( config[:zero], *numerals ).strip
1046
+ }
1047
+ else
1048
+ phrase = num.to_s
1049
+ phrase.sub!( /\A\s*0+/, '' )
1050
+ mill = 0
1051
+
1052
+ # Match backward from the end of the digits in the string, turning
1053
+ # chunks of three, of two, and of one into words.
1054
+ mill += 1 while
1055
+ phrase.sub!( /(\d)(\d)(\d)(?=\D*\Z)/ ) {
1056
+ words = to_hundreds( $1.to_i, $2.to_i, $3.to_i, mill,
1057
+ config[:and] )
1058
+ chunks.unshift words.strip.squeeze(' ') unless words.nil?
1059
+ ''
1060
+ }
1061
+
1062
+ phrase.sub!( /(\d)(\d)(?=\D*\Z)/ ) {
1063
+ chunks.unshift to_tens( $1.to_i, $2.to_i, mill ).strip.squeeze(' ')
1064
+ ''
1065
+ }
1066
+ phrase.sub!( /(\d)(?=\D*\Z)/ ) {
1067
+ chunks.unshift to_units( $1.to_i, mill ).strip.squeeze(' ')
1068
+ ''
1069
+ }
1070
+ end
1071
+
1072
+ return chunks
1073
+ end
1074
+
1075
+
1076
+ #################################################################
1077
+ ### P U B L I C F U N C T I O N S
1078
+ #################################################################
1079
+
1080
+ ###############
1081
+ module_function
1082
+ ###############
1083
+
1084
+ ### Return the name of the language this module is for.
1085
+ def language
1086
+ "English"
1087
+ end
1088
+
1089
+
1090
+ ### Return the plural of the given +phrase+ if +count+ indicates it should
1091
+ ### be plural.
1092
+ def plural( phrase, count=nil )
1093
+ md = /\A(\s*)(.+?)(\s*)\Z/.match( phrase.to_s )
1094
+ pre, word, post = md.to_a[1,3]
1095
+ return phrase if word.nil? or word.empty?
1096
+
1097
+ plural = postprocess( word,
1098
+ pluralize_special_adjective(word, count) ||
1099
+ pluralize_special_verb(word, count) ||
1100
+ pluralize_noun(word, count) )
1101
+
1102
+ return pre + plural + post
1103
+ end
1104
+ alias_method :PL, :plural
1105
+
1106
+
1107
+ ### Return the plural of the given noun +phrase+ if +count+ indicates it
1108
+ ### should be plural.
1109
+ def plural_noun( phrase, count=nil )
1110
+ md = /\A(\s*)(.+?)(\s*)\Z/.match( phrase.to_s )
1111
+ pre, word, post = md.to_a[1,3]
1112
+ return phrase if word.nil? or word.empty?
1113
+
1114
+ plural = postprocess( word, pluralize_noun(word, count) )
1115
+ return pre + plural + post
1116
+ end
1117
+ alias_method :PL_N, :plural_noun
1118
+
1119
+
1120
+ ### Return the plural of the given verb +phrase+ if +count+ indicates it
1121
+ ### should be plural.
1122
+ def plural_verb( phrase, count=nil )
1123
+ md = /\A(\s*)(.+?)(\s*)\Z/.match( phrase.to_s )
1124
+ pre, word, post = md.to_a[1,3]
1125
+ return phrase if word.nil? or word.empty?
1126
+
1127
+ plural = postprocess( word,
1128
+ pluralize_special_verb(word, count) ||
1129
+ pluralize_general_verb(word, count) )
1130
+ return pre + plural + post
1131
+ end
1132
+ alias_method :PL_V, :plural_verb
1133
+
1134
+
1135
+ ### Return the plural of the given adjectival +phrase+ if +count+ indicates
1136
+ ### it should be plural.
1137
+ def plural_adjective( phrase, count=nil )
1138
+ md = /\A(\s*)(.+?)(\s*)\Z/.match( phrase.to_s )
1139
+ pre, word, post = md.to_a[1,3]
1140
+ return phrase if word.nil? or word.empty?
1141
+
1142
+ plural = postprocess( word,
1143
+ pluralize_special_adjective(word, count) || word )
1144
+ return pre + plural + post
1145
+ end
1146
+ alias_method :plural_adj, :plural_adjective
1147
+ alias_method :PL_ADJ, :plural_adjective
1148
+
1149
+
1150
+ ### Return the given phrase with the appropriate indefinite article ("a" or
1151
+ ### "an") prepended.
1152
+ def a( phrase, count=nil )
1153
+ md = /\A(\s*)(.+?)(\s*)\Z/.match( phrase.to_s )
1154
+ pre, word, post = md.to_a[1,3]
1155
+ return phrase if word.nil? or word.empty?
1156
+
1157
+ result = indef_article( word, count )
1158
+ return pre + result + post
1159
+ end
1160
+ alias_method :an, :a
1161
+ alias_method :A, :a
1162
+ alias_method :AN, :a
1163
+
1164
+
1165
+ ### Translate zero-quantified +phrase+ to "no +phrase.plural+"
1166
+ def no( phrase, count=nil )
1167
+ md = /\A(\s*)(.+?)(\s*)\Z/.match( phrase.to_s )
1168
+ pre, word, post = md.to_a[1,3]
1169
+ count ||= Linguistics::num || 0
1170
+
1171
+ unless /^#{PL_count_zero}$/ =~ count.to_s
1172
+ return "#{pre}#{count} " + plural( word, count ) + post
1173
+ else
1174
+ return "#{pre}no " + plural( word, 0 ) + post
1175
+ end
1176
+ end
1177
+ alias_method :NO, :no
1178
+
1179
+
1180
+ ### Participles
1181
+ def present_participle( word )
1182
+ plural = plural_verb( word.to_s, 2 )
1183
+
1184
+ plural.sub!( /ie$/, 'y' ) or
1185
+ plural.sub!( /ue$/, 'u' ) or
1186
+ plural.sub!( /([auy])e$/, '$1' ) or
1187
+ plural.sub!( /i$/, '' ) or
1188
+ plural.sub!( /([^e])e$/, "\\1" ) or
1189
+ /er$/.match( plural ) or
1190
+ plural.sub!( /([^aeiou][aeiouy]([bdgmnprst]))$/, "\\1\\2" )
1191
+
1192
+ return "#{plural}ing"
1193
+ end
1194
+ alias_method :part_pres, :present_participle
1195
+ alias_method :PART_PRES, :present_participle
1196
+
1197
+
1198
+
1199
+ ### Return the specified number as english words. One or more configuration
1200
+ ### values may be passed to control the returned String:
1201
+ ###
1202
+ ### [<b>:group</b>]
1203
+ ### Controls how many numbers at a time are grouped together. Valid values
1204
+ ### are +0+ (normal grouping), +1+ (single-digit grouping, e.g., "one,
1205
+ ### two, three, four"), +2+ (double-digit grouping, e.g., "twelve,
1206
+ ### thirty-four", or +3+ (triple-digit grouping, e.g., "one twenty-three,
1207
+ ### four").
1208
+ ### [<b>:comma</b>]
1209
+ ### Set the character/s used to separate word groups. Defaults to +", "+.
1210
+ ### [<b>:and</b>]
1211
+ ### Set the word and/or characters used where ' and ' (the default) is
1212
+ ### normally used. Setting <tt>:and</tt> to +' '+, for example, will cause
1213
+ ### +2556+ to be returned as "two-thousand, five hundred fifty-six"
1214
+ ### instead of ""two-thousand, five hundred and fifty-six".
1215
+ ### [<b>:zero</b>]
1216
+ ### Set the word used to represent the numeral +0+ in the result. +'zero'+
1217
+ ### is the default.
1218
+ ### [<b>:decimal</b>]
1219
+ ### Set the translation of any decimal points in the number; the default
1220
+ ### is +'point'+.
1221
+ ### [<b>:asArray</b>]
1222
+ ### If set to a true value, the number will be returned as an array of
1223
+ ### word groups instead of a String.
1224
+ def numwords( number, hashargs={} )
1225
+ num = number.to_s
1226
+ config = NumwordDefaults.dup.update( hashargs )
1227
+ raise "Bad chunking option: #{config[:group]}" unless
1228
+ config[:group].between?( 0, 3 )
1229
+
1230
+ # Array of number parts: first is everything to the left of the first
1231
+ # decimal, followed by any groups of decimal-delimted numbers after that
1232
+ parts = []
1233
+
1234
+ # Wordify any sign prefix
1235
+ sign = (/\A\s*\+/ =~ num) ? 'plus' : (/\A\s*\-/ =~ num) ? 'minus' : ''
1236
+
1237
+ # Strip any ordinal suffixes
1238
+ ord = true if num.sub!( /(st|nd|rd|th)\Z/, '' )
1239
+
1240
+ # Split the number into chunks delimited by '.'
1241
+ chunks = if !config[:decimal].empty? then
1242
+ if config[:group].nonzero?
1243
+ num.split(/\./)
1244
+ else
1245
+ num.split(/\./, 2)
1246
+ end
1247
+ else
1248
+ [ num ]
1249
+ end
1250
+
1251
+ # Wordify each chunk, pushing arrays into the parts array
1252
+ chunks.each_with_index {|chunk,section|
1253
+ chunk.gsub!( /\D+/, '' )
1254
+
1255
+ # If there's nothing in this chunk of the number, set it to zero
1256
+ # unless it's the whole-number part, in which case just push an
1257
+ # empty array.
1258
+ if chunk.empty?
1259
+ if section.zero?
1260
+ parts.push []
1261
+ next
1262
+ end
1263
+ end
1264
+
1265
+ # Split the number section into wordified parts unless this is the
1266
+ # second or succeeding part of a non-group number
1267
+ unless config[:group].zero? && section.nonzero?
1268
+ parts.push number_to_words( chunk, config )
1269
+ else
1270
+ parts.push number_to_words( chunk, config.dup.update(:group => 1) )
1271
+ end
1272
+ }
1273
+
1274
+ debugMsg "Parts => #{parts.inspect}"
1275
+
1276
+ # Turn the last word of the whole-number part back into an ordinal if
1277
+ # the original number came in that way.
1278
+ if ord && !parts[0].empty?
1279
+ parts[0][-1] = ordinal( parts[0].last )
1280
+ end
1281
+
1282
+ # If the caller's expecting an Array return, just flatten and return the
1283
+ # parts array.
1284
+ if config[:asArray]
1285
+ unless sign.empty?
1286
+ parts[0].unshift( sign )
1287
+ end
1288
+ return parts.flatten
1289
+ end
1290
+
1291
+ # Catenate each sub-parts array into a whole number part and one or more
1292
+ # post-decimal parts. If grouping is turned on, all sub-parts get joined
1293
+ # with commas, otherwise just the whole-number part is.
1294
+ if config[:group].zero?
1295
+ if parts[0].nitems > 1
1296
+
1297
+ # Join all but the last part together with commas
1298
+ wholenum = parts[0][0...-1].join( config[:comma] )
1299
+
1300
+ # If the last part is just a single word, append it to the
1301
+ # wholenum part with an 'and'. This is to get things like 'three
1302
+ # thousand and three' instead of 'three thousand, three'.
1303
+ if /^\s*(\S+)\s*$/ =~ parts[0].last
1304
+ wholenum += " and #{parts[0].last}"
1305
+ else
1306
+ wholenum += config[:comma] + parts[0].last
1307
+ end
1308
+ else
1309
+ wholenum = parts[0][0]
1310
+ end
1311
+ decimals = parts[1..-1].collect {|part| part.join(" ")}
1312
+
1313
+ debugMsg "Wholenum: #{wholenum.inspect}; decimals: #{decimals.inspect}"
1314
+
1315
+ # Join with the configured decimal; if it's empty, just join with
1316
+ # spaces.
1317
+ unless config[:decimal].empty?
1318
+ return sign + ([ wholenum ] + decimals).
1319
+ join( " #{config[:decimal]} " ).strip
1320
+ else
1321
+ return sign + ([ wholenum ] + decimals).
1322
+ join( " " ).strip
1323
+ end
1324
+ else
1325
+ return parts.compact.
1326
+ separate( config[:decimal] ).
1327
+ delete_if {|el| el.empty?}.
1328
+ join( config[:comma] ).
1329
+ strip
1330
+ end
1331
+ end
1332
+
1333
+
1334
+ ### Transform the given +number+ into an ordinal word. The +number+ object
1335
+ ### can be either an Integer or a String.
1336
+ def ordinal( number )
1337
+ case number
1338
+ when Integer
1339
+ return number.to_s + (Nth[ number % 100 ] || Nth[ number % 10 ])
1340
+
1341
+ else
1342
+ return number.to_s.sub( /(#{OrdinalSuffixes})\Z/ ) { Ordinals[$1] }
1343
+ end
1344
+ end
1345
+ alias_method :ORD, :ordinal
1346
+
1347
+
1348
+ ### Return a phrase describing the specified +number+ of objects in the
1349
+ ### given +phrase+. The following options can be used to control the makeup
1350
+ ### of the returned quantity String:
1351
+ ###
1352
+ ### [<b>:joinword</b>]
1353
+ ### Sets the word (and any surrounding spaces) used as the word separating the
1354
+ ### quantity from the noun in the resulting string. Defaults to <tt>' of
1355
+ ### '</tt>.
1356
+ def quantify( phrase, number=0, args={} )
1357
+ num = number.to_i
1358
+ config = QuantifyDefaults.dup.update( args )
1359
+
1360
+ case num
1361
+ when 0
1362
+ no( phrase )
1363
+ when 1
1364
+ a( phrase )
1365
+ when SeveralRange
1366
+ "several " + plural( phrase, num )
1367
+ when NumberRange
1368
+ "a number of " + plural( phrase, num )
1369
+ when NumerousRange
1370
+ "numerous " + plural( phrase, num )
1371
+ when ManyRange
1372
+ "many " + plural( phrase, num )
1373
+ else
1374
+
1375
+ # Anything bigger than the ManyRange gets described like
1376
+ # "hundreds of thousands of..." or "millions of..."
1377
+ # depending, of course, on how many there are.
1378
+ thousands, subthousands = Math::log10( num ).to_i.divmod( 3 )
1379
+ stword =
1380
+ case subthousands
1381
+ when 2
1382
+ "hundreds"
1383
+ when 1
1384
+ "tens"
1385
+ else
1386
+ nil
1387
+ end
1388
+ thword = plural( to_thousands(thousands).strip )
1389
+ thword = nil if thword.empty?
1390
+
1391
+ [ # Hundreds (of)...
1392
+ stword,
1393
+
1394
+ # thousands (of)
1395
+ thword,
1396
+
1397
+ # stars.
1398
+ plural(phrase, number)
1399
+ ].compact.join( config[:joinword] )
1400
+ end
1401
+ end
1402
+
1403
+
1404
+ ### Return the specified +obj+ (which must support the <tt>#collect</tt>
1405
+ ### method) as a conjunction. Each item is converted to a String if it is
1406
+ ### not already (using #to_s) unless a block is given, in which case it is
1407
+ ### called once for each object in the array, and the stringified return
1408
+ ### value from the block is used instead. Returning +nil+ causes that
1409
+ ### particular element to be omitted from the resulting conjunction. The
1410
+ ### following options can be used to control the makeup of the returned
1411
+ ### conjunction String:
1412
+ ###
1413
+ ### [<b>:separator</b>]
1414
+ ### Specify one or more characters to separate items in the resulting
1415
+ ### list. Defaults to <tt>', '</tt>.
1416
+ ### [<b>:altsep</b>]
1417
+ ### An alternate separator to use if any of the resulting conjunction's
1418
+ ### clauses contain the <tt>:separator</tt> character/s. Defaults to <tt>'; '</tt>.
1419
+ ### [<b>:penultimate</b>]
1420
+ ### Flag that indicates whether or not to join the last clause onto the
1421
+ ### rest of the conjunction using a penultimate <tt>:separator</tt>. E.g.,
1422
+ ### %w{duck, cow, dog}.en.conjunction
1423
+ ### # => "a duck, a cow, and a dog"
1424
+ ### %w{duck cow dog}.en.conjunction( :penultimate => false )
1425
+ ### "a duck, a cow and a dog"
1426
+ ### Default to <tt>true</tt>.
1427
+ ### [<b>:conjunctive</b>]
1428
+ ### Sets the word used as the conjunctive (separating word) of the
1429
+ ### resulting string. Default to <tt>'and'</tt>.
1430
+ ### [<b>:combine</b>]
1431
+ ### If set to <tt>true</tt> (the default), items which are indentical (after
1432
+ ### surrounding spaces are stripped) will be combined in the resulting
1433
+ ### conjunction. E.g.,
1434
+ ### %w{goose cow goose dog}.en.conjunction
1435
+ ### # => "two geese, a cow, and a dog"
1436
+ ### %w{goose cow goose dog}.en.conjunction( :combine => false )
1437
+ ### # => "a goose, a cow, a goose, and a dog"
1438
+ ### [<b>:casefold</b>]
1439
+ ### If set to <tt>true</tt> (the default), then items are compared
1440
+ ### case-insensitively when combining them. This has no effect if
1441
+ ### <tt>:combine</tt> is <tt>false</tt>.
1442
+ ### [<b>:generalize</b>]
1443
+ ### If set to <tt>true</tt>, then quantities of combined items are turned into
1444
+ ### general descriptions instead of exact amounts.
1445
+ ### ary = %w{goose pig dog horse goose reindeer goose dog horse}
1446
+ ### ary.en.conjunction
1447
+ ### # => "three geese, two dogs, two horses, a pig, and a reindeer"
1448
+ ### ary.en.conjunction( :generalize => true )
1449
+ ### # => "several geese, several dogs, several horses, a pig, and a reindeer"
1450
+ ### See the #quantify method for specifics on how quantities are
1451
+ ### generalized. Generalization defaults to <tt>false</tt>, and has no effect if
1452
+ ### :combine is <tt>false</tt>.
1453
+ ### [<b>:quantsort</b>]
1454
+ ### If set to <tt>true</tt> (the default), items which are combined in the
1455
+ ### resulting conjunction will be listed in order of amount, with greater
1456
+ ### quantities sorted first. If <tt>:quantsort</tt> is <tt>false</tt>, combined items
1457
+ ### will appear where the first instance of them occurred in the
1458
+ ### list. This sort is also the fallback for indentical quantities (ie.,
1459
+ ### items of the same quantity will be listed in the order they appeared
1460
+ ### in the source list).
1461
+ ###
1462
+ def conjunction( obj, args={} )
1463
+ config = ConjunctionDefaults.dup.update( args )
1464
+ phrases = []
1465
+
1466
+ # Transform items in the obj to phrases
1467
+ if block_given?
1468
+ phrases = obj.collect {|item| yield(item) }.compact
1469
+ else
1470
+ phrases = obj.collect {|item| item.to_s }
1471
+ end
1472
+
1473
+ # No need for a conjunction if there's only one thing
1474
+ return a(phrases[0]) if phrases.length < 2
1475
+
1476
+ # Set up a Proc to derive a collector key from a phrase depending on the
1477
+ # configuration
1478
+ keyfunc =
1479
+ if config[:casefold]
1480
+ proc {|key| key.downcase.strip}
1481
+ else
1482
+ proc {|key| key.strip}
1483
+ end
1484
+
1485
+ # Count and delete phrases that hash the same when the keyfunc munges
1486
+ # them into the same thing if we're combining (:combine => true).
1487
+ collector = {}
1488
+ if config[:combine]
1489
+
1490
+ phrases.each_index do |i|
1491
+ # Stop when reaching the end of a truncated list
1492
+ break if phrases[i].nil?
1493
+
1494
+ # Make the key using the configured key function
1495
+ phrase = keyfunc[ phrases[i] ]
1496
+
1497
+ # If the collector already has this key, increment its count,
1498
+ # eliminate the duplicate from the phrase list, and redo the loop.
1499
+ if collector.key?( phrase )
1500
+ collector[ phrase ] += 1
1501
+ phrases.delete_at( i )
1502
+ redo
1503
+ end
1504
+
1505
+ collector[ phrase ] = 1
1506
+ end
1507
+ else
1508
+ # If we're not combining, just make everything have a count of 1.
1509
+ phrases.uniq.each {|key| collector[ keyfunc[key] ] = 1}
1510
+ end
1511
+
1512
+ # If sort-by-quantity is turned on, sort the phrases first by how many
1513
+ # there are (most-first), and then by the order they were specified in.
1514
+ if config[:quantsort] && config[:combine]
1515
+ origorder = {}
1516
+ phrases.each_with_index {|phrase,i| origorder[ keyfunc[phrase] ] ||= i }
1517
+ phrases.sort! {|a,b|
1518
+ (collector[ keyfunc[b] ] <=> collector[ keyfunc[a] ]).nonzero? ||
1519
+ (origorder[ keyfunc[a] ] <=> origorder[ keyfunc[b] ])
1520
+ }
1521
+ end
1522
+
1523
+ # Set up a filtering function that adds either an indefinite article, an
1524
+ # indefinite quantifier, or a definite quantifier to each phrase
1525
+ # depending on the configuration and the count of phrases in the
1526
+ # collector.
1527
+ filter =
1528
+ if config[:generalize]
1529
+ proc {|phrase, count| quantify(phrase, count) }
1530
+ else
1531
+ proc {|phrase, count|
1532
+ if count > 1
1533
+ "%s %s" % [
1534
+ # :TODO: Make this threshold settable
1535
+ count < 10 ? count.en.numwords : count.to_s,
1536
+ plural(phrase, count)
1537
+ ]
1538
+ else
1539
+ a( phrase )
1540
+ end
1541
+ }
1542
+ end
1543
+
1544
+ # Now use the configured filter to turn each phrase into its final
1545
+ # form. Hmmm... square-bracket Lisp?
1546
+ phrases.collect! {|phrase| filter[phrase, collector[ keyfunc[phrase] ]] }
1547
+
1548
+ # Prepend the conjunctive to the last element unless it's empty or
1549
+ # there's only one element
1550
+ phrases[-1].insert( 0, config[:conjunctive] + " " ) unless
1551
+ config[:conjunctive].strip.empty? or
1552
+ phrases.length < 2
1553
+
1554
+ # Catenate the last two elements if there's no penultimate separator,
1555
+ # and pick a separator based on how many phrases there are and whether
1556
+ # or not there's already an instance of it in the phrases.
1557
+ phrases[-2] << " " << phrases.pop unless config[:penultimate]
1558
+ sep = if phrases.length <= 2
1559
+ ' '
1560
+ elsif phrases.grep( /#{config[:separator]}/ ).empty?
1561
+ config[:separator]
1562
+ else
1563
+ config[:altsep]
1564
+ end
1565
+
1566
+ return phrases.join( sep )
1567
+ end
1568
+
1569
+ end # module EN
1570
+ end # module Linguistics
1571
+
1572
+ ### Add the #separate and #separate! methods to Array.
1573
+ class Array # :nodoc:
1574
+
1575
+ ### Returns a new Array that has had a new member inserted between all of
1576
+ ### the current ones. The value used is the given +value+ argument unless a
1577
+ ### block is given, in which case the block is called once for each pair of
1578
+ ### the Array, and the return value is used as the separator.
1579
+ def separate( value=:__no_arg__, &block )
1580
+ ary = self.dup
1581
+ ary.separate!( value, &block )
1582
+ return ary
1583
+ end
1584
+
1585
+ ### The same as #separate, but modifies the Array in place.
1586
+ def separate!( value=:__no_arg__ )
1587
+ raise ArgumentError, "wrong number of arguments: (0 for 1)" if
1588
+ value == :__no_arg__ && !block_given?
1589
+
1590
+ (1..( (self.length * 2) - 2 )).step(2) do |i|
1591
+ if block_given?
1592
+ self.insert( i, yield(self[i-1,2]) )
1593
+ else
1594
+ self.insert( i, value )
1595
+ end
1596
+ end
1597
+ self
1598
+ end
1599
+
1600
+ end
1601
+