muding 0.1.1 → 0.2.0

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