oflow 0.6.0 → 0.8.0
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.
- 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
|