xpflow 0.1b

Sign up to get free protection for your applications and to get access to all the features.
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