muding 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,7 @@ class Handle
8
8
 
9
9
  @@handles = [] #All of the connections.
10
10
 
11
- attr_accessor :route, :session, :default_command, :peer
11
+ attr_accessor :route, :session, :default_command, :peer, :redirect, :args
12
12
 
13
13
  def Handle.this s
14
14
  @@handles << h = Handle.new(s)
@@ -26,7 +26,11 @@ class Handle
26
26
 
27
27
  while true
28
28
  update_route(response) if response
29
- response = Muding.run(peer.gets, self) #blocks on peer.gets
29
+ if self.redirect
30
+ response = Muding.run(self.args, self)
31
+ else
32
+ response = Muding.run(peer.gets, self) #blocks on peer.gets
33
+ end
30
34
  end
31
35
 
32
36
  rescue Errno::EPIPE
@@ -37,6 +41,8 @@ class Handle
37
41
  self.route = response.new_route if response.new_route
38
42
  self.default_command = nil
39
43
  self.default_command = response.default_command if response.default_command
44
+ self.redirect = response.redirect
45
+ self.args = response.args
40
46
  end
41
47
 
42
48
  def message(message_string)
@@ -0,0 +1,612 @@
1
+ require 'logger'
2
+ require 'set'
3
+ MUDING_FRAMEWORK_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..'))
4
+ MUDING_ENV = (ENV['MUDING_ENV'] || 'development').dup unless defined?(MUDING_ENV)
5
+
6
+ module Muding
7
+ # The Initializer is responsible for processing the Muding configuration, such
8
+ # as setting the $LOAD_PATH, requiring the right frameworks, initializing
9
+ # logging, and more. It can be run either as a single command that'll just
10
+ # use the default configuration, like this:
11
+ #
12
+ # Muding::Initializer.run
13
+ #
14
+ # But normally it's more interesting to pass in a custom configuration
15
+ # through the block running:
16
+ #
17
+ # Muding::Initializer.run do |config|
18
+ # config.frameworks -= [ :action_web_service ]
19
+ # end
20
+ #
21
+ # This will use the default configuration options from Muding::Configuration,
22
+ # but allow for overwriting on select areas.
23
+ class Initializer
24
+ # The Configuration instance used by this Initializer instance.
25
+ attr_reader :configuration
26
+
27
+ # The set of loaded plugins.
28
+ attr_reader :loaded_plugins
29
+
30
+ # Runs the initializer. By default, this will invoke the #process method,
31
+ # which simply executes all of the initialization routines. Alternately,
32
+ # you can specify explicitly which initialization routine you want:
33
+ #
34
+ # Muding::Initializer.run(:set_load_path)
35
+ #
36
+ # This is useful if you only want the load path initialized, without
37
+ # incuring the overhead of completely loading the entire environment.
38
+ def self.run(command = :process, configuration = Configuration.new)
39
+ yield configuration if block_given?
40
+ initializer = new configuration
41
+ initializer.send(command)
42
+ initializer
43
+ end
44
+
45
+ # Create a new Initializer instance that references the given Configuration
46
+ # instance.
47
+ def initialize(configuration)
48
+ @configuration = configuration
49
+ @loaded_plugins = Set.new
50
+ end
51
+
52
+ # Sequentially step through all of the available initialization routines,
53
+ # in order:
54
+ #
55
+ # * #set_load_path
56
+ # * #set_connection_adapters
57
+ # * #require_frameworks
58
+ # * #load_environment
59
+ # * #initialize_database
60
+ # * #initialize_logger
61
+ # * #initialize_framework_logging
62
+ # * #initialize_framework_views
63
+ # * #initialize_dependency_mechanism
64
+ # * #initialize_breakpoints
65
+ # * #initialize_whiny_nils
66
+ # * #initialize_framework_settings
67
+ # * #load_environment
68
+ # * #load_plugins
69
+ # * #initialize_routing
70
+ #
71
+ # (Note that #load_environment is invoked twice, once at the start and
72
+ # once at the end, to support the legacy configuration style where the
73
+ # environment could overwrite the defaults directly, instead of via the
74
+ # Configuration instance.
75
+ def process
76
+ check_ruby_version
77
+ set_load_path
78
+ set_connection_adapters
79
+
80
+ require_frameworks
81
+ #load_environment
82
+
83
+ initialize_database
84
+ initialize_logger
85
+ initialize_framework_logging
86
+ initialize_framework_views
87
+ initialize_dependency_mechanism
88
+ initialize_breakpoints
89
+ initialize_whiny_nils
90
+ initialize_temporary_directories
91
+
92
+ initialize_framework_settings
93
+
94
+ add_support_load_paths
95
+
96
+ load_plugins
97
+
98
+ # Routing must be initialized after plugins to allow the former to extend the routes
99
+ initialize_routing
100
+
101
+ # the framework is now fully initialized
102
+ after_initialize
103
+ end
104
+
105
+ # Check for valid Ruby version
106
+ # This is done in an external file, so we can use it
107
+ # from the `muding` program as well without duplication.
108
+ def check_ruby_version
109
+ require 'ruby_version_check'
110
+ end
111
+
112
+ # Set the <tt>$LOAD_PATH</tt> based on the value of
113
+ # Configuration#load_paths. Duplicates are removed.
114
+ def set_load_path
115
+ configuration.load_paths.reverse.each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
116
+ $LOAD_PATH.uniq!
117
+ end
118
+
119
+ # Sets the +MUDING_CONNECTION_ADAPTERS+ constant based on the value of
120
+ # Configuration#connection_adapters. This constant is used to determine
121
+ # which database adapters should be loaded (by default, all adapters are
122
+ # loaded).
123
+ def set_connection_adapters
124
+ Object.const_set("MUDING_CONNECTION_ADAPTERS", configuration.connection_adapters) if configuration.connection_adapters
125
+ end
126
+
127
+ # Requires all frameworks specified by the Configuration#frameworks
128
+ # list. By default, all frameworks (ActiveRecord, ActiveSupport,
129
+ # ActionPack, ActionMailer, and ActionWebService) are loaded.
130
+ def require_frameworks
131
+ configuration.frameworks.each { |framework| require(framework.to_s) }
132
+ end
133
+
134
+ # Add the load paths used by support functions such as the info controller
135
+ def add_support_load_paths
136
+ builtins = File.join(File.dirname(File.dirname(__FILE__)), 'builtin', '*')
137
+ $LOAD_PATH.concat(Dir[builtins])
138
+ end
139
+
140
+ # Loads all plugins in <tt>config.plugin_paths</tt>. <tt>plugin_paths</tt>
141
+ # defaults to <tt>vendor/plugins</tt> but may also be set to a list of
142
+ # paths, such as
143
+ # config.plugin_paths = ['lib/plugins', 'vendor/plugins']
144
+ #
145
+ # Each plugin discovered in <tt>plugin_paths</tt> is initialized:
146
+ # * add its +lib+ directory, if present, to the beginning of the load path
147
+ # * evaluate <tt>init.rb</tt> if present
148
+ #
149
+ # After all plugins are loaded, duplicates are removed from the load path.
150
+ # Plugins are loaded in alphabetical order.
151
+ def load_plugins
152
+ find_plugins(configuration.plugin_paths).sort.each { |path| load_plugin path }
153
+ $LOAD_PATH.uniq!
154
+ end
155
+
156
+ # Loads the environment specified by Configuration#environment_path, which
157
+ # is typically one of development, testing, or production.
158
+ def load_environment
159
+ silence_warnings do
160
+ config = configuration
161
+ constants = self.class.constants
162
+ eval(IO.read(configuration.environment_path), binding)
163
+ (self.class.constants - constants).each do |const|
164
+ Object.const_set(const, self.class.const_get(const))
165
+ end
166
+ end
167
+ end
168
+
169
+ # This initialization routine does nothing unless <tt>:active_record</tt>
170
+ # is one of the frameworks to load (Configuration#frameworks). If it is,
171
+ # this sets the database configuration from Configuration#database_configuration
172
+ # and then establishes the connection.
173
+ def initialize_database
174
+ return unless configuration.frameworks.include?(:active_record)
175
+ ActiveRecord::Base.configurations = configuration.database_configuration
176
+ ActiveRecord::Base.establish_connection(MUDING_ENV)
177
+ end
178
+
179
+ # If the +MUDING_DEFAULT_LOGGER+ constant is already set, this initialization
180
+ # routine does nothing. If the constant is not set, and Configuration#logger
181
+ # is not +nil+, this also does nothing. Otherwise, a new logger instance
182
+ # is created at Configuration#log_path, with a default log level of
183
+ # Configuration#log_level.
184
+ #
185
+ # If the log could not be created, the log will be set to output to
186
+ # +STDERR+, with a log level of +WARN+.
187
+ def initialize_logger
188
+ # if the environment has explicitly defined a logger, use it
189
+ return if defined?(MUDING_DEFAULT_LOGGER)
190
+
191
+ unless logger = configuration.logger
192
+ begin
193
+ logger = Logger.new(configuration.log_path)
194
+ logger.level = Logger.const_get(configuration.log_level.to_s.upcase)
195
+ rescue StandardError
196
+ logger = Logger.new(STDERR)
197
+ logger.level = Logger::WARN
198
+ logger.warn(
199
+ "Muding Error: Unable to access log file. Please ensure that #{configuration.log_path} exists and is chmod 0666. " +
200
+ "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
201
+ )
202
+ end
203
+ end
204
+
205
+ silence_warnings { Object.const_set "MUDING_DEFAULT_LOGGER", logger }
206
+ end
207
+
208
+ # Sets the logger for ActiveRecord, ActionController, and ActionMailer
209
+ # (but only for those frameworks that are to be loaded). If the framework's
210
+ # logger is already set, it is not changed, otherwise it is set to use
211
+ # +MUDING_DEFAULT_LOGGER+.
212
+ def initialize_framework_logging
213
+ for framework in ([ :active_record, :action_controller, :action_mailer ] & configuration.frameworks)
214
+ framework.to_s.camelize.constantize.const_get("Base").logger ||= MUDING_DEFAULT_LOGGER
215
+ end
216
+ end
217
+
218
+ # Sets the +template_root+ for ActionController::Base and ActionMailer::Base
219
+ # (but only for those frameworks that are to be loaded). If the framework's
220
+ # +template_root+ has already been set, it is not changed, otherwise it is
221
+ # set to use Configuration#view_path.
222
+ def initialize_framework_views
223
+ for framework in ([ :action_controller, :action_mailer ] & configuration.frameworks)
224
+ framework.to_s.camelize.constantize.const_get("Base").template_root ||= configuration.view_path
225
+ end
226
+ end
227
+
228
+ # If ActionController is not one of the loaded frameworks (Configuration#frameworks)
229
+ # this does nothing. Otherwise, it loads the routing definitions and sets up
230
+ # loading module used to lazily load controllers (Configuration#controller_paths).
231
+ def initialize_routing
232
+ return unless configuration.frameworks.include?(:action_controller)
233
+ ActionController::Routing::Routes.reload
234
+ end
235
+
236
+ # Sets the dependency loading mechanism based on the value of
237
+ # Configuration#cache_classes.
238
+ def initialize_dependency_mechanism
239
+ Dependencies.mechanism = configuration.cache_classes ? :require : :load
240
+ end
241
+
242
+ # Sets the +BREAKPOINT_SERVER_PORT+ if Configuration#breakpoint_server
243
+ # is true.
244
+ def initialize_breakpoints
245
+ silence_warnings { Object.const_set("BREAKPOINT_SERVER_PORT", 42531) if configuration.breakpoint_server }
246
+ end
247
+
248
+ # Loads support for "whiny nil" (noisy warnings when methods are invoked
249
+ # on +nil+ values) if Configuration#whiny_nils is true.
250
+ def initialize_whiny_nils
251
+ require('active_support/whiny_nil') if configuration.whiny_nils
252
+ end
253
+
254
+ def initialize_temporary_directories
255
+ if configuration.frameworks.include?(:action_controller)
256
+ session_path = "#{MUDING_ROOT}/tmp/sessions/"
257
+ ActionController::Base.session_options[:tmpdir] = File.exist?(session_path) ? session_path : Dir::tmpdir
258
+
259
+ cache_path = "#{MUDING_ROOT}/tmp/cache/"
260
+ if File.exist?(cache_path)
261
+ ActionController::Base.fragment_cache_store = :file_store, cache_path
262
+ end
263
+ end
264
+ end
265
+
266
+ # Initializes framework-specific settings for each of the loaded frameworks
267
+ # (Configuration#frameworks). The available settings map to the accessors
268
+ # on each of the corresponding Base classes.
269
+ def initialize_framework_settings
270
+ configuration.frameworks.each do |framework|
271
+ base_class = framework.to_s.camelize.constantize.const_get("Base")
272
+
273
+ configuration.send(framework).each do |setting, value|
274
+ base_class.send("#{setting}=", value)
275
+ end
276
+ end
277
+ end
278
+
279
+ # Fires the user-supplied after_initialize block (Configuration#after_initialize)
280
+ def after_initialize
281
+ configuration.after_initialize_block.call if configuration.after_initialize_block
282
+ end
283
+
284
+
285
+ protected
286
+ # Return a list of plugin paths within base_path. A plugin path is
287
+ # a directory that contains either a lib directory or an init.rb file.
288
+ # This recurses into directories which are not plugin paths, so you
289
+ # may organize your plugins within the plugin path.
290
+ def find_plugins(*base_paths)
291
+ base_paths.flatten.inject([]) do |plugins, base_path|
292
+ Dir.glob(File.join(base_path, '*')).each do |path|
293
+ if plugin_path?(path)
294
+ plugins << path
295
+ elsif File.directory?(path)
296
+ plugins += find_plugins(path)
297
+ end
298
+ end
299
+ plugins
300
+ end
301
+ end
302
+
303
+ def plugin_path?(path)
304
+ File.directory?(path) and (File.directory?(File.join(path, 'lib')) or File.file?(File.join(path, 'init.rb')))
305
+ end
306
+
307
+ # Load the plugin at <tt>path</tt> unless already loaded.
308
+ #
309
+ # Each plugin is initialized:
310
+ # * add its +lib+ directory, if present, to the beginning of the load path
311
+ # * evaluate <tt>init.rb</tt> if present
312
+ #
313
+ # Returns <tt>true</tt> if the plugin is successfully loaded or
314
+ # <tt>false</tt> if it is already loaded (similar to Kernel#require).
315
+ # Raises <tt>LoadError</tt> if the plugin is not found.
316
+ def load_plugin(directory)
317
+ name = File.basename(directory)
318
+ return false if loaded_plugins.include?(name)
319
+
320
+ # Catch nonexistent and empty plugins.
321
+ raise LoadError, "No such plugin: #{directory}" unless plugin_path?(directory)
322
+
323
+ lib_path = File.join(directory, 'lib')
324
+ init_path = File.join(directory, 'init.rb')
325
+ has_lib = File.directory?(lib_path)
326
+ has_init = File.file?(init_path)
327
+
328
+ # Add lib to load path *after* the application lib, to allow
329
+ # application libraries to override plugin libraries.
330
+ if has_lib
331
+ application_lib_index = $LOAD_PATH.index(File.join(MUDING_ROOT, "lib")) || 0
332
+ $LOAD_PATH.insert(application_lib_index + 1, lib_path)
333
+ end
334
+
335
+ # Allow plugins to reference the current configuration object
336
+ config = configuration
337
+
338
+ # Add to set of loaded plugins before 'name' collapsed in eval.
339
+ loaded_plugins << name
340
+
341
+ # Evaluate init.rb.
342
+ silence_warnings { eval(IO.read(init_path), binding, init_path) } if has_init
343
+
344
+ true
345
+ end
346
+ end
347
+
348
+ # The Configuration class holds all the parameters for the Initializer and
349
+ # ships with defaults that suites most Muding applications. But it's possible
350
+ # to overwrite everything. Usually, you'll create an Configuration file
351
+ # implicitly through the block running on the Initializer, but it's also
352
+ # possible to create the Configuration instance in advance and pass it in
353
+ # like this:
354
+ #
355
+ # config = Muding::Configuration.new
356
+ # Muding::Initializer.run(:process, config)
357
+ class Configuration
358
+ # A stub for setting options on ActionController::Base
359
+ attr_accessor :action_controller
360
+
361
+ # A stub for setting options on ActionMailer::Base
362
+ attr_accessor :action_mailer
363
+
364
+ # A stub for setting options on ActionView::Base
365
+ attr_accessor :action_view
366
+
367
+ # A stub for setting options on ActionWebService::Base
368
+ attr_accessor :action_web_service
369
+
370
+ # A stub for setting options on ActiveRecord::Base
371
+ attr_accessor :active_record
372
+
373
+ # Whether or not to use the breakpoint server (boolean)
374
+ attr_accessor :breakpoint_server
375
+
376
+ # Whether or not classes should be cached (set to false if you want
377
+ # application classes to be reloaded on each request)
378
+ attr_accessor :cache_classes
379
+
380
+ # The list of connection adapters to load. (By default, all connection
381
+ # adapters are loaded. You can set this to be just the adapter(s) you
382
+ # will use to reduce your application's load time.)
383
+ attr_accessor :connection_adapters
384
+
385
+ # The list of paths that should be searched for controllers. (Defaults
386
+ # to <tt>app/controllers</tt> and <tt>components</tt>.)
387
+ attr_accessor :controller_paths
388
+
389
+ # The path to the database configuration file to use. (Defaults to
390
+ # <tt>config/database.yml</tt>.)
391
+ attr_accessor :database_configuration_file
392
+
393
+ # The list of muding framework components that should be loaded. (Defaults
394
+ # to <tt>:active_record</tt>, <tt>:action_controller</tt>,
395
+ # <tt>:action_view</tt>, <tt>:action_mailer</tt>, and
396
+ # <tt>:action_web_service</tt>).
397
+ attr_accessor :frameworks
398
+
399
+ # An array of additional paths to prepend to the load path. By default,
400
+ # all +app+, +lib+, +vendor+ and mock paths are included in this list.
401
+ attr_accessor :load_paths
402
+
403
+ # The log level to use for the default Muding logger. In production mode,
404
+ # this defaults to <tt>:info</tt>. In development mode, it defaults to
405
+ # <tt>:debug</tt>.
406
+ attr_accessor :log_level
407
+
408
+ # The path to the log file to use. Defaults to log/#{environment}.log
409
+ # (e.g. log/development.log or log/production.log).
410
+ attr_accessor :log_path
411
+
412
+ # The specific logger to use. By default, a logger will be created and
413
+ # initialized using #log_path and #log_level, but a programmer may
414
+ # specifically set the logger to use via this accessor and it will be
415
+ # used directly.
416
+ attr_accessor :logger
417
+
418
+ # The root of the application's views. (Defaults to <tt>app/views</tt>.)
419
+ attr_accessor :view_path
420
+
421
+ # Set to +true+ if you want to be warned (noisily) when you try to invoke
422
+ # any method of +nil+. Set to +false+ for the standard Ruby behavior.
423
+ attr_accessor :whiny_nils
424
+
425
+ # The path to the root of the plugins directory. By default, it is in
426
+ # <tt>vendor/plugins</tt>.
427
+ attr_accessor :plugin_paths
428
+
429
+ # Create a new Configuration instance, initialized with the default
430
+ # values.
431
+ def initialize
432
+ self.frameworks = default_frameworks
433
+ self.load_paths = default_load_paths
434
+ self.log_path = default_log_path
435
+ self.log_level = default_log_level
436
+ self.view_path = default_view_path
437
+ self.controller_paths = default_controller_paths
438
+ self.cache_classes = default_cache_classes
439
+ self.breakpoint_server = default_breakpoint_server
440
+ self.whiny_nils = default_whiny_nils
441
+ self.plugin_paths = default_plugin_paths
442
+ self.database_configuration_file = default_database_configuration_file
443
+
444
+ for framework in default_frameworks
445
+ self.send("#{framework}=", OrderedOptions.new)
446
+ end
447
+ end
448
+
449
+ # Loads and returns the contents of the #database_configuration_file. The
450
+ # contents of the file are processed via ERB before being sent through
451
+ # YAML::load.
452
+ def database_configuration
453
+ YAML::load(ERB.new(IO.read(database_configuration_file)).result)
454
+ end
455
+
456
+ # The path to the current environment's file (development.rb, etc.). By
457
+ # default the file is at <tt>config/environments/#{environment}.rb</tt>.
458
+ def environment_path
459
+ "#{root_path}/config/environments/#{environment}.rb"
460
+ end
461
+
462
+ # Return the currently selected environment. By default, it returns the
463
+ # value of the +MUDING_ENV+ constant.
464
+ def environment
465
+ ::MUDING_ENV
466
+ end
467
+
468
+ # Sets a block which will be executed after muding has been fully initialized.
469
+ # Useful for per-environment configuration which depends on the framework being
470
+ # fully initialized.
471
+ def after_initialize(&after_initialize_block)
472
+ @after_initialize_block = after_initialize_block
473
+ end
474
+
475
+ # Returns the block set in Configuration#after_initialize
476
+ def after_initialize_block
477
+ @after_initialize_block
478
+ end
479
+
480
+ private
481
+ def root_path
482
+ ::MUDING_ROOT
483
+ end
484
+
485
+ def framework_root_path
486
+ defined?(::MUDING_FRAMEWORK_ROOT) ? ::MUDING_FRAMEWORK_ROOT : "#{root_path}/vendor/muding"
487
+ end
488
+
489
+ def default_frameworks
490
+ [ :active_record, :action_view, :action_mailer ]
491
+ end
492
+
493
+ def default_load_paths
494
+ paths = ["#{root_path}/test/mocks/#{environment}"]
495
+
496
+ # Add the app's controller directory
497
+ paths.concat(Dir["#{root_path}/app/controllers/"])
498
+
499
+ # Then model subdirectories.
500
+ # TODO: Don't include .rb models as load paths
501
+ paths.concat(Dir["#{root_path}/app/models/[_a-z]*"])
502
+ paths.concat(Dir["#{root_path}/components/[_a-z]*"])
503
+
504
+ # Followed by the standard includes.
505
+ paths.concat %w(
506
+ mud
507
+ mud/models
508
+ mud/controllers
509
+ mud/helpers
510
+ config
511
+ lib
512
+ vendor
513
+ ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
514
+
515
+ # TODO: Don't include dirs for frameworks that are not used
516
+ paths.concat %w(
517
+ muding
518
+ muding/lib
519
+ activesupport/lib
520
+ activerecord/lib
521
+ ).map { |dir| "#{framework_root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
522
+ end
523
+
524
+ def default_log_path
525
+ File.join(root_path, 'log', "#{environment}.log")
526
+ end
527
+
528
+ def default_log_level
529
+ environment == 'production' ? :info : :debug
530
+ end
531
+
532
+ def default_database_configuration_file
533
+ File.join(root_path, 'config', 'database.yml')
534
+ end
535
+
536
+ def default_view_path
537
+ File.join(root_path, 'mud', 'views')
538
+ end
539
+
540
+ def default_controller_paths
541
+ [ File.join(root_path, 'mud', 'controllers')]
542
+ end
543
+
544
+ def default_dependency_mechanism
545
+ :load
546
+ end
547
+
548
+ def default_cache_classes
549
+ false
550
+ end
551
+
552
+ def default_breakpoint_server
553
+ false
554
+ end
555
+
556
+ def default_whiny_nils
557
+ false
558
+ end
559
+
560
+ def default_plugin_paths
561
+ ["#{root_path}/vendor/plugins"]
562
+ end
563
+ end
564
+ end
565
+
566
+ # Needs to be duplicated from Active Support since its needed before Active
567
+ # Support is available.
568
+ class OrderedHash < Array #:nodoc:
569
+ def []=(key, value)
570
+ if pair = find_pair(key)
571
+ pair.pop
572
+ pair << value
573
+ else
574
+ self << [key, value]
575
+ end
576
+ end
577
+
578
+ def [](key)
579
+ pair = find_pair(key)
580
+ pair ? pair.last : nil
581
+ end
582
+
583
+ def keys
584
+ self.collect { |i| i.first }
585
+ end
586
+
587
+ private
588
+ def find_pair(key)
589
+ self.each { |i| return i if i.first == key }
590
+ return false
591
+ end
592
+ end
593
+
594
+ class OrderedOptions < OrderedHash #:nodoc:
595
+ def []=(key, value)
596
+ super(key.to_sym, value)
597
+ end
598
+
599
+ def [](key)
600
+ super(key.to_sym)
601
+ end
602
+
603
+ def method_missing(name, *args)
604
+ if name.to_s =~ /(.*)=$/
605
+ self[$1.to_sym] = args.first
606
+ else
607
+ self[name]
608
+ end
609
+ end
610
+ end
611
+
612
+