oflow 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- include HasTasks
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 flow [Flow] Flow containing the Flow
17
+ # @param env [Env] Env containing the Flow
16
18
  # @param name [name] Flow base name
17
- # @param options [Hash] additional options for the Flow
18
- def initialize(flow, name, options)
19
- init_name(flow, name)
20
- init_tasks()
21
- init_links()
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
- # Add a Link from the edge of the Flow to a Task contained in the Flow.
25
- # @param label [Symbol|String] identifier for the Link
26
- # @param task_name [Symbol|String] _name base name of teh Task to link to
27
- # @param op [Symbol|String] operation to call when forwarding a request to the target Task
28
- def route(label, task_name, op)
29
- op = op.to_sym unless op.nil?
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
- # Receive a request which is redirected to a Linked target Task.
36
- # @param op [Symbol] identifies the link that points to the destination Task or Flow
37
- # @param box [Box] contents or data for the request
38
- def receive(op, box)
39
- box = box.receive(full_name, op)
40
- lnk = find_link(op)
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
- # Returns true if the Flow has a Link identified by the op.
46
- # @param op [Symbol] identifies the Link in question
47
- def has_input(op)
48
- !find_link(op).nil?
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