Linguistics 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+