gamebox 0.0.1

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