gamebox 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/History.txt +18 -0
  2. data/Manifest.txt +85 -0
  3. data/README.txt +33 -0
  4. data/Rakefile +42 -0
  5. data/TODO.txt +29 -0
  6. data/bin/gamebox +49 -0
  7. data/docs/gamebox04_big.png +0 -0
  8. data/docs/getting_started.rdoc +99 -0
  9. data/docs/logo.png +0 -0
  10. data/lib/gamebox.rb +6 -0
  11. data/lib/gamebox/actor.rb +143 -0
  12. data/lib/gamebox/actor_factory.rb +64 -0
  13. data/lib/gamebox/actor_view.rb +35 -0
  14. data/lib/gamebox/ai/line_of_site.rb +61 -0
  15. data/lib/gamebox/ai/polaris.rb +107 -0
  16. data/lib/gamebox/ai/two_d_grid_location.rb +21 -0
  17. data/lib/gamebox/ai/two_d_grid_map.rb +77 -0
  18. data/lib/gamebox/aliasing.rb +16 -0
  19. data/lib/gamebox/animated.rb +84 -0
  20. data/lib/gamebox/behavior.rb +16 -0
  21. data/lib/gamebox/config_manager.rb +22 -0
  22. data/lib/gamebox/console_app.rb +39 -0
  23. data/lib/gamebox/data/fonts/Asimov.ttf +0 -0
  24. data/lib/gamebox/data/fonts/GAMEBOX_FONTS_GO_HERE +0 -0
  25. data/lib/gamebox/data/graphics/GAMEBOX_GRAPHICS_GO_HERE +0 -0
  26. data/lib/gamebox/data/graphics/logo.png +0 -0
  27. data/lib/gamebox/data/music/GAMEBOX_MUSIC_GOES_HERE +0 -0
  28. data/lib/gamebox/data/sounds/GAMEBOX_SOUND_FX_GO_HERE +0 -0
  29. data/lib/gamebox/director.rb +47 -0
  30. data/lib/gamebox/gamebox_application.rb +77 -0
  31. data/lib/gamebox/graphical.rb +24 -0
  32. data/lib/gamebox/graphical_actor_view.rb +31 -0
  33. data/lib/gamebox/inflections.rb +52 -0
  34. data/lib/gamebox/inflector.rb +278 -0
  35. data/lib/gamebox/input_manager.rb +104 -0
  36. data/lib/gamebox/layered.rb +34 -0
  37. data/lib/gamebox/level.rb +64 -0
  38. data/lib/gamebox/linked_list.rb +137 -0
  39. data/lib/gamebox/logo.rb +11 -0
  40. data/lib/gamebox/metaclass.rb +6 -0
  41. data/lib/gamebox/mode.rb +123 -0
  42. data/lib/gamebox/mode_manager.rb +80 -0
  43. data/lib/gamebox/numbers_ext.rb +3 -0
  44. data/lib/gamebox/physical.rb +139 -0
  45. data/lib/gamebox/physical_director.rb +17 -0
  46. data/lib/gamebox/physical_level.rb +89 -0
  47. data/lib/gamebox/physics.rb +27 -0
  48. data/lib/gamebox/publisher_ext.rb +13 -0
  49. data/lib/gamebox/resource_manager.rb +122 -0
  50. data/lib/gamebox/score.rb +35 -0
  51. data/lib/gamebox/sorted_list.rb +59 -0
  52. data/lib/gamebox/sound_manager.rb +84 -0
  53. data/lib/gamebox/surface_ext.rb +37 -0
  54. data/lib/gamebox/svg_actor.rb +55 -0
  55. data/lib/gamebox/svg_document.rb +160 -0
  56. data/lib/gamebox/template_app/README +30 -0
  57. data/lib/gamebox/template_app/Rakefile +20 -0
  58. data/lib/gamebox/template_app/config/boot.rb +5 -0
  59. data/lib/gamebox/template_app/config/environment.rb +29 -0
  60. data/lib/gamebox/template_app/config/game.yml +6 -0
  61. data/lib/gamebox/template_app/config/mode_level_config.yml +3 -0
  62. data/lib/gamebox/template_app/config/objects.yml +29 -0
  63. data/lib/gamebox/template_app/data/fonts/FONTS_GO_HERE +0 -0
  64. data/lib/gamebox/template_app/data/graphics/GRAPHICS_GO_HERE +0 -0
  65. data/lib/gamebox/template_app/data/music/MUSIC_GOES_HERE +0 -0
  66. data/lib/gamebox/template_app/data/sounds/SOUND_FX_GO_HERE +0 -0
  67. data/lib/gamebox/template_app/doc/README_FOR_APP +1 -0
  68. data/lib/gamebox/template_app/lib/code_statistics.rb +107 -0
  69. data/lib/gamebox/template_app/lib/diy.rb +371 -0
  70. data/lib/gamebox/template_app/lib/platform.rb +16 -0
  71. data/lib/gamebox/template_app/src/app.rb +8 -0
  72. data/lib/gamebox/template_app/src/demo_level.rb +20 -0
  73. data/lib/gamebox/template_app/src/game.rb +22 -0
  74. data/lib/gamebox/template_app/src/my_actor.rb +17 -0
  75. data/lib/gamebox/version.rb +10 -0
  76. data/lib/gamebox/viewport.rb +81 -0
  77. data/lib/gamebox/wrapped_screen.rb +15 -0
  78. data/script/perf_polaris.rb +36 -0
  79. data/test/helper.rb +25 -0
  80. data/test/test_actor.rb +38 -0
  81. data/test/test_animated.rb +64 -0
  82. data/test/test_line_of_site.rb +14 -0
  83. data/test/test_physical.rb +26 -0
  84. data/test/test_polaris.rb +193 -0
  85. data/test/test_viewport.rb +116 -0
  86. metadata +188 -0
@@ -0,0 +1,20 @@
1
+ libdir = File.dirname(__FILE__)+"/lib"
2
+ $: << libdir
3
+ confdir = File.dirname(__FILE__)+"/config"
4
+ $: << confdir
5
+
6
+ require 'environment'
7
+ STATS_DIRECTORIES = [
8
+ %w(Source src/),
9
+ %w(Config config/),
10
+ %w(Maps maps/),
11
+ %w(Unit\ tests specs/),
12
+ %w(Libraries lib/),
13
+ ].collect { |name, dir| [ name, "#{APP_ROOT}/#{dir}" ] }.select { |name, dir| File.directory?(dir) }
14
+
15
+ desc "Report code statistics (KLOCs, etc) from the application"
16
+ task :stats do
17
+ require 'code_statistics'
18
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
19
+ end
20
+
@@ -0,0 +1,5 @@
1
+ # Don't change this file!
2
+ # Configure your app in config/environment.rb
3
+
4
+ GAMEBOX_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(GAMEBOX_ROOT)
5
+
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ ADDITIONAL_LOAD_PATHS = []
3
+ ADDITIONAL_LOAD_PATHS.concat %w(
4
+ src
5
+ lib
6
+ config
7
+ ../../lib
8
+ ).map { |dir| File.dirname(__FILE__) + "/../" + dir }.select { |dir| File.directory?(dir) }
9
+
10
+ ADDITIONAL_LOAD_PATHS.each do |path|
11
+ $:.push path
12
+ end
13
+
14
+ APP_ROOT = File.dirname(__FILE__) + "/../"
15
+ CONFIG_PATH = APP_ROOT + "config/"
16
+ DATA_PATH = APP_ROOT + "data/"
17
+ SOUND_PATH = APP_ROOT + "data/sounds/"
18
+ MUSIC_PATH = APP_ROOT + "data/music/"
19
+ GFX_PATH = APP_ROOT + "data/graphics/"
20
+ FONTS_PATH = APP_ROOT + "data/fonts/"
21
+
22
+ require 'gamebox'
23
+
24
+ GAMEBOX_DATA_PATH = GAMEBOX_PATH + "data/"
25
+ GAMEBOX_SOUND_PATH = GAMEBOX_PATH + "data/sounds/"
26
+ GAMEBOX_MUSIC_PATH = GAMEBOX_PATH + "data/music/"
27
+ GAMEBOX_GFX_PATH = GAMEBOX_PATH + "data/graphics/"
28
+ GAMEBOX_FONTS_PATH = GAMEBOX_PATH + "data/fonts/"
29
+
@@ -0,0 +1,6 @@
1
+ ---
2
+ :sound: true
3
+ :fullscreen: false
4
+ :screen_resolution:
5
+ - 1024
6
+ - 800
@@ -0,0 +1,3 @@
1
+ :modes:
2
+ - :default:
3
+ - :demo:
@@ -0,0 +1,29 @@
1
+ game:
2
+ compose:
3
+ - wrapped_screen
4
+ - input_manager
5
+ - sound_manager
6
+ - mode_manager
7
+ resource_manager:
8
+ mode_manager:
9
+ compose:
10
+ - input_manager
11
+ - resource_manager
12
+ - sound_manager
13
+ - config_manager
14
+ - actor_factory
15
+ sound_manager:
16
+ compose:
17
+ - resource_manager
18
+ - config_manager
19
+ input_manager:
20
+ wrapped_screen:
21
+ compose:
22
+ - config_manager
23
+ actor_factory:
24
+ compose:
25
+ - input_manager
26
+ - sound_manager
27
+ config_manager:
28
+ compose:
29
+ - resource_manager
@@ -0,0 +1 @@
1
+ This is where you put documentation for your app.
@@ -0,0 +1,107 @@
1
+ class CodeStatistics #:nodoc:
2
+
3
+ TEST_TYPES = %w(Units Functionals Unit\ tests Functional\ tests Integration\ tests)
4
+
5
+ def initialize(*pairs)
6
+ @pairs = pairs
7
+ @statistics = calculate_statistics
8
+ @total = calculate_total if pairs.length > 1
9
+ end
10
+
11
+ def to_s
12
+ print_header
13
+ @pairs.each { |pair| print_line(pair.first, @statistics[pair.first]) }
14
+ print_splitter
15
+
16
+ if @total
17
+ print_line("Total", @total)
18
+ print_splitter
19
+ end
20
+
21
+ print_code_test_stats
22
+ end
23
+
24
+ private
25
+ def calculate_statistics
26
+ @pairs.inject({}) { |stats, pair| stats[pair.first] = calculate_directory_statistics(pair.last); stats }
27
+ end
28
+
29
+ def calculate_directory_statistics(directory, pattern = /.*\.rb$/)
30
+ stats = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
31
+
32
+ Dir.foreach(directory) do |file_name|
33
+ if File.stat(directory + "/" + file_name).directory? and (/^\./ !~ file_name)
34
+ newstats = calculate_directory_statistics(directory + "/" + file_name, pattern)
35
+ stats.each { |k, v| stats[k] += newstats[k] }
36
+ end
37
+
38
+ next unless file_name =~ pattern
39
+
40
+ f = File.open(directory + "/" + file_name)
41
+
42
+ while line = f.gets
43
+ stats["lines"] += 1
44
+ stats["classes"] += 1 if line =~ /class [A-Z]/
45
+ stats["methods"] += 1 if line =~ /def [a-z]/
46
+ stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/
47
+ end
48
+ end
49
+
50
+ stats
51
+ end
52
+
53
+ def calculate_total
54
+ total = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
55
+ @statistics.each_value { |pair| pair.each { |k, v| total[k] += v } }
56
+ total
57
+ end
58
+
59
+ def calculate_code
60
+ code_loc = 0
61
+ @statistics.each { |k, v| code_loc += v['codelines'] unless TEST_TYPES.include? k }
62
+ code_loc
63
+ end
64
+
65
+ def calculate_tests
66
+ test_loc = 0
67
+ @statistics.each { |k, v| test_loc += v['codelines'] if TEST_TYPES.include? k }
68
+ test_loc
69
+ end
70
+
71
+ def print_header
72
+ print_splitter
73
+ puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |"
74
+ print_splitter
75
+ end
76
+
77
+ def print_splitter
78
+ puts "+----------------------+-------+-------+---------+---------+-----+-------+"
79
+ end
80
+
81
+ def print_line(name, statistics)
82
+ m_over_c = (statistics["methods"] / statistics["classes"]) rescue m_over_c = 0
83
+ loc_over_m = (statistics["codelines"] / statistics["methods"]) - 2 rescue loc_over_m = 0
84
+
85
+ start = if TEST_TYPES.include? name
86
+ "| #{name.ljust(18)} "
87
+ else
88
+ "| #{name.ljust(20)} "
89
+ end
90
+
91
+ puts start +
92
+ "| #{statistics["lines"].to_s.rjust(5)} " +
93
+ "| #{statistics["codelines"].to_s.rjust(5)} " +
94
+ "| #{statistics["classes"].to_s.rjust(7)} " +
95
+ "| #{statistics["methods"].to_s.rjust(7)} " +
96
+ "| #{m_over_c.to_s.rjust(3)} " +
97
+ "| #{loc_over_m.to_s.rjust(5)} |"
98
+ end
99
+
100
+ def print_code_test_stats
101
+ code = calculate_code
102
+ tests = calculate_tests
103
+
104
+ puts " Code LOC: #{code} Test LOC: #{tests} Code to Test Ratio: 1:#{sprintf("%.1f", tests.to_f/code)}"
105
+ puts ""
106
+ end
107
+ end
@@ -0,0 +1,371 @@
1
+ require 'yaml'
2
+ require 'set'
3
+
4
+ #
5
+ # DIY (Dependency Injection in Yaml) is a simple dependency injection library
6
+ # which focuses on declarative composition of objects through setter injection.
7
+ #
8
+ # == Examples
9
+ #
10
+ # === A Simple Context
11
+ #
12
+ # The context is a hash specified in in a yaml file. Each top-level key identifies
13
+ # an object. When the context is created and queried for an object, by default,
14
+ # the context will require a file with the same name:
15
+ # require 'foo'
16
+ # next, by default, it will call new on a class from the camel-cased name of the key:
17
+ # Foo.new
18
+ #
19
+ # foo.rb:
20
+ # class Foo; end
21
+ #
22
+ # context.yml:
23
+ # ---
24
+ # foo:
25
+ # bar:
26
+ #
27
+ # c = DIY::Context.from_file('context.yml')
28
+ # c[:foo] #=> #<Foo:0x81eb0>
29
+ #
30
+ # === Specifying Class Name
31
+ #
32
+ # If the class name isn't the camel-cased key:
33
+ #
34
+ # foo.rb:
35
+ # class MyFoo; end
36
+ #
37
+ # context.yml:
38
+ # ---
39
+ # foo:
40
+ # class: MyFoo
41
+ # bar:
42
+ #
43
+ # === Specifying Ruby File to Require
44
+ #
45
+ # If the file the class resides in isn't named after they key:
46
+ #
47
+ # fun_stuff.rb:
48
+ # class Foo; end
49
+ #
50
+ # context.yml:
51
+ # ---
52
+ # foo:
53
+ # lib: fun_stuff
54
+ # bar:
55
+ #
56
+ # === Constructor Arguments
57
+ #
58
+ # DIY allows specification of constructor arguments as hash key-value pairs
59
+ # using the <tt>compose</tt> directive.
60
+ #
61
+ # foo.rb:
62
+ # class Foo
63
+ # def initialize(args)
64
+ # @bar = args[:bar]
65
+ # @other = args[:other]
66
+ # end
67
+ # end
68
+ #
69
+ # context.yml:
70
+ # ---
71
+ # foo:
72
+ # compose: bar, other
73
+ # bar:
74
+ # other:
75
+ #
76
+ # To make constructor definition easier use constructor:
77
+ #
78
+ # foo.rb:
79
+ # class Foo
80
+ # constructor :bar, :other
81
+ # end
82
+ #
83
+ # If the constructor argument names don't match up with the object keys
84
+ # in the context, they can be mapped explicitly.
85
+ #
86
+ # foo.rb:
87
+ # class Foo
88
+ # constructor :bar, :other
89
+ # end
90
+ #
91
+ # context.yml:
92
+ # ---
93
+ # foo:
94
+ # bar: my_bar
95
+ # other: the_other_one
96
+ # my_bar:
97
+ # the_other_one:
98
+ #
99
+ # === Non-singleton objects
100
+ #
101
+ # Non-singletons will be re-instantiated each time they are needed.
102
+ #
103
+ # context.yml:
104
+ # ---
105
+ # foo:
106
+ # singleton: false
107
+ #
108
+ # bar:
109
+ #
110
+ # engine:
111
+ # compose: foo, bar
112
+ #
113
+ module DIY
114
+ class Context
115
+ def initialize(context_hash, extra_inputs={})
116
+ raise "Nil context hash" unless context_hash
117
+ raise "Need a hash" unless context_hash.kind_of?(Hash)
118
+ [ "[]", "keys" ].each do |mname|
119
+ unless extra_inputs.respond_to?(mname)
120
+ raise "Extra inputs must respond to hash-like [] operator and methods #keys and #each"
121
+ end
122
+ end
123
+
124
+ # store extra inputs
125
+ if extra_inputs.kind_of?(Hash)
126
+ @extra_inputs = {}
127
+ extra_inputs.each { |k,v| @extra_inputs[k.to_s] = v } # smooth out the names
128
+ else
129
+ @extra_inputs = extra_inputs
130
+ end
131
+
132
+ # Collect object and subcontext definitions
133
+ @defs = {}
134
+ @sub_context_defs = {}
135
+ context_hash.each do |name,info|
136
+ name = name.to_s
137
+ case name
138
+ when /^\+/
139
+ # subcontext
140
+ @sub_context_defs[name.gsub(/^\+/,'')] = info
141
+
142
+ else
143
+ # Normal singleton object def
144
+ if extra_inputs_has(name)
145
+ raise ConstructionError.new(name, "Object definition conflicts with parent context")
146
+ end
147
+ @defs[name] = ObjectDef.new(:name => name, :info => info)
148
+ end
149
+ end
150
+
151
+
152
+ # init the cache
153
+ @cache = {}
154
+ @cache['this_context'] = self
155
+ end
156
+
157
+
158
+ def self.from_yaml(io_or_string, extra_inputs={})
159
+ raise "nil input to YAML" unless io_or_string
160
+ Context.new(YAML.load(io_or_string), extra_inputs)
161
+ end
162
+
163
+ def self.from_file(fname, extra_inputs={})
164
+ raise "nil file name" unless fname
165
+ self.from_yaml(File.read(fname), extra_inputs)
166
+ end
167
+
168
+ def get_object(obj_name)
169
+ key = obj_name.to_s
170
+ obj = @cache[key]
171
+ unless obj
172
+ if extra_inputs_has(key)
173
+ obj = @extra_inputs[key]
174
+ end
175
+ end
176
+ unless obj
177
+ obj = construct_object(key)
178
+ @cache[key] = obj if @defs[key].singleton?
179
+ end
180
+ obj
181
+ end
182
+ alias :[] :get_object
183
+
184
+ def set_object(obj_name,obj)
185
+ key = obj_name.to_s
186
+ raise "object '#{key}' already exists in context" if @cache.keys.include?(key)
187
+ @cache[key] = obj
188
+ end
189
+ alias :[]= :set_object
190
+
191
+ def keys
192
+ (@defs.keys.to_set + @extra_inputs.keys.to_set).to_a
193
+ end
194
+
195
+ # Instantiate and yield the named subcontext
196
+ def within(sub_context_name)
197
+ # Find the subcontext definitaion:
198
+ context_def = @sub_context_defs[sub_context_name.to_s]
199
+ raise "No sub-context named #{sub_context_name}" unless context_def
200
+ # Instantiate a new context using self as parent:
201
+ context = Context.new( context_def, self )
202
+
203
+ yield context
204
+ end
205
+
206
+ def contains_object(obj_name)
207
+ key = obj_name.to_s
208
+ @defs.keys.member?(key) or extra_inputs_has(key)
209
+ end
210
+
211
+ def build_everything
212
+ @defs.keys.each { |k| self[k] }
213
+ end
214
+ alias :build_all :build_everything
215
+ alias :preinstantiate_singletons :build_everything
216
+
217
+ private
218
+
219
+ def construct_object(key)
220
+ # Find the object definition
221
+ obj_def = @defs[key]
222
+ raise "No object definition for '#{key}'" unless obj_def
223
+
224
+ # If object def mentions a library, load it
225
+ require obj_def.library if obj_def.library
226
+
227
+ # Resolve all components for the object
228
+ arg_hash = {}
229
+ obj_def.components.each do |name,value|
230
+ case value
231
+ when Lookup
232
+ arg_hash[name.to_sym] = get_object(value.name)
233
+ when StringValue
234
+ arg_hash[name.to_sym] = value.literal_value
235
+ else
236
+ raise "Cannot cope with component definition '#{value.inspect}'"
237
+ end
238
+ end
239
+
240
+ if obj_def.code.nil?
241
+ # Get a reference to the class for the object
242
+ big_c = get_class_for_name_with_module_delimeters(obj_def.class_name)
243
+ # Make and return the instance
244
+ if arg_hash.keys.size > 0
245
+ return big_c.new(arg_hash)
246
+ else
247
+ return big_c.new
248
+ end
249
+ else
250
+ eval_ret = eval(obj_def.code)
251
+ return eval_ret
252
+ end
253
+ rescue Exception => oops
254
+ cerr = ConstructionError.new(key,oops)
255
+ cerr.set_backtrace(oops.backtrace)
256
+ raise cerr
257
+ end
258
+
259
+ def get_class_for_name_with_module_delimeters(class_name)
260
+ class_name.split(/::/).inject(Object) do |mod,const_name| mod.const_get(const_name) end
261
+ end
262
+
263
+ def extra_inputs_has(key)
264
+ if key.nil? or key.strip == ''
265
+ raise ArgumentError.new("Cannot lookup objects with nil keys")
266
+ end
267
+ @extra_inputs.keys.member?(key) or @extra_inputs.keys.member?(key.to_sym)
268
+ end
269
+ end
270
+
271
+ class Lookup #:nodoc:
272
+ attr_reader :name
273
+ def initialize(obj_name)
274
+ @name = obj_name
275
+ end
276
+ end
277
+
278
+ class ObjectDef #:nodoc:
279
+ attr_accessor :name, :class_name, :library, :components, :code
280
+ def initialize(opts)
281
+ name = opts[:name]
282
+ raise "Can't make an ObjectDef without a name" if name.nil?
283
+
284
+ info = opts[:info] || {}
285
+ info = info.clone
286
+
287
+ @components = {}
288
+
289
+ # Object name
290
+ @name = name
291
+
292
+ # Class name
293
+ @class_name = info.delete 'class'
294
+ @class_name ||= info.delete 'type'
295
+ @class_name ||= camelize(@name)
296
+
297
+ # Library
298
+ @library = info.delete 'library'
299
+ @library ||= info.delete 'lib'
300
+ @library ||= underscore(@class_name)
301
+
302
+ @library = nil if @library == "nil"
303
+
304
+ # Code
305
+ @code = info.delete 'code'
306
+
307
+ # Auto-compose
308
+ compose = info.delete 'compose'
309
+ if compose
310
+ case compose
311
+ when Array
312
+ auto_names = compose.map { |x| x.to_s }
313
+ when String
314
+ auto_names = compose.split(',').map { |x| x.to_s.strip }
315
+ when Symbol
316
+ auto_names = [ compose.to_s ]
317
+ else
318
+ raise "Cannot auto compose object #{@name}, bad 'compose' format: #{compose.inspect}"
319
+ end
320
+ end
321
+ auto_names ||= []
322
+ auto_names.each do |cname|
323
+ @components[cname] = Lookup.new(cname)
324
+ end
325
+
326
+ # Singleton status
327
+ if info['singleton'].nil?
328
+ @singleton = true
329
+ else
330
+ @singleton = info['singleton']
331
+ end
332
+ info.delete 'singleton'
333
+
334
+ # Remaining keys
335
+ info.each do |key,val|
336
+ @components[key.to_s] = Lookup.new(val.to_s)
337
+ end
338
+
339
+ end
340
+
341
+ def singleton?
342
+ @singleton
343
+ end
344
+
345
+ private
346
+ # Ganked this from Inflector:
347
+ def camelize(lower_case_and_underscored_word)
348
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
349
+ end
350
+ # Ganked this from Inflector:
351
+ def underscore(camel_cased_word)
352
+ camel_cased_word.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
353
+ end
354
+ end
355
+
356
+ # Exception raised when an object can't be created which is defined in the context.
357
+ class ConstructionError < RuntimeError
358
+ def initialize(object_name, cause=nil) #:nodoc:
359
+ object_name = object_name
360
+ cause = cause
361
+ m = "Failed to construct '#{object_name}'"
362
+ if cause
363
+ m << "\n ...caused by:\n >>> #{cause}"
364
+ end
365
+ super m
366
+ end
367
+ end
368
+ end
369
+
370
+
371
+