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