andromeda 0.1 → 0.1.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/.gitignore +1 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +17 -9
- data/Gemfile.lock +5 -0
- data/LICENSE.txt +1 -2
- data/README.md +150 -15
- data/ROADMAP.md +73 -0
- data/Rakefile +4 -6
- data/andromeda.gemspec +2 -2
- data/lib/andromeda.rb +52 -9
- data/lib/andromeda/atom.rb +105 -0
- data/lib/andromeda/cmd.rb +242 -0
- data/lib/andromeda/common.rb +0 -0
- data/lib/andromeda/copy_clone.rb +44 -0
- data/lib/andromeda/error.rb +42 -0
- data/lib/andromeda/guide.rb +50 -0
- data/lib/andromeda/guide_track.rb +98 -0
- data/lib/andromeda/id.rb +10 -83
- data/lib/andromeda/impl/atom.rb +47 -0
- data/lib/andromeda/impl/class_attr.rb +31 -0
- data/lib/andromeda/impl/proto_plan.rb +219 -0
- data/lib/andromeda/impl/to_s.rb +48 -0
- data/lib/andromeda/impl/xor_id.rb +89 -0
- data/lib/andromeda/kit.rb +172 -0
- data/lib/andromeda/map_reduce.rb +3 -0
- data/lib/andromeda/plan.rb +130 -0
- data/lib/andromeda/pool_guide.rb +70 -0
- data/lib/andromeda/spot.rb +132 -0
- data/lib/andromeda/sugar.rb +41 -0
- data/lib/andromeda/sync.rb +68 -0
- data/lib/andromeda/version.rb +1 -1
- data/yard_extensions/andromeda.rb +28 -0
- metadata +30 -13
- data/lib/andromeda/andromeda.rb +0 -225
- data/lib/andromeda/commando.rb +0 -106
- data/lib/andromeda/helpers.rb +0 -134
- data/lib/andromeda/join.rb +0 -48
- data/lib/andromeda/pools.rb +0 -69
- data/lib/andromeda/scope.rb +0 -38
@@ -0,0 +1,105 @@
|
|
1
|
+
module Andromeda
|
2
|
+
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
class Atom < Atomic
|
6
|
+
include To_S
|
7
|
+
|
8
|
+
def initialize(init_val = nil)
|
9
|
+
super init_val
|
10
|
+
end
|
11
|
+
|
12
|
+
def empty? ; value.nil? end
|
13
|
+
def full? ; ! value.nil? end
|
14
|
+
|
15
|
+
def to_short_s ; "(#{To_S.short_s(value)})" end
|
16
|
+
alias_method :inspect, :to_s
|
17
|
+
|
18
|
+
def wait_while(&test)
|
19
|
+
while test.call(value) ; Thread::pass end
|
20
|
+
end
|
21
|
+
|
22
|
+
def wait_until_eq(val = nil)
|
23
|
+
raise ArgumentError unless val.kind_of?(Fixnum)
|
24
|
+
wait_while { |v| v != val }
|
25
|
+
end
|
26
|
+
|
27
|
+
def wait_until_ne(val = nil)
|
28
|
+
raise ArgumentError unless val.kind_of?(Fixnum)
|
29
|
+
wait_while { |v| v == val }
|
30
|
+
end
|
31
|
+
|
32
|
+
def wait_until_empty?
|
33
|
+
wait_until_eq nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def wait_until_full?
|
37
|
+
wait_until_ne nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_value
|
41
|
+
update { |v| yield v ; v }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
module Atom
|
48
|
+
|
49
|
+
class Region
|
50
|
+
include Impl::To_S
|
51
|
+
|
52
|
+
def to_short_s ; "(#{Impl::To_S.short_s(value)})" end
|
53
|
+
alias_method :inspect, :to_s
|
54
|
+
|
55
|
+
def initialize(init_value = 0)
|
56
|
+
init_value = init_value[:init_value].to_i if init_value.kind_of?(Hash)
|
57
|
+
raise ArgumentError unless init_value.kind_of?(Fixnum)
|
58
|
+
@count = Impl::Atom.new init_value
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :inspect, :to_s
|
62
|
+
|
63
|
+
def value ; @count.value end
|
64
|
+
|
65
|
+
def enter(amount = 1)
|
66
|
+
raise ArgumentError unless amount.kind_of?(Fixnum)
|
67
|
+
raise ArgumentError unless amount >= 0
|
68
|
+
@count.update { |v| v + amount }
|
69
|
+
end
|
70
|
+
|
71
|
+
def leave(amount = 1)
|
72
|
+
raise ArgumentError unless amount >= 0
|
73
|
+
raise ArgumentError unless amount.kind_of?(Fixnum)
|
74
|
+
@count.update { |v| v - amount }
|
75
|
+
end
|
76
|
+
|
77
|
+
def wait_until_eq(val) ; @count.wait_until_eq(val) end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Var < Impl::Atom
|
81
|
+
end
|
82
|
+
|
83
|
+
class FillOnce < Var
|
84
|
+
def empty! ; super.update nil end
|
85
|
+
|
86
|
+
def update(v)
|
87
|
+
super.update do |o|
|
88
|
+
raise ArgumentError, 'Attempt to refill FillOnce' if o
|
89
|
+
v
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Combiner < Var
|
95
|
+
attr_reader :combiner
|
96
|
+
|
97
|
+
def update(v)
|
98
|
+
super.update do |o|
|
99
|
+
if combiner then combiner.call o, v else v end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
module Andromeda
|
2
|
+
|
3
|
+
module Cmd
|
4
|
+
|
5
|
+
class Cmd
|
6
|
+
attr_reader :cmd
|
7
|
+
attr_reader :data
|
8
|
+
attr_reader :time
|
9
|
+
|
10
|
+
def initialize(cmd, data = {}, cmd_time = nil)
|
11
|
+
raise ArgumentError unless cmd.kind_of?(Symbol)
|
12
|
+
@cmd = cmd
|
13
|
+
@data = data
|
14
|
+
@time = if cmd_time then cmd_time else Time.now.to_i end
|
15
|
+
@comment = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def comment ; @comment || '' end
|
19
|
+
def comment=(str = nil) ; @comment = str end
|
20
|
+
|
21
|
+
def if_cmd(sym) ; if sym == cmd then yield data else seld end end
|
22
|
+
|
23
|
+
def as_json
|
24
|
+
h = { cmd: cmd, data: (data.as_json rescue data), time: time }
|
25
|
+
c = @comment
|
26
|
+
h[:comment] = c if c
|
27
|
+
h
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s ; as_json.to_json end
|
31
|
+
|
32
|
+
def with_comment(str = nil)
|
33
|
+
@comment = str
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.new_input(cmd, data = {}, cmd_time = nil)
|
38
|
+
inner = Cmd.new cmd, data, cmd_time
|
39
|
+
Cmd.new :input, inner, cmd_time
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.from_json(json)
|
43
|
+
Cmd.new json['cmd'].to_sym, json['data'], (json['time'] rescue nil)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class FileCmdPlan < Kit::InlineKeyRouter
|
48
|
+
attr_reader :path
|
49
|
+
attr_reader :mode
|
50
|
+
attr_reader :file
|
51
|
+
|
52
|
+
meth_spot :open
|
53
|
+
meth_spot :sync
|
54
|
+
meth_spot :close
|
55
|
+
meth_spot :input
|
56
|
+
|
57
|
+
signal_spot :open
|
58
|
+
signal_spot :close
|
59
|
+
signal_spot :sync
|
60
|
+
|
61
|
+
def data_map(name, data)
|
62
|
+
if data.is_a?(Cmd) then data else Cmd.new(data) end
|
63
|
+
end
|
64
|
+
|
65
|
+
def data_key(name, data) ; data.cmd end
|
66
|
+
def data_val(name, data) ; data.data end
|
67
|
+
|
68
|
+
def data_tag(name, key, val, tags_in)
|
69
|
+
tags_out = super
|
70
|
+
if name == :input
|
71
|
+
tags_out[:time] = val.time
|
72
|
+
tags_out[:comment] = val.comment
|
73
|
+
end
|
74
|
+
tags_out
|
75
|
+
end
|
76
|
+
|
77
|
+
def on_open(key, val)
|
78
|
+
if @file
|
79
|
+
signal_error ArgumentError.new("associated file already open")
|
80
|
+
else
|
81
|
+
@path = val[:path] if val[:path]
|
82
|
+
@mode = val[:mode] if val[:mode]
|
83
|
+
@mode ||= init_mode
|
84
|
+
@file = File.open @path, @mode
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def on_input(key, val)
|
89
|
+
exit << val if exit
|
90
|
+
end
|
91
|
+
|
92
|
+
def on_sync(key, val)
|
93
|
+
f = @file ; sync_file f if f
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_close(key, val)
|
97
|
+
if @file
|
98
|
+
begin
|
99
|
+
close_file(@file)
|
100
|
+
ensure
|
101
|
+
@file = nil
|
102
|
+
end
|
103
|
+
else
|
104
|
+
signal_error ArgumentError.new("associated file not open")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
|
110
|
+
def close_file(f)
|
111
|
+
begin
|
112
|
+
sync_file f
|
113
|
+
ensure
|
114
|
+
f.close
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def sync_file(f) ; end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Writer < FileCmdPlan
|
122
|
+
|
123
|
+
def init_mode ; 'w' end
|
124
|
+
|
125
|
+
def on_input(key, val)
|
126
|
+
signal_error ArgumentError.new("associated filed not open") unless file
|
127
|
+
cmd = val.cmd
|
128
|
+
raise ArgumentError, "invalid cmd" unless cmd.kind_of?(Symbol)
|
129
|
+
data = val.data
|
130
|
+
str = if data then data.to_json else '' end
|
131
|
+
len = str.length + 1
|
132
|
+
tim = val.time if val.time
|
133
|
+
tim = Time.now unless tim
|
134
|
+
tim = tim.to_i unless tim.kind_of?(Fixnum)
|
135
|
+
new_str = ''
|
136
|
+
str.each_line { |line| new_str << "... #{line}\n" }
|
137
|
+
str = nil
|
138
|
+
len = new_str.length
|
139
|
+
val = tags[:comment]
|
140
|
+
val.each_line { |line| file.write "\# #{line}\n" } if val
|
141
|
+
file.write "<<< ANDROMEDA START :#{cmd} TIME #{tim} LEN #{len} >>>\n"
|
142
|
+
file.write new_str
|
143
|
+
file.write "<<< ANDROMEDA END :#{cmd} >>>\n"
|
144
|
+
super key, val
|
145
|
+
end
|
146
|
+
|
147
|
+
protected
|
148
|
+
|
149
|
+
def sync_file(f)
|
150
|
+
f.sync
|
151
|
+
f.fsync rescue nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class Reader < Kit::FileReader
|
156
|
+
attr_reader :start_matcher, :end_matcher, :comment_matcher, :line_matcher
|
157
|
+
|
158
|
+
def initialize(config = {})
|
159
|
+
super config
|
160
|
+
@start_matcher = /<<< ANDROMEDA START :(\w+) TIME (\d+) LEN (\d+) >>>/
|
161
|
+
@end_matcher = /<<< ANDROMEDA END :(\w+) >>>/
|
162
|
+
@comment_matcher = /#(.*)/
|
163
|
+
# remember to change the offset for :line, too whenever you change this
|
164
|
+
@line_matcher = /... (.*)/
|
165
|
+
end
|
166
|
+
|
167
|
+
def match_line(state, line)
|
168
|
+
m = @start_matcher.match line
|
169
|
+
return yield :start, state, ({ cmd: m[1].to_sym, tim: m[2].to_i, len: m[3].to_i }) if m
|
170
|
+
m = @end_matcher.match line
|
171
|
+
return yield :end, state, m[1].to_sym if m
|
172
|
+
m = @comment_matcher.match line
|
173
|
+
return yield :comment, state, m[1] if m
|
174
|
+
m = @line_matcher.match line
|
175
|
+
return yield :line, state, m[1] if m
|
176
|
+
yield :garbage, state, line
|
177
|
+
end
|
178
|
+
|
179
|
+
def on_enter(key, val)
|
180
|
+
super key, val do |file|
|
181
|
+
fst = tags[:first]
|
182
|
+
lst = tags[:last]
|
183
|
+
|
184
|
+
state = { :comment => true, :start => true, :cont => true }
|
185
|
+
while (line = file.gets) && state[:cont]
|
186
|
+
line = line.chomp
|
187
|
+
match_line(state, line) do |token, state, parts|
|
188
|
+
signal_error ArgumentError.new("Skipping unexpected token #{token} in line '#{line}' (state: #{state})") unless state[token]
|
189
|
+
case token
|
190
|
+
when :comment
|
191
|
+
if state[:comment_str]
|
192
|
+
then state[:comment_str] << parts
|
193
|
+
else state[:comment_str] = parts end
|
194
|
+
when :start
|
195
|
+
state.delete :comment
|
196
|
+
state.delete :start
|
197
|
+
state[:line] = true
|
198
|
+
state[:end] = true
|
199
|
+
parts[:data] = ''
|
200
|
+
state[:len] = 0
|
201
|
+
state[:cur] = parts
|
202
|
+
parts[:comment] = state[:comment_str]
|
203
|
+
state.delete :comment_str
|
204
|
+
when :line
|
205
|
+
state[:len] += parts.length + 5
|
206
|
+
state[:cur][:data] << "#{parts}\n"
|
207
|
+
when :end
|
208
|
+
state.delete :line
|
209
|
+
state.delete :end
|
210
|
+
state[:start] = true
|
211
|
+
state[:comment] = true
|
212
|
+
cur = state[:cur]
|
213
|
+
data = cur[:data]
|
214
|
+
signal_error ArgumentError.new("Start (#{cur[:cmd]}) and end (#{parts})cmd mismatch") unless cur[:cmd] == parts
|
215
|
+
signal_error ArgumentError.new("Length mismatch (expected: #{cur[:len]}, found: #{state[:len]})") unless cur[:len] == state[:len]
|
216
|
+
exit << cur if exit
|
217
|
+
state[:cont] = false unless file.pos <= lst
|
218
|
+
else
|
219
|
+
signal_error ArgumentError.new("Garbage encountered (line: '#{line}')")
|
220
|
+
return
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
class Parser < Plan
|
230
|
+
|
231
|
+
def on_enter(key, val)
|
232
|
+
data = val[:data].chomp
|
233
|
+
data = JSON::parse(data)
|
234
|
+
cmd = Cmd.new val[:cmd], data, val[:time]
|
235
|
+
rem = val[:comment] rescue nil
|
236
|
+
cmd.with_comment rem if rem
|
237
|
+
exit << cmd if cmd
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|
File without changes
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Andromeda
|
2
|
+
|
3
|
+
class ::Object
|
4
|
+
|
5
|
+
def identical_copy
|
6
|
+
case clone_to_copy?
|
7
|
+
when false then self
|
8
|
+
when true then clone
|
9
|
+
else clone rescue self end
|
10
|
+
end
|
11
|
+
|
12
|
+
def clone_to_copy? ; cloneable? end
|
13
|
+
def cloneable? ; nil end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ::NilClass
|
17
|
+
def cloneable? ; false end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ::FalseClass
|
21
|
+
def cloneable? ; false end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ::FalseClass
|
25
|
+
def cloneable? ; false end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ::Numeric
|
29
|
+
def cloneable? ; false end
|
30
|
+
end
|
31
|
+
|
32
|
+
class ::Symbol
|
33
|
+
def cloneable? ; false end
|
34
|
+
end
|
35
|
+
|
36
|
+
class ::Thread
|
37
|
+
def cloneable? ; false end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ::Regexp
|
41
|
+
def cloneable? ; false end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Andromeda
|
2
|
+
|
3
|
+
class InfoMsg
|
4
|
+
|
5
|
+
def self.str(msg = '', details = {}, cause_ = nil)
|
6
|
+
details[:cause] = cause_ if cause_
|
7
|
+
(InfoMsg.new msg, details).to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :details
|
11
|
+
attr_reader :cause
|
12
|
+
attr_reader :msg
|
13
|
+
|
14
|
+
def initialize(msg = '', details = {})
|
15
|
+
@msg = msg
|
16
|
+
@details = details
|
17
|
+
@cause = details[:cause]
|
18
|
+
details.delete :cause if @cause
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
out = msg.dup
|
23
|
+
if details && details.length > 0
|
24
|
+
if cause
|
25
|
+
out << " (cause = #{cause}; details = \{"
|
26
|
+
else
|
27
|
+
out << ' (details = {'
|
28
|
+
end
|
29
|
+
details.each_pair { |k,v| out << " #{k}: #{v}" }
|
30
|
+
out << ' })'
|
31
|
+
else
|
32
|
+
out << " (cause = #{cause})" if cause
|
33
|
+
end
|
34
|
+
out << '.'
|
35
|
+
out
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class SendError < RuntimeError ; end
|
40
|
+
class ExecError < RuntimeError ; end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Andromeda
|
2
|
+
|
3
|
+
module Guides
|
4
|
+
|
5
|
+
class Guide
|
6
|
+
def track(spot, key, suggested_track = nil)
|
7
|
+
raise NoMethodError
|
8
|
+
end
|
9
|
+
|
10
|
+
def pack(track, was_suggested = false)
|
11
|
+
return plan.copy if was_suggested
|
12
|
+
if plan.frozen? then plan else plan.copy end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Track
|
17
|
+
def follow(*args, &thunk) ; thunk.call *args end
|
18
|
+
end
|
19
|
+
|
20
|
+
class LocalGuide < Guide
|
21
|
+
include Singleton
|
22
|
+
|
23
|
+
def track(spot, key, suggested_track = nil)
|
24
|
+
return suggested_track if suggested_track
|
25
|
+
LocalTrack.instance
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class LocalTrack < Track
|
30
|
+
include Singleton
|
31
|
+
end
|
32
|
+
|
33
|
+
class SpawnGuide < Guide
|
34
|
+
include Singleton
|
35
|
+
|
36
|
+
def track(spot, key, suggested_track = nil)
|
37
|
+
SpawnGuide.instance
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class SpawnTrack < Track
|
42
|
+
include Singleton
|
43
|
+
|
44
|
+
def follow(*args, &thunk)
|
45
|
+
Thread.new { || thunk.call *args }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|