Linguistics 1.0.3

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.
@@ -0,0 +1,368 @@
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-2005 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 78 2005-07-13 19:58:43Z ged $
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
+
37
+ # Subversion revision
38
+ SVNRev = %q$Rev: 78 $
39
+
40
+ # Subversion ID
41
+ SVNid = %q$Id: linguistics.rb 78 2005-07-13 19:58:43Z ged $
42
+
43
+ # Language module implementors should do something like:
44
+ # Linguistics::DefaultLanguages.push( :ja ) # or whatever
45
+ # so that direct requiring of a language module sets the default.
46
+ DefaultLanguages = []
47
+
48
+ # The list of Classes to add linguistic behaviours to.
49
+ DefaultExtClasses = [String, Numeric, Array]
50
+
51
+
52
+ #################################################################
53
+ ### I N F L E C T O R C L A S S F A C T O R Y
54
+ #################################################################
55
+
56
+ ### A class which is inherited from by proxies for classes being extended
57
+ ### with one or more linguistic interfaces. It provides on-the-fly creation
58
+ ### of linguistic methods when the <tt>:installProxy</tt> option is passed
59
+ ### to the call to Linguistics#use.
60
+ class LanguageProxyClass
61
+
62
+ ### Class instance variable + accessor. Contains the module which knows
63
+ ### the specifics of the language the languageProxy class is providing
64
+ ### methods for.
65
+ @langmod = nil
66
+ class << self
67
+ attr_accessor :langmod
68
+ end
69
+
70
+
71
+ ### Create a new LanguageProxy for the given +receiver+.
72
+ def initialize( receiver )
73
+ @receiver = receiver
74
+ end
75
+
76
+
77
+ ######
78
+ public
79
+ ######
80
+
81
+ ### Overloaded to take into account the proxy method.
82
+ def respond_to?( sym )
83
+ self.class.langmod.respond_to?( sym ) || super
84
+ end
85
+
86
+
87
+ ### Autoload linguistic methods defined in the module this object's
88
+ ### class uses for inflection.
89
+ def method_missing( sym, *args )
90
+ return super unless self.class.langmod.respond_to?( sym )
91
+
92
+ self.class.module_eval %{
93
+ def #{sym}( *args, &block )
94
+ self.class.langmod.#{sym}( @receiver, *args, &block )
95
+ end
96
+ }, "{Autoloaded: " + __FILE__ + "}", __LINE__
97
+
98
+ self.method( sym ).call( *args )
99
+ end
100
+
101
+
102
+ ### Returns a human-readable representation of the languageProxy for
103
+ ### debugging, logging, etc.
104
+ def inspect
105
+ "<%s languageProxy for %s object %s>" % [
106
+ self.class.langmod.language,
107
+ @receiver.class.name,
108
+ @receiver.inspect,
109
+ ]
110
+ end
111
+
112
+ end
113
+
114
+
115
+ ### Extend the specified target object with one or more language proxy
116
+ ### methods, each of which provides access to one or more linguistic methods
117
+ ### for that language.
118
+ def self::extend_object( obj )
119
+ case obj
120
+ when Class
121
+ # $stderr.puts "Extending %p" % obj if $DEBUG
122
+ self::installLanguageProxy( obj )
123
+ else
124
+ sclass = (class << obj; self; end)
125
+ # $stderr.puts "Extending a object's metaclass: %p" % obj if $DEBUG
126
+ self::installLanguageProxy( sclass )
127
+ end
128
+
129
+ super
130
+ end
131
+
132
+
133
+ ### Extend the including class with linguistics proxy methods.
134
+ def self::included( mod )
135
+ # $stderr.puts "Including Linguistics in %p" % mod if $DEBUG
136
+ mod.extend( self ) unless mod == Linguistics
137
+ end
138
+
139
+
140
+ ### Make an languageProxy class that encapsulates all of the inflect operations
141
+ ### using the given language module.
142
+ def self::makeLanguageProxy( mod )
143
+ # $stderr.puts "Making language proxy for mod %p" % [mod]
144
+ Class::new( LanguageProxyClass ) {
145
+ @langmod = mod
146
+ }
147
+ end
148
+
149
+
150
+ ### Install the language proxy
151
+ def self::installLanguageProxy( klass, languages=DefaultLanguages )
152
+ languages.replace( DefaultLanguages ) if languages.empty?
153
+
154
+ # Create an languageProxy class for each language specified
155
+ languages.each {|lang|
156
+ # $stderr.puts "Extending the %p class with %p" %
157
+ # [ klass, lang ] if $DEBUG
158
+
159
+ # Load the language module (skipping to the next if it's already
160
+ # loaded), make an languageProxy class that delegates to it, and figure
161
+ # out what the languageProxy method will be called.
162
+ mod = loadLanguage( lang.to_s.downcase )
163
+ ifaceMeth = mod.name.downcase.sub( /.*:/, '' )
164
+ languageProxyClass = makeLanguageProxy( mod )
165
+
166
+ # Install a hash for languageProxy classes and an accessor for the
167
+ # hash if it's not already present.
168
+ if !klass.class_variables.include?( "@@__languageProxy_class" )
169
+ klass.module_eval %{
170
+ @@__languageProxy_class = {}
171
+ def self::__languageProxy_class; @@__languageProxy_class; end
172
+ }, __FILE__, __LINE__
173
+ end
174
+
175
+ # Merge the current languageProxy into the hash
176
+ klass.__languageProxy_class.merge!( ifaceMeth => languageProxyClass )
177
+
178
+ # Set the language-code proxy method for the class unless it has one
179
+ # already
180
+ unless klass.instance_methods(true).include?( ifaceMeth )
181
+ klass.module_eval %{
182
+ def #{ifaceMeth}
183
+ @__#{ifaceMeth}_languageProxy ||=
184
+ self.class.__languageProxy_class["#{ifaceMeth}"].
185
+ new( self )
186
+ end
187
+ }, __FILE__, __LINE__
188
+ end
189
+ }
190
+ end
191
+
192
+
193
+
194
+ ### Install a regular proxy method in the given klass that will delegate
195
+ ### calls to missing method to the languageProxy for the given +language+.
196
+ def self::installDelegatorProxy( klass, langcode )
197
+
198
+ # Alias any currently-extant
199
+ if klass.instance_methods( false ).include?( "method_missing" )
200
+ klass.module_eval %{
201
+ alias_method :__orig_method_missing, :method_missing
202
+ }
203
+ end
204
+
205
+ # Add the #method_missing method that auto-installs delegator methods
206
+ # for methods supported by the linguistic proxy objects.
207
+ klass.module_eval {
208
+ define_method( :method_missing ) do |sym, *args|
209
+
210
+ if self.send( langcode ).respond_to?( sym )
211
+
212
+ # $stderr.puts "Installing linguistic delegator method #{sym} " \
213
+ # "for the '#{langcode}' proxy"
214
+ self.class.module_eval %{
215
+ def #{sym}( *args )
216
+ self.#{langcode}.#{sym}( *args )
217
+ end
218
+ }
219
+ self.method( sym ).call( *args )
220
+
221
+ # Otherwise either call the overridden proxy method if there is
222
+ # one, or just let our parent deal with it.
223
+ else
224
+ if self.respond_to?( :__orig_method_missing )
225
+ return self.__orig_method_missing( sym, *args )
226
+ else
227
+ super( sym, *args )
228
+ end
229
+ end
230
+ end
231
+ }
232
+ end
233
+
234
+
235
+
236
+ #################################################################
237
+ ### 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
238
+ #################################################################
239
+
240
+ ###############
241
+ module_function
242
+ ###############
243
+
244
+ ### Add linguistics functions for the specified languages to Ruby's core
245
+ ### classes. The interface to all linguistic functions for a given language
246
+ ### is through a method which is the same the language's international 2- or
247
+ ### 3-letter code (ISO 639). You can also specify a Hash of configuration
248
+ ### options which control which classes are extended:
249
+ ###
250
+ ### [<b>:classes</b>]
251
+ ### Specify the classes which are to be extended. If this is not specified,
252
+ ### the Class objects in Linguistics::DefaultExtClasses (an Array) are
253
+ ### extended.
254
+ ### [<b>:installProxy</b>]
255
+ ### Install a proxy method in each of the classes which are to be extended
256
+ ### which will search for missing methods in the languageProxy for the
257
+ ### language code specified as the value. This allows linguistics methods
258
+ ### to be called directly on extended objects directly (e.g.,
259
+ ### 12.en.ordinal becomes 12.ordinal). Obviously, methods which would
260
+ ### collide with the object's builtin methods will need to be invoked
261
+ ### through the languageProxy. Any existing proxy methods in the extended
262
+ ### classes will be preserved.
263
+ def use( *languages )
264
+ config = {}
265
+ config = languages.pop if languages.last.is_a?( Hash )
266
+
267
+ classes = config.key?( :classes ) ? config[:classes] : DefaultExtClasses
268
+ classes = [ classes ] unless classes.is_a?( Array )
269
+
270
+ # Install the languageProxy in each class.
271
+ classes.each {|klass|
272
+
273
+ # Create an languageProxy class for each installed language
274
+ installLanguageProxy( klass, languages )
275
+
276
+ # Install the delegator proxy if configured
277
+ if config[:installProxy]
278
+ case config[:installProxy]
279
+ when Symbol
280
+ langcode = config[:installProxy]
281
+ when String
282
+ langcode = config[:installProxy].intern
283
+ when TrueClass
284
+ langcode = DefaultLanguages[0]
285
+ else
286
+ raise ArgumentError,
287
+ "Unexpected value %p for :installProxy" %
288
+ config[:installProxy]
289
+ end
290
+
291
+ installDelegatorProxy( klass, langcode )
292
+ end
293
+ }
294
+ end
295
+
296
+
297
+
298
+ ### Support Lingua::EN::Inflect-style globals in a threadsafe way by using
299
+ ### Thread-local variables.
300
+
301
+ ### Set the default count for all unspecified plurals to +val+. Setting is
302
+ ### local to calling thread.
303
+ def num=( val )
304
+ Thread.current[:persistent_count] = val
305
+ end
306
+ alias_method :NUM=, :num=
307
+
308
+ ### Get the default count for all unspecified plurals. Setting is local to
309
+ ### calling thread.
310
+ def num
311
+ Thread.current[:persistent_count]
312
+ end
313
+ alias_method :NUM, :num
314
+
315
+
316
+ ### Set the 'classical pluralizations' flag to +val+. Setting is local to
317
+ ### calling thread.
318
+ def classical=( val )
319
+ Thread.current[:classical_plurals] = val
320
+ end
321
+
322
+ ### Return the value of the 'classical pluralizations' flag. Setting is
323
+ ### local to calling thread.
324
+ def classical?
325
+ Thread.current[:classical_plurals] ? true : false
326
+ end
327
+
328
+
329
+ #######
330
+ private
331
+ #######
332
+
333
+ ### Try to load the module that implements the given language, returning
334
+ ### the Module object if successful.
335
+ def self::loadLanguage( lang )
336
+ raise "Unknown language code '#{lang}'" unless
337
+ LanguageCodes.key?( lang )
338
+
339
+ # Sort all the codes for the specified language, trying the 2-letter
340
+ # versions first in alphabetical order, then the 3-letter ones
341
+ msgs = []
342
+ mod = LanguageCodes[ lang ][:codes].sort {|a,b|
343
+ (a.length <=> b.length).nonzero? ||
344
+ (a <=> b)
345
+ }.each {|code|
346
+ unless Linguistics::const_defined?( code.upcase )
347
+ begin
348
+ require "linguistics/#{code}"
349
+ rescue LoadError => err
350
+ msgs << "Tried 'linguistics/#{code}': #{err.message}\n"
351
+ next
352
+ end
353
+ end
354
+
355
+ break Linguistics::const_get( code.upcase ) if
356
+ Linguistics::const_defined?( code.upcase )
357
+ }
358
+
359
+ if mod.is_a?( Array )
360
+ raise LoadError,
361
+ "Failed to load language extension %s:\n%s" %
362
+ [ lang, msgs.join ]
363
+ end
364
+ return mod
365
+ end
366
+
367
+ end # class linguistics
368
+
@@ -0,0 +1,298 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # CrossCase - a mixin for making methods look the way you like 'em. Or for
4
+ # making them look the way other people like 'em. Or something.
5
+ #
6
+ # == Synopsis
7
+ #
8
+ # class MyClass
9
+ # include CrossCase
10
+ #
11
+ # def underbarred_method; ...; end
12
+ # def camelCasedMethod; ...; end
13
+ # end
14
+ #
15
+ # obj = MyClass::new
16
+ # obj.underbarredMethod
17
+ # obj.camel_cased_method
18
+ #
19
+ # # -or-
20
+ #
21
+ # class MyClass
22
+ # def underbarred_method; ...; end
23
+ # def camelCasedMethod; ...; end
24
+ # end
25
+ #
26
+ # MyClass.extend( CrossCase )
27
+ # obj = MyClass::new
28
+ # obj.underbarredMethod
29
+ # obj.camel_cased_method
30
+ #
31
+ # == Description
32
+ #
33
+ # This module, when mixed into a Class or another Module, will provide
34
+ # under_barred aliases for class or instance methods with names which follow the
35
+ # camelCased naming conventions, and vice-versa. E.g., in a class which mixes in
36
+ # CrossCase, defining a method which is called +foo_bar+ will also create an
37
+ # alias for that method called +fooBar+.
38
+ #
39
+ # I wrote this module because I prefer camelCased method names, but also wish to
40
+ # respect the preferences of my fellow Rubyists for whom such practices are an
41
+ # abomination. And I'm too lazy to type
42
+ # alias :twinkle_twinkle :twinkleTwinkle
43
+ # for every method. It's all about laziness. Or perhaps I'm catering to my
44
+ # hubris. Or both; I'll shut up now.
45
+ #
46
+ # == Caveats
47
+ #
48
+ # This module uses the +method_added+ and +singleton_method_added+ hooks to
49
+ # generate aliases for new methods. If either or both of these methods are
50
+ # already defined, they will be preserved as aliases when the Class or Module is
51
+ # extended. It's up to you to return the favor should you create your own hook
52
+ # after this module is mixed in to your class.
53
+ #
54
+ # == Authors
55
+ #
56
+ # * Michael Granger <ged@FaerieMUD.org>
57
+ #
58
+ # === Thanks To
59
+ #
60
+ # * The denizens of irc://irc.freenode.net/#ruby-lang, and especially dblack,
61
+ # oGMo, and rubyhacker1 for name suggestions.
62
+ #
63
+ # == Copyright
64
+ #
65
+ # Copyright (c) 2003 The FaerieMUD Consortium. All rights reserved.
66
+ #
67
+ # This module is free software. You may use, modify, and/or redistribute this
68
+ # software under the same terms as Ruby
69
+ #
70
+ # This program is distributed in the hope that it will be useful, but WITHOUT
71
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
72
+ # FOR A PARTICULAR PURPOSE.
73
+ #
74
+ # == Version
75
+ #
76
+ # $Id: crosscase.rb 78 2005-07-13 19:58:43Z ged $
77
+ #
78
+
79
+
80
+ ### A mixin which causes aliases for methods with either under_barred or
81
+ ### camelCased naming conventions to be created in the opposing
82
+ ### convention. E.g., in a class which mixes in CrossCase, defining a method
83
+ ### which is called +foo_bar+ will also create an alias for that method called
84
+ ### +fooBar+.
85
+ module CrossCase
86
+
87
+ ### Versioning constants
88
+ Version = /([\d\.]+)/.match( %q{$Revision: 78 $} )[1]
89
+ Rcsid = %q$Id: crosscase.rb 78 2005-07-13 19:58:43Z ged $
90
+
91
+ ### The inclusion callback -- uses the Ouroboros trick to extend including
92
+ ### classes.
93
+ def self::included( mod )
94
+ mod.extend( self )
95
+ end
96
+
97
+
98
+ ### Object-extension callback -- installs aliases for any currently-extant
99
+ ### class or instance methods, and installs callbacks that will create
100
+ ### aliases for any subsequently-defined methods. Raises an error if any
101
+ ### object except a Class or Module is extended.
102
+ def self::extend_object( mod )
103
+ raise TypeError, "Expected a Module or a Class, got a " +
104
+ mod.class.name unless
105
+ mod.is_a?( Module )
106
+
107
+ self::transformClassMethods( mod )
108
+ self::transformInstanceMethods( mod )
109
+ self::installMethodHooks( mod )
110
+ end
111
+
112
+
113
+ ### Install +method_added+ and +singleton_method_added+ hooks into the given
114
+ ### Module +mod+ which auto-generate aliases for new methods.
115
+ def self::installMethodHooks( mod )
116
+ mod.module_eval {
117
+ class << self
118
+ if respond_to?( :singleton_method_added )
119
+ alias :__cc_sma :singleton_method_added
120
+ end
121
+ def singleton_method_added( id )
122
+ if aliasName = CrossCase::transform( id )
123
+ CrossCase::installClassAlias( self, id, aliasName )
124
+ end
125
+ if respond_to?( :__cc_sma )
126
+ __cc_sma( id )
127
+ else
128
+ super
129
+ end
130
+ end
131
+
132
+ if respond_to?( :method_added )
133
+ alias :__cc_ma :method_added
134
+ end
135
+ def method_added( id )
136
+ if instance_methods( false ).include?( id.to_s ) &&
137
+ ( aliasName = CrossCase::transform(id) )
138
+ CrossCase::installAlias( self, id, aliasName )
139
+ end
140
+ if respond_to?( :__cc_ma )
141
+ __cc_ma( id )
142
+ else
143
+ super
144
+ end
145
+ end
146
+ end
147
+ }
148
+ end
149
+
150
+
151
+ ### Search for and install aliases for either underbarred or camelCased
152
+ ### class methods for +mod+ (a Class or Module).
153
+ def self::transformClassMethods( mod )
154
+ self::findTargetMethods( mod.singleton_methods(false) ) {|meth, aliasName|
155
+ self::installClassAlias( mod, meth, aliasName )
156
+ }
157
+ end
158
+
159
+
160
+ ### Install an alias +aliasName+ for the given class method +meth+ of the
161
+ ### Class or Module +mod+.
162
+ def self::installClassAlias( mod, meth, aliasName )
163
+ unless mod.respond_to?( aliasName )
164
+ code = %{
165
+ class << self; alias_method( :#{aliasName}, :#{meth} ); end
166
+ }
167
+ mod.module_eval( code, __FILE__, __LINE__ )
168
+ end
169
+ end
170
+
171
+
172
+ ### Search for and install aliases for either underbarred or camelCased
173
+ ### instance methods for +mod+ (a Class or Module).
174
+ def self::transformInstanceMethods( mod )
175
+ self::findTargetMethods( mod.instance_methods(false) ) {|meth, aliasName|
176
+ self::installAlias( mod, meth, aliasName )
177
+ }
178
+ end
179
+
180
+
181
+ ### Install an alias +aliasName+ for the given instance method +meth+ of the
182
+ ### Class or Module +mod+.
183
+ def self::installAlias( mod, meth, aliasName )
184
+ unless mod.instance_methods(true).include?( aliasName )
185
+ mod.module_eval %{alias_method :#{aliasName}, :#{meth}}
186
+ end
187
+ end
188
+
189
+
190
+ ### Find methods in the given +methodList+ which are candidates for
191
+ ### aliasing.
192
+ def self::findTargetMethods( *methodList )
193
+ methodList.flatten.each {|meth|
194
+ next if /(singleton_)?method_added/ =~ meth
195
+ transformedName = transform( meth ) or next
196
+ yield( meth, transformedName )
197
+ }
198
+ end
199
+
200
+
201
+ ### Return an alternate name for the given method id +mid+. If the method id
202
+ ### is an under_barred method, returns a camelCased version, and
203
+ ### vice-versa. If no alternate is called for, returns +nil+.
204
+ def self::transform( mid )
205
+ methodName = mid.to_s
206
+ transformedName = ''
207
+
208
+ # camelCased methods
209
+ if /[A-Z]/.match( methodName ) && !/_/.match( methodName )
210
+ transformedName = methodName.gsub( /([a-z0-9])([A-Z])/ ) {|match|
211
+ $1 + '_' + $2
212
+ }.downcase
213
+
214
+ # underbarred_methods
215
+ elsif !/A-Z/.match( methodName ) && /[a-z0-9]_[a-z]/.match( methodName )
216
+ transformedName = methodName.gsub( /([a-z0-9])_([a-z])/ ) {|match|
217
+ $1 + $2.upcase
218
+ }
219
+
220
+ else
221
+ transformedName = nil
222
+ end
223
+
224
+ return transformedName
225
+ end
226
+
227
+
228
+ end
229
+
230
+
231
+ if $0 == __FILE__
232
+ require './utils'
233
+ include UtilityFunctions
234
+ $yaml = false
235
+
236
+ class Foo #:nodoc:
237
+ def self::singleton_method_added( id )
238
+ $stderr.puts "Original sma: Added #{id} to #{self.inspect}"
239
+ end
240
+
241
+ def self::method_added( id )
242
+ $stderr.puts "Original ma: Added #{id} to #{self.inspect}"
243
+ end
244
+
245
+ def self::classPreCamelMethod
246
+ "classPreCamelMethod"
247
+ end
248
+
249
+ def self::class_pre_underbarred_method
250
+ "class_pre_underbarred_method"
251
+ end
252
+
253
+ def preCamelCasedMethod
254
+ "preCamelCasedMethod"
255
+ end
256
+
257
+ def pre_underbarred_method
258
+ "pre_underbarred_method"
259
+ end
260
+
261
+ extend CrossCase
262
+
263
+ def self::classCamelMethod
264
+ "classCamelMethod"
265
+ end
266
+
267
+ def self::class_underbarred_method
268
+ "class_underbarred_method"
269
+ end
270
+
271
+ def camelCasedMethod
272
+ "camelCasedMethod"
273
+ end
274
+
275
+ def underbarred_method
276
+ "underbarred_method"
277
+ end
278
+ end
279
+
280
+ f = nil
281
+ try( "to instantiate Foo" ) { f = Foo::new }
282
+ %w{classPreCamelMethod class_pre_camel_method
283
+ class_pre_underbarred_method classPreUnderbarredMethod
284
+ classCamelMethod class_camel_method
285
+ class_underbarred_method classUnderbarredMethod
286
+ }.
287
+ sort.each {|meth|
288
+ try( "to call #{meth} on Foo" ) {
289
+ Foo.send( meth )
290
+ }
291
+ }
292
+ Foo.instance_methods(false).sort.each {|meth|
293
+ try( "to call #{meth} on the instance of Foo" ) {
294
+ f.send( meth )
295
+ }
296
+ }
297
+ end
298
+