iotas 0.0.2

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.
data/lib/iotas/room.rb ADDED
@@ -0,0 +1,192 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2012 Jérémy Zurcher <jeremy@asynk.ch>
5
+ #
6
+ # This file is part of iotas.
7
+ #
8
+ # iotas is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # iotas is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with iotas. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ #
22
+ module Iotas
23
+ #
24
+ ERROR_ROUTE_NS = 'routing error: no source'.freeze
25
+ ERROR_ROUTE_RRWD = 'routing error: right room, wrong door'.freeze
26
+ ERROR_ROUTE_DDWR = 'routing error: drill down, wrong room'.freeze
27
+ ERROR_ROUTE_TRWR = 'routing error: top room, wrong room'.freeze
28
+ ERROR_ROUTE_NDNL = 'routing error: no destination, no link'.freeze
29
+ ERROR_ROUTE_SND = 'routing error: system no destination'.freeze
30
+ #
31
+ class Room < Iota
32
+ #
33
+ def initialize n, p
34
+ super n, p
35
+ @iotas = {}
36
+ @links = {}
37
+ end
38
+ #
39
+ def to_json *a
40
+ {
41
+ 'kls' => self.class.name,
42
+ 'name' => @name,
43
+ 'iotas' => @iotas,
44
+ 'links' => @links
45
+ }.to_json *a
46
+ end
47
+ #
48
+ def self.json_create o
49
+ raise Iotas::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name
50
+ room = self.new o['name'], o['parent']
51
+ o['iotas'].each do |name,iota|
52
+ eval( iota['kls'] ).json_create(iota.merge!('parent'=>room))
53
+ end
54
+ o['links'].each do |src,links|
55
+ links.each do |link|
56
+ room.add_link Iotas::Link.json_create(link)
57
+ end
58
+ end
59
+ room
60
+ end
61
+ #
62
+ def add_iota s
63
+ raise Iotas::Exception.new "Iota #{s.name} already has #{s.parent.name} as parent" if not s.parent.nil? and s.parent!=self
64
+ raise Iotas::Exception.new "Iota #{s.name} already exists in #{path}" if @iotas.has_key? s.name
65
+ s.parent = self if s.parent.nil?
66
+ @iotas[s.name]=s
67
+ end
68
+ #
69
+ def add_link l
70
+ l.door = @iotas[l.src]
71
+ raise Iotas::Exception.new "Link source #{l.src} does not exist in #{path}" if l.door.nil?
72
+ (@links[l.src] ||= [])<< l
73
+ end
74
+ #
75
+ def start!
76
+ puts " * start #{path}" if @spin.debug_routing
77
+ @iotas.values.each do |iota| iota.start! end
78
+ end
79
+ #
80
+ def stop!
81
+ puts " * stop #{path}" if @spin.debug_routing
82
+ @iotas.values.each do |iota| iota.stop! end
83
+ end
84
+ #
85
+ def search_down spath
86
+ return self if spath==path
87
+ return nil if (spath=~/^#{path}\/(\w+)\/?/)!=0
88
+ if iota = @iotas[$1]
89
+ return iota if iota.path==spath # needed as Door doesn't implement #search_down
90
+ return iota.search_down spath
91
+ end
92
+ nil
93
+ end
94
+ #
95
+ def try_links p
96
+ puts " -> try_links ..." if @spin.debug_routing
97
+ links = @links[p.src.name]
98
+ return false if links.nil?
99
+ pending_link = nil
100
+ apply_link = false
101
+ links.each do |link|
102
+ apply_link = link.cond_fields.nil? # unconditional link
103
+ p.set_link_fields link.cond_fields if not apply_link
104
+ if apply_link or (p.link_value==link.cond_value)
105
+ # link matches !
106
+ if pending_link
107
+ p2 = @spin.require_p p.class
108
+ p2.clone_data p
109
+ p2.apply_link! link
110
+ send_p p2
111
+ end
112
+ pending_link = link
113
+ end
114
+ end
115
+ if pending_link
116
+ p.apply_link! pending_link
117
+ send_p p
118
+ end
119
+ (not pending_link.nil?)
120
+ end
121
+ #
122
+ def route_p p
123
+ if p.room.nil? or p.room==path
124
+ if door = @iotas[p.door]
125
+ p.dst_routed! door
126
+ else
127
+ p.error! Iotas::ERROR_ROUTE_RRWD
128
+ end
129
+ elsif (p.room=~/^#{path}\/(.*)/)==0
130
+ room, *more = $1.split Iotas::PATH_SEP
131
+ if child=@iotas[room]
132
+ child.route_p p
133
+ else
134
+ p.error! Iotas::ERROR_ROUTE_DDWR
135
+ end
136
+ elsif @parent
137
+ @parent.route_p p
138
+ else
139
+ p.error! Iotas::ERROR_ROUTE_TRWR
140
+ end
141
+ end
142
+ #
143
+ def send_p p
144
+ puts " * send_p #{(p.next_dst.nil? ? 'no dst' : p.next_dst)} ..." if @spin.debug_routing
145
+ if p.src.nil?
146
+ # do not route orphan particles !!
147
+ p.error! Iotas::ERROR_ROUTE_NS, @spin
148
+ elsif p.next_dst
149
+ p.split_dst!
150
+ if p.door
151
+ route_p p
152
+ else
153
+ # boomerang
154
+ p.dst_routed! p.src
155
+ end
156
+ elsif try_links p
157
+ return
158
+ else
159
+ p.error! Iotas::ERROR_ROUTE_NDNL
160
+ end
161
+ puts " -> #{p.dst.path}#{Iotas::ACT_SEP}#{p.action}" if @spin.debug_routing
162
+ @spin.post_p p
163
+ end
164
+ #
165
+ def send_sys_p p
166
+ puts " * send_sys_p #{(p.next_dst.nil? ? 'no dst' : p.next_dst)} ..." if @spin.debug_routing
167
+ if p.next_dst
168
+ p.split_dst!
169
+ if p.door
170
+ route_p p
171
+ elsif p.action
172
+ p.dst_routed! @spin
173
+ end
174
+ else
175
+ p.error! Iotas::ERROR_ROUTE_SND
176
+ end
177
+ puts " -> #{p.dst.path}#{Iotas::ACT_SEP}#{p.action}" if @spin.debug_routing
178
+ @spin.post_sys_p p
179
+ end
180
+ #
181
+ def process_sys_p p
182
+ if p.action==Iotas::SYS_ACT_ADD_LINK
183
+ add_link Iotas::Link.from_particle_data p
184
+ end
185
+ @spin.release_p p
186
+ end
187
+ #
188
+ end
189
+ #
190
+ end
191
+ #
192
+ # EOF
data/lib/iotas/spin.rb ADDED
@@ -0,0 +1,150 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2012 Jérémy Zurcher <jeremy@asynk.ch>
5
+ #
6
+ # This file is part of iotas.
7
+ #
8
+ # iotas is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # iotas is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with iotas. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ #
22
+ module Iotas
23
+ #
24
+ class Spin < Room
25
+ #
26
+ def initialize n, o={}
27
+ super n, nil
28
+ #
29
+ @pool = {} # per particle class free list
30
+ @sys_fifo = [] # system particles fifo list
31
+ @app_fifo = [] # application particles fifo list
32
+ #
33
+ @run = false
34
+ @hibernation = o['hibernation']||false
35
+ @hibernate_path = 'iotas-hibernate-'+n+'.json'
36
+ @debug_errors = o[:debug_errors]||o['debug_errors']||false
37
+ @debug_routing = o[:debug_routing]||o['debug_routing']||false
38
+ #
39
+ if not o.empty?
40
+ o['iotas'].each do |name,iota|
41
+ Iotas::Room.json_create(iota.merge!('parent'=>self))
42
+ end if o['iotas']
43
+ o['app_fifo'].each do |particle|
44
+ @app_fifo << Iotas::Particle.json_create(particle.merge!('spin'=>self))
45
+ end if o['app_fifo']
46
+ o['sys_fifo'].each do |particle|
47
+ @sys_fifo << Iotas::Particle.json_create(particle.merge!('spin'=>self))
48
+ end if o['sys_fifo']
49
+ end
50
+ end
51
+ #
52
+ attr_accessor :run, :hibernate_path, :debug_errors, :debug_routing
53
+ #
54
+ def to_json *a
55
+ {
56
+ 'kls' => self.class.name,
57
+ 'timestamp' => Time.now,
58
+ 'name' => @name,
59
+ 'hibernation' => @hibernation,
60
+ 'iotas' => @iotas,
61
+ 'sys_fifo' => @sys_fifo,
62
+ 'app_fifo' => @app_fifo,
63
+ 'debug_errors' => @debug_errors,
64
+ 'debug_routing' => @debug_routing
65
+ }.to_json(*a)
66
+ end
67
+ #
68
+ def self.json_create o
69
+ raise Iotas::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name
70
+ self.new o['name'], o
71
+ end
72
+ #
73
+ def clear!
74
+ @iotas.clear
75
+ @pool.clear
76
+ @sys_fifo.clear
77
+ @app_fifo.clear
78
+ end
79
+ #
80
+ #
81
+ def release_p p
82
+ # hope there is no circular loop
83
+ while p2=p.merged_shift
84
+ release_p p2
85
+ end
86
+ ( @pool[p.class] ||= [] ) << p
87
+ end
88
+ #
89
+ def require_p p_kls
90
+ l = @pool[p_kls]
91
+ return p_kls.new if l.nil?
92
+ p = l.pop
93
+ return p_kls.new if p.nil?
94
+ p.reset!
95
+ p
96
+ end
97
+ #
98
+ def post_p p
99
+ @app_fifo << p
100
+ end
101
+ #
102
+ def post_sys_p p
103
+ @sys_fifo << p
104
+ end
105
+ #
106
+ def process_sys_p p
107
+ if p.action==Iotas::SYS_ACT_HIBERNATE
108
+ stop!
109
+ hibernate! p[FIELD_HIBERNATE_PATH]
110
+ else
111
+ super p
112
+ end
113
+ end
114
+ #
115
+ def spin!
116
+ @iotas.values.each do |iota| iota.start! end unless @hibernation
117
+ @run = true
118
+ @hibernation = false
119
+ while @run and (@sys_fifo.length>0 or @app_fifo.length>0)
120
+ while @run and @sys_fifo.length>0
121
+ p = @sys_fifo.shift
122
+ p.dst.process_sys_p p
123
+ end
124
+ while @run and @app_fifo.length>0
125
+ p = @app_fifo.shift
126
+ p.dst.process_p p
127
+ break
128
+ end
129
+ end
130
+ @iotas.values.each do |iota| iota.stop! end unless @hibernation
131
+ end
132
+ #
133
+ def stop!
134
+ @run=false
135
+ end
136
+ #
137
+ def hibernate! path=nil
138
+ @hibernation = true
139
+ File.open(path||@hibernate_path,'w') do |f| f << JSON.pretty_generate(self) end
140
+ end
141
+ #
142
+ def self.resume! path
143
+ self.json_create JSON.load File.open(path,'r') { |f| f.read }
144
+ end
145
+ #
146
+ end
147
+ #
148
+ end
149
+ #
150
+ # EOF
data/lib/iotas.rb ADDED
@@ -0,0 +1,51 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2012 Jérémy Zurcher <jeremy@asynk.ch>
5
+ #
6
+ # This file is part of iotas.
7
+ #
8
+ # iotas is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # iotas is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with iotas. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ require 'version'
22
+ #
23
+ module Iotas
24
+ #
25
+ PATH_SEP = '/'.freeze
26
+ LINK_SEP = ','.freeze
27
+ ACT_SEP = '?'.freeze
28
+ #
29
+ ACT_GET = 'get'.freeze
30
+ ACT_ERROR = 'error'.freeze
31
+ #
32
+ SYS_ACT_HIBERNATE = 'hibernate'.freeze
33
+ SYS_ACT_ADD_LINK = 'sys_add_link'.freeze
34
+ #
35
+ FIELD_ERROR_MSG = 'edoors_error'.freeze
36
+ FIELD_HIBERNATE_PATH= 'hibernate_path'.freeze
37
+ #
38
+ class Exception < ::Exception; end
39
+ #
40
+ end
41
+ #
42
+ require 'json'
43
+ require 'iotas/particle'
44
+ require 'iotas/iota'
45
+ require 'iotas/room'
46
+ require 'iotas/spin'
47
+ require 'iotas/door'
48
+ require 'iotas/board'
49
+ require 'iotas/link'
50
+ #
51
+ # EOF
data/lib/version.rb ADDED
@@ -0,0 +1,28 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2012 Jérémy Zurcher <jeremy@asynk.ch>
5
+ #
6
+ # This file is part of iotas.
7
+ #
8
+ # iotas is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # iotas is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with iotas. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ #
22
+ module Iotas
23
+ #
24
+ VERSION = "0.0.2"
25
+ #
26
+ end
27
+ #
28
+ # EOF
@@ -0,0 +1,95 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+
5
+ require 'spec_helper'
6
+ #
7
+ describe Iotas::Board do
8
+ #
9
+ before (:all) do
10
+ @spin = Iotas::Spin.new 'dom0'
11
+ end
12
+ #
13
+ before(:each) do
14
+ @spin.clear!
15
+ end
16
+ #
17
+ it "require_p release_p" do
18
+ board = Iotas::Board.new 'hell', @spin
19
+ p0 = board.require_p Iotas::Particle
20
+ p0.src.should be board
21
+ p1 = board.require_p Iotas::Particle
22
+ p1.src.should be board
23
+ (p0===p1).should be_false
24
+ board.release_p p0
25
+ p2 = board.require_p Iotas::Particle
26
+ p2.src.should be board
27
+ (p0===p2).should be_true
28
+ end
29
+ #
30
+ it "particle wait and merge" do
31
+ p0 = Iotas::Particle.new
32
+ p0['k0'] = 'v0'
33
+ p0['k1'] = 'neither'
34
+ p0['k2'] = 'v2'
35
+ p0.set_link_fields 'k0,k2'
36
+ p0.link_value.should eql 'v0v2'
37
+ p1 = Iotas::Particle.new
38
+ p1['k0'] = 'v0'
39
+ p1['k1'] = 'nore'
40
+ p1['k2'] = 'v2'
41
+ p1.set_link_fields 'k0,k2'
42
+ p1.link_value.should eql 'v0v2'
43
+ P0 = p0
44
+ P1 = p1
45
+ class Board0 < Iotas::Board
46
+ attr_reader :ok, :follow
47
+ def receive_p p
48
+ @ok = false
49
+ case p.action
50
+ when Iotas::ACT_FOLLOW
51
+ @follow = true
52
+ @ok = (p===P0 and p.merged(0)===P1)
53
+ else
54
+ @follow = false
55
+ @ok = (p===P1 and p.merged(0)===P0)
56
+ end
57
+ end
58
+ end
59
+ b0 = Board0.new 'door0', @spin
60
+ b0.process_p p0
61
+ p0.merged(0).should be_nil
62
+ b0.process_p p1
63
+ b0.ok.should be_true
64
+ b0.follow.should be_false
65
+ #
66
+ p1.merged_shift
67
+ #
68
+ b0.process_p p0
69
+ p0.merged(0).should be_nil
70
+ # need to set it to p0 too, so case in Board0 is ok
71
+ p0.set_dst! Iotas::ACT_FOLLOW
72
+ p0.split_dst!
73
+ p1.set_dst! Iotas::ACT_FOLLOW
74
+ p1.split_dst!
75
+ b0.process_p p1
76
+ b0.ok.should be_true
77
+ b0.follow.should be_true
78
+ end
79
+ #
80
+ it "board->json->board" do
81
+ board = Iotas::Board.new 'hell', @spin
82
+ p0 = Iotas::Particle.new
83
+ p1 = Iotas::Particle.new
84
+ p1['v0']=0
85
+ p1.set_link_fields 'v0'
86
+ board.process_p p0
87
+ board.process_p p1
88
+ hell = Iotas::Board.json_create( JSON.load( JSON.generate(board) ) )
89
+ board.name.should eql hell.name
90
+ JSON.generate(board).should eql JSON.generate(hell)
91
+ end
92
+ #
93
+ end
94
+ #
95
+ # EOF
data/spec/door_spec.rb ADDED
@@ -0,0 +1,94 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+
5
+ require 'spec_helper'
6
+ #
7
+ describe Iotas::Door do
8
+ #
9
+ before (:all) do
10
+ @spin = Iotas::Spin.new 'dom0'
11
+ end
12
+ #
13
+ before(:each) do
14
+ @spin.clear!
15
+ end
16
+ #
17
+ it "require_p release_p" do
18
+ door = Iotas::Door.new 'hell', @spin
19
+ p0 = door.require_p Iotas::Particle
20
+ p0.src.should be door
21
+ p1 = door.require_p Iotas::Particle
22
+ p1.src.should be door
23
+ (p0===p1).should be_false
24
+ door.release_p p0
25
+ p2 = door.require_p Iotas::Particle
26
+ p2.src.should be door
27
+ (p0===p2).should be_true
28
+ end
29
+ #
30
+ it "NoMethodError when receive_p not overridden" do
31
+ class Door0 < Iotas::Door
32
+ end
33
+ f = Fake.new 'fake', @spin
34
+ d0 = Door0.new 'door0', f
35
+ p0 = d0.require_p Iotas::Particle
36
+ lambda { d0.process_p p0 }.should raise_error(NoMethodError)
37
+ end
38
+ #
39
+ it "send_p, send_sys_p, release_p and release of lost particles" do
40
+ class Door0 < Iotas::Door
41
+ def receive_p p
42
+ case p.action
43
+ when 'RELEASE'
44
+ release_p p
45
+ when 'SEND'
46
+ send_p p
47
+ when 'SEND_SYS'
48
+ send_sys_p p
49
+ else
50
+ # lost!!
51
+ end
52
+ end
53
+ end
54
+ f = Fake.new 'fake', @spin
55
+ d0 = Door0.new 'door0', f
56
+ p0 = d0.require_p Iotas::Particle
57
+ #
58
+ p0.set_dst! 'SEND'
59
+ p0.split_dst!
60
+ d0.process_p p0
61
+ f.p.should eql p0
62
+ #
63
+ p0.set_dst! 'SEND_SYS'
64
+ p0.split_dst!
65
+ d0.process_p p0
66
+ f.sp.should eql p0
67
+ #
68
+ p0.set_dst! 'RELEASE'
69
+ p0.split_dst!
70
+ d0.process_p p0
71
+ p1 = d0.require_p Iotas::Particle
72
+ p1.should be p0
73
+ #
74
+ p0.set_dst! 'LOST'
75
+ p0.split_dst!
76
+ d0.process_p p0
77
+ p1 = d0.require_p Iotas::Particle
78
+ p1.should be p0
79
+ #
80
+ d0.process_sys_p p0
81
+ p1 = @spin.require_p Iotas::Particle
82
+ p1.should be p0
83
+ end
84
+ #
85
+ it "door->json->door" do
86
+ door = Iotas::Door.new 'hell', @spin
87
+ hell = Iotas::Door.json_create( JSON.load( JSON.generate(door) ) )
88
+ door.name.should eql hell.name
89
+ JSON.generate(door).should eql JSON.generate(hell)
90
+ end
91
+ #
92
+ end
93
+ #
94
+ # EOF
data/spec/link_spec.rb ADDED
@@ -0,0 +1,38 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+
5
+ require 'spec_helper'
6
+ #
7
+ describe Iotas::Link do
8
+ #
9
+ it "from particle data" do
10
+ @spin = Iotas::Spin.new 'dom0'
11
+ p = @spin.require_p Iotas::Particle
12
+ p.set_data Iotas::LNK_SRC, 'input1'
13
+ p.set_data Iotas::LNK_DSTS, 'concat1?follow,output1'
14
+ p.set_data Iotas::LNK_FIELDS, 'f0,f2'
15
+ p.set_data Iotas::LNK_CONDF, 'f0,f1,f2'
16
+ p.set_data Iotas::LNK_CONDV, 'v0v1v2'
17
+ lnk = Iotas::Link.from_particle_data p
18
+ lnk.src.should eql 'input1'
19
+ lnk.dsts.should eql 'concat1?follow,output1'
20
+ lnk.fields.should eql 'f0,f2'
21
+ lnk.cond_fields.should eql 'f0,f1,f2'
22
+ lnk.cond_value.should eql 'v0v1v2'
23
+ end
24
+ #
25
+ it "link->json->link" do
26
+ link = Iotas::Link.new 'input1', 'concat1?follow,output1', 'f0,f2', 'f0,f1,f2', 'v0v1v2'
27
+ lnk = Iotas::Link.json_create( JSON.load( JSON.generate(link) ) )
28
+ link.src.should eql lnk.src
29
+ link.dsts.should eql lnk.dsts
30
+ link.fields.should eql lnk.fields
31
+ link.cond_fields.should eql lnk.cond_fields
32
+ link.cond_value.should eql lnk.cond_value
33
+ JSON.generate(link).should eql JSON.generate(lnk)
34
+ end
35
+ #
36
+ end
37
+ #
38
+ # EOF