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.
Files changed (74) hide show
  1. data/bin/xpflow +96 -0
  2. data/lib/colorado.rb +198 -0
  3. data/lib/json/add/core.rb +243 -0
  4. data/lib/json/add/rails.rb +8 -0
  5. data/lib/json/common.rb +423 -0
  6. data/lib/json/editor.rb +1369 -0
  7. data/lib/json/ext.rb +28 -0
  8. data/lib/json/pure/generator.rb +442 -0
  9. data/lib/json/pure/parser.rb +320 -0
  10. data/lib/json/pure.rb +15 -0
  11. data/lib/json/version.rb +8 -0
  12. data/lib/json.rb +62 -0
  13. data/lib/mime/types.rb +881 -0
  14. data/lib/mime-types.rb +3 -0
  15. data/lib/restclient/abstract_response.rb +106 -0
  16. data/lib/restclient/exceptions.rb +193 -0
  17. data/lib/restclient/net_http_ext.rb +55 -0
  18. data/lib/restclient/payload.rb +235 -0
  19. data/lib/restclient/raw_response.rb +34 -0
  20. data/lib/restclient/request.rb +316 -0
  21. data/lib/restclient/resource.rb +169 -0
  22. data/lib/restclient/response.rb +24 -0
  23. data/lib/restclient.rb +174 -0
  24. data/lib/xpflow/bash.rb +341 -0
  25. data/lib/xpflow/bundle.rb +113 -0
  26. data/lib/xpflow/cmdline.rb +249 -0
  27. data/lib/xpflow/collection.rb +122 -0
  28. data/lib/xpflow/concurrency.rb +79 -0
  29. data/lib/xpflow/data.rb +393 -0
  30. data/lib/xpflow/dsl.rb +816 -0
  31. data/lib/xpflow/engine.rb +574 -0
  32. data/lib/xpflow/ensemble.rb +135 -0
  33. data/lib/xpflow/events.rb +56 -0
  34. data/lib/xpflow/experiment.rb +65 -0
  35. data/lib/xpflow/exts/facter.rb +30 -0
  36. data/lib/xpflow/exts/g5k.rb +931 -0
  37. data/lib/xpflow/exts/g5k_use.rb +50 -0
  38. data/lib/xpflow/exts/gui.rb +140 -0
  39. data/lib/xpflow/exts/model.rb +155 -0
  40. data/lib/xpflow/graph.rb +1603 -0
  41. data/lib/xpflow/graph_xpflow.rb +251 -0
  42. data/lib/xpflow/import.rb +196 -0
  43. data/lib/xpflow/library.rb +349 -0
  44. data/lib/xpflow/logging.rb +153 -0
  45. data/lib/xpflow/manager.rb +147 -0
  46. data/lib/xpflow/nodes.rb +1250 -0
  47. data/lib/xpflow/runs.rb +773 -0
  48. data/lib/xpflow/runtime.rb +125 -0
  49. data/lib/xpflow/scope.rb +168 -0
  50. data/lib/xpflow/ssh.rb +186 -0
  51. data/lib/xpflow/stat.rb +50 -0
  52. data/lib/xpflow/stdlib.rb +381 -0
  53. data/lib/xpflow/structs.rb +369 -0
  54. data/lib/xpflow/taktuk.rb +193 -0
  55. data/lib/xpflow/templates/ssh-config.basic +14 -0
  56. data/lib/xpflow/templates/ssh-config.inria +18 -0
  57. data/lib/xpflow/templates/ssh-config.proxy +13 -0
  58. data/lib/xpflow/templates/taktuk +6590 -0
  59. data/lib/xpflow/templates/utils/batch +4 -0
  60. data/lib/xpflow/templates/utils/bootstrap +12 -0
  61. data/lib/xpflow/templates/utils/hostname +3 -0
  62. data/lib/xpflow/templates/utils/ping +3 -0
  63. data/lib/xpflow/templates/utils/rsync +12 -0
  64. data/lib/xpflow/templates/utils/scp +17 -0
  65. data/lib/xpflow/templates/utils/scp_many +8 -0
  66. data/lib/xpflow/templates/utils/ssh +3 -0
  67. data/lib/xpflow/templates/utils/ssh-interactive +4 -0
  68. data/lib/xpflow/templates/utils/taktuk +19 -0
  69. data/lib/xpflow/threads.rb +187 -0
  70. data/lib/xpflow/utils.rb +569 -0
  71. data/lib/xpflow/visual.rb +230 -0
  72. data/lib/xpflow/with_g5k.rb +7 -0
  73. data/lib/xpflow.rb +349 -0
  74. metadata +135 -0
@@ -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