oflow 0.6.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +23 -41
- data/lib/oflow.rb +2 -3
- data/lib/oflow/actor.rb +3 -0
- data/lib/oflow/actors/httpserver.rb +3 -1
- data/lib/oflow/actors/log.rb +3 -2
- data/lib/oflow/actors/persister.rb +29 -6
- data/lib/oflow/actors/timer.rb +29 -12
- data/lib/oflow/box.rb +2 -2
- data/lib/oflow/env.rb +221 -15
- data/lib/oflow/flow.rb +217 -37
- data/lib/oflow/graffle.rb +293 -0
- data/lib/oflow/haserrorhandler.rb +3 -22
- data/lib/oflow/haslog.rb +21 -15
- data/lib/oflow/inspector.rb +18 -17
- data/lib/oflow/link.rb +11 -6
- data/lib/oflow/task.rb +134 -22
- data/lib/oflow/test/actorwrap.rb +1 -1
- data/lib/oflow/version.rb +1 -1
- data/test/actors/balancer_test.rb +17 -12
- data/test/actors/httpserver_test.rb +11 -10
- data/test/actors/log_test.rb +3 -6
- data/test/actors/merger_test.rb +23 -18
- data/test/actors/persister_test.rb +6 -8
- data/test/actors/timer_test.rb +63 -35
- data/test/actorwrap_test.rb +2 -6
- data/test/all_tests.rb +3 -7
- data/test/box_test.rb +4 -10
- data/test/flow_basic_test.rb +24 -22
- data/test/flow_cfg_error_test.rb +17 -13
- data/test/flow_linked_test.rb +146 -0
- data/test/flow_log_test.rb +43 -29
- data/test/flow_rescue_test.rb +41 -27
- data/test/flow_tracker_test.rb +26 -30
- data/test/helper.rb +15 -0
- data/test/task_test.rb +3 -7
- data/test/tracker_test.rb +3 -11
- metadata +5 -7
- data/lib/oflow/haslinks.rb +0 -68
- data/lib/oflow/hasname.rb +0 -31
- data/lib/oflow/hastasks.rb +0 -214
- data/test/flow_nest_test.rb +0 -215
data/lib/oflow/flow.rb
CHANGED
@@ -5,47 +5,234 @@ module OFlow
|
|
5
5
|
# be thought of as a container for Tasks where the Flow keeps track of the
|
6
6
|
# Links between the Tasks.
|
7
7
|
class Flow
|
8
|
-
|
9
|
-
include HasLinks
|
10
|
-
include HasName
|
8
|
+
|
11
9
|
include HasErrorHandler
|
12
10
|
include HasLog
|
13
11
|
|
12
|
+
# The name.
|
13
|
+
attr_reader :name
|
14
|
+
attr_reader :env
|
15
|
+
|
14
16
|
# Create a new Flow.
|
15
|
-
# @param
|
17
|
+
# @param env [Env] Env containing the Flow
|
16
18
|
# @param name [name] Flow base name
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def initialize(env, name)
|
20
|
+
@name = name.to_sym
|
21
|
+
@tasks = {}
|
22
|
+
@prepared = false
|
23
|
+
@log = nil
|
24
|
+
@error_handler = nil
|
25
|
+
@env = env
|
26
|
+
end
|
27
|
+
|
28
|
+
# Similar to a full file path. The full_name described the containment of
|
29
|
+
# the named item.
|
30
|
+
# @return [String] full name of item
|
31
|
+
def full_name()
|
32
|
+
@name.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a log Task by looking for that Task in an attribute and then in
|
36
|
+
# the contained Tasks or Tasks in outer Flows.
|
37
|
+
# @return [Task] log Task.
|
38
|
+
def log()
|
39
|
+
return @log unless @log.nil?
|
40
|
+
lg = find_task(:log)
|
41
|
+
return lg unless lg.nil?
|
42
|
+
@env.log
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a error_handler Task by looking for that Task in an attribute and then in
|
46
|
+
# the contained Tasks or Tasks in outer Flows.
|
47
|
+
# @return [Task] error_handler Task.
|
48
|
+
def error_handler()
|
49
|
+
return @error_handler unless @error_handler.nil?
|
50
|
+
eh = find_task(:error)
|
51
|
+
return eh unless eh.nil?
|
52
|
+
@env.error_handler
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates a Task and yield to a block with the newly create Task. Used to
|
56
|
+
# configure Tasks.
|
57
|
+
# @param name [Symbol|String] base name for the Task
|
58
|
+
# @param actor_class [Class] Class to create an Actor instance of
|
59
|
+
# @param options [Hash] optional parameters
|
60
|
+
# @param block [Proc] block to yield to with the new Task instance
|
61
|
+
# @return [Task] new Task
|
62
|
+
def task(name, actor_class, options={}, &block)
|
63
|
+
has_state = options.has_key?(:state)
|
64
|
+
unless has_state
|
65
|
+
options = options.clone
|
66
|
+
options[:state] = Task::STOPPED
|
67
|
+
end
|
68
|
+
t = Task.new(self, name, actor_class, options)
|
69
|
+
@tasks[t.name] = t
|
70
|
+
yield(t) if block_given?
|
71
|
+
t
|
72
|
+
end
|
73
|
+
|
74
|
+
# Validates the container by verifying all links on a task have been set to
|
75
|
+
# a valid destination and that destination has been resolved.
|
76
|
+
# @raise [ValidateError] if there is an error in validation
|
77
|
+
def validate()
|
78
|
+
# collects errors and raises all errors at once if there are any
|
79
|
+
errors = _validation_errors()
|
80
|
+
raise ValidateError.new(errors) unless errors.empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns an Array of validation errors.
|
84
|
+
def _validation_errors()
|
85
|
+
errors = []
|
86
|
+
@tasks.each_value { |t| errors += t._validation_errors() }
|
87
|
+
errors
|
88
|
+
end
|
89
|
+
|
90
|
+
# Resolves all the Links on all the Tasks and Flows being managed as well as
|
91
|
+
# any Links in the instance itself.
|
92
|
+
def resolve_all_links()
|
93
|
+
@tasks.each_value { |t|
|
94
|
+
t.resolve_all_links()
|
95
|
+
}
|
96
|
+
@prepared = true
|
97
|
+
end
|
98
|
+
|
99
|
+
# Iterates over each Task and yields to the provided block with each Task.
|
100
|
+
# @param blk [Proc] Proc to call on each iteration
|
101
|
+
def each_task(&blk)
|
102
|
+
@tasks.each { |name,task| blk.yield(task) }
|
103
|
+
end
|
104
|
+
|
105
|
+
# Performs a recursive walk over all Tasks and yields to the provided block
|
106
|
+
# for each. Flows are followed recusively.
|
107
|
+
# @param tasks_only [true|false] indicates on Tasks and not Flows are yielded to
|
108
|
+
# @param blk [Proc] Proc to call on each iteration
|
109
|
+
def walk_tasks(tasks_only=true, &blk)
|
110
|
+
@tasks.each_value do |t|
|
111
|
+
if t.is_a?(Task)
|
112
|
+
blk.yield(t)
|
113
|
+
else
|
114
|
+
blk.yield(t) unless tasks_only
|
115
|
+
t.walk_tasks(tasks_only, &blk)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Locates and return a Task with the specified name.
|
121
|
+
# @param name [String] name of the Task
|
122
|
+
# @return [Task|nil] the Task with the name specified or nil
|
123
|
+
def find_task(name)
|
124
|
+
name = name.to_sym unless name.nil?
|
125
|
+
@tasks[name]
|
126
|
+
end
|
127
|
+
|
128
|
+
# Locates and return a Task with the specified full name.
|
129
|
+
# @param name [String] full name of the Task
|
130
|
+
# @return [Task|nil] the Task with the name specified or nil
|
131
|
+
def locate(name)
|
132
|
+
name = name[1..-1] if name.start_with?(':')
|
133
|
+
name = name[0..-2] if name.end_with?(':')
|
134
|
+
path = name.split(':')
|
135
|
+
_locate(path)
|
136
|
+
end
|
137
|
+
|
138
|
+
def _locate(path)
|
139
|
+
t = @tasks[path[0].to_sym]
|
140
|
+
return t if t.nil? || 1 == path.size
|
141
|
+
t._locate(path[1..-1])
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the number of active Tasks.
|
145
|
+
def task_count()
|
146
|
+
@tasks.size
|
22
147
|
end
|
23
148
|
|
24
|
-
#
|
25
|
-
# @
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
label = label.to_sym unless label.nil?
|
31
|
-
raise ConfigError.new("Link #{label} already exists.") unless find_link(label).nil?
|
32
|
-
@links[label] = Link.new(task_name.to_sym, op, true)
|
149
|
+
# Returns the sum of all the requests in all the Tasks's queues.
|
150
|
+
# @return [Fixnum] total number of items waiting to be processed
|
151
|
+
def queue_count()
|
152
|
+
cnt = 0
|
153
|
+
@tasks.each_value { |task| cnt += task.queue_count() }
|
154
|
+
cnt
|
33
155
|
end
|
34
156
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# @
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
raise LinkError.new(op) if lnk.nil? || lnk.target.nil?
|
42
|
-
lnk.target.receive(lnk.op, box)
|
157
|
+
# Returns true of one or more Tasks is either processing a request or has a
|
158
|
+
# request waiting to be processed on it's input queue.
|
159
|
+
# @return [true|false] the busy state across all Tasks
|
160
|
+
def busy?
|
161
|
+
@tasks.each_value { |task| return true if task.busy? }
|
162
|
+
false
|
43
163
|
end
|
44
164
|
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
165
|
+
# Calls the stop() method on all Tasks.
|
166
|
+
def stop()
|
167
|
+
@tasks.each_value { |task| task.stop() }
|
168
|
+
end
|
169
|
+
|
170
|
+
# Calls the step() method one Task that is stopped and has an item in the
|
171
|
+
# queue. The Tasks with the highest backed_up() value is selected.
|
172
|
+
def step()
|
173
|
+
max = 0.0
|
174
|
+
best = nil
|
175
|
+
walk_tasks() do |t|
|
176
|
+
if Task::STOPPED == t.state
|
177
|
+
bu = t.backed_up()
|
178
|
+
if max < bu
|
179
|
+
best = t
|
180
|
+
max = bu
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
best.step() unless best.nil?
|
185
|
+
best
|
186
|
+
end
|
187
|
+
|
188
|
+
# Calls the start() method on all Tasks.
|
189
|
+
def start()
|
190
|
+
raise ValidateError.new("#{full_name} not validated.") unless @prepared
|
191
|
+
@tasks.each_value { |task| task.start() }
|
192
|
+
end
|
193
|
+
|
194
|
+
# Wakes up all the Tasks in the Flow.
|
195
|
+
def wakeup()
|
196
|
+
@tasks.each_value { |t| t.wakeup() }
|
197
|
+
end
|
198
|
+
|
199
|
+
# Wakes up all the Tasks in the Flow and waits for the system to become idle
|
200
|
+
# before returning.
|
201
|
+
def flush()
|
202
|
+
wakeup()
|
203
|
+
@tasks.each_value { |t| t.flush() }
|
204
|
+
while busy?
|
205
|
+
sleep(0.2)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Sets the state of all Tasks recursively. This should not be called
|
210
|
+
# directly.
|
211
|
+
def state=(s)
|
212
|
+
@tasks.each_value do |task|
|
213
|
+
task.state = s
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Shuts down all Tasks.
|
218
|
+
# @param flush_first [true|false] flag indicating shutdown should occur after the system becomes idle
|
219
|
+
def shutdown(flush_first=false)
|
220
|
+
# block all tasks first so threads can empty queues
|
221
|
+
@tasks.each_value do |task|
|
222
|
+
task.state = Task::BLOCKED
|
223
|
+
end
|
224
|
+
# shutdown and wait for queues to empty if necessary
|
225
|
+
@tasks.each_value do |task|
|
226
|
+
task.shutdown(flush_first)
|
227
|
+
end
|
228
|
+
@tasks = {}
|
229
|
+
end
|
230
|
+
|
231
|
+
# Clears out all Tasks and Flows and resets the object back to a empty state.
|
232
|
+
def clear()
|
233
|
+
shutdown()
|
234
|
+
@tasks = {}
|
235
|
+
_clear()
|
49
236
|
end
|
50
237
|
|
51
238
|
# Returns a String describing the Flow.
|
@@ -57,13 +244,6 @@ module OFlow
|
|
57
244
|
@tasks.each_value { |t|
|
58
245
|
lines << t.describe(detail, indent + 2)
|
59
246
|
}
|
60
|
-
@links.each { |local,link|
|
61
|
-
if link.ingress
|
62
|
-
lines << " #{i}#{local} * #{link.target_name}:#{link.op}"
|
63
|
-
else
|
64
|
-
lines << " #{i}#{local} => #{link.target_name}:#{link.op}"
|
65
|
-
end
|
66
|
-
}
|
67
247
|
lines << i + "}"
|
68
248
|
lines.join("\n")
|
69
249
|
end
|
@@ -0,0 +1,293 @@
|
|
1
|
+
|
2
|
+
require 'ox'
|
3
|
+
|
4
|
+
module OFlow
|
5
|
+
|
6
|
+
class Graffle
|
7
|
+
attr_accessor :name
|
8
|
+
attr_accessor :line_info
|
9
|
+
attr_accessor :task_info
|
10
|
+
|
11
|
+
def self.load(env, filename)
|
12
|
+
doc = Ox.load_file(filename, mode: :generic)
|
13
|
+
g = Graffle.new(env, filename, doc)
|
14
|
+
env.debug(g.to_s()) if Logger::Severity::DEBUG >= ::OFlow::Env.log_level
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(env, filename, doc)
|
18
|
+
@line_info = { } # key is id
|
19
|
+
@task_info = { } # key is id
|
20
|
+
@name = File.basename(filename, '.graffle')
|
21
|
+
|
22
|
+
nodes = doc.locate('plist/dict')[0].nodes
|
23
|
+
nodes = Graffle.get_key_value(nodes, 'GraphicsList')
|
24
|
+
|
25
|
+
raise ConfigError.new("Empty flow.") if nodes.nil?
|
26
|
+
|
27
|
+
nodes.each do |node|
|
28
|
+
load_node(node)
|
29
|
+
end
|
30
|
+
|
31
|
+
# set label in lines
|
32
|
+
task_info.each do |id,ti|
|
33
|
+
next if ti.line_id.nil?
|
34
|
+
if !(li = line_info[ti.line_id]).nil?
|
35
|
+
li.label, li.op = ti.first_option
|
36
|
+
end
|
37
|
+
end
|
38
|
+
task_info.each do |_, ti|
|
39
|
+
if ti.name.nil? && !(n = ti.options[:flow]).nil?
|
40
|
+
@name = n.strip
|
41
|
+
end
|
42
|
+
end
|
43
|
+
env.flow(@name.to_sym) { |f|
|
44
|
+
task_info.each_value { |ti|
|
45
|
+
next unless ti.line_id.nil?
|
46
|
+
next if ti.name.nil?
|
47
|
+
c = ti.get_class()
|
48
|
+
next if c.nil?
|
49
|
+
f.task(ti.name, c, ti.options) { |t|
|
50
|
+
t.bounds = ti.bounds
|
51
|
+
t.color = ti.color
|
52
|
+
t.shape = ti.shape
|
53
|
+
line_info.each_value { |li|
|
54
|
+
next unless li.tail == ti.id
|
55
|
+
target_info = task_info[li.head]
|
56
|
+
if target_info.nil?
|
57
|
+
flow_name, task_name, op = li.op.split('/')
|
58
|
+
t.link(li.label, task_name, op, flow_name)
|
59
|
+
else
|
60
|
+
t.link(li.label, target_info.name, li.op)
|
61
|
+
end
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_node(node)
|
69
|
+
nodes = node.nodes
|
70
|
+
case Graffle.get_key_value(nodes, 'Class')
|
71
|
+
when 'LineGraphic'
|
72
|
+
line = LineInfo.new(nodes)
|
73
|
+
@line_info[line.id] = line
|
74
|
+
when 'ShapedGraphic'
|
75
|
+
task = TaskInfo.new(nodes)
|
76
|
+
@task_info[task.id] = task unless task.nil?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s()
|
81
|
+
s = "Graffle{\n name: #{@name}\n tasks{\n"
|
82
|
+
task_info.each { |_,ti| s += " #{ti}\n" }
|
83
|
+
s += " }\n lines{\n"
|
84
|
+
line_info.each { |_,li| s += " #{li}\n" }
|
85
|
+
s += " }\n}\n"
|
86
|
+
s
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.get_key_value(nodes, key)
|
90
|
+
return if nodes.nil?
|
91
|
+
nodes.each_with_index do |node,i|
|
92
|
+
next unless 'key' == node.name && key == node.text
|
93
|
+
n = nodes[i + 1]
|
94
|
+
if 'dict' == n.name || 'array' == n.name
|
95
|
+
return n.nodes
|
96
|
+
else
|
97
|
+
return n.text
|
98
|
+
end
|
99
|
+
end
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.get_text(nodes)
|
104
|
+
return nil if nodes.nil?
|
105
|
+
strip_rtf(get_key_value(nodes, 'Text'))
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.strip_rtf(rtf)
|
109
|
+
return rtf unless rtf.start_with?('{\rtf')
|
110
|
+
depth = 0
|
111
|
+
txt = ''
|
112
|
+
ctrl = nil
|
113
|
+
str = nil
|
114
|
+
rtf.each_char do |c|
|
115
|
+
case c
|
116
|
+
when ' '
|
117
|
+
if !str.nil?
|
118
|
+
str << ' ' unless 1 < depth
|
119
|
+
elsif !ctrl.nil?
|
120
|
+
txt << rtf_ctrl_char(ctrl) unless 1 < depth
|
121
|
+
ctrl = nil
|
122
|
+
str = ''
|
123
|
+
else
|
124
|
+
str = ''
|
125
|
+
end
|
126
|
+
when "\n", "\r"
|
127
|
+
if !str.nil? # should not happen but...
|
128
|
+
str << "\n" unless 1 < depth
|
129
|
+
elsif !ctrl.nil?
|
130
|
+
txt << rtf_ctrl_char(ctrl) unless 1 < depth
|
131
|
+
ctrl = nil
|
132
|
+
end
|
133
|
+
when '\\'
|
134
|
+
if !str.nil?
|
135
|
+
txt << str unless 1 < depth
|
136
|
+
str = nil
|
137
|
+
elsif !ctrl.nil?
|
138
|
+
txt << rtf_ctrl_char(ctrl) unless 1 < depth
|
139
|
+
end
|
140
|
+
ctrl = ''
|
141
|
+
when '{'
|
142
|
+
if !ctrl.nil?
|
143
|
+
txt << rtf_ctrl_char(ctrl) unless 1 < depth
|
144
|
+
ctrl = nil
|
145
|
+
end
|
146
|
+
depth += 1
|
147
|
+
when '}'
|
148
|
+
if !ctrl.nil?
|
149
|
+
txt << rtf_ctrl_char(ctrl) unless 1 < depth
|
150
|
+
ctrl = nil
|
151
|
+
end
|
152
|
+
depth -= 1
|
153
|
+
else
|
154
|
+
if !ctrl.nil?
|
155
|
+
ctrl << c
|
156
|
+
elsif 1 < depth
|
157
|
+
# ignore
|
158
|
+
else
|
159
|
+
str = '' if str.nil?
|
160
|
+
str << c
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
txt << str unless str.nil?
|
165
|
+
txt
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.rtf_ctrl_char(ctrl)
|
169
|
+
c = ''
|
170
|
+
case ctrl
|
171
|
+
when '', 'line'
|
172
|
+
c = "\n"
|
173
|
+
when 'tab'
|
174
|
+
c = "\t"
|
175
|
+
else
|
176
|
+
if "'" == ctrl[0]
|
177
|
+
c = ctrl[1..3].hex().chr
|
178
|
+
end
|
179
|
+
end
|
180
|
+
c
|
181
|
+
end
|
182
|
+
|
183
|
+
class Element
|
184
|
+
attr_accessor :id
|
185
|
+
|
186
|
+
def initialize(nodes)
|
187
|
+
return if nodes.nil?
|
188
|
+
@id = Graffle.get_key_value(nodes, 'ID')
|
189
|
+
end
|
190
|
+
|
191
|
+
end # Element
|
192
|
+
|
193
|
+
class LineInfo < Element
|
194
|
+
attr_accessor :tail
|
195
|
+
attr_accessor :head
|
196
|
+
attr_accessor :label
|
197
|
+
attr_accessor :op
|
198
|
+
|
199
|
+
def initialize(nodes)
|
200
|
+
super
|
201
|
+
@tail = Graffle.get_key_value(Graffle.get_key_value(nodes, 'Tail'), 'ID')
|
202
|
+
@head = Graffle.get_key_value(Graffle.get_key_value(nodes, 'Head'), 'ID')
|
203
|
+
end
|
204
|
+
|
205
|
+
def to_s()
|
206
|
+
"LineInfo{id:#{@id}, tail:#{@tail}, head:#{@head}, label: #{label}:#{op}}"
|
207
|
+
end
|
208
|
+
end # LineInfo
|
209
|
+
|
210
|
+
class TaskInfo < Element
|
211
|
+
attr_accessor :line_id
|
212
|
+
attr_accessor :name
|
213
|
+
attr_accessor :options
|
214
|
+
attr_accessor :color
|
215
|
+
attr_accessor :shape
|
216
|
+
attr_accessor :bounds
|
217
|
+
|
218
|
+
def initialize(nodes)
|
219
|
+
super
|
220
|
+
if (ln = Graffle.get_key_value(nodes, 'Line')).nil?
|
221
|
+
@line_id = nil
|
222
|
+
else
|
223
|
+
@line_id = Graffle.get_key_value(ln, 'ID')
|
224
|
+
end
|
225
|
+
@shape = Graffle.get_key_value(nodes, 'Shape')
|
226
|
+
color_node = Graffle.get_key_value(Graffle.get_key_value(Graffle.get_key_value(nodes, 'Style'), 'fill'), 'Color')
|
227
|
+
if color_node.nil?
|
228
|
+
@color = nil
|
229
|
+
else
|
230
|
+
@color = [Graffle.get_key_value(color_node, 'r').to_f,
|
231
|
+
Graffle.get_key_value(color_node, 'g').to_f,
|
232
|
+
Graffle.get_key_value(color_node, 'b').to_f]
|
233
|
+
end
|
234
|
+
unless (@bounds = Graffle.get_key_value(nodes, 'Bounds')).nil?
|
235
|
+
@bounds = @bounds.delete('{}').split(',')
|
236
|
+
@bounds.map! { |s| s.to_i }
|
237
|
+
end
|
238
|
+
@options = { }
|
239
|
+
text = Graffle.get_text(Graffle.get_key_value(nodes, 'Text'))
|
240
|
+
unless text.nil?
|
241
|
+
text.split("\n").each do |line|
|
242
|
+
pair = line.split(':', 2)
|
243
|
+
if 1 == pair.length
|
244
|
+
@name = pair[0]
|
245
|
+
else
|
246
|
+
k = pair[0]
|
247
|
+
if 0 == k.length
|
248
|
+
k = nil
|
249
|
+
else
|
250
|
+
k = k.to_sym
|
251
|
+
end
|
252
|
+
@options[k] = pair[1]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def first_option()
|
259
|
+
target = nil
|
260
|
+
op = nil
|
261
|
+
if name.nil?
|
262
|
+
options.each { |k,v| target, op = k, v; break }
|
263
|
+
else
|
264
|
+
target = name
|
265
|
+
end
|
266
|
+
[target, op]
|
267
|
+
end
|
268
|
+
|
269
|
+
def get_class()
|
270
|
+
return nil if options.nil?
|
271
|
+
return nil if (s = options[:class]).nil?
|
272
|
+
s.strip!
|
273
|
+
c = nil
|
274
|
+
begin
|
275
|
+
# TBD search all modules and classes for a match that is also an Actor
|
276
|
+
|
277
|
+
# Assume the environment is safe since it is being used to create
|
278
|
+
# processes from user provided code anyway.
|
279
|
+
c = Object.class_eval(s)
|
280
|
+
rescue Exception
|
281
|
+
c = ::OFlow::Actors.module_eval(s)
|
282
|
+
end
|
283
|
+
c
|
284
|
+
end
|
285
|
+
|
286
|
+
def to_s()
|
287
|
+
"TaskInfo{id:#{@id}, line_id:#{line_id}, name: #{name}, options: #{options}, bounds: #{@bounds}, shape: #{@shape}, color: #{@color}}"
|
288
|
+
end
|
289
|
+
|
290
|
+
end # TaskInfo
|
291
|
+
|
292
|
+
end # Graffle
|
293
|
+
end # OFlow
|