flor 0.9.5 → 0.10.0

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 (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
+