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.
- data/CHANGELOG.md +10 -0
- data/Makefile +13 -5
- data/README.md +0 -35
- data/flor.gemspec +1 -0
- data/lib/flor.rb +6 -24
- data/lib/flor/changes.rb +26 -0
- data/lib/flor/colours.rb +65 -31
- data/lib/flor/conf.rb +84 -54
- data/lib/flor/core.rb +0 -23
- data/lib/flor/core/executor.rb +12 -42
- data/lib/flor/core/node.rb +19 -24
- data/lib/flor/core/procedure.rb +13 -24
- data/lib/flor/core/texecutor.rb +10 -28
- data/lib/flor/deep.rb +152 -0
- data/lib/flor/djan.rb +200 -0
- data/lib/flor/dollar.rb +0 -24
- data/lib/flor/errors.rb +0 -24
- data/lib/flor/flor.rb +43 -296
- data/lib/flor/id.rb +90 -0
- data/lib/flor/log.rb +12 -35
- data/lib/flor/migrations/0002_cunit_and_munit.rb +86 -0
- data/lib/flor/parser.rb +40 -46
- data/lib/flor/pcore/_arr.rb +0 -24
- data/lib/flor/pcore/_atom.rb +0 -24
- data/lib/flor/pcore/_att.rb +3 -25
- data/lib/flor/pcore/_dump.rb +0 -24
- data/lib/flor/pcore/_err.rb +0 -24
- data/lib/flor/pcore/_happly.rb +0 -24
- data/lib/flor/pcore/_obj.rb +0 -24
- data/lib/flor/pcore/_skip.rb +0 -24
- data/lib/flor/pcore/arith.rb +0 -24
- data/lib/flor/pcore/break.rb +0 -24
- data/lib/flor/pcore/case.rb +127 -0
- data/lib/flor/pcore/cmp.rb +0 -24
- data/lib/flor/pcore/cond.rb +24 -24
- data/lib/flor/pcore/cursor.rb +0 -24
- data/lib/flor/pcore/define.rb +0 -24
- data/lib/flor/pcore/fail.rb +0 -24
- data/lib/flor/pcore/if.rb +39 -0
- data/lib/flor/pcore/loop.rb +0 -24
- data/lib/flor/pcore/map.rb +0 -24
- data/lib/flor/pcore/match.rb +0 -24
- data/lib/flor/pcore/move.rb +0 -24
- data/lib/flor/pcore/noeval.rb +0 -24
- data/lib/flor/pcore/noret.rb +0 -24
- data/lib/flor/pcore/push.rb +1 -25
- data/lib/flor/pcore/rand.rb +59 -0
- data/lib/flor/pcore/sequence.rb +0 -24
- data/lib/flor/pcore/set.rb +0 -24
- data/lib/flor/pcore/stall.rb +0 -24
- data/lib/flor/pcore/until.rb +0 -24
- data/lib/flor/pcore/val.rb +0 -24
- data/lib/flor/punit/cancel.rb +0 -24
- data/lib/flor/punit/cmap.rb +0 -24
- data/lib/flor/punit/concurrence.rb +54 -24
- data/lib/flor/punit/every.rb +0 -24
- data/lib/flor/punit/graft.rb +41 -0
- data/lib/flor/punit/on.rb +0 -24
- data/lib/flor/punit/schedule.rb +0 -24
- data/lib/flor/punit/signal.rb +0 -24
- data/lib/flor/punit/sleep.rb +0 -24
- data/lib/flor/punit/task.rb +0 -26
- data/lib/flor/punit/trace.rb +0 -24
- data/lib/flor/punit/trap.rb +0 -24
- data/lib/flor/to_string.rb +4 -25
- data/lib/flor/tools/env.rb +0 -23
- data/lib/flor/tools/shell.rb +810 -0
- data/lib/flor/unit.rb +0 -23
- data/lib/flor/unit/executor.rb +35 -31
- data/lib/flor/unit/ganger.rb +9 -34
- data/lib/flor/unit/hooker.rb +5 -25
- data/lib/flor/unit/journal.rb +0 -23
- data/lib/flor/unit/loader.rb +63 -94
- data/lib/flor/unit/logger.rb +8 -27
- data/lib/flor/unit/models.rb +0 -24
- data/lib/flor/unit/models/execution.rb +13 -24
- data/lib/flor/unit/models/pointer.rb +0 -24
- data/lib/flor/unit/models/timer.rb +0 -24
- data/lib/flor/unit/models/trace.rb +0 -24
- data/lib/flor/unit/models/trap.rb +0 -24
- data/lib/flor/unit/scheduler.rb +157 -128
- data/lib/flor/unit/storage.rb +224 -167
- data/lib/flor/unit/taskers.rb +38 -25
- data/lib/flor/unit/waiter.rb +7 -26
- data/lib/flor/unit/wlist.rb +8 -24
- metadata +28 -7
- data/fail.txt +0 -16
- data/intercepted.txt +0 -123
- data/lib/flor/pcore/ife.rb +0 -56
- data/lib/flor/tools/repl.rb +0 -231
- data/out.txt +0 -206
data/lib/flor/tools/env.rb
CHANGED
@@ -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
|
+
|