classroom 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ task :default => [:test_units]
5
+
6
+ desc "Run basic tests"
7
+ Rake::TestTask.new("test_units") do |t|
8
+ t.pattern = 'test/*_test.rb'
9
+ t.verbose = true
10
+ t.warning = true
11
+ end
12
+
13
+ task :run_server do
14
+ system 'ruby -Ilib examples/server.rb'
15
+ end
data/classroom.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+
3
+ spec = Gem::Specification.new do |s|
4
+ s.name = 'classroom'
5
+ s.version = "0.0.1"
6
+ s.platform = Gem::Platform::RUBY
7
+ s.summary = "ClassRoom is a 'class server' based on DRb"
8
+ s.files = Dir.glob("**/**/**").delete_if {|item| item.include?(".svn")}
9
+ s.test_files = Dir.glob("test/*_test.rb")
10
+ s.require_path = 'lib'
11
+ s.autorequire = 'classroom'
12
+ s.author = "Peter Cooper"
13
+ s.email = "coops@petercooper.co.uk"
14
+ s.rubyforge_project = "classroom"
15
+ s.homepage = "http://classroom.rubyforge.org"
16
+ end
@@ -0,0 +1,35 @@
1
+ # Add a basic class to the ClassRoom server and use it
2
+
3
+ require 'rubygems'
4
+ require 'classroom'
5
+
6
+ # A basic 'queue' class with a fixed length queue and
7
+ # push and pull methods to use the queue.
8
+ klass = %q{
9
+ class BasicQueue
10
+ def initialize(size = 10); @size = size; @data = Array.new; end
11
+ def push(item); @data.length < @size ? (@data << item) && true : nil; end
12
+ def pull; @data.shift; end
13
+ def self.dummy_class_method; (1..10).to_a; end
14
+ end
15
+ }
16
+
17
+ # Here's the magic..
18
+ class_server = ClassRoom::Client.new(ARGV.first || 'classroom://:2001')
19
+ class_server.add_class(klass)
20
+ class_server.load_class(binding, :all)
21
+
22
+ # Create and use objects as you would normally, but remember
23
+ # 'BasicQueue' actually exists on the ClassRoom server and,
24
+ # if you wished, could be changed mid-execution by another
25
+ # client!
26
+ q = BasicQueue.new(5)
27
+ 10.times { |i| q.push("test #{i}") }
28
+ 10.times { puts q.pull }
29
+
30
+ r = BasicQueue.new(10)
31
+ 10.times { |i| r.push("test #{i}") }
32
+ 10.times { puts r.pull }
33
+
34
+ # Yes, class methods work out of the box too!
35
+ puts BasicQueue.dummy_class_method.inspect
@@ -0,0 +1,20 @@
1
+ # This client will run ONLY IF the BasicQueue class exists on the
2
+ # ClassRoom server. If you ran demo_client.rb first, this should be
3
+ # fine.
4
+
5
+ # This client should return exactly the same as demo_client.rb,
6
+ # proving the class is running entirely off of the ClassRoom server.
7
+
8
+ require 'rubygems'
9
+ require 'classroom'
10
+
11
+ class_server = ClassRoom::Client.new(ARGV.first || 'classroom://:2001')
12
+ class_server.load_class(binding, :BasicQueue)
13
+
14
+ q = BasicQueue.new(5)
15
+ 10.times { |i| q.push("test #{i}") }
16
+ 10.times { puts q.pull }
17
+
18
+ r = BasicQueue.new(10)
19
+ 10.times { |i| r.push("test #{i}") }
20
+ 10.times { puts r.pull }
@@ -0,0 +1,32 @@
1
+ # A basic demo with a basic class
2
+
3
+ require 'rubygems'
4
+ require 'classroom'
5
+
6
+ # A basic class whose function you can mostly ignore
7
+ klass = %q{
8
+ class BasicDataClass
9
+ def initialize(params); @data = params; end
10
+ def method_missing(method, *args); @data[method]; end
11
+ def size; @data.size; end
12
+ def self.dummy_class_method; (1..10).to_a; end
13
+ end
14
+ }
15
+
16
+ # Connect to the ClassRoom server and upload the class
17
+ class_server = ClassRoom::Client.new(ARGV.first || 'classroom://:2001')
18
+ class_server.add_class(klass)
19
+
20
+ # Load local references to all the classes on the ClassRoom server,
21
+ # including BasicDataClass
22
+ class_server.load_class(binding, :all)
23
+
24
+ # Create and use objects as you would normally, but remember
25
+ # 'BasicDataClass' actually exists on the ClassRoom server
26
+ c = BasicDataClass.new(:chunky => "Bacon", :roger => "Rabbit")
27
+ puts c.class # => BasicDataClass
28
+ puts c.chunky # => Bacon
29
+ puts c.size # => 2
30
+
31
+ # Yes, class methods work out of the box too!
32
+ puts BasicDataClass.dummy_class_method
@@ -0,0 +1,49 @@
1
+ # A demonstration of nested classes all working perfectly..
2
+ # (we hope!)
3
+
4
+ require 'rubygems'
5
+ require 'classroom'
6
+
7
+ # A basic 'queue' class with a fixed length queue and
8
+ # push and pull methods to use the queue.
9
+ klass = %q{
10
+ class BasicParent
11
+ def self.give_child
12
+ BasicParent::BasicChild.new
13
+ end
14
+ def self.x
15
+ "PARENT CLASS METHOD X"
16
+ end
17
+ def x
18
+ "PARENT CHILD METHOD X"
19
+ end
20
+ attr_accessor :blah
21
+ class BasicChild
22
+ def self.x
23
+ "CHILD CLASS METHOD X"
24
+ end
25
+ def x
26
+ "CHILD OBJECT METHOD X"
27
+ end
28
+ class BasicSubChild
29
+ def self.x
30
+ "GRANDCHILD CLASS METHOD X"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ }
36
+
37
+ # Here's the magic..
38
+ class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
39
+ class_server.add_class(klass)
40
+ class_server.load_class(binding, :all)
41
+ #class_server.load_non_present_classes(binding)
42
+
43
+ puts BasicParent.give_child.x
44
+ puts BasicParent.x
45
+ puts BasicParent::BasicChild::BasicSubChild.x
46
+
47
+ x = BasicParent.new
48
+ x.blah = 50
49
+ puts x.blah
@@ -0,0 +1,51 @@
1
+ # Attempt to do nasty / insecure operations!
2
+
3
+ require 'rubygems'
4
+ require 'classroom'
5
+
6
+ # A basic 'queue' class with a fixed length queue and
7
+ # push and pull methods to use the queue.
8
+ klass = %q{
9
+ class BasicParent
10
+ def do_nasty
11
+ `ls -l`
12
+ end
13
+ def do_nasty2
14
+ x = 'ls'
15
+ y = '-l'
16
+ z = 'system'
17
+ eval z + '"' + x + ' ' + y + '"'
18
+ end
19
+ def do_nasty3
20
+ ClassRoom::ClassServerContainer.attr_writer(:xyzzy)
21
+ end
22
+ end
23
+ }
24
+
25
+ # Here's the magic..
26
+ class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
27
+ class_server.add_class(klass)
28
+ class_server.load_class(binding, :all)
29
+
30
+ a = BasicParent.new
31
+
32
+ puts "Nasty 1"
33
+ begin
34
+ a.do_nasty
35
+ rescue
36
+ puts "FAILED WITH #{$!}}!"
37
+ end
38
+
39
+ puts "Nasty 2"
40
+ begin
41
+ a.do_nasty2
42
+ rescue
43
+ puts "FAILED WITH #{$!}}!"
44
+ end
45
+
46
+ puts "Nasty 3"
47
+ begin
48
+ a.do_nasty3
49
+ rescue
50
+ puts "FAILED WITH #{$!}}!"
51
+ end
@@ -0,0 +1,9 @@
1
+ # A very basic ClassRoom server
2
+
3
+ require 'rubygems'
4
+ require 'classroom'
5
+
6
+ url = ClassRoom::ClassServer.prepare
7
+ puts "ClassRoom server running at #{url}"
8
+ ClassRoom::ClassServer.start
9
+
@@ -0,0 +1,48 @@
1
+ # Tests that modules work in one sense
2
+
3
+ require 'rubygems'
4
+ require 'classroom'
5
+
6
+ klass = %q{
7
+ module TestModule
8
+ class BasicParent
9
+ def self.give_child
10
+ BasicParent::BasicChild.new
11
+ end
12
+ def self.x
13
+ "PARENT CLASS METHOD X"
14
+ end
15
+ def x
16
+ "PARENT CHILD METHOD X"
17
+ end
18
+ attr_accessor :blah
19
+ class BasicChild
20
+ def self.x
21
+ "CHILD CLASS METHOD X"
22
+ end
23
+ def x
24
+ "CHILD OBJECT METHOD X"
25
+ end
26
+ class BasicSubChild
27
+ def self.x
28
+ "GRANDCHILD CLASS METHOD X"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ }
35
+
36
+ # Here's the magic..
37
+ class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
38
+ class_server.add_class(klass)
39
+ class_server.load_non_present_classes(binding)
40
+
41
+ puts class_server.classes.inspect
42
+ puts TestModule::BasicParent.give_child.x
43
+ puts TestModule::BasicParent.x
44
+ puts TestModule::BasicParent::BasicChild::BasicSubChild.x
45
+
46
+ x = TestModule::BasicParent.new
47
+ x.blah = 50
48
+ puts x.blah
@@ -0,0 +1,32 @@
1
+ # Tests that module mixins work entirely on the server end
2
+
3
+ require 'rubygems'
4
+ require 'classroom'
5
+
6
+ # A basic 'queue' class with a fixed length queue and
7
+ # push and pull methods to use the queue.
8
+ klass = %q{
9
+ module TestModule
10
+ def blah
11
+ "blah #{@s}"
12
+ end
13
+ end
14
+ }
15
+
16
+ klass2 = %q{
17
+ class Something
18
+ include TestModule
19
+ def initialize(s)
20
+ @s = s
21
+ end
22
+ end
23
+ }
24
+
25
+ # Here's the magic..
26
+ class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
27
+ class_server.add_class(klass)
28
+ class_server.add_class(klass2)
29
+ class_server.load_class(binding, :all)
30
+
31
+ x = Something.new(10)
32
+ puts x.blah
@@ -0,0 +1,35 @@
1
+ # THIS TEST WILL NOT WORK!! UNIMPLEMENTED FEATURE!!
2
+
3
+ # Use a remote module from the ClassRoom server as a mixin
4
+ # on a local class.. (currently not possible)
5
+
6
+ require 'rubygems'
7
+ require 'classroom'
8
+
9
+ # A basic 'queue' class with a fixed length queue and
10
+ # push and pull methods to use the queue.
11
+ klass = %q{
12
+ module TestModule
13
+ def blah
14
+ "blah #{@s}"
15
+ end
16
+ end
17
+ }
18
+
19
+
20
+ # Here's the magic..
21
+ class_server = ClassRoom::Client.new(ARGV.first || 'druby://coop-pb:2001')
22
+ class_server.add_class(klass)
23
+ class_server.load_class(binding, :all)
24
+
25
+
26
+ class Something
27
+ include TestModule
28
+ def initialize(s)
29
+ @s = s
30
+ end
31
+ end
32
+
33
+
34
+ x = Something.new(10)
35
+ puts x.blah
data/lib/classroom.rb ADDED
@@ -0,0 +1,241 @@
1
+ require 'drb/drb'
2
+ require 'thread'
3
+
4
+ module ClassRoom
5
+
6
+ # Create a 'blank slate' class template so we don't have any extraneous
7
+ # methods lying around to be (potentially) abused.
8
+ # Technique by http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
9
+ class BlankSlate
10
+ instance_methods.each { |m| undef_method m unless m =~ /^__/ }
11
+ end
12
+
13
+ # Provide a separate module as the container for our user classes
14
+ module ClassContainer
15
+ end
16
+
17
+ # The class that proxies between user classes on the ClassRoom server and
18
+ # the requests made by client apps. It stores references to the user classes,
19
+ # evals the code, and allows instances to be created of the user classes.
20
+ class ClassServerContainer < BlankSlate
21
+ @@classes = []
22
+
23
+ # Add class(es) to the ClassRoom server
24
+ def self.add_class(code)
25
+ base_class = code.scan(/(class|module)\s+([\w\:]+)/).collect{|c| c[1]}.flatten.first
26
+ base_class.untaint
27
+ @@classes << base_class
28
+
29
+ # Actually add the class to the ClassRoom server
30
+ begin
31
+ # Force the code to run by untainting it
32
+ code.untaint
33
+ # Force the code into the ClassContainer module and force a
34
+ # SAFE mode where all data is considered tainted. This stops
35
+ # users running system commands, etc.
36
+ eval("module ClassRoom; module ClassContainer\n$SAFE = 3\n" + code + "\nend; end")
37
+ rescue
38
+ # If the user's code was bad, duck out.
39
+ return nil
40
+ end
41
+
42
+ # Now the code is loaded.. loop through all of the classes and add their
43
+ # full (nested) names to the class list so automatic loading
44
+ # can work for clients
45
+ classes_to_check = [base_class]
46
+ classes_to_check.each do |class_name|
47
+ class_internal = eval("ClassRoom::ClassContainer::#{class_name}")
48
+ class_internal.constants.each do |subclass_name|
49
+ if class_internal.const_get(subclass_name).is_a?(Class) || class_internal.const_get(subclass_name).is_a?(Module)
50
+ sub_class = class_name + "::" + subclass_name
51
+ @@classes << sub_class
52
+ classes_to_check << sub_class
53
+ end
54
+ end
55
+ end
56
+
57
+ # Hack to make sure the classes array is clean
58
+ @@classes.collect! { |c| c.to_sym }
59
+ @@classes.uniq!
60
+ true
61
+ end
62
+
63
+ # THESE ARE NOT WORKING
64
+ #def self.remove_class(class_name)
65
+ # class_internal = eval("ClassRoom::ClassContainer")
66
+ # class_internal.constants.each do |subclass_name|
67
+ # if class_internal.const_get(subclass_name).is_a?(Class) || class_internal.const_get(subclass_name).is_a?(Module)
68
+ # class_internal.remove_const(subclass_name) if subclass_name.to_s == class_name.to_s
69
+ # end
70
+ # end
71
+ #end
72
+ #
73
+ #def self.remove_all
74
+ # class_internal = eval("ClassRoom::ClassContainer")
75
+ # class_internal.constants.each do |subclass_name|
76
+ # if class_internal.const_get(subclass_name).is_a?(Class) || class_internal.const_get(subclass_name).is_a?(Module)
77
+ # class_internal.remove_const(subclass_name)
78
+ # end
79
+ # end
80
+ #end
81
+
82
+ # Return a list of all classes in the ClassContainer
83
+ # (must be a nicer way to do this without logging it ourselves..?)
84
+ def self.classes
85
+ return @@classes
86
+ end
87
+
88
+ # Return a new instance of a class in the ClassContainer
89
+ def self.new_instance_of(class_name, *args)
90
+ begin
91
+ obj = eval('ClassRoom::ClassContainer::' + class_name.to_s).new(*args)
92
+ obj.extend(DRbUndumped)
93
+ obj
94
+ rescue
95
+ nil
96
+ end
97
+ end
98
+
99
+ # Call a class method on a class in the ClassContainer
100
+ # (this might need work to check what comes back and whether to
101
+ # mix in DRbUndumped if it's an object)
102
+ def self.class_method(class_name, method_name, *args)
103
+ begin
104
+ obj = eval('ClassRoom::ClassContainer::' + class_name.to_s).send(method_name.to_s, *args)
105
+ obj.extend(DRbUndumped) if self.classes.detect { |c| obj.class.to_s.index(c.to_s) }
106
+ obj
107
+ rescue
108
+ nil
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ # Provides methods to get the server up and running easily
115
+ class ClassServer < BlankSlate
116
+ # Prepare DRb to act as a server using the correct URL
117
+ # Do some messy hacks to get our own URL scheme
118
+ def self.prepare(drb_url = 'classroom://:2001')
119
+ $SAFE = 1
120
+ drb_url.gsub!('classroom:', 'druby:')
121
+ DRb.start_service(drb_url, ClassServerContainer)
122
+ return DRb.uri.gsub('druby:', 'classroom:')
123
+ end
124
+
125
+ # Do the thread.join with DRb to actually get the server processing requests
126
+ # TODO: Get this daemonizing!
127
+ def self.start
128
+ #fork do
129
+ # Process.setsid
130
+ # exit if fork
131
+ # trap("TERM") {daemon.stop; exit}
132
+ Kernel.send :remove_method, :system
133
+ class << Kernel; self; end.send :remove_method, :system
134
+ $SAFE = 3
135
+ DRb.thread.join
136
+ #end
137
+ end
138
+ end
139
+
140
+ # Provides methods for the client side
141
+ class Client < BlankSlate
142
+
143
+ def self.get_drb(drb_url)
144
+ @@drb[drb_url]
145
+ end
146
+
147
+ # Set up the DRbObject referring to the ClassRoom server
148
+ def initialize(drb_url)
149
+ @drb_url = drb_url.gsub('classroom:', 'druby:')
150
+ (@@drb ||= {})[@drb_url] = DRbObject.new(nil, @drb_url)
151
+ end
152
+
153
+ # Passes through calls for non-local methods to the DRb server
154
+ def method_missing(method, *args)
155
+ @@drb[@drb_url].send(method, *args)
156
+ end
157
+
158
+ # Returns a proxy object referring to the remote class required
159
+ def remote_class(class_name)
160
+ ClassRoom::Client::ProxyObject.new(@drb, class_name)
161
+ end
162
+
163
+ # Load a class (or all classes, if :all is specified) from the
164
+ # ClassRoom server to be used locally
165
+ def load_class(b, *classes)
166
+ return nil unless classes && b
167
+ classes = self.classes if classes.first == :all
168
+ classes.each do |class_name|
169
+ eval %Q{
170
+ class #{class_name} < ClassRoom::Client::ProxyObject
171
+ end
172
+ #{class_name}.class_name = :\"#{class_name}\"
173
+ #{class_name}.drb = ClassRoom::Client::get_drb('#{@drb_url}')
174
+ }, b
175
+ end
176
+ end
177
+
178
+ # Load all classes on the ClassRoom server that do not already
179
+ # exist locally (not fully tested yet!)
180
+ def load_non_present_classes(b)
181
+ return nil unless b
182
+ classes = self.classes
183
+ classes.each do |class_name|
184
+ eval %Q{
185
+ unless defined?(#{class_name})
186
+ class #{class_name} < ClassRoom::Client::ProxyObject
187
+ end
188
+ end
189
+ }, b
190
+ eval %Q{
191
+ begin
192
+ #{class_name}.drb = @drb
193
+ rescue
194
+ end
195
+ }
196
+ eval %Q{
197
+ begin
198
+ #{class_name}.class_name = :\"#{class_name}\"
199
+ rescue
200
+ end
201
+ }
202
+ end
203
+ end
204
+
205
+ # ProxyObject acts as a local proxy for ClassRoom clients and
206
+ # handles passing through requests to run class methods and initializations
207
+ class ProxyObject
208
+
209
+ # Set up a weird /true/ class variable system (that is, class variable ==
210
+ # class variable, not weird hierarchy variable-foo as is Ruby's wont)
211
+ def self.drb; @@drb[self.to_s.to_sym]; end
212
+ def self.drb=(d); (@@drb ||= {})[self.to_s.to_sym] = d; end
213
+ def self.class_name; @@class_name[self.to_s.to_sym]; end
214
+ def self.class_name=(d); (@@class_name ||= {})[self.to_s.to_sym] = d; end
215
+ def drb; @@drb[self.class.to_s.to_sym]; end
216
+ def class_name; @@class_name[self.class.to_s.to_sym]; end
217
+
218
+ # Create an instance of the remote class
219
+ def initialize(*args)
220
+ @drbed_object = drb.send(:new_instance_of, self.class.to_s.to_sym, *args)
221
+ end
222
+
223
+ # If we're getting methods called on the proxy, they must be for
224
+ # the remote class rather than a method, so ask the ClassRoom server
225
+ # to run the class_method on the supplied object
226
+ def self.method_missing(method, *args)
227
+ drb.send(:class_method, class_name, method, *args)
228
+ end
229
+
230
+ # Return the 'real' class name
231
+ #def class; @class_name; end
232
+
233
+ def method_missing(method, *args)
234
+ @drbed_object.send(method, *args)
235
+ end
236
+
237
+ end
238
+
239
+ end
240
+
241
+ end
@@ -0,0 +1,46 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'classroom'
4
+
5
+ # Get a binding for the top level
6
+ $top_level_binding = binding
7
+
8
+ class ClassRoomTest < Test::Unit::TestCase
9
+
10
+ def setup
11
+ $class_server = ClassRoom::Client.new('classroom://:2001')
12
+
13
+ end
14
+
15
+ def test_a_classroom_server_responds
16
+ assert $class_server.classes.class == Array
17
+ end
18
+
19
+ def test_b_can_add_basic_class
20
+ klass = %q{
21
+ class SmallClass
22
+ def self.result; "GOOD CLASS"; end
23
+ def result; "GOOD INSTANCE"; end
24
+ end
25
+ }
26
+ assert $class_server.add_class(klass)
27
+ end
28
+
29
+ def test_c_can_use_class_method_directly
30
+ assert_equal "GOOD CLASS", $class_server.class_method(:SmallClass, :result)
31
+ end
32
+
33
+ def test_d_can_create_instance_directly
34
+ assert_equal "GOOD INSTANCE", $class_server.new_instance_of(:SmallClass).result
35
+ end
36
+
37
+ def test_e_can_import_classes
38
+ assert $class_server.load_class($top_level_binding, :all)
39
+ end
40
+
41
+ def test_f_can_use_imported_classes
42
+ assert_equal "GOOD CLASS", SmallClass.result
43
+ assert_equal "GOOD INSTANCE", SmallClass.new.result
44
+ end
45
+
46
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: classroom
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2006-07-12 00:00:00 +01:00
8
+ summary: ClassRoom is a 'class server' based on DRb
9
+ require_paths:
10
+ - lib
11
+ email: coops@petercooper.co.uk
12
+ homepage: http://classroom.rubyforge.org
13
+ rubyforge_project: classroom
14
+ description:
15
+ autorequire: classroom
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Peter Cooper
30
+ files:
31
+ - classroom.gemspec
32
+ - examples
33
+ - lib
34
+ - Rakefile
35
+ - test
36
+ - examples/demo_client.rb
37
+ - examples/demo_client2.rb
38
+ - examples/demo_client3.rb
39
+ - examples/demo_client4.rb
40
+ - examples/demo_client5.rb
41
+ - examples/server.rb
42
+ - examples/test_modules.rb
43
+ - examples/test_modules2.rb
44
+ - examples/test_modules3.rb
45
+ - lib/classroom.rb
46
+ - test/classroom_test.rb
47
+ test_files:
48
+ - test/classroom_test.rb
49
+ rdoc_options: []
50
+
51
+ extra_rdoc_files: []
52
+
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ requirements: []
58
+
59
+ dependencies: []
60
+