rwdeliza 0.03

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.
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
+