minilab 1.0.0-mswin32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. data/.document +2 -0
  2. data/CHANGES +2 -0
  3. data/LICENSE +19 -0
  4. data/README +107 -0
  5. data/Rakefile +145 -0
  6. data/config/environment.rb +15 -0
  7. data/config/objects.yml +22 -0
  8. data/lib/analog_io.rb +30 -0
  9. data/lib/digital_auxport_io.rb +49 -0
  10. data/lib/digital_configuration.rb +66 -0
  11. data/lib/digital_port_io.rb +68 -0
  12. data/lib/extension/extconf.rb +4 -0
  13. data/lib/extension/minilab_hardware.c +235 -0
  14. data/lib/extension/minilab_hardware.so +0 -0
  15. data/lib/library_translator.rb +48 -0
  16. data/lib/minilab.rb +149 -0
  17. data/lib/minilab_context.rb +39 -0
  18. data/lib/result_verifier.rb +14 -0
  19. data/test/integration/analog_input_output_test.rb +43 -0
  20. data/test/integration/connect_to_hardware_test.rb +13 -0
  21. data/test/integration/digital_input_output_test.rb +114 -0
  22. data/test/integration/integration_test.rb +53 -0
  23. data/test/integration/require_minilab_test.rb +9 -0
  24. data/test/system/analog_input.test +3 -0
  25. data/test/system/analog_output.test +37 -0
  26. data/test/system/digital_port_input.test +5 -0
  27. data/test/system/digital_port_output.test +39 -0
  28. data/test/system/digital_port_read_byte.test +26 -0
  29. data/test/system/digital_screw_terminals_input.test +2 -0
  30. data/test/system/digital_screw_terminals_output.test +11 -0
  31. data/test/system/minilab_driver.rb +85 -0
  32. data/test/test_helper.rb +11 -0
  33. data/test/unit/analog_io_test.rb +87 -0
  34. data/test/unit/digital_auxport_io_test.rb +114 -0
  35. data/test/unit/digital_configuration_test.rb +136 -0
  36. data/test/unit/digital_port_io_test.rb +117 -0
  37. data/test/unit/library_translator_test.rb +100 -0
  38. data/test/unit/minilab_context_test.rb +82 -0
  39. data/test/unit/minilab_hardware_test.rb +83 -0
  40. data/test/unit/minilab_test.rb +131 -0
  41. data/test/unit/result_verifier_test.rb +33 -0
  42. data/vendor/behaviors/lib/behaviors.rb +50 -0
  43. data/vendor/behaviors/tasks/behaviors_tasks.rake +140 -0
  44. data/vendor/behaviors/test/behaviors_tasks_test.rb +71 -0
  45. data/vendor/behaviors/test/behaviors_test.rb +50 -0
  46. data/vendor/behaviors/test/tasks_test/Rakefile +16 -0
  47. data/vendor/behaviors/test/tasks_test/doc/behaviors.html +55 -0
  48. data/vendor/behaviors/test/tasks_test/lib/user.rb +2 -0
  49. data/vendor/behaviors/test/tasks_test/test/user_test.rb +17 -0
  50. data/vendor/constructor/Rakefile +44 -0
  51. data/vendor/constructor/config/environment.rb +12 -0
  52. data/vendor/constructor/lib/constructor.rb +132 -0
  53. data/vendor/constructor/test/constructor_test.rb +366 -0
  54. data/vendor/constructor/test/helper.rb +3 -0
  55. data/vendor/diy/README +26 -0
  56. data/vendor/diy/Rakefile +18 -0
  57. data/vendor/diy/lib/constructor.rb +114 -0
  58. data/vendor/diy/lib/diy.rb +329 -0
  59. data/vendor/diy/proto/context.rb +117 -0
  60. data/vendor/diy/proto/context.yml +20 -0
  61. data/vendor/diy/test/diy_test.rb +370 -0
  62. data/vendor/diy/test/files/broken_construction.yml +7 -0
  63. data/vendor/diy/test/files/cat/cat.rb +4 -0
  64. data/vendor/diy/test/files/cat/extra_conflict.yml +5 -0
  65. data/vendor/diy/test/files/cat/heritage.rb +2 -0
  66. data/vendor/diy/test/files/cat/needs_input.yml +3 -0
  67. data/vendor/diy/test/files/cat/the_cat_lineage.rb +1 -0
  68. data/vendor/diy/test/files/dog/dog_model.rb +4 -0
  69. data/vendor/diy/test/files/dog/dog_presenter.rb +4 -0
  70. data/vendor/diy/test/files/dog/dog_view.rb +2 -0
  71. data/vendor/diy/test/files/dog/file_resolver.rb +2 -0
  72. data/vendor/diy/test/files/dog/other_thing.rb +2 -0
  73. data/vendor/diy/test/files/dog/simple.yml +11 -0
  74. data/vendor/diy/test/files/donkey/foo.rb +8 -0
  75. data/vendor/diy/test/files/donkey/foo/bar/qux.rb +7 -0
  76. data/vendor/diy/test/files/fud/objects.yml +13 -0
  77. data/vendor/diy/test/files/fud/toy.rb +15 -0
  78. data/vendor/diy/test/files/gnu/objects.yml +14 -0
  79. data/vendor/diy/test/files/gnu/thinger.rb +8 -0
  80. data/vendor/diy/test/files/goat/base.rb +8 -0
  81. data/vendor/diy/test/files/goat/can.rb +6 -0
  82. data/vendor/diy/test/files/goat/goat.rb +6 -0
  83. data/vendor/diy/test/files/goat/objects.yml +12 -0
  84. data/vendor/diy/test/files/goat/paper.rb +6 -0
  85. data/vendor/diy/test/files/goat/plane.rb +8 -0
  86. data/vendor/diy/test/files/goat/shirt.rb +6 -0
  87. data/vendor/diy/test/files/goat/wings.rb +8 -0
  88. data/vendor/diy/test/files/horse/holder_thing.rb +4 -0
  89. data/vendor/diy/test/files/horse/objects.yml +7 -0
  90. data/vendor/diy/test/files/yak/core_model.rb +4 -0
  91. data/vendor/diy/test/files/yak/core_presenter.rb +4 -0
  92. data/vendor/diy/test/files/yak/core_view.rb +1 -0
  93. data/vendor/diy/test/files/yak/data_source.rb +1 -0
  94. data/vendor/diy/test/files/yak/fringe_model.rb +4 -0
  95. data/vendor/diy/test/files/yak/fringe_presenter.rb +4 -0
  96. data/vendor/diy/test/files/yak/fringe_view.rb +1 -0
  97. data/vendor/diy/test/files/yak/my_objects.yml +21 -0
  98. data/vendor/diy/test/test_helper.rb +40 -0
  99. data/vendor/hardmock/CHANGES +8 -0
  100. data/vendor/hardmock/LICENSE +7 -0
  101. data/vendor/hardmock/README +48 -0
  102. data/vendor/hardmock/Rakefile +100 -0
  103. data/vendor/hardmock/TODO +7 -0
  104. data/vendor/hardmock/config/environment.rb +12 -0
  105. data/vendor/hardmock/homepage/demo.rb +21 -0
  106. data/vendor/hardmock/homepage/hardmock_sample.png +0 -0
  107. data/vendor/hardmock/homepage/index.html +65 -0
  108. data/vendor/hardmock/init.rb +3 -0
  109. data/vendor/hardmock/lib/hardmock.rb +634 -0
  110. data/vendor/hardmock/lib/method_cleanout.rb +14 -0
  111. data/vendor/hardmock/rcov.rake +18 -0
  112. data/vendor/hardmock/test/functional/assert_error_test.rb +52 -0
  113. data/vendor/hardmock/test/functional/auto_verify_test.rb +192 -0
  114. data/vendor/hardmock/test/functional/direct_mock_usage_test.rb +396 -0
  115. data/vendor/hardmock/test/functional/hardmock_test.rb +380 -0
  116. data/vendor/hardmock/test/test_helper.rb +23 -0
  117. data/vendor/hardmock/test/unit/expectation_builder_test.rb +18 -0
  118. data/vendor/hardmock/test/unit/expector_test.rb +56 -0
  119. data/vendor/hardmock/test/unit/method_cleanout_test.rb +35 -0
  120. data/vendor/hardmock/test/unit/mock_control_test.rb +172 -0
  121. data/vendor/hardmock/test/unit/mock_test.rb +273 -0
  122. data/vendor/hardmock/test/unit/simple_expectation_test.rb +345 -0
  123. data/vendor/hardmock/test/unit/trapper_test.rb +60 -0
  124. data/vendor/hardmock/test/unit/verify_error_test.rb +34 -0
  125. data/vendor/systir/systir.rb +403 -0
  126. data/vendor/systir/test/unit/ui/xml/testrunner.rb +192 -0
  127. data/vendor/systir/test/unit/ui/xml/xmltestrunner.xslt +109 -0
  128. metadata +235 -0
@@ -0,0 +1,140 @@
1
+ desc "List behavioral definitions for the classes under test (use for=<term> also)"
2
+ task :behaviors do
3
+ specifications.each do |spec|
4
+ puts "#{spec.name} should:\n"
5
+ spec.requirements.each do |req|
6
+ puts " - #{req}"
7
+ end
8
+ end
9
+ end
10
+
11
+ desc "List behavioral definitions for the classes under test (HTML output)"
12
+ task :behaviors_html do
13
+ require 'erb'
14
+ txt =<<-EOS
15
+ <html>
16
+ <head>
17
+ <style>
18
+
19
+ div.title
20
+ {
21
+ width: 600px;
22
+ font: bold 14pt trebuchet ms;
23
+ }
24
+
25
+ div.specification
26
+ {
27
+ font: bold 12pt trebuchet ms;
28
+ border: solid 1px black;
29
+ width: 600px;
30
+ padding: 5px;
31
+ margin: 5px;
32
+ }
33
+
34
+ ul.requirements
35
+ {
36
+ font: normal 11pt verdana;
37
+ padding-left: 0;
38
+ margin-left: 0;
39
+ border-bottom: 1px solid gray;
40
+ width: 600px;
41
+ }
42
+
43
+ ul.requirements li
44
+ {
45
+ list-style: none;
46
+ margin: 0;
47
+ padding: 0.25em;
48
+ border-top: 1px solid gray;
49
+ }
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <div class="title">Specifications for <%= title %></div>
54
+ <% specifications.each do |spec| %>
55
+ <div class="specification">
56
+ <%= spec.name %> should:
57
+ <ul class="requirements">
58
+ <% spec.requirements.each do |req| %>
59
+ <li><%= req %></li>
60
+ <% end %>
61
+ </ul>
62
+ </div>
63
+ <% end %>
64
+ </body>
65
+ </html>
66
+ EOS
67
+ output_dir = File.expand_path("#{APP_ROOT}/doc")
68
+ mkdir_p output_dir
69
+ output_filename = output_dir + "/behaviors.html"
70
+ File.open(output_filename,"w") do |f|
71
+ f.write ERB.new(txt).result(binding)
72
+ end
73
+ puts "(Wrote #{output_filename})"
74
+ end
75
+
76
+ def title
77
+ File.basename(File.expand_path(APP_ROOT))
78
+ end
79
+
80
+ def specifications
81
+ test_files.map do |file|
82
+ spec = OpenStruct.new
83
+ m = %r".*/([^/].*)_test.rb".match(file)
84
+ class_name = titleize(m[1]) if m[1]
85
+ spec.name = class_name
86
+ spec.requirements = []
87
+ File::readlines(file).each do |line|
88
+ if line =~ /^\s*should\s+\(?\s*["'](.*)["']/
89
+ spec.requirements << $1
90
+ end
91
+ end
92
+ spec
93
+ end
94
+ end
95
+
96
+ def test_files
97
+ test_list = FileList["#{APP_ROOT}/test/**/*_test.rb"]
98
+ if ENV['for']
99
+ test_list = test_list.grep(/#{ENV['for']}/i)
100
+ end
101
+ test_list
102
+ end
103
+
104
+
105
+ ############################################################
106
+ # STOLEN FROM inflector.rb
107
+ ############################################################
108
+ #--
109
+ # Copyright (c) 2005 David Heinemeier Hansson
110
+ #
111
+ # Permission is hereby granted, free of charge, to any person obtaining
112
+ # a copy of this software and associated documentation files (the
113
+ # "Software"), to deal in the Software without restriction, including
114
+ # without limitation the rights to use, copy, modify, merge, publish,
115
+ # distribute, sublicense, and/or sell copies of the Software, and to
116
+ # permit persons to whom the Software is furnished to do so, subject to
117
+ # the following conditions:
118
+ #
119
+ # The above copyright notice and this permission notice shall be
120
+ # included in all copies or substantial portions of the Software.
121
+ #
122
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
123
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
124
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
125
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
126
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
127
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
128
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
129
+ #++
130
+ def titleize(word)
131
+ humanize(underscore(word)).gsub(/\b([a-z])/) { $1.capitalize }
132
+ end
133
+
134
+ def underscore(camel_cased_word) camel_cased_word.to_s.gsub(/::/, '/').
135
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
136
+ end
137
+
138
+ def humanize(lower_case_and_underscored_word)
139
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
140
+ end
@@ -0,0 +1,71 @@
1
+ require 'test/unit'
2
+
3
+ APP_ROOT = File.expand_path(File.dirname(__FILE__) + '/../')
4
+
5
+ class BehaviorsTasksTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ here = File.expand_path(File.dirname(__FILE__))
9
+ cmd = RUBY_PLATFORM[/mswin/] ? 'rake.cmd' : 'rake'
10
+ @base_cmd = "#{cmd} -f \"#{here}/tasks_test/Rakefile\" "
11
+ end
12
+
13
+ #
14
+ # HELPERS
15
+ #
16
+ def run_behaviors_task
17
+ run_cmd "behaviors"
18
+ end
19
+
20
+ def run_behaviors_html_task
21
+ run_cmd "behaviors_html"
22
+ end
23
+
24
+ def run_cmd(cmd)
25
+ @report = %x[ #{@base_cmd} #{cmd} ]
26
+ end
27
+
28
+ def see_html_task_output_message
29
+ @html_output_filename = "#{APP_ROOT}/test/tasks_test/doc/behaviors.html"
30
+ assert_match /Wrote #{@html_output_filename}/, @report
31
+ end
32
+
33
+ def see_that_html_report_file_exits
34
+ assert File.exists?(@html_output_filename), "html output file should exist"
35
+ end
36
+
37
+ def html_report_file_should_contain(user_behaviors)
38
+ file_contents = File.read(@html_output_filename)
39
+ user_behaviors.each do |line|
40
+ assert_match /#{line}/, file_contents
41
+ end
42
+ end
43
+
44
+ #
45
+ # TESTS
46
+ #
47
+ def test_that_behaviors_tasks_should_list_behavioral_definitions_for_the_classes_under_test
48
+ run_behaviors_task
49
+ user_behaviors = [
50
+ "User should:",
51
+ " - be able set user name and age during construction",
52
+ " - be able to get user name and age",
53
+ " - be able to ask if a user is an adult"
54
+ ]
55
+ assert_match /#{user_behaviors.join("\n")}/, @report
56
+ end
57
+
58
+ def test_that_behaviors_tasks_should_list_behavioral_definitions_for_the_classes_under_test_in_html_output
59
+ run_behaviors_html_task
60
+ see_html_task_output_message
61
+ see_that_html_report_file_exits
62
+ user_behaviors = [
63
+ "User should:",
64
+ "be able set user name and age during construction",
65
+ "be able to get user name and age",
66
+ "be able to ask if a user is an adult"
67
+ ]
68
+ html_report_file_should_contain user_behaviors
69
+ end
70
+
71
+ end
@@ -0,0 +1,50 @@
1
+ require 'test/unit'
2
+ require File.expand_path(File.dirname(__FILE__)) + '/../lib/behaviors'
3
+ require 'stringio'
4
+
5
+ loading_developer_test_class_stdout = StringIO.new
6
+ saved_stdout = $stdout.dup
7
+ $stdout = loading_developer_test_class_stdout
8
+
9
+ class DeveloperTest
10
+ extend Behaviors
11
+ attr_accessor :flunk_msg, :tested_code
12
+
13
+ should "test their code" do
14
+ @tested_code = true
15
+ end
16
+ should "go to meetings"
17
+ end
18
+
19
+ $stdout = saved_stdout
20
+ loading_developer_test_class_stdout.rewind
21
+ $loading_developer_test_class_output = loading_developer_test_class_stdout.read
22
+
23
+ class BehaviorsTest < Test::Unit::TestCase
24
+
25
+
26
+ def setup
27
+ @target = DeveloperTest.new
28
+ assert_nil @target.tested_code, "block called too early"
29
+ end
30
+
31
+ #
32
+ # TESTS
33
+ #
34
+ def test_should_called_with_a_block_defines_a_test
35
+ assert @target.methods.include?("test_should_test their code"), "Missing test method"
36
+
37
+ @target.send("test_should_test their code")
38
+
39
+ assert @target.tested_code, "block not called"
40
+ end
41
+
42
+ def test_should_called_without_a_block_does_not_create_a_test_method
43
+ assert !@target.methods.include?("test_should_go to meetings"), "Should not have method"
44
+ end
45
+
46
+ def test_should_called_without_a_block_will_give_unimplemented_output_when_class_loads
47
+ unimplemented_output = "UNIMPLEMENTED CASE: Developer should go to meetings"
48
+ assert_match /#{unimplemented_output}/, $loading_developer_test_class_output
49
+ end
50
+ end
@@ -0,0 +1,16 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ APP_ROOT = File.expand_path(File.dirname(__FILE__))
5
+ load "#{APP_ROOT}/../../tasks/behaviors_tasks.rake"
6
+
7
+ cd APP_ROOT
8
+
9
+ desc 'Default: run unit tests.'
10
+ task :default => :test
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << "#{APP_ROOT}/../../lib"
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
@@ -0,0 +1,55 @@
1
+ <html>
2
+ <head>
3
+ <style>
4
+
5
+ div.title
6
+ {
7
+ width: 600px;
8
+ font: bold 14pt trebuchet ms;
9
+ }
10
+
11
+ div.specification
12
+ {
13
+ font: bold 12pt trebuchet ms;
14
+ border: solid 1px black;
15
+ width: 600px;
16
+ padding: 5px;
17
+ margin: 5px;
18
+ }
19
+
20
+ ul.requirements
21
+ {
22
+ font: normal 11pt verdana;
23
+ padding-left: 0;
24
+ margin-left: 0;
25
+ border-bottom: 1px solid gray;
26
+ width: 600px;
27
+ }
28
+
29
+ ul.requirements li
30
+ {
31
+ list-style: none;
32
+ margin: 0;
33
+ padding: 0.25em;
34
+ border-top: 1px solid gray;
35
+ }
36
+ </style>
37
+ </head>
38
+ <body>
39
+ <div class="title">Specifications for tasks_test</div>
40
+
41
+ <div class="specification">
42
+ User should:
43
+ <ul class="requirements">
44
+
45
+ <li>be able set user name and age during construction</li>
46
+
47
+ <li>be able to get user name and age</li>
48
+
49
+ <li>be able to ask if a user is an adult</li>
50
+
51
+ </ul>
52
+ </div>
53
+
54
+ </body>
55
+ </html>
@@ -0,0 +1,2 @@
1
+ class User
2
+ end
@@ -0,0 +1,17 @@
1
+ require 'test/unit'
2
+ require 'behaviors'
3
+
4
+ require 'user'
5
+
6
+ class UserTest < Test::Unit::TestCase
7
+ extend Behaviors
8
+
9
+ def setup
10
+ end
11
+
12
+ should "be able set user name and age during construction"
13
+ should "be able to get user name and age"
14
+ should "be able to ask if a user is an adult"
15
+ def test_DELETEME
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rubygems'
5
+ Gem::manage_gems
6
+ require 'rake/gempackagetask'
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ GEM_VERSION = '1.0.0'
12
+ GEM_NAME = 'constructor'
13
+
14
+ gem_spec = Gem::Specification.new do |s|
15
+ s.name = GEM_NAME
16
+ s.version = GEM_VERSION
17
+ s.author = "Atomic Object LLC"
18
+ s.email = "dev@atomicobject.com"
19
+ s.homepage = "http://atomicobject.com"
20
+ s.platform = Gem::Platform::RUBY
21
+ s.summary = "Declarative, named constructor arguments"
22
+ s.files = FileList["{test,lib}/**/*"].exclude("rdoc").to_a
23
+ s.has_rdoc = true
24
+ s.add_dependency "behaviors", ">= 1.0.0"
25
+ end
26
+
27
+ Rake::GemPackageTask.new(gem_spec) do |pkg|
28
+ pkg.need_tar = true
29
+ end
30
+
31
+ desc 'Run all tests.'
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.pattern = 'test/*_test.rb'
35
+ t.verbose = true
36
+ end
37
+
38
+ desc 'Generate documentation.'
39
+ Rake::RDocTask.new(:rdoc) do |rdoc|
40
+ rdoc.rdoc_dir = 'rdoc'
41
+ rdoc.title = 'Constructor'
42
+ rdoc.options << '--line-numbers' << '--inline-source'
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
@@ -0,0 +1,12 @@
1
+ # The path to the root directory of your application.
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ ADDITIONAL_LOAD_PATHS = []
5
+ ADDITIONAL_LOAD_PATHS.concat %w(
6
+ src
7
+ ).map { |dir| "#{APP_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) }
8
+
9
+ # Prepend to $LOAD_PATH
10
+ ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) }
11
+
12
+ # Require any additional libraries needed
@@ -0,0 +1,132 @@
1
+ class Class
2
+ #
3
+ # Declarative means to define object properties by passing a hash
4
+ # to the constructor, which will set the corresponding ivars.
5
+ # Eg,
6
+ # class Horse
7
+ # constructor :name, :breed, :weight
8
+ # end
9
+ # Horse.new :name => 'Ed', :breed => 'Mustang', :weight => 342
10
+ #
11
+ # By default the ivars do not get accessors defined.
12
+ # But you can get them auto-made if you want:
13
+ # class Horse
14
+ # constructor :name, :breed, :weight, :accessors => true
15
+ # end
16
+ # ...
17
+ # puts my_horse.weight
18
+ #
19
+ # Arguments specified are required by default. You can disable
20
+ # strict argument checking with :strict option. This means that
21
+ # the constructor will not raise an error if you pass more or
22
+ # fewer arguments than declared.
23
+ # Eg,
24
+ # class Donkey
25
+ # constructor :age, :odor, :strict => false
26
+ # end
27
+ # ... this allows you to pass either an age or odor key (or neither) to
28
+ # the Donkey constructor.
29
+ #
30
+ def constructor(*attrs)
31
+ # Look for embedded options in the listing:
32
+ opts = attrs.find { |a| a.kind_of?(Hash) and attrs.delete(a) }
33
+ do_acc = opts.nil? ? false : opts[:accessors] == true
34
+ require_args = opts.nil? ? true : opts[:strict] != false
35
+ super_args = opts.nil? ? nil : opts[:super]
36
+
37
+ # Incorporate superclass's constructor keys, if our superclass
38
+ if superclass.constructor_keys
39
+ attrs = [attrs,superclass.constructor_keys].flatten
40
+ end
41
+ # Generate ivar assigner code lines
42
+ assigns = ''
43
+ attrs.each do |k|
44
+ assigns += "@#{k.to_s} = args[:#{k.to_s}]\n"
45
+ end
46
+
47
+ # If accessors option is on, declare accessors for the attributes:
48
+ if do_acc
49
+ self.class_eval "attr_accessor " + attrs.map {|x| ":#{x.to_s}"}.join(',')
50
+ end
51
+
52
+ # If user supplied super-constructor hints:
53
+ super_call = ''
54
+ if super_args
55
+ list = super_args.map do |a|
56
+ case a
57
+ when String
58
+ %|"#{a}"|
59
+ when Symbol
60
+ %|:#{a}|
61
+ end
62
+ end
63
+ super_call = %|super(#{list.join(',')})|
64
+ end
65
+
66
+ # If strict is on, define the constructor argument validator method,
67
+ # and setup the initializer to invoke the validator method.
68
+ # Otherwise, insert lax code into the initializer.
69
+ validation_code = "return if args.nil?"
70
+ if require_args
71
+ self.class_eval do
72
+ def _validate_constructor_args(args)
73
+ # First, make sure we've got args of some kind
74
+ unless args and args.keys and args.keys.size > 0
75
+ raise ConstructorArgumentError.new(self.class.constructor_keys)
76
+ end
77
+ # Scan for missing keys in the argument hash
78
+ a_keys = args.keys
79
+ missing = []
80
+ self.class.constructor_keys.each do |ck|
81
+ unless a_keys.member?(ck)
82
+ missing << ck
83
+ end
84
+ a_keys.delete(ck) # Delete inbound keys as we address them
85
+ end
86
+ if missing.size > 0 || a_keys.size > 0
87
+ raise ConstructorArgumentError.new(missing,a_keys)
88
+ end
89
+ end
90
+ end
91
+ # Setup the code to insert into the initializer:
92
+ validation_code = "_validate_constructor_args args "
93
+ end
94
+
95
+ # Generate the initializer code
96
+ self.class_eval %{
97
+ def initialize(args=nil)
98
+ #{super_call}
99
+ #{validation_code}
100
+ #{assigns}
101
+ setup if respond_to?(:setup)
102
+ end
103
+ }
104
+
105
+ # Remember our constructor keys
106
+ @_ctor_keys = attrs
107
+ end
108
+
109
+ # Access the constructor keys for this class
110
+ def constructor_keys; @_ctor_keys; end
111
+ end
112
+
113
+ # Fancy validation exception, based on missing and extraneous keys.
114
+ class ConstructorArgumentError < RuntimeError
115
+ def initialize(missing,rejected=[])
116
+ err_msg = ''
117
+ if missing.size > 0
118
+ err_msg = "Missing constructor args [#{missing.join(',')}]"
119
+ end
120
+ if rejected.size > 0
121
+ # Some inbound keys were not addressed earlier; this means they're unwanted
122
+ if err_msg
123
+ err_msg << "; " # Appending to earlier message about missing items
124
+ else
125
+ err_msg = ''
126
+ end
127
+ # Enumerate the rejected key names
128
+ err_msg << "Rejected constructor args [#{rejected.join(',')}]"
129
+ end
130
+ super err_msg
131
+ end
132
+ end