flor 0.9.5 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/CHANGELOG.md +10 -0
  2. data/Makefile +13 -5
  3. data/README.md +0 -35
  4. data/flor.gemspec +1 -0
  5. data/lib/flor.rb +6 -24
  6. data/lib/flor/changes.rb +26 -0
  7. data/lib/flor/colours.rb +65 -31
  8. data/lib/flor/conf.rb +84 -54
  9. data/lib/flor/core.rb +0 -23
  10. data/lib/flor/core/executor.rb +12 -42
  11. data/lib/flor/core/node.rb +19 -24
  12. data/lib/flor/core/procedure.rb +13 -24
  13. data/lib/flor/core/texecutor.rb +10 -28
  14. data/lib/flor/deep.rb +152 -0
  15. data/lib/flor/djan.rb +200 -0
  16. data/lib/flor/dollar.rb +0 -24
  17. data/lib/flor/errors.rb +0 -24
  18. data/lib/flor/flor.rb +43 -296
  19. data/lib/flor/id.rb +90 -0
  20. data/lib/flor/log.rb +12 -35
  21. data/lib/flor/migrations/0002_cunit_and_munit.rb +86 -0
  22. data/lib/flor/parser.rb +40 -46
  23. data/lib/flor/pcore/_arr.rb +0 -24
  24. data/lib/flor/pcore/_atom.rb +0 -24
  25. data/lib/flor/pcore/_att.rb +3 -25
  26. data/lib/flor/pcore/_dump.rb +0 -24
  27. data/lib/flor/pcore/_err.rb +0 -24
  28. data/lib/flor/pcore/_happly.rb +0 -24
  29. data/lib/flor/pcore/_obj.rb +0 -24
  30. data/lib/flor/pcore/_skip.rb +0 -24
  31. data/lib/flor/pcore/arith.rb +0 -24
  32. data/lib/flor/pcore/break.rb +0 -24
  33. data/lib/flor/pcore/case.rb +127 -0
  34. data/lib/flor/pcore/cmp.rb +0 -24
  35. data/lib/flor/pcore/cond.rb +24 -24
  36. data/lib/flor/pcore/cursor.rb +0 -24
  37. data/lib/flor/pcore/define.rb +0 -24
  38. data/lib/flor/pcore/fail.rb +0 -24
  39. data/lib/flor/pcore/if.rb +39 -0
  40. data/lib/flor/pcore/loop.rb +0 -24
  41. data/lib/flor/pcore/map.rb +0 -24
  42. data/lib/flor/pcore/match.rb +0 -24
  43. data/lib/flor/pcore/move.rb +0 -24
  44. data/lib/flor/pcore/noeval.rb +0 -24
  45. data/lib/flor/pcore/noret.rb +0 -24
  46. data/lib/flor/pcore/push.rb +1 -25
  47. data/lib/flor/pcore/rand.rb +59 -0
  48. data/lib/flor/pcore/sequence.rb +0 -24
  49. data/lib/flor/pcore/set.rb +0 -24
  50. data/lib/flor/pcore/stall.rb +0 -24
  51. data/lib/flor/pcore/until.rb +0 -24
  52. data/lib/flor/pcore/val.rb +0 -24
  53. data/lib/flor/punit/cancel.rb +0 -24
  54. data/lib/flor/punit/cmap.rb +0 -24
  55. data/lib/flor/punit/concurrence.rb +54 -24
  56. data/lib/flor/punit/every.rb +0 -24
  57. data/lib/flor/punit/graft.rb +41 -0
  58. data/lib/flor/punit/on.rb +0 -24
  59. data/lib/flor/punit/schedule.rb +0 -24
  60. data/lib/flor/punit/signal.rb +0 -24
  61. data/lib/flor/punit/sleep.rb +0 -24
  62. data/lib/flor/punit/task.rb +0 -26
  63. data/lib/flor/punit/trace.rb +0 -24
  64. data/lib/flor/punit/trap.rb +0 -24
  65. data/lib/flor/to_string.rb +4 -25
  66. data/lib/flor/tools/env.rb +0 -23
  67. data/lib/flor/tools/shell.rb +810 -0
  68. data/lib/flor/unit.rb +0 -23
  69. data/lib/flor/unit/executor.rb +35 -31
  70. data/lib/flor/unit/ganger.rb +9 -34
  71. data/lib/flor/unit/hooker.rb +5 -25
  72. data/lib/flor/unit/journal.rb +0 -23
  73. data/lib/flor/unit/loader.rb +63 -94
  74. data/lib/flor/unit/logger.rb +8 -27
  75. data/lib/flor/unit/models.rb +0 -24
  76. data/lib/flor/unit/models/execution.rb +13 -24
  77. data/lib/flor/unit/models/pointer.rb +0 -24
  78. data/lib/flor/unit/models/timer.rb +0 -24
  79. data/lib/flor/unit/models/trace.rb +0 -24
  80. data/lib/flor/unit/models/trap.rb +0 -24
  81. data/lib/flor/unit/scheduler.rb +157 -128
  82. data/lib/flor/unit/storage.rb +224 -167
  83. data/lib/flor/unit/taskers.rb +38 -25
  84. data/lib/flor/unit/waiter.rb +7 -26
  85. data/lib/flor/unit/wlist.rb +8 -24
  86. metadata +28 -7
  87. data/fail.txt +0 -16
  88. data/intercepted.txt +0 -123
  89. data/lib/flor/pcore/ife.rb +0 -56
  90. data/lib/flor/tools/repl.rb +0 -231
  91. data/out.txt +0 -206
@@ -1,26 +1,3 @@
1
- #--
2
- # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
21
- #
22
- # Made in Japan.
23
- #++
24
1
 
25
2
  require 'fileutils'
26
3
 
@@ -0,0 +1,810 @@
1
+
2
+ require 'terminal-table'
3
+
4
+ require 'flor'
5
+ require 'flor/unit'
6
+
7
+
8
+ module Flor::Tools
9
+
10
+ class Shell
11
+
12
+ def initialize(argv=nil)
13
+
14
+ env = ENV['FLOR_ENV'] || 'shell'
15
+ @root = "envs/#{env}"
16
+
17
+ prepare_home
18
+
19
+ @unit = Flor::Unit.new("#{@root}/etc/conf.json")
20
+
21
+ @unit.conf['unit'] = 'cli'
22
+ #unit.hooker.add('journal', Flor::Journal)
23
+
24
+ prepare_db
25
+ prepare_hooks
26
+
27
+ @hook = 'on'
28
+
29
+ @unit.start
30
+
31
+ @flow_path = File.join(@root, 'home/scratch.flo')
32
+ @ra_flow_path = File.join(@root, 'home/ra_scratch.flo')
33
+ @payload_path = File.join(@root, 'home/payload.json')
34
+ @variables_path = File.join(@root, 'home/variables.json')
35
+
36
+ @c = Flor.colours({})
37
+
38
+ if argv.any?
39
+ do_eval(argv.join(' '))
40
+ else
41
+ print_header
42
+ do_loop
43
+ end
44
+ end
45
+
46
+ attr_reader :c
47
+
48
+ protected
49
+
50
+ def prepare_home
51
+
52
+ home = File.join(@root, 'home')
53
+ return unless Dir.exists?(home)
54
+
55
+ %w[ payload.json scratch.flo variables.json ].each do |fn|
56
+
57
+ hfn = File.join(home, fn); next if File.exists?(hfn)
58
+
59
+ FileUtils.cp(hfn + '.template', hfn)
60
+ puts ".. prepared #{hfn}"
61
+ end
62
+ end
63
+
64
+ def prepare_db
65
+
66
+ @unit.storage.delete_tables if @unit.conf['sto_uri'].match(/memory/)
67
+ @unit.storage.migrate unless @unit.storage.ready?
68
+ end
69
+
70
+ def prepare_hooks
71
+
72
+ @unit.hook do |message|
73
+
74
+ if @hook == 'off'
75
+
76
+ # do nothing
77
+
78
+ elsif ! message['consumed']
79
+
80
+ # do nothing
81
+
82
+ elsif %w[ terminated failed ].include?(message['point'])
83
+
84
+ sleep 0.4 # let eventual debug print its stuff
85
+
86
+ message['payload'] = message.delete('payload')
87
+ message['consumed'] = message.delete('consumed')
88
+ # reorganize to make payload/consumed stand out
89
+
90
+ #puts Flor.to_d(message, colour: true, indent: 1, width: true)
91
+ #puts Flor.to_d(message, colour: true)
92
+
93
+ c = Flor.colours
94
+ s = StringIO.new
95
+ s << c.dg("\n /---\n")
96
+ message.each do |k, v|
97
+ s << c.dg << (' | ') << k << ': ' << v.inspect << c.rs << "\n"
98
+ end
99
+ s << c.dg(" \\---")
100
+ puts s.string
101
+ end
102
+ end
103
+ end
104
+
105
+ def print_header
106
+
107
+ puts "flosh - a flor #{Flor::VERSION} shell"
108
+ end
109
+
110
+ def prompt
111
+
112
+ root = @c.dark_gray(@root + '/')
113
+
114
+ ec = @unit.executions.where(status: 'active').count
115
+ exes = ' ' + @c.yellow("ex#{ec}")
116
+
117
+ ti = @unit.timers.where(status: 'active').count
118
+ tis = ti > 0 ? ' ' + @c.yellow("ti#{ti}") : ''
119
+
120
+ ta = nil
121
+ if Dir.exist?(File.join(@root, 'var/tasks'))
122
+ ta = Dir[File.join(@root, 'var/tasks/**/*.json')].count
123
+ end
124
+ tas = ta > 0 ? ' ' + @c.yellow("ta#{ta}") : ''
125
+
126
+ "#{root}#{exes}#{tis}#{tas} > "
127
+ end
128
+
129
+ def do_eval(line)
130
+
131
+ line = line.strip
132
+ return if line[0, 1] == '#'
133
+
134
+ md = line.split(/\s/).first
135
+ cmd = "cmd_#{md}".to_sym
136
+
137
+ if cmd.size > 4 && methods.include?(cmd)
138
+ begin
139
+ send(cmd, line)
140
+ rescue ArgumentError => aerr
141
+ puts @c.dark_gray(aerr.message)
142
+ rescue StandardError, NotImplementedError => err
143
+ p err
144
+ err.backtrace[0, 7].each { |l| puts " #{l}" }
145
+ end
146
+ else
147
+ puts "unknown command #{md.inspect}"
148
+ end
149
+ end
150
+
151
+ def do_loop
152
+
153
+ loop do
154
+
155
+ line = prompt_and_read
156
+
157
+ break unless line
158
+ next if line.strip == ''
159
+
160
+ do_eval(line)
161
+ end
162
+
163
+ $stdout.puts
164
+ end
165
+
166
+ #
167
+ # command helpers
168
+
169
+ def self.make_alias(a, b)
170
+
171
+ define_method("hlp_#{a}") { "alias to #{b.inspect}" }
172
+ alias_method "man_#{a}", "man_#{b}" rescue nil
173
+ alias_method "cmd_#{a}", "cmd_#{b}"
174
+ end
175
+
176
+ def fname(line, index=1); line.split(/\s+/)[index]; end
177
+ alias arg fname
178
+
179
+ def choose_path(line)
180
+
181
+ b = arg(line, 2)
182
+
183
+ case fname(line)
184
+ when /\Av/,
185
+ @variables_path
186
+ when /\Ap/
187
+ @payload_path
188
+ when /\At/
189
+ Dir[File.join(@root, 'var/tasks/**/*.json')].find { |pa| pa.index(b) }
190
+ when /\Ar/
191
+ @ra_flow_path
192
+ else
193
+ @flow_path
194
+ end
195
+ end
196
+
197
+ def table_style
198
+
199
+ { border_x: "#{@c.dg}-#{@c.rs}",
200
+ border_y: "#{@c.dg}|#{@c.rs}",
201
+ border_i: "#{@c.dg}+#{@c.rs}" }
202
+ end
203
+
204
+ def aright(val)
205
+
206
+ { value: val, alignment: :right }
207
+ end
208
+
209
+ def lookup_exid_nid(line)
210
+
211
+ a, b = [ arg(line, 1), arg(line, 2) ]
212
+
213
+ fail ArgumentError.new(
214
+ "please specify an exid fragment and a full nid"
215
+ ) unless b
216
+
217
+ exid, nid = a.match(/\A[0-9_]+\z/) ? [ b, a ] : [ a, b ]
218
+
219
+ onid = nid
220
+
221
+ exes = @unit.executions
222
+ .select(:exid, :content)
223
+ .where(Sequel.like(:exid, "%#{exid}%"))
224
+ .where(status: 'active')
225
+ .all
226
+
227
+ fail ArgumentError.new(
228
+ "found no execution matching \"%#{exid}%\""
229
+ ) if exes.empty?
230
+
231
+ exe = exes
232
+ .find { |e| e.data['nodes'][nid] }
233
+
234
+ fail ArgumentError.new(
235
+ "found #{exes.count} execution#{exes.count > 1 ? 's' : ''} " +
236
+ "matching \"%#{exid}%\", but none with a #{onid.inspect} node"
237
+ ) unless exe
238
+
239
+ [ exe.exid, nid ]
240
+ end
241
+
242
+ #
243
+ # the commands
244
+
245
+ def hlp_launch
246
+ %{ launches a new execution of #{@flow_path} }
247
+ end
248
+ def cmd_launch(line)
249
+
250
+ flow = File.read(@flow_path)
251
+ variables = Flor::ConfExecutor.interpret(@variables_path)
252
+ payload = Flor::ConfExecutor.interpret(@payload_path)
253
+ domain = 'shell'
254
+
255
+ exid = @unit.launch(
256
+ flow, domain: domain, vars: variables, payload: payload)
257
+
258
+ puts " launched #{@c.green}#{exid}#{@c.reset}"
259
+ end
260
+
261
+ def hlp_help
262
+ %{ displays this help }
263
+ end
264
+ def man_help
265
+ %{
266
+ * help|man
267
+ displays the list of commands and one-line help for each of them
268
+ * help|man cmd
269
+ displays the "manual" for the given command
270
+ }
271
+ end
272
+ def cmd_help(line)
273
+
274
+ if cmd = arg(line)
275
+ begin
276
+ send("man_#{cmd}").split("\n").collect(&:strip).each do |l|
277
+ if l[0, 1] == '*'
278
+ puts " #{@c.dg}*#{@c.rs} #{l[1..-1].strip}"
279
+ else
280
+ puts " #{@c.dark_gray(l)}"
281
+ end
282
+ end
283
+ rescue => err
284
+ fail ArgumentError.new("no 'manual' for #{cmd.inspect}")
285
+ end
286
+
287
+ else
288
+
289
+ puts
290
+ puts "## available commands:"
291
+ puts
292
+ COMMANDS.each do |cmd|
293
+ print "* #{@c.yellow(cmd)}"
294
+ if hlp = (send("hlp_#{cmd}") rescue nil); print " - #{hlp.strip}"; end
295
+ puts
296
+ end
297
+ puts
298
+ end
299
+ end
300
+ make_alias('h', 'help')
301
+ make_alias('man', 'help')
302
+
303
+ def hlp_exit
304
+ %{ exits this repl, with the given int exit code or 0 }
305
+ end
306
+ def cmd_exit(line)
307
+
308
+ exit(line.split(/\s+/)[1].to_i)
309
+ end
310
+
311
+ def hlp_parse
312
+ %{ parses #{@flow_path} and displays the resulting tree }
313
+ end
314
+ def man_parse
315
+ %{
316
+ * parse
317
+ parses current #{@flow_path} and render with nids (best)
318
+ * parse d|raw
319
+ parses current #{@flow_path} and render with `.to_djan`
320
+ * parse pp
321
+ parses current #{@flow_path} and render with `pp`
322
+ }
323
+ end
324
+ def cmd_parse(line)
325
+
326
+ source = File.read(@flow_path)
327
+ tree = Flor::Lang.parse(source, nil, {})
328
+
329
+ case arg(line)
330
+ when 'd', 'raw'
331
+ puts Flor.to_d(tree, colours: true, indent: 1, max_width: 80)
332
+ when 'djan'
333
+ puts Flor.to_d(tree, colours: true, indent: 1)
334
+ when 'pp'
335
+ pp tree
336
+ when 'p'
337
+ p tree
338
+ else
339
+ Flor.print_tree(tree, '0', headers: false)
340
+ end
341
+ end
342
+
343
+ def hlp_edit
344
+ %{ edit vars, payload, flow or a task }
345
+ end
346
+ def man_edit
347
+ %{
348
+ * edit v|variables
349
+ edits the variables for the next execution
350
+ * edit p|payload
351
+ edits the payload for the next execution
352
+ * edit [f|flow]
353
+ edits the flow for the next execution
354
+ * edit t|task frag
355
+ edits a task currently under var/tasks/
356
+ * edit r
357
+ edits the flow for re-applying
358
+ }
359
+ end
360
+ def cmd_edit(line)
361
+
362
+ if path = choose_path(line)
363
+ system("$EDITOR #{path}")
364
+ else
365
+ puts "not found"
366
+ end
367
+ end
368
+
369
+ def hlp_conf
370
+ %{ prints current unit configuration }
371
+ end
372
+ def cmd_conf(line)
373
+ puts Flor.to_d(@unit.conf, colour: true, indent: 1, width: true)
374
+ end
375
+
376
+ def hlp_t
377
+ %{ prints the file hierarchy for #{@root} }
378
+ end
379
+ def cmd_t(line)
380
+ puts
381
+ system("tree -C #{@root}")
382
+ end
383
+
384
+ def hlp_tasks
385
+ %{ lists the tasks currently under var/tasks/ }
386
+ end
387
+ def man_tasks
388
+ %{
389
+ * tasks
390
+ lists all tasks currently under var/tasks/{tasker}/
391
+ * tasks frag
392
+ lists all tasks whose filename matches the frag
393
+ }
394
+ end
395
+ def cmd_tasks(line)
396
+
397
+ frag = arg(line)
398
+
399
+ table = Terminal::Table.new(
400
+ #title: 'tasks',
401
+ headings: %w[ id tasker nid exid payload mtime ],
402
+ style: table_style)
403
+
404
+ tas = Dir[File.join(@root, 'var/tasks/**/*.json')]
405
+ tas = tas.select { |pa| pa.index(frag) } if frag
406
+
407
+ tas
408
+ .each_with_index { |pa, i|
409
+
410
+ ss = pa.split('/')
411
+ tasker = ss[-2]
412
+
413
+ exid, nid = Flor.extract_exid_and_nid(ss[-1])
414
+
415
+ data = JSON.parse(File.read(pa)) rescue nil
416
+ data = data || { 'payload' => nil }
417
+ #
418
+ pl = Flor.to_d(
419
+ data['payload'],
420
+ colour: false, indent: nil, compact: true)
421
+ pl = @c.dark_gray(pl[0, 21] + '...')
422
+
423
+ mt = @c.dark_gray(File.mtime(pa).to_s[0, 19])
424
+
425
+ table.add_row([
426
+ aright(i), tasker, nid, @c.yellow(exid), pl, mt ]) }
427
+
428
+ puts table
429
+ puts "#{tas.count} task#{tas.count > 1 ? 's' : ''}.\n"
430
+ end
431
+ make_alias('tas', 'tasks')
432
+
433
+ def hlp_executions
434
+ %{ lists the executions currently active }
435
+ end
436
+ def cmd_executions(line)
437
+
438
+ exes = @unit.executions
439
+ exes = exes.where(status: 'active') unless arg(line) == 'all'
440
+
441
+ table = Terminal::Table.new(
442
+ #title: 'executions',
443
+ headings: %w[ id exid started ],
444
+ style: table_style)
445
+ #table.align_column(0, :right)
446
+
447
+ exes
448
+ .each { |e|
449
+ table.add_row([
450
+ aright(e.id), @c.yl(e.exid), e.ctime[0, 19]
451
+ ]) }
452
+
453
+ puts table
454
+ puts "#{exes.count} execution#{exes.count > 1 ? 's' : ''}.\n"
455
+ end
456
+ make_alias('exes', 'executions')
457
+
458
+ def hlp_timers
459
+ %{ list the timers currently active }
460
+ end
461
+ def cmd_timers(line)
462
+
463
+ tis = @unit.timers
464
+ tis = tis.where(status: 'active') unless arg(line) == 'all'
465
+
466
+ table = Terminal::Table.new(
467
+ #title: 'timers',
468
+ headings: [ 'id', 'nid', 'exid', 'type', 'schedule', 'next time' ],
469
+ style: table_style)
470
+ #table.align_column(0, :right)
471
+ #table.align_column(4, :right)
472
+
473
+ tis
474
+ .each_with_index { |t, i|
475
+ table.add_row([
476
+ aright(t.id), t.nid, @c.yl(t.exid), t.type,
477
+ aright(t.schedule), t.ntime[0, 19]
478
+ ]) }
479
+
480
+ puts table
481
+ puts "#{tis.count} timer#{tis.count > 1 ? 's' : ''}.\n"
482
+ end
483
+ make_alias('tis', 'timers')
484
+
485
+ def hlp_return
486
+ %{ returns a task to the unit }
487
+ end
488
+ def man_return
489
+ %{
490
+ * return fragment
491
+ given a task filename fragment (for example "ache-0_0")
492
+ reads the task payload and returns it to the unit (making the
493
+ execution go on)
494
+ }
495
+ end
496
+ def cmd_return(line)
497
+
498
+ t = arg(line)
499
+
500
+ fail ArgumentError.new(
501
+ "please specify a task's exid-nid or a fragment of it"
502
+ ) unless t
503
+
504
+ path = Dir[File.join(@root, 'var/tasks/**/*.json')]
505
+ .find { |pa| pa.index(t) }
506
+
507
+ fail ArgumentError.new(
508
+ "couldn't find a task matching #{t.inspect}"
509
+ ) unless path
510
+
511
+ m = JSON.parse(File.read(path))
512
+ @unit.return(m)
513
+ FileUtils.rm(path)
514
+ end
515
+ make_alias('ret', 'return')
516
+
517
+ def hlp_debug
518
+ %{ re-sets debug flags }
519
+ end
520
+ def man_debug
521
+ %{
522
+ * debug off
523
+ mute unit activity
524
+ * debug on
525
+ verbose unit activity
526
+ }
527
+ end
528
+ def cmd_debug(line)
529
+
530
+ @unit.conf.select! { |k, v| ! k.match(/\Alog_/) }
531
+
532
+ rest = line.match(/\A[a-z]+(\s+.+)?/)[1]
533
+ rest = nil if rest && rest.strip == 'off'
534
+ rest = 'stdout,dbg' if rest && rest.strip == 'on'
535
+
536
+ @unit.conf.merge!(Flor::Conf.interpret_flor_debug(rest)) if rest
537
+ end
538
+
539
+ def hlp_hook
540
+ %{ turns hook for "failed"/"terminated" messages on or off }
541
+ end
542
+ def man_hook
543
+ %{
544
+ * hook on
545
+ hook off, "failed" or "terminated" messages will get printed
546
+ (asynchronously) to console
547
+ * hook off
548
+ messages will not get printed
549
+ }
550
+ end
551
+ def cmd_hook(line)
552
+
553
+ @hook =
554
+ case (a = arg(line))
555
+ when 'true', 'on'
556
+ 'on'
557
+ when 'false', 'off'
558
+ 'off'
559
+ else
560
+ puts "hook is #{@hook.inspect}"
561
+ @hook
562
+ end
563
+ end
564
+
565
+ def indent(s, o)
566
+
567
+ io = StringIO.new
568
+ o.to_s.split("\n").each do |line|
569
+ io.print(s); io.puts(line)
570
+ end
571
+
572
+ io.string
573
+ end
574
+
575
+ def h_to_table(h)
576
+
577
+ table = Terminal::Table.new(
578
+ headings: %w[ key value ],
579
+ style: table_style)#.merge(all_separators: true))
580
+ h.each do |k, v|
581
+ table.add_row([ k, Flor.to_d(v, colour: true, indent: 0, width: 65) ])
582
+ end
583
+
584
+ table
585
+ end
586
+
587
+ def detail_execution(id)
588
+
589
+ exe =
590
+ if id.match(/\A\d+\z/)
591
+ @unit.executions[id.to_i] ||
592
+ fail(ArgumentError.new("execution #{id} not found"))
593
+ else
594
+ @unit.executions.first(Sequel.like(:exid, "%#{id}%")) ||
595
+ fail(ArgumentError.new("execution matching \"%#{id}%\" not found"))
596
+ end
597
+
598
+ eid = { id: exe.id, exid: exe.exid }.inspect
599
+ con = Flor::Storage.from_blob(exe.values.delete(:content))
600
+ exe.values[:content] = '...'
601
+
602
+ puts @c.dg("--- exe #{eid} :")
603
+ puts h_to_table(exe.values)
604
+ puts @c.dg(" exe #{eid} content:")
605
+ nodes = con.delete('nodes')
606
+ con['nodes'] = '...'
607
+ puts indent(' ', h_to_table(con))
608
+ puts @c.dg(" exe #{eid} content/nodes:")
609
+ table = Terminal::Table.new(
610
+ headings: %w[ nid data ],
611
+ style: table_style.merge(all_separators: true))
612
+ nodes.each do |k, v|
613
+ table.add_row([ k, Flor.to_d(v, colour: true, indent: 0, width: 70) ])
614
+ end
615
+ puts indent(' ', table)
616
+ end
617
+
618
+ def detail_timer(id)
619
+
620
+ timer = @unit.timers[id.to_i]
621
+
622
+ fail ArgumentError.new("timer #{id} not found") unless timer
623
+
624
+ tid = { id: timer.id, exid: timer.exid, nid: timer.nid }.inspect
625
+ timer = timer.values
626
+ con = Flor::Storage.from_blob(timer.delete(:content))
627
+ timer[:content] = '...'
628
+
629
+ puts @c.dg("--- timer #{tid} :")
630
+ puts Flor.to_d(timer, colour: true, indent: 1, width: true)
631
+ puts @c.dg("--- timer #{tid} content:")
632
+ puts Flor.to_d(con, colour: true, indent: 1, width: true)
633
+ end
634
+
635
+ def detail_task(id)
636
+
637
+ fail NotImplementedError
638
+ end
639
+
640
+ def hlp_detail
641
+ %{ detail an execution, a task or a timer }
642
+ end
643
+ def man_detail
644
+ %{
645
+ * detail e|execution id|frag
646
+ displays full execution information
647
+ * detail ti|timer id
648
+ displays full timer information
649
+ * detail task frag
650
+ displays task
651
+ }
652
+ end
653
+ def cmd_detail(line)
654
+
655
+ type, id = arg(line), arg(line, 2)
656
+
657
+ fail ArgumentError.new(
658
+ "please specify type (exe, ti, ta) and id (int) or fragment of id"
659
+ ) unless type && id
660
+
661
+ case type
662
+ when /\Ae/ then detail_execution(id)
663
+ when /\Ati/ then detail_timer(id)
664
+ when /\At/ then detail_task(id)
665
+ else puts "unknown type #{type.inspect}"
666
+ end
667
+ end
668
+ make_alias('det', 'detail')
669
+
670
+ def hlp_cancel
671
+ %{ cancel an execution node }
672
+ end
673
+ def man_cancel
674
+ %{
675
+ * cancel exid_fragment nid
676
+ cancel the first node that matches
677
+ * cancel exid_fragment 0
678
+ cancel the first execution that matches
679
+ }
680
+ end
681
+ def cmd_cancel(line)
682
+
683
+ exid, nid = lookup_exid_nid(line)
684
+
685
+ @unit.cancel(exid: exid, nid: nid)
686
+
687
+ puts @c.yellow("cancel message queued for #{exid} #{nid}")
688
+ end
689
+ make_alias('can', 'cancel')
690
+
691
+ def render_node(t, nid)
692
+
693
+ ni = nid
694
+ head = t[0]
695
+ h = t.last.is_a?(Hash) ? t.last : {}
696
+ if h[:node]
697
+ else
698
+ ni = @c.dark_gray(ni)
699
+ head = @c.dark_gray(head)
700
+ end
701
+ if h[:tasker]
702
+ #p h[:tasker]
703
+ rest = @c.green(" <--")
704
+ end
705
+
706
+ puts " #{ni} #{head}#{rest}"
707
+
708
+ return unless t[1].is_a?(Array)
709
+
710
+ t[1].each_with_index do |ct, i|
711
+ render_node(t[1][i], "#{nid}_#{i}")
712
+ end
713
+ end
714
+
715
+ def hlp_render
716
+ %{ render execution tree with error and tasks locations }
717
+ end
718
+ def man_render
719
+ %{
720
+ * render frag
721
+ finds first execution matching fragment and renders its tree
722
+ highlight current tasks positions
723
+ }
724
+ end
725
+ def cmd_render(line)
726
+
727
+ frag = arg(line)
728
+
729
+ exe =
730
+ @unit.executions.first(Sequel.like(:exid, "%#{frag}%")) ||
731
+ fail(ArgumentError.new("execution matching \"%#{id}%\" not found"))
732
+ #p exe
733
+ tree = exe.full_tree
734
+ nodes = exe.nodes
735
+
736
+ orphans = []
737
+
738
+ nodes.each do |nid, n|
739
+ if st = Flor.tree_locate(tree, nid)
740
+ st << { node: n }
741
+ else
742
+ orphans << n
743
+ end
744
+ end
745
+
746
+ @unit.pointers.where(exid: exe.exid, type: 'tasker').each do |pt|
747
+ if st = Flor.tree_locate(tree, pt.nid)
748
+ h = st.last.is_a?(Hash) ? st.last : (st << {})
749
+ h[:tasker] = pt
750
+ else
751
+ orphans << pt
752
+ end
753
+ end
754
+
755
+ render_node(tree, '0')
756
+ end
757
+ make_alias('r', 'render')
758
+
759
+ def hlp_reapply
760
+ %{ reapply at a given node with tree ra_sratch.flo }
761
+ end
762
+ def man_reapply
763
+ %{
764
+ * reapply exid_fragment nid
765
+ }
766
+ end
767
+ def cmd_reapply(line)
768
+
769
+ exid, nid = lookup_exid_nid(line)
770
+
771
+ t = File.read(@ra_flow_path)
772
+ pl = Flor::ConfExecutor.interpret(@payload_path)
773
+
774
+ @unit.re_apply(exid: exid, nid: nid, tree: t, payload: pl)
775
+
776
+ puts @c.yellow("re-apply message queued for #{exid} #{nid}")
777
+ end
778
+
779
+ #
780
+ # use Readline if possible
781
+
782
+ COMMANDS = self.allocate.methods \
783
+ .select { |m| m.to_s.match(/^cmd_/) }.collect { |m| m[4..-1] }.sort
784
+
785
+ begin
786
+ require 'readline'
787
+ def prompt_and_read
788
+ Readline.readline(prompt, true)
789
+ end
790
+ Readline.completion_proc =
791
+ proc { |s|
792
+ r = /^#{Regexp.escape(s)}/
793
+ COMMANDS.grep(r) + Dir["#{s}*"].grep(r)
794
+ }
795
+ #Readline.completion_append_character =
796
+ # " "
797
+ rescue LoadError => le
798
+ def prompt_and_read
799
+ print(prompt)
800
+ ($stdin.readline rescue false)
801
+ end
802
+ end
803
+ end
804
+ end
805
+
806
+ if __FILE__ == $0
807
+
808
+ Flor::Tools::Shell.new(ARGV)
809
+ end
810
+