xpflow 0.1b
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/bin/xpflow +96 -0
- data/lib/colorado.rb +198 -0
- data/lib/json/add/core.rb +243 -0
- data/lib/json/add/rails.rb +8 -0
- data/lib/json/common.rb +423 -0
- data/lib/json/editor.rb +1369 -0
- data/lib/json/ext.rb +28 -0
- data/lib/json/pure/generator.rb +442 -0
- data/lib/json/pure/parser.rb +320 -0
- data/lib/json/pure.rb +15 -0
- data/lib/json/version.rb +8 -0
- data/lib/json.rb +62 -0
- data/lib/mime/types.rb +881 -0
- data/lib/mime-types.rb +3 -0
- data/lib/restclient/abstract_response.rb +106 -0
- data/lib/restclient/exceptions.rb +193 -0
- data/lib/restclient/net_http_ext.rb +55 -0
- data/lib/restclient/payload.rb +235 -0
- data/lib/restclient/raw_response.rb +34 -0
- data/lib/restclient/request.rb +316 -0
- data/lib/restclient/resource.rb +169 -0
- data/lib/restclient/response.rb +24 -0
- data/lib/restclient.rb +174 -0
- data/lib/xpflow/bash.rb +341 -0
- data/lib/xpflow/bundle.rb +113 -0
- data/lib/xpflow/cmdline.rb +249 -0
- data/lib/xpflow/collection.rb +122 -0
- data/lib/xpflow/concurrency.rb +79 -0
- data/lib/xpflow/data.rb +393 -0
- data/lib/xpflow/dsl.rb +816 -0
- data/lib/xpflow/engine.rb +574 -0
- data/lib/xpflow/ensemble.rb +135 -0
- data/lib/xpflow/events.rb +56 -0
- data/lib/xpflow/experiment.rb +65 -0
- data/lib/xpflow/exts/facter.rb +30 -0
- data/lib/xpflow/exts/g5k.rb +931 -0
- data/lib/xpflow/exts/g5k_use.rb +50 -0
- data/lib/xpflow/exts/gui.rb +140 -0
- data/lib/xpflow/exts/model.rb +155 -0
- data/lib/xpflow/graph.rb +1603 -0
- data/lib/xpflow/graph_xpflow.rb +251 -0
- data/lib/xpflow/import.rb +196 -0
- data/lib/xpflow/library.rb +349 -0
- data/lib/xpflow/logging.rb +153 -0
- data/lib/xpflow/manager.rb +147 -0
- data/lib/xpflow/nodes.rb +1250 -0
- data/lib/xpflow/runs.rb +773 -0
- data/lib/xpflow/runtime.rb +125 -0
- data/lib/xpflow/scope.rb +168 -0
- data/lib/xpflow/ssh.rb +186 -0
- data/lib/xpflow/stat.rb +50 -0
- data/lib/xpflow/stdlib.rb +381 -0
- data/lib/xpflow/structs.rb +369 -0
- data/lib/xpflow/taktuk.rb +193 -0
- data/lib/xpflow/templates/ssh-config.basic +14 -0
- data/lib/xpflow/templates/ssh-config.inria +18 -0
- data/lib/xpflow/templates/ssh-config.proxy +13 -0
- data/lib/xpflow/templates/taktuk +6590 -0
- data/lib/xpflow/templates/utils/batch +4 -0
- data/lib/xpflow/templates/utils/bootstrap +12 -0
- data/lib/xpflow/templates/utils/hostname +3 -0
- data/lib/xpflow/templates/utils/ping +3 -0
- data/lib/xpflow/templates/utils/rsync +12 -0
- data/lib/xpflow/templates/utils/scp +17 -0
- data/lib/xpflow/templates/utils/scp_many +8 -0
- data/lib/xpflow/templates/utils/ssh +3 -0
- data/lib/xpflow/templates/utils/ssh-interactive +4 -0
- data/lib/xpflow/templates/utils/taktuk +19 -0
- data/lib/xpflow/threads.rb +187 -0
- data/lib/xpflow/utils.rb +569 -0
- data/lib/xpflow/visual.rb +230 -0
- data/lib/xpflow/with_g5k.rb +7 -0
- data/lib/xpflow.rb +349 -0
- metadata +135 -0
data/lib/xpflow/graph.rb
ADDED
@@ -0,0 +1,1603 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require('cairo') # this is to hide it from bundle.rb
|
4
|
+
$check_cairo = proc {}
|
5
|
+
rescue LoadError
|
6
|
+
$check_cairo = proc { raise "Please install cairo for Ruby." }
|
7
|
+
end
|
8
|
+
|
9
|
+
module Graphing
|
10
|
+
|
11
|
+
class LatexGrapher
|
12
|
+
|
13
|
+
def initialize(flow, opts = {})
|
14
|
+
@flow = flow
|
15
|
+
@opts = opts
|
16
|
+
@indent = 0
|
17
|
+
@text = [ "% flow-latex workflow" ]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.indent_arrays(array)
|
21
|
+
lines = []
|
22
|
+
array.each do |x|
|
23
|
+
if x.is_a?(String)
|
24
|
+
lines.push(x)
|
25
|
+
elsif x.is_a?(Array)
|
26
|
+
x = indent_arrays(x)
|
27
|
+
arr = x.map { |it| " " + it }
|
28
|
+
lines += arr
|
29
|
+
elsif x.nil?
|
30
|
+
# nop
|
31
|
+
else
|
32
|
+
raise "What is #{x.class}?"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return lines
|
36
|
+
end
|
37
|
+
|
38
|
+
def draw()
|
39
|
+
lines = @flow.latex()
|
40
|
+
lines = LatexGrapher.indent_arrays(lines)
|
41
|
+
# puts lines
|
42
|
+
return lines.join("\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class TikzGrapher
|
50
|
+
|
51
|
+
attr_reader :nodes
|
52
|
+
attr_reader :coords
|
53
|
+
attr_reader :boxes
|
54
|
+
# and also paths
|
55
|
+
|
56
|
+
def initialize(flow, opts = {})
|
57
|
+
@flow = flow
|
58
|
+
@nodes = {}
|
59
|
+
@coords = {}
|
60
|
+
@links = Hash.new { |h, k| h[k] = [] }
|
61
|
+
@lines = []
|
62
|
+
@styles = {}
|
63
|
+
@boxes = []
|
64
|
+
@joins = []
|
65
|
+
@count = 0
|
66
|
+
@opts = opts
|
67
|
+
@matrix = [ Size.new(0, 0) ]
|
68
|
+
end
|
69
|
+
|
70
|
+
def name
|
71
|
+
"tikz"
|
72
|
+
end
|
73
|
+
|
74
|
+
def transform(p)
|
75
|
+
# transform a given point
|
76
|
+
return p + @matrix.last
|
77
|
+
end
|
78
|
+
|
79
|
+
def tr(p)
|
80
|
+
return transform(p)
|
81
|
+
end
|
82
|
+
|
83
|
+
def push_origin(x, y)
|
84
|
+
# pushes transformation stack and puts origin in p
|
85
|
+
p = transform(Size.new(x, y))
|
86
|
+
begin
|
87
|
+
@matrix.push(p)
|
88
|
+
yield
|
89
|
+
ensure
|
90
|
+
@matrix.pop
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def option(x)
|
96
|
+
return @opts[x] == true
|
97
|
+
end
|
98
|
+
|
99
|
+
def final?(label)
|
100
|
+
raise if not @nodes.key?(label) and not @coords.key?(label)
|
101
|
+
return @nodes.key?(label)
|
102
|
+
end
|
103
|
+
|
104
|
+
def check_links
|
105
|
+
@links.each do |s, ends|
|
106
|
+
raise if !final?(s) and ends.length > 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_position(label)
|
111
|
+
return @coords[label] if @coords.key?(label)
|
112
|
+
return @nodes[label][:pos] if @nodes.key?(label)
|
113
|
+
raise "Unknown label '#{label}'"
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_path(l1, l2)
|
117
|
+
# gets path l1 -> l2 -> ... -> (final node)
|
118
|
+
path = [ l1, l2 ]
|
119
|
+
while !final?(path.last) do
|
120
|
+
succ = @links[path.last]
|
121
|
+
raise if succ.length > 1
|
122
|
+
path.push(succ.first)
|
123
|
+
raise if path.length > 1000 # in case there is a bug ...
|
124
|
+
end
|
125
|
+
# TODO: remove repetitions on the path?
|
126
|
+
return path
|
127
|
+
end
|
128
|
+
|
129
|
+
def paths
|
130
|
+
check_links() # check if links are ok
|
131
|
+
list = []
|
132
|
+
@links.each do |s, ends|
|
133
|
+
ends.each do |e|
|
134
|
+
path = get_path(s, e)
|
135
|
+
list.push(path)
|
136
|
+
end if final?(s)
|
137
|
+
end
|
138
|
+
return list
|
139
|
+
end
|
140
|
+
|
141
|
+
def text_measurer
|
142
|
+
# by default we don't have it
|
143
|
+
return nil
|
144
|
+
end
|
145
|
+
|
146
|
+
def draw(box = nil)
|
147
|
+
Thread.current[:text_measure] = text_measurer() # TODO: ugly hack!
|
148
|
+
box = @flow.size if box.nil?
|
149
|
+
@flow.tikz(self, Size.ZERO, box)
|
150
|
+
|
151
|
+
els = {
|
152
|
+
:nodes => @nodes,
|
153
|
+
:paths => paths,
|
154
|
+
:coords => @coords,
|
155
|
+
:boxes => @boxes,
|
156
|
+
:positions => {},
|
157
|
+
:lines => @lines,
|
158
|
+
:joins => @joins
|
159
|
+
}
|
160
|
+
|
161
|
+
els[:nodes].each { |label, n| els[:positions][label] = n[:pos] }
|
162
|
+
els[:coords].each { |label, pos| els[:positions][label] = pos }
|
163
|
+
|
164
|
+
return draw_elements(els, box)
|
165
|
+
end
|
166
|
+
|
167
|
+
def draw_elements(els, box)
|
168
|
+
|
169
|
+
lines = []
|
170
|
+
lines += [ "", "% Special curves", "" ]
|
171
|
+
|
172
|
+
lines += [ "", "% Boxes", "" ]
|
173
|
+
els[:boxes].each do |b|
|
174
|
+
pos, box, opts = b[:pos], b[:box], b[:opts]
|
175
|
+
style = "dashed"
|
176
|
+
if opts[:style]
|
177
|
+
style = "#{style},#{opts[:style]}"
|
178
|
+
end
|
179
|
+
lines.push("\\draw[#{style}] #{pos} rectangle #{pos + box};")
|
180
|
+
end
|
181
|
+
|
182
|
+
lines += [ "", "% Intermediate nodes", "" ]
|
183
|
+
els[:coords].each do |label, pos|
|
184
|
+
lines.push("\\coordinate (#{label}) at #{pos};")
|
185
|
+
end
|
186
|
+
|
187
|
+
els[:lines].each do |l|
|
188
|
+
coords, opts = l
|
189
|
+
type = opts[:type]
|
190
|
+
if type == :bezier
|
191
|
+
raise "Wrong number of points (should be = 1 mod 3)" if coords.length % 3 != 1
|
192
|
+
# strictly speaking tikz has no beziers, but it will suffice
|
193
|
+
lines.push("\\draw[dashed,gray] #{coords.first} % total #{coords.length} points")
|
194
|
+
(coords.length / 3).times do |i|
|
195
|
+
a, b, c = coords[(i*3 + 1)...(i*3 + 4)]
|
196
|
+
lines.push(" .. controls #{a} and #{b} .. #{c}")
|
197
|
+
end
|
198
|
+
lines[-1] = lines[-1] + ";"
|
199
|
+
elsif type == :solid
|
200
|
+
path = coords.map(&:to_s).join(" -- ")
|
201
|
+
lines.push("\\draw[solid,#{opts[:style]}] #{path};")
|
202
|
+
else
|
203
|
+
raise "Unsupported curve of type '#{type}'"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
lines += [ "% Final nodes", "" ]
|
208
|
+
els[:nodes].each do |label, n|
|
209
|
+
style, pos, name = n[:style], n[:pos], n[:name]
|
210
|
+
anchor = n[:anchor]
|
211
|
+
# TODO: support anchor
|
212
|
+
# puts n.inspect
|
213
|
+
name = name.gsub('_', '\_')
|
214
|
+
|
215
|
+
style = "" if style == "text"
|
216
|
+
lines.push("\\node[#{style},fill=white] (#{label}) at #{pos} {#{name}};")
|
217
|
+
end
|
218
|
+
|
219
|
+
lines += [ "", "% Links between final nodes", "" ]
|
220
|
+
els[:paths].each do |p|
|
221
|
+
p = p.map { |x| "(#{x})" }.join(" to ")
|
222
|
+
lines.push("\\draw[sequence] #{p};")
|
223
|
+
end
|
224
|
+
|
225
|
+
lines += [ "", "% Joins", "" ]
|
226
|
+
els[:joins].each do |pts, type|
|
227
|
+
s = pts.map { |x| "(#{x})" }.join(" to ")
|
228
|
+
style = case type
|
229
|
+
when :plain
|
230
|
+
""
|
231
|
+
when :arrow
|
232
|
+
"->"
|
233
|
+
when :arrow_dashed
|
234
|
+
"dashed,->"
|
235
|
+
else
|
236
|
+
raise "Unknown type '#{type}'"
|
237
|
+
end
|
238
|
+
lines.push("\\draw[#{style}] #{s};")
|
239
|
+
end
|
240
|
+
|
241
|
+
lines.push("")
|
242
|
+
return lines.join("\n")
|
243
|
+
end
|
244
|
+
|
245
|
+
def get_label
|
246
|
+
@count += 1
|
247
|
+
return "node-#{@count}"
|
248
|
+
end
|
249
|
+
|
250
|
+
def add_task(name, pos, style = 'task', anchor = nil, size = nil, label = nil)
|
251
|
+
label = get_label() if label.nil?
|
252
|
+
@nodes[label] = { :name => name, :pos => tr(pos), :style => style,
|
253
|
+
:anchor => anchor, :size => size }
|
254
|
+
return label
|
255
|
+
end
|
256
|
+
|
257
|
+
def add_text(name, pos, anchor = "cm", size = 1.0)
|
258
|
+
return add_task(name, pos, 'text', anchor, size)
|
259
|
+
end
|
260
|
+
|
261
|
+
def add_gateway(pos, text); add_task(text, pos, 'gateway'); end
|
262
|
+
def add_start(pos); add_task('', pos, 'start'); end
|
263
|
+
def add_finish(pos); add_task('', pos, 'finish'); end
|
264
|
+
|
265
|
+
def add_coord(pos)
|
266
|
+
label = get_label()
|
267
|
+
@coords[label] = tr(pos)
|
268
|
+
return label
|
269
|
+
end
|
270
|
+
|
271
|
+
def add_box(pos, box, force = false, opts = {})
|
272
|
+
@boxes.push({ :pos => tr(pos), :box => box, :opts => opts }) \
|
273
|
+
if force or option(:boxes) or option(:debug)
|
274
|
+
end
|
275
|
+
|
276
|
+
def add_link(*labels)
|
277
|
+
(1...labels.length).each do |i|
|
278
|
+
@links[labels[i - 1]].push(labels[i])
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def add_join(points, type = :plain)
|
283
|
+
points = points.map do |p|
|
284
|
+
p = self.add_coord(p) if p.is_a?(Size)
|
285
|
+
p
|
286
|
+
end
|
287
|
+
@joins.push([ points, type ])
|
288
|
+
end
|
289
|
+
|
290
|
+
def add_line(path, opts = { :style => :solid })
|
291
|
+
@lines.push([ path, opts ])
|
292
|
+
end
|
293
|
+
|
294
|
+
def label_pos(name)
|
295
|
+
return @nodes[name][:pos]
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
class CairoGrapher < TikzGrapher
|
301
|
+
|
302
|
+
WHITE = [ 1, 1, 1 ]
|
303
|
+
BLACK = [ 0, 0, 0 ]
|
304
|
+
LGRAY = [ 0.8, 0.8, 0.8 ]
|
305
|
+
RED = [ 1, 0, 0]
|
306
|
+
|
307
|
+
def initialize(flow, writer, opts = {})
|
308
|
+
super(flow, opts)
|
309
|
+
@writer = writer
|
310
|
+
@ctx = writer.context
|
311
|
+
@dims = writer.size
|
312
|
+
@font_size = 0.35
|
313
|
+
end
|
314
|
+
|
315
|
+
def name
|
316
|
+
"cairo"
|
317
|
+
end
|
318
|
+
|
319
|
+
# rescales Cairo canvas so that it fits the workflow nicely
|
320
|
+
|
321
|
+
def rescale(box)
|
322
|
+
cw, ch = @dims.map(&:to_f)
|
323
|
+
factor = [ cw / box.width, ch / box.height ].min
|
324
|
+
w, h = factor * box.width, factor * box.height
|
325
|
+
@ctx.translate( (cw - w) / 2.0, (ch - h) / 2.0)
|
326
|
+
@ctx.scale(factor, factor)
|
327
|
+
# TODO
|
328
|
+
end
|
329
|
+
|
330
|
+
def background
|
331
|
+
set_white
|
332
|
+
@ctx.rectangle(0, 0, *@dims)
|
333
|
+
@ctx.fill
|
334
|
+
end
|
335
|
+
|
336
|
+
def bbox(box)
|
337
|
+
@ctx.set_line_width(0.005)
|
338
|
+
@ctx.set_dash(0.04)
|
339
|
+
set_black
|
340
|
+
@ctx.rectangle(0, 0, box.width, box.height)
|
341
|
+
@ctx.stroke
|
342
|
+
end
|
343
|
+
|
344
|
+
def set_black
|
345
|
+
@ctx.set_source_rgb(*BLACK)
|
346
|
+
end
|
347
|
+
|
348
|
+
def set_white
|
349
|
+
@ctx.set_source_rgb(*WHITE)
|
350
|
+
end
|
351
|
+
|
352
|
+
def fill_and_stroke(fill = WHITE, stroke = BLACK)
|
353
|
+
unless fill.nil?
|
354
|
+
@ctx.set_source_rgb(*fill)
|
355
|
+
@ctx.fill_preserve
|
356
|
+
end
|
357
|
+
@ctx.set_source_rgb(*stroke)
|
358
|
+
@ctx.stroke
|
359
|
+
end
|
360
|
+
|
361
|
+
def find_pos(pos, extents, anchor)
|
362
|
+
# finds a position according to the extents and anchor
|
363
|
+
|
364
|
+
x_shifts = {
|
365
|
+
'l' => 0.0 - extents.x_bearing,
|
366
|
+
'c' => -extents.width * 0.5 - extents.x_bearing ,
|
367
|
+
'r' => -extents.width - extents.x_bearing
|
368
|
+
}
|
369
|
+
|
370
|
+
y_shifts = {
|
371
|
+
't' => -extents.y_bearing,
|
372
|
+
'm' => -extents.y_bearing - extents.height * 0.5,
|
373
|
+
'b' => -extents.y_bearing - extents.height,
|
374
|
+
'v' => 0.0
|
375
|
+
}
|
376
|
+
|
377
|
+
dy = y_shifts.select { |k, v| anchor.include?(k) }
|
378
|
+
dy = dy.empty? ? y_shifts['m'] : dy.values.first
|
379
|
+
|
380
|
+
dx = x_shifts.select { |k, v| anchor.include?(k) }
|
381
|
+
dx = dx.empty? ? x_shifts['c'] : dx.values.first
|
382
|
+
|
383
|
+
return pos.at(dx, dy)
|
384
|
+
end
|
385
|
+
|
386
|
+
def show_text(text, p)
|
387
|
+
if option(:debug) # draw bearing and all that typographic stuff
|
388
|
+
@ctx.save
|
389
|
+
@ctx.set_dash(0.03)
|
390
|
+
@ctx.move_to(*p.coords)
|
391
|
+
e = @ctx.text_extents(text)
|
392
|
+
@ctx.rel_line_to(0, e.y_bearing)
|
393
|
+
@ctx.rel_line_to(+e.x_bearing + e.width, 0)
|
394
|
+
@ctx.rel_line_to(0, e.height)
|
395
|
+
@ctx.rel_line_to(-e.x_bearing - e.width, 0)
|
396
|
+
@ctx.close_path
|
397
|
+
fill_and_stroke(nil, BLACK)
|
398
|
+
|
399
|
+
@ctx.set_dash(nil)
|
400
|
+
@ctx.move_to(*p.coords)
|
401
|
+
@ctx.rel_line_to(e.x_bearing + e.width, 0)
|
402
|
+
@ctx.move_to(*p.coords)
|
403
|
+
@ctx.rel_move_to(e.x_bearing, e.y_bearing)
|
404
|
+
@ctx.rel_line_to(0, e.height)
|
405
|
+
fill_and_stroke(nil, RED)
|
406
|
+
|
407
|
+
@ctx.restore
|
408
|
+
end
|
409
|
+
set_black
|
410
|
+
@ctx.move_to(*p.coords)
|
411
|
+
@ctx.show_text(text)
|
412
|
+
@ctx.stroke
|
413
|
+
end
|
414
|
+
|
415
|
+
def approximate_size(text, size)
|
416
|
+
if size.is_a?(Size)
|
417
|
+
@ctx.set_font_size(@font_size)
|
418
|
+
extents = @ctx.text_extents(text)
|
419
|
+
sx = size.width / extents.width
|
420
|
+
sy = size.height / extents.height
|
421
|
+
return [ sx, sy ].min
|
422
|
+
else
|
423
|
+
size = -size
|
424
|
+
@ctx.set_font_size(@font_size)
|
425
|
+
extents = @ctx.text_extents(text)
|
426
|
+
return size / extents.height
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def text_measurer
|
431
|
+
return proc { |text|
|
432
|
+
@ctx.save
|
433
|
+
@ctx.set_font_size(@font_size)
|
434
|
+
ex = @ctx.text_extents(text)
|
435
|
+
@ctx.restore
|
436
|
+
ex
|
437
|
+
}
|
438
|
+
end
|
439
|
+
|
440
|
+
def draw_elements(els, box)
|
441
|
+
|
442
|
+
background()
|
443
|
+
rescale(box)
|
444
|
+
# bbox(box)
|
445
|
+
|
446
|
+
@ctx.select_font_face("Droid Serif",
|
447
|
+
Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL)
|
448
|
+
|
449
|
+
pos = els[:positions]
|
450
|
+
|
451
|
+
set_black
|
452
|
+
@ctx.set_line_width(0.01)
|
453
|
+
@ctx.set_dash(nil)
|
454
|
+
|
455
|
+
els[:paths].each do |path|
|
456
|
+
raise if path.length < 2
|
457
|
+
@ctx.move_to(*pos[path.first].coords)
|
458
|
+
path[1..-1].each do |n|
|
459
|
+
@ctx.line_to(*pos[n].coords)
|
460
|
+
end
|
461
|
+
@ctx.stroke
|
462
|
+
end
|
463
|
+
|
464
|
+
@ctx.set_dash(0.03)
|
465
|
+
els[:boxes].each do |b|
|
466
|
+
pos, box = b[:pos], b[:box]
|
467
|
+
rect = pos.coords + box.coords
|
468
|
+
@ctx.rectangle(*rect)
|
469
|
+
@ctx.stroke
|
470
|
+
end
|
471
|
+
|
472
|
+
els[:lines].each do |path, style|
|
473
|
+
if style == :bezier or style == { :type => :bezier }
|
474
|
+
raise "Bezier curve must have >= 4 and % 3 == 1 points!" \
|
475
|
+
if path.length < 4 or path.length % 3 != 1
|
476
|
+
|
477
|
+
if option(:debug)
|
478
|
+
@ctx.save
|
479
|
+
(path.length / 3).times do |i|
|
480
|
+
[ 3*i + 1, 3*i + 2 ].each do |j|
|
481
|
+
r = 0.05
|
482
|
+
params = path[j].coords + [ r, 0, 6.28 ]
|
483
|
+
@ctx.set_dash(nil)
|
484
|
+
@ctx.arc(*params)
|
485
|
+
fill_and_stroke(RED, RED)
|
486
|
+
end
|
487
|
+
@ctx.set_dash(0.03)
|
488
|
+
@ctx.move_to(*path[3*i + 1].coords)
|
489
|
+
@ctx.line_to(*path[3*i + 2].coords)
|
490
|
+
fill_and_stroke(nil, RED)
|
491
|
+
end
|
492
|
+
@ctx.restore
|
493
|
+
end
|
494
|
+
|
495
|
+
path = path.dup
|
496
|
+
first = path.shift
|
497
|
+
@ctx.move_to(*first.coords)
|
498
|
+
while path.length > 0
|
499
|
+
|
500
|
+
|
501
|
+
args = path[0].coords + path[1].coords + path[2].coords
|
502
|
+
@ctx.curve_to(*args)
|
503
|
+
path.shift(3)
|
504
|
+
end
|
505
|
+
@ctx.stroke
|
506
|
+
else
|
507
|
+
raise "Unknown style of a line (#{style})!"
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
@ctx.set_dash(nil)
|
512
|
+
els[:nodes].each do |label, n|
|
513
|
+
@ctx.set_font_size(@font_size)
|
514
|
+
@ctx.set_source_rgb(*BLACK)
|
515
|
+
@ctx.save
|
516
|
+
style = n[:style]
|
517
|
+
position = n[:pos]
|
518
|
+
name = n[:name]
|
519
|
+
anchor = n[:anchor].to_s
|
520
|
+
size = n[:size]
|
521
|
+
anchor = "cm" if anchor.nil? # center, middle
|
522
|
+
size = 1.0 if size.nil?
|
523
|
+
# anchor can contain: l, r, t, b, c, m
|
524
|
+
if style == "start" or style == "finish"
|
525
|
+
x, y = position.coords
|
526
|
+
@ctx.set_line_width(style == "start" ? 0.02 : 0.04)
|
527
|
+
@ctx.arc(x, y, 0.2, 0, 2 * Math::PI)
|
528
|
+
fill_and_stroke
|
529
|
+
elsif style == "task"
|
530
|
+
extents = @ctx.text_extents(name)
|
531
|
+
bw, bh = extents.width + 0.2, extents.height + 0.2
|
532
|
+
x, y = position.width - bw / 2.0, position.height - bh / 2.0
|
533
|
+
@ctx.rectangle(x, y, bw, bh)
|
534
|
+
fill_and_stroke
|
535
|
+
if option(:debug) # showing middle points of tasks
|
536
|
+
@ctx.move_to(x + bw * 0.5, y)
|
537
|
+
@ctx.rel_line_to(0, bh)
|
538
|
+
@ctx.move_to(x, y + bh * 0.5)
|
539
|
+
@ctx.rel_line_to(bw, 0)
|
540
|
+
fill_and_stroke(WHITE, [ 0.7, 0.7, 0.7 ])
|
541
|
+
end
|
542
|
+
fill_and_stroke
|
543
|
+
p = find_pos(position, extents, "cm")
|
544
|
+
show_text(name, p)
|
545
|
+
elsif style == "text"
|
546
|
+
size = approximate_size(name, size) if size.is_a?(Size ) or size < 0.0
|
547
|
+
@ctx.set_font_size(@font_size * size) # set font size here
|
548
|
+
p = find_pos(position, @ctx.text_extents(name), anchor)
|
549
|
+
show_text(name, p)
|
550
|
+
elsif style == "gateway"
|
551
|
+
x, y = position.coords
|
552
|
+
@ctx.move_to(x, y)
|
553
|
+
@ctx.rel_move_to(-0.2, 0)
|
554
|
+
@ctx.rel_line_to(+0.2, +0.2)
|
555
|
+
@ctx.rel_line_to(+0.2, -0.2)
|
556
|
+
@ctx.rel_line_to(-0.2, -0.2)
|
557
|
+
@ctx.close_path
|
558
|
+
fill_and_stroke
|
559
|
+
p = find_pos(position, @ctx.text_extents(name), "cm")
|
560
|
+
show_text(name, p)
|
561
|
+
else
|
562
|
+
raise "Unknown style #{style}!"
|
563
|
+
end
|
564
|
+
@ctx.restore
|
565
|
+
end
|
566
|
+
|
567
|
+
@writer.finish
|
568
|
+
end
|
569
|
+
|
570
|
+
end
|
571
|
+
|
572
|
+
class PNGWriter
|
573
|
+
|
574
|
+
attr_reader :size
|
575
|
+
attr_reader :context
|
576
|
+
|
577
|
+
def initialize(filename, size = [ 800, 600 ])
|
578
|
+
$check_cairo.call
|
579
|
+
@filename = filename
|
580
|
+
@size = size.map(&:to_f)
|
581
|
+
@surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, *@size)
|
582
|
+
@context = Cairo::Context.new(@surface)
|
583
|
+
end
|
584
|
+
|
585
|
+
def finish
|
586
|
+
@surface.write_to_png(@filename)
|
587
|
+
end
|
588
|
+
|
589
|
+
end
|
590
|
+
|
591
|
+
class PDFWriter
|
592
|
+
|
593
|
+
attr_reader :size
|
594
|
+
attr_reader :context
|
595
|
+
|
596
|
+
def initialize(filename, size = [ 800, 600 ])
|
597
|
+
$check_cairo.call
|
598
|
+
@filename = filename
|
599
|
+
@size = size
|
600
|
+
@surface = Cairo::PDFSurface.new(filename, *@size)
|
601
|
+
@context = Cairo::Context.new(@surface)
|
602
|
+
end
|
603
|
+
|
604
|
+
def finish
|
605
|
+
@context.show_page
|
606
|
+
end
|
607
|
+
|
608
|
+
end
|
609
|
+
|
610
|
+
def self.to_pdf(process, filename, opts = {})
|
611
|
+
writer = Graphing::PDFWriter.new(filename)
|
612
|
+
Graphing::CairoGrapher.new(process, writer, opts).draw()
|
613
|
+
end
|
614
|
+
|
615
|
+
def self.to_png(process, filename, opts = {})
|
616
|
+
writer = Graphing::PNGWriter.new(filename)
|
617
|
+
Graphing::CairoGrapher.new(process, writer, opts).draw()
|
618
|
+
end
|
619
|
+
|
620
|
+
def self.to_tikz(process, filename, opts = {})
|
621
|
+
File.open(filename, "w") do |f|
|
622
|
+
f.write(to_text(process, opts))
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
def self.to_latex(process, filename, opts = {})
|
627
|
+
File.open(filename, "w") do |f|
|
628
|
+
f.write(to_tex(process, opts))
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
def self.to_text(process, opts = {})
|
633
|
+
return Graphing::TikzGrapher.new(process, opts).draw()
|
634
|
+
end
|
635
|
+
|
636
|
+
def self.to_tex(process, opts = {})
|
637
|
+
process = process.simplify
|
638
|
+
return Graphing::LatexGrapher.new(process, opts).draw()
|
639
|
+
end
|
640
|
+
|
641
|
+
def self.to_file(process, filename, format)
|
642
|
+
m = "to_#{format}".downcase.to_sym
|
643
|
+
raise "Format #{format} unsupported." unless self.respond_to?(m)
|
644
|
+
return self.__send__(m, process, filename)
|
645
|
+
end
|
646
|
+
|
647
|
+
|
648
|
+
|
649
|
+
|
650
|
+
class Size
|
651
|
+
|
652
|
+
attr_reader :width
|
653
|
+
attr_reader :height
|
654
|
+
|
655
|
+
def initialize(w, h)
|
656
|
+
@width = w
|
657
|
+
@height = h
|
658
|
+
end
|
659
|
+
|
660
|
+
def self.flatten(*args)
|
661
|
+
if args.length == 1
|
662
|
+
el = args.first
|
663
|
+
return el if el.is_a?(Size)
|
664
|
+
return Size.new(*el)
|
665
|
+
elsif args.length == 2
|
666
|
+
return Size.new(*args)
|
667
|
+
else
|
668
|
+
raise "What is #{args}?"
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
def to_s
|
673
|
+
return "(%.6f, %.6f)" % [@width, @height]
|
674
|
+
end
|
675
|
+
|
676
|
+
def self.ZERO
|
677
|
+
return Size.new(0, 0)
|
678
|
+
end
|
679
|
+
|
680
|
+
def +(o)
|
681
|
+
o = Size.flatten(o)
|
682
|
+
return Size.new(@width + o.width, @height + o.height)
|
683
|
+
end
|
684
|
+
|
685
|
+
def -(o)
|
686
|
+
o = Size.flatten(o)
|
687
|
+
return self + [ -o.width, -o.height ]
|
688
|
+
end
|
689
|
+
|
690
|
+
def *(x)
|
691
|
+
return Size.new(@width * x, @height * x)
|
692
|
+
end
|
693
|
+
|
694
|
+
def /(x)
|
695
|
+
x = x.to_f
|
696
|
+
return Size.new(@width / x, @height / x)
|
697
|
+
end
|
698
|
+
|
699
|
+
def at(*args)
|
700
|
+
return self + args.first if args.length == 1
|
701
|
+
return self + args
|
702
|
+
end
|
703
|
+
|
704
|
+
def xproj(y = 0)
|
705
|
+
# project width
|
706
|
+
return Size.new(@width, y)
|
707
|
+
end
|
708
|
+
|
709
|
+
def yproj
|
710
|
+
return Size.new(0, height)
|
711
|
+
end
|
712
|
+
|
713
|
+
def coords
|
714
|
+
return [ @width, @height ]
|
715
|
+
end
|
716
|
+
|
717
|
+
def shrink(scalex, scaley = nil)
|
718
|
+
scaley = scalex if scaley.nil?
|
719
|
+
x = Size.new(@width * scalex, @height * scaley)
|
720
|
+
offset = (self - x) / 2
|
721
|
+
return [ offset, x ]
|
722
|
+
end
|
723
|
+
|
724
|
+
def center
|
725
|
+
return self * 0.5
|
726
|
+
end
|
727
|
+
|
728
|
+
def vh_path(endpoint, height = nil)
|
729
|
+
# creates a path |-| from self to endpoint, returns all 4 points
|
730
|
+
height = (self.height + endpoint.height) * 0.5 if height.nil?
|
731
|
+
return [ self, self.xproj(height), endpoint.xproj(height), endpoint ]
|
732
|
+
end
|
733
|
+
|
734
|
+
def x; @width end
|
735
|
+
def y; @height end
|
736
|
+
end
|
737
|
+
|
738
|
+
class Flow
|
739
|
+
|
740
|
+
def initialize(opts)
|
741
|
+
@opts = standard_opts().merge(default_opts().merge(opts))
|
742
|
+
end
|
743
|
+
|
744
|
+
def size
|
745
|
+
raise 'Error'
|
746
|
+
end
|
747
|
+
|
748
|
+
def simplify
|
749
|
+
return self
|
750
|
+
end
|
751
|
+
|
752
|
+
def default_opts
|
753
|
+
return { }
|
754
|
+
end
|
755
|
+
|
756
|
+
def standard_opts
|
757
|
+
return { 'box' => { :value => false } }
|
758
|
+
end
|
759
|
+
|
760
|
+
def option(name, &block)
|
761
|
+
x = @opts[name.to_s][:value]
|
762
|
+
x = block.call(x) if block_given?
|
763
|
+
return x
|
764
|
+
end
|
765
|
+
|
766
|
+
def has_box?
|
767
|
+
return option(:box, &:to_s) == "true"
|
768
|
+
end
|
769
|
+
|
770
|
+
def draw_common(state, pos, box)
|
771
|
+
state.add_box(pos, box, true) if has_box?
|
772
|
+
end
|
773
|
+
|
774
|
+
def tikz(state, pos, box)
|
775
|
+
draw_common(state, pos, box)
|
776
|
+
end
|
777
|
+
|
778
|
+
end
|
779
|
+
|
780
|
+
class Block < Flow
|
781
|
+
|
782
|
+
def initialize(name, opts = {})
|
783
|
+
super({ :flow => :right }.merge(opts))
|
784
|
+
@name = name
|
785
|
+
end
|
786
|
+
|
787
|
+
def size
|
788
|
+
measurer = Thread.current[:text_measure]
|
789
|
+
if measurer.nil?
|
790
|
+
chars = @name.to_s.split("\\\\").map(&:length).max
|
791
|
+
w = 1 + (chars - 1) * 0.18
|
792
|
+
return Size.new(w, 1)
|
793
|
+
else
|
794
|
+
# we have measurer helper here
|
795
|
+
extents = measurer.call(@name)
|
796
|
+
return Size.new(extents.width, 1)
|
797
|
+
end
|
798
|
+
end
|
799
|
+
|
800
|
+
def tikz(state, pos, box, label = nil)
|
801
|
+
super(state, pos, box)
|
802
|
+
width2 = box.width * 0.5
|
803
|
+
height2 = box.height * 0.5
|
804
|
+
first = state.add_coord(pos + [ 0.0, height2 ])
|
805
|
+
last = state.add_coord(pos + [ box.width, height2 ])
|
806
|
+
node = state.add_task(@name, pos + [ width2, height2 ], 'task', nil, nil, label)
|
807
|
+
if @opts[:flow] == :left
|
808
|
+
state.add_link(last, node)
|
809
|
+
state.add_link(node, first)
|
810
|
+
return [ last, first ]
|
811
|
+
else
|
812
|
+
state.add_link(first, node)
|
813
|
+
state.add_link(node, last)
|
814
|
+
return [ first, last ]
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
818
|
+
def latex()
|
819
|
+
opts = { :text => @name }.merge(@opts)
|
820
|
+
return nil if opts[:hide]
|
821
|
+
return [ "\\task {#{opts[:text]}}" ]
|
822
|
+
end
|
823
|
+
|
824
|
+
end
|
825
|
+
|
826
|
+
class GroupFlow < Flow
|
827
|
+
|
828
|
+
def initialize(list, opts = {})
|
829
|
+
super(opts)
|
830
|
+
@list = list
|
831
|
+
end
|
832
|
+
|
833
|
+
end
|
834
|
+
|
835
|
+
class SequenceFlow < GroupFlow
|
836
|
+
|
837
|
+
@@margin = 0.3
|
838
|
+
|
839
|
+
def size
|
840
|
+
if @list.length == 0
|
841
|
+
return Size.new(0.0, 0.0)
|
842
|
+
end
|
843
|
+
sizes = @list.map(&:size)
|
844
|
+
w = sizes.map(&:width).reduce(:+)
|
845
|
+
h = sizes.map(&:height).max
|
846
|
+
w += sizes.length * @@margin
|
847
|
+
return Size.new(w, h)
|
848
|
+
end
|
849
|
+
|
850
|
+
def self.simplify_array(array)
|
851
|
+
# flattens an array of sequences
|
852
|
+
newarray = []
|
853
|
+
newlist = array.each do |it|
|
854
|
+
it = it.simplify
|
855
|
+
if it.is_a?(SequenceFlow)
|
856
|
+
newarray += it.sequence()
|
857
|
+
else
|
858
|
+
newarray.push(it)
|
859
|
+
end
|
860
|
+
end
|
861
|
+
return newarray
|
862
|
+
end
|
863
|
+
|
864
|
+
def into_lines
|
865
|
+
# finds nl's inside sequence, and if they exist turns everything into lines
|
866
|
+
lines = []
|
867
|
+
sizes = []
|
868
|
+
line = []
|
869
|
+
@list.each do |item|
|
870
|
+
if item.is_a?(NewLine)
|
871
|
+
lines.push(line)
|
872
|
+
sizes.push(item.option(:size, &:to_f))
|
873
|
+
line = []
|
874
|
+
else
|
875
|
+
line.push(item)
|
876
|
+
end
|
877
|
+
end
|
878
|
+
lines.push(line) if line != []
|
879
|
+
if lines.length == 1
|
880
|
+
return self
|
881
|
+
else
|
882
|
+
lines = lines.map { |x| SequenceFlow.new(x) }
|
883
|
+
opts = @opts.merge({ 'padding' => { :value => sizes }})
|
884
|
+
return LinesFlow.new(lines, opts)
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
def simplify
|
889
|
+
array = SequenceFlow.simplify_array(@list)
|
890
|
+
return SequenceFlow.new(array, @opts)
|
891
|
+
end
|
892
|
+
|
893
|
+
def sequence
|
894
|
+
return @list
|
895
|
+
end
|
896
|
+
|
897
|
+
def els_sizes
|
898
|
+
return @list.map(&:size)
|
899
|
+
end
|
900
|
+
|
901
|
+
def tikz(state, pos, box)
|
902
|
+
super
|
903
|
+
first = pos.at(0, box.height * 0.5)
|
904
|
+
last = pos.at(box.width, box.height * 0.5)
|
905
|
+
first = state.add_coord(first)
|
906
|
+
last = state.add_coord(last)
|
907
|
+
if @list.length == 0
|
908
|
+
state.add_link(first, last)
|
909
|
+
return [ first, last ]
|
910
|
+
end
|
911
|
+
s = self.els_sizes
|
912
|
+
sum = s.map(&:width).reduce(:+).to_f
|
913
|
+
m = (box.width - sum) / s.length.to_f
|
914
|
+
m2 = m * 0.5
|
915
|
+
state.add_box(pos, box)
|
916
|
+
|
917
|
+
# m is a size of every margin
|
918
|
+
|
919
|
+
b, e = nil, nil # first and last node
|
920
|
+
prev = nil
|
921
|
+
offset = m2
|
922
|
+
for el in @list do
|
923
|
+
els = el.size
|
924
|
+
middle = (box.height - els.height) * 0.5
|
925
|
+
start, ending = el.tikz(state, pos.at(offset, middle), els)
|
926
|
+
b = start if b.nil?
|
927
|
+
e = ending
|
928
|
+
state.add_link(prev, start) unless prev.nil?
|
929
|
+
prev = ending
|
930
|
+
offset += els.width + m
|
931
|
+
end
|
932
|
+
|
933
|
+
state.add_link(first, b)
|
934
|
+
state.add_link(e, last)
|
935
|
+
|
936
|
+
return [ first, last ]
|
937
|
+
end
|
938
|
+
|
939
|
+
def latex()
|
940
|
+
children = @list.map(&:latex)
|
941
|
+
return [ '\seq' ] + children + [ '\end' ]
|
942
|
+
end
|
943
|
+
|
944
|
+
end
|
945
|
+
|
946
|
+
class LoopFlow < SequenceFlow
|
947
|
+
|
948
|
+
@@hsize = 1.0
|
949
|
+
@@vsize = 1.0
|
950
|
+
|
951
|
+
def initialize(*args)
|
952
|
+
super
|
953
|
+
@breaks = []
|
954
|
+
end
|
955
|
+
|
956
|
+
def size
|
957
|
+
s = super()
|
958
|
+
return s.at(@@vsize, @@hsize)
|
959
|
+
end
|
960
|
+
|
961
|
+
def tikz(state, pos, box)
|
962
|
+
Thread.current[:__loop__] = [] if Thread.current[:__loop__].nil?
|
963
|
+
Thread.current[:__loop__].push(self)
|
964
|
+
|
965
|
+
# state.add_box(pos, box, true)
|
966
|
+
|
967
|
+
finish = pos.at(box.width, box.height * 0.5)
|
968
|
+
|
969
|
+
box = box.at(-@@vsize, -@@hsize)
|
970
|
+
pos = pos.at(0, @@hsize * 0.5)
|
971
|
+
|
972
|
+
# state.add_box(pos, box, true)
|
973
|
+
|
974
|
+
s, e = super(state, pos, box)
|
975
|
+
|
976
|
+
ee = state.get_position(e)
|
977
|
+
ss = state.get_position(s)
|
978
|
+
a = ee.xproj(pos.height)
|
979
|
+
b = ss.xproj(pos.height)
|
980
|
+
state.add_join([ e, a, b, s ], :arrow)
|
981
|
+
|
982
|
+
# TODO: this will only work nicely with one
|
983
|
+
# break
|
984
|
+
@breaks.each do |x|
|
985
|
+
xx = state.get_position(x)
|
986
|
+
a = xx.xproj(pos.height + box.height)
|
987
|
+
b = finish.xproj(pos.height + box.height)
|
988
|
+
state.add_join([x, a, b, finish])
|
989
|
+
end
|
990
|
+
|
991
|
+
ff = state.add_coord(finish)
|
992
|
+
|
993
|
+
state.add_link(e, ff)
|
994
|
+
|
995
|
+
Thread.current[:__loop__].pop()
|
996
|
+
|
997
|
+
return [s, ff]
|
998
|
+
end
|
999
|
+
|
1000
|
+
def add_break(label)
|
1001
|
+
@breaks.push(label)
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
class BreakLoop < Block
|
1007
|
+
|
1008
|
+
@@counter = 0
|
1009
|
+
|
1010
|
+
def tikz(state, pos, box)
|
1011
|
+
@@counter += 1
|
1012
|
+
label = "break-#{@@counter}"
|
1013
|
+
s, e = super(state, pos, box, label)
|
1014
|
+
Thread.current[:__loop__].last.add_break(label)
|
1015
|
+
return [ s, e ]
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
class SubprocessFlow < SequenceFlow
|
1021
|
+
# this one contains a subprocess, with its name
|
1022
|
+
# we ask for +2 in height so that we can nicely
|
1023
|
+
# fit the name of the process
|
1024
|
+
|
1025
|
+
def initialize(list, name)
|
1026
|
+
super(list)
|
1027
|
+
@name = name
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
def size
|
1031
|
+
s = super()
|
1032
|
+
return s.at(0, 2)
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
def tikz(state, pos, box)
|
1036
|
+
s = self.size
|
1037
|
+
links = super(state, pos, box)
|
1038
|
+
state.add_box(pos, box, true)
|
1039
|
+
label = Size.new(box.width, 1)
|
1040
|
+
offset, box = label.shrink(0.8, 0.7)
|
1041
|
+
state.add_text("#{@name}", pos + offset + box.center, "cm", box)
|
1042
|
+
return links
|
1043
|
+
end
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
class BoxedFlow < Flow
|
1047
|
+
|
1048
|
+
def initialize(flow, name, opts = {})
|
1049
|
+
super(opts)
|
1050
|
+
@flow = flow
|
1051
|
+
@name = name
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
def size
|
1055
|
+
s = @flow.size
|
1056
|
+
return s.at(0, 2)
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
def space
|
1060
|
+
space = 0.0
|
1061
|
+
space = @opts['space'][:value].to_f if @opts['space']
|
1062
|
+
return space
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def tikz(state, pos, box)
|
1066
|
+
itsize = @flow.size
|
1067
|
+
diff = box - itsize
|
1068
|
+
flowpos = pos + diff * 0.5
|
1069
|
+
links = @flow.tikz(state, pos.at(0, 1), Size.new(box.width, itsize.height))
|
1070
|
+
|
1071
|
+
pos = pos.at(0, self.space)
|
1072
|
+
|
1073
|
+
title = state.add_task("{\\Large\\sc{}#{@name}}",
|
1074
|
+
pos.at(box.width/2, box.height - 0.5), 'draw', "cm", 1.0)
|
1075
|
+
|
1076
|
+
m1 = state.get_position(title)
|
1077
|
+
c1 = pos.at(box.width/2, box.height - 1)
|
1078
|
+
p1 = pos.at(0, box.height - 1 - 0.4)
|
1079
|
+
p2 = pos.at(0, box.height - 1)
|
1080
|
+
p3 = pos.at(box.width, box.height - 1)
|
1081
|
+
p4 = pos.at(box.width, box.height - 1 - 0.4)
|
1082
|
+
|
1083
|
+
state.add_line([ m1, c1 ], :type => :solid)
|
1084
|
+
state.add_line([ p1, p2, p3, p4 ], :type => :solid)
|
1085
|
+
|
1086
|
+
return links
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
class SplitFlow < GroupFlow
|
1092
|
+
|
1093
|
+
def initialize(list, text)
|
1094
|
+
super(list)
|
1095
|
+
@text = text
|
1096
|
+
@hjustify = 0.5
|
1097
|
+
@vjustify = 0.75
|
1098
|
+
@hmargin = 0.0
|
1099
|
+
@vmargin = 0.0
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
def split_size
|
1103
|
+
sizes = @list.map(&:size)
|
1104
|
+
w = sizes.map(&:width).max
|
1105
|
+
h = sizes.map(&:height).reduce(:+)
|
1106
|
+
return Size.new(w + 2, h).at(@hmargin * 2, @vmargin * 2)
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def size
|
1110
|
+
return split_size
|
1111
|
+
end
|
1112
|
+
|
1113
|
+
def justify(pos, box)
|
1114
|
+
s = self.size
|
1115
|
+
dx = (box.width - s.width) * 0.5 * (1 - @hjustify)
|
1116
|
+
dy = (box.height - s.height) * 0.5 * (1 - @vjustify)
|
1117
|
+
pos = pos.at(dx, dy)
|
1118
|
+
box = Size.new(box.width - 2*dx, box.height - 2*dy)
|
1119
|
+
return [ pos, box ]
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
def margine(pos, box)
|
1123
|
+
box = box.at(-2 * @hmargin, -2 * @vmargin)
|
1124
|
+
pos = pos.at(@hmargin, @vmargin)
|
1125
|
+
return [ pos, box ]
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
def tikz(state, pos, box)
|
1129
|
+
# state.add_box(pos, box, true)
|
1130
|
+
pos, box = margine(pos, box)
|
1131
|
+
# state.add_box(pos, box, true)
|
1132
|
+
pos, box = justify(pos, box)
|
1133
|
+
# state.add_box(pos, box, true)
|
1134
|
+
first = pos.at(0.5, box.height * 0.5)
|
1135
|
+
last = pos.at(box.width - 0.5, box.height * 0.5)
|
1136
|
+
first = @split = state.add_gateway(first, @text)
|
1137
|
+
last = @merge = state.add_gateway(last, @text)
|
1138
|
+
|
1139
|
+
s = self.split_size
|
1140
|
+
pos = pos + [ (box.width - s.width) * 0.5, 0.0 ]
|
1141
|
+
offset = (box.height - s.height) * 0.5
|
1142
|
+
for el in @list do
|
1143
|
+
els = el.size
|
1144
|
+
start, ending = el.tikz(state, pos.at(1, offset), Size.new(s.width - 2, els.height))
|
1145
|
+
state.add_link(first, start)
|
1146
|
+
state.add_link(ending, last)
|
1147
|
+
offset += els.height
|
1148
|
+
end
|
1149
|
+
|
1150
|
+
return [ first, last ]
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
class ParallelFlow < SplitFlow
|
1156
|
+
|
1157
|
+
def initialize(list)
|
1158
|
+
super(list, '+')
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
def latex()
|
1162
|
+
children = @list.map(&:latex)
|
1163
|
+
return [ '\parallel' ] + children + [ '\end' ]
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
class ForallFlow < SplitFlow
|
1169
|
+
|
1170
|
+
def initialize(list, opts = {})
|
1171
|
+
# TODO
|
1172
|
+
opts = { :symbol => '\normalsize{\&}' }.merge(opts)
|
1173
|
+
super(list, opts[:symbol])
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
def size
|
1177
|
+
s = super()
|
1178
|
+
return s.at(0, 2)
|
1179
|
+
end
|
1180
|
+
|
1181
|
+
def tikz(state, pos, box)
|
1182
|
+
# state.add_box(pos, box, true)
|
1183
|
+
# state.add_box(pos.at(0, 1), box.at(0, -2), true)
|
1184
|
+
|
1185
|
+
endings = super(state, pos, box)
|
1186
|
+
first, last = endings
|
1187
|
+
|
1188
|
+
first = state.label_pos(@split)
|
1189
|
+
last = state.label_pos(@merge)
|
1190
|
+
|
1191
|
+
span = (last - first).width
|
1192
|
+
|
1193
|
+
steps = 2
|
1194
|
+
|
1195
|
+
(-steps..steps).each do |i|
|
1196
|
+
|
1197
|
+
next if i == 0
|
1198
|
+
|
1199
|
+
x = i.to_f / steps
|
1200
|
+
lcorner = first + [ 0, x ]
|
1201
|
+
rcorner = last + [ 0, x ]
|
1202
|
+
middle = (lcorner + rcorner) / 2
|
1203
|
+
|
1204
|
+
c1 = (lcorner + first) / 2
|
1205
|
+
c2 = (lcorner + middle) / 2
|
1206
|
+
c3 = (rcorner + middle) / 2
|
1207
|
+
c4 = (rcorner + last) / 2
|
1208
|
+
|
1209
|
+
state.add_line([ first, c1, c2, middle, c3, c4, last ], :type => :bezier)
|
1210
|
+
end
|
1211
|
+
|
1212
|
+
return endings
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
def latex()
|
1216
|
+
# TODO
|
1217
|
+
children = @list.map(&:latex).first[1...-1]
|
1218
|
+
return [ '\forall' ] + children + [ '\end' ]
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
class ForeachFlow < ForallFlow
|
1224
|
+
|
1225
|
+
def initialize(list, opts = {})
|
1226
|
+
super(list, :symbol => '\normalsize\textbf{=}')
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
def latex()
|
1230
|
+
# TODO
|
1231
|
+
children = @list.map(&:latex).first[1...-1]
|
1232
|
+
return [ '\foreach' ] + children + [ '\end' ]
|
1233
|
+
end
|
1234
|
+
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
class SubFlow < Flow
|
1238
|
+
|
1239
|
+
def initialize(flow)
|
1240
|
+
super({ })
|
1241
|
+
@flow = flow
|
1242
|
+
end
|
1243
|
+
|
1244
|
+
def size
|
1245
|
+
return @flow.size
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
def tikz(state, pos, box)
|
1249
|
+
endings = @flow.tikz(state, pos, box)
|
1250
|
+
state.add_box(pos, box, true)
|
1251
|
+
return endings
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
class NewLine < Flow
|
1257
|
+
|
1258
|
+
def default_opts
|
1259
|
+
return { 'size' => { :value => 0 } }
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
def initialize(opts = {})
|
1263
|
+
super(opts)
|
1264
|
+
end
|
1265
|
+
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
class LinesFlow < GroupFlow
|
1269
|
+
## multiple line flow
|
1270
|
+
|
1271
|
+
def default_opts
|
1272
|
+
return {
|
1273
|
+
'padding' => { :value => 0 },
|
1274
|
+
'align' => { :value => :left }
|
1275
|
+
}
|
1276
|
+
end
|
1277
|
+
|
1278
|
+
def padding
|
1279
|
+
pad = option(:padding)
|
1280
|
+
if pad.is_a?(Array) # comes from nl's
|
1281
|
+
return pad
|
1282
|
+
else
|
1283
|
+
return [ pad.to_f ] * (@list.length - 1)
|
1284
|
+
end
|
1285
|
+
end
|
1286
|
+
|
1287
|
+
def total_padding
|
1288
|
+
return self.padding.reduce(:+)
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
def align
|
1292
|
+
return option(:align, &:to_sym)
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
def size
|
1296
|
+
sizes = @list.map(&:size)
|
1297
|
+
w = sizes.map(&:width).max
|
1298
|
+
h = sizes.map(&:height).reduce(:+)
|
1299
|
+
h += self.total_padding
|
1300
|
+
return Size.new(w, h)
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
def tikz(state, pos, box)
|
1304
|
+
super
|
1305
|
+
w = box.width
|
1306
|
+
# state.add_box(pos, box, true)
|
1307
|
+
mysize = self.size()
|
1308
|
+
offset = (box.height - mysize.height) * 0.5
|
1309
|
+
|
1310
|
+
pairs = []
|
1311
|
+
links = []
|
1312
|
+
|
1313
|
+
paddings = self.padding() + [ 0.0 ] # fake padding
|
1314
|
+
position_y = pos.height + box.height - offset
|
1315
|
+
@list.each_with_index do |flow, idx|
|
1316
|
+
half_padding = paddings[idx] * 0.5
|
1317
|
+
size = flow.size()
|
1318
|
+
position_y -= size.height
|
1319
|
+
line_pos = pos.xproj(position_y)
|
1320
|
+
if align() == :center
|
1321
|
+
xdelta = (mysize.width - size.width) * 0.5
|
1322
|
+
line_pos = line_pos.at(xdelta, 0)
|
1323
|
+
elsif align() == :right
|
1324
|
+
xdelta = (mysize.width - size.width)
|
1325
|
+
line_pos = line_pos.at(xdelta, 0)
|
1326
|
+
end
|
1327
|
+
line_box = Size.new(size.width, size.height)
|
1328
|
+
x = flow.tikz(state, line_pos, line_box)
|
1329
|
+
pairs.push(x)
|
1330
|
+
links.push(position_y - half_padding)
|
1331
|
+
position_y -= paddings[idx]
|
1332
|
+
end
|
1333
|
+
|
1334
|
+
(1...pairs.length).each do |i|
|
1335
|
+
pred = pairs[i - 1].last
|
1336
|
+
succ = pairs[i].first
|
1337
|
+
pos_pred = state.get_position(pred)
|
1338
|
+
pos_succ = state.get_position(succ)
|
1339
|
+
_, a, b, _ = pos_pred.vh_path(pos_succ, links[i - 1])
|
1340
|
+
n1, n2 = [ a, b ].map { |x| state.add_coord(x) }
|
1341
|
+
state.add_link(pred, n1, n2, succ)
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
return [pairs.first.first, pairs.last.last]
|
1345
|
+
end
|
1346
|
+
|
1347
|
+
|
1348
|
+
end
|
1349
|
+
|
1350
|
+
class ProcessFlow < Flow
|
1351
|
+
|
1352
|
+
def initialize(process)
|
1353
|
+
super({})
|
1354
|
+
@process = process
|
1355
|
+
end
|
1356
|
+
|
1357
|
+
def simplify
|
1358
|
+
return ProcessFlow.new(@process.simplify)
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
def size
|
1362
|
+
size = @process.size
|
1363
|
+
return size.at(2, 0)
|
1364
|
+
end
|
1365
|
+
|
1366
|
+
def tikz(state, pos, box)
|
1367
|
+
state.add_box(pos, box)
|
1368
|
+
first, last = @process.tikz(state, pos.at(1, 0), box.at(-2, 0))
|
1369
|
+
ff = state.get_position(first)
|
1370
|
+
ll = state.get_position(last)
|
1371
|
+
m = box.height * 0.5
|
1372
|
+
start = state.add_start(pos.at(0.5, ff.height))
|
1373
|
+
state.add_link(start, first)
|
1374
|
+
finish = state.add_finish( (pos + box + [-0.5, 0]).xproj(ll.height))
|
1375
|
+
state.add_link(last, finish)
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
def latex()
|
1379
|
+
return @process.latex()
|
1380
|
+
end
|
1381
|
+
|
1382
|
+
end
|
1383
|
+
|
1384
|
+
def self.blockify(it)
|
1385
|
+
it = Block.new(it) unless it.is_a?(Flow)
|
1386
|
+
return it
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
def self.seq(arr, opts = {})
|
1390
|
+
arr = arr.map { |x| blockify(x) }
|
1391
|
+
return SequenceFlow.new(arr, opts)
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
def self.box(flow, name, opts = {})
|
1395
|
+
flow = blockify(flow)
|
1396
|
+
return BoxedFlow.new(flow, name, opts)
|
1397
|
+
end
|
1398
|
+
|
1399
|
+
def self.nl(opts)
|
1400
|
+
return NewLine.new(opts)
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
def self.break(name, opts)
|
1404
|
+
return BreakLoop.new(name, opts)
|
1405
|
+
end
|
1406
|
+
|
1407
|
+
def self.loop(arr, opts)
|
1408
|
+
arr = arr.map { |x| blockify(x) }
|
1409
|
+
return LoopFlow.new(arr, opts)
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
def self.par(*arr)
|
1413
|
+
arr = arr.map { |x| blockify(x) }
|
1414
|
+
return ParallelFlow.new(arr)
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
def self.lines(arr, opts)
|
1418
|
+
arr = arr.map { |x| blockify(x) }
|
1419
|
+
return LinesFlow.new(arr, opts)
|
1420
|
+
end
|
1421
|
+
|
1422
|
+
def self.forall(arr, opts)
|
1423
|
+
arr = arr.map { |x| blockify(x) }
|
1424
|
+
seq = SequenceFlow.new(arr)
|
1425
|
+
return ForallFlow.new([ seq ], opts)
|
1426
|
+
end
|
1427
|
+
|
1428
|
+
def self.foreach(arr, opts)
|
1429
|
+
arr = arr.map { |x| blockify(x) }
|
1430
|
+
seq = SequenceFlow.new(arr)
|
1431
|
+
return ForeachFlow.new([ seq ], opts)
|
1432
|
+
end
|
1433
|
+
|
1434
|
+
module DSL
|
1435
|
+
|
1436
|
+
class Element
|
1437
|
+
|
1438
|
+
def initialize
|
1439
|
+
@list = []
|
1440
|
+
end
|
1441
|
+
|
1442
|
+
def push(x)
|
1443
|
+
@list.push(x)
|
1444
|
+
end
|
1445
|
+
|
1446
|
+
def push_many(arr)
|
1447
|
+
@list += arr
|
1448
|
+
end
|
1449
|
+
|
1450
|
+
|
1451
|
+
def task(name)
|
1452
|
+
push(Block.new(name))
|
1453
|
+
end
|
1454
|
+
|
1455
|
+
def seq_tasks(*names)
|
1456
|
+
blocks = names.map { |x| Block.new(x) }
|
1457
|
+
push(SequenceFlow.new(blocks))
|
1458
|
+
end
|
1459
|
+
|
1460
|
+
def tasks(*names)
|
1461
|
+
blocks = names.map { |x| Block.new(x) }
|
1462
|
+
push_many(blocks)
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
def sequence(&block)
|
1466
|
+
push(Sequence.new.parse(&block))
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
def parallel(&block)
|
1470
|
+
push(Parallel.new.parse(&block))
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
def lines(&block)
|
1474
|
+
push(Lines.new.parse(&block))
|
1475
|
+
end
|
1476
|
+
|
1477
|
+
def parse(&block)
|
1478
|
+
instance_exec(&block)
|
1479
|
+
return build()
|
1480
|
+
end
|
1481
|
+
|
1482
|
+
alias seq sequence
|
1483
|
+
|
1484
|
+
end
|
1485
|
+
|
1486
|
+
class LineMarker; end
|
1487
|
+
|
1488
|
+
class Sequence < Element
|
1489
|
+
|
1490
|
+
def build
|
1491
|
+
multiline = @list.any? { |x| x.is_a?(LineMarker) }
|
1492
|
+
if multiline
|
1493
|
+
lines = []
|
1494
|
+
curr = []
|
1495
|
+
(@list + [ LineMarker.new ]).each do |x|
|
1496
|
+
if x.is_a?(LineMarker)
|
1497
|
+
lines.push(SequenceFlow.new(curr))
|
1498
|
+
curr = []
|
1499
|
+
else
|
1500
|
+
curr.push(x)
|
1501
|
+
end
|
1502
|
+
end
|
1503
|
+
return LinesFlow.new(lines)
|
1504
|
+
else
|
1505
|
+
return SequenceFlow.new(@list)
|
1506
|
+
end
|
1507
|
+
end
|
1508
|
+
|
1509
|
+
def newline
|
1510
|
+
push(LineMarker.new)
|
1511
|
+
end
|
1512
|
+
|
1513
|
+
end
|
1514
|
+
|
1515
|
+
class Process < Sequence
|
1516
|
+
|
1517
|
+
alias seq_build build
|
1518
|
+
|
1519
|
+
def build
|
1520
|
+
return ProcessFlow.new(seq_build)
|
1521
|
+
end
|
1522
|
+
|
1523
|
+
end
|
1524
|
+
|
1525
|
+
class Parallel < Element
|
1526
|
+
|
1527
|
+
def build
|
1528
|
+
return ParallelFlow.new(@list)
|
1529
|
+
end
|
1530
|
+
|
1531
|
+
end
|
1532
|
+
|
1533
|
+
class Lines < Element
|
1534
|
+
|
1535
|
+
def build
|
1536
|
+
return LinesFlow.new(@list)
|
1537
|
+
end
|
1538
|
+
|
1539
|
+
end
|
1540
|
+
|
1541
|
+
def self.workflow(&block)
|
1542
|
+
return Process.new.parse(&block)
|
1543
|
+
end
|
1544
|
+
|
1545
|
+
end
|
1546
|
+
|
1547
|
+
end
|
1548
|
+
|
1549
|
+
if $0 == __FILE__
|
1550
|
+
if false
|
1551
|
+
G = Graphing
|
1552
|
+
seq1 = G.seq('D')
|
1553
|
+
seq2 = G.seq('E', 'H')
|
1554
|
+
par1 = G.par('F', 'G')
|
1555
|
+
seq3 = G.seq('C', par1)
|
1556
|
+
|
1557
|
+
seq3 = G::SubFlow.new(seq3)
|
1558
|
+
|
1559
|
+
par2 = G.par(seq1, seq2, seq3)
|
1560
|
+
main = G.seq('Ale jaja jak berety', 'B', par2)
|
1561
|
+
p = G::ProcessFlow.new(main)
|
1562
|
+
|
1563
|
+
g = G::TikzGrapher.new(p, :boxes => false)
|
1564
|
+
puts g.draw()
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
if true
|
1568
|
+
DSL = Graphing::DSL
|
1569
|
+
|
1570
|
+
p = DSL.workflow do
|
1571
|
+
task 'cze'
|
1572
|
+
parallel do
|
1573
|
+
tasks 'Clean', 'Find'
|
1574
|
+
seq_tasks 'A', 'B', 'C', 'D'
|
1575
|
+
parallel do
|
1576
|
+
seq_tasks 'one', 'two'
|
1577
|
+
task 'wow'
|
1578
|
+
end
|
1579
|
+
end
|
1580
|
+
end
|
1581
|
+
|
1582
|
+
p = DSL.workflow do
|
1583
|
+
parallel do
|
1584
|
+
lines do
|
1585
|
+
seq_tasks(*'a b c'.split)
|
1586
|
+
seq_tasks(*'x y'.split)
|
1587
|
+
seq_tasks(*'1 2 3'.split)
|
1588
|
+
parallel do
|
1589
|
+
task 'O rany'
|
1590
|
+
task 'banany'
|
1591
|
+
end
|
1592
|
+
end
|
1593
|
+
seq_tasks 'Clean', 'Find'
|
1594
|
+
end
|
1595
|
+
end
|
1596
|
+
|
1597
|
+
writer = Graphing::PDFWriter.new('/tmp/draw.pdf')
|
1598
|
+
g = Graphing::CairoGrapher.new(p, writer, :debug => false)
|
1599
|
+
g.draw()
|
1600
|
+
end
|
1601
|
+
|
1602
|
+
|
1603
|
+
end
|