rbbt-util 5.21.7 → 5.21.8
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.
- checksums.yaml +4 -4
- data/lib/rbbt/resource/path.rb +1 -1
- data/lib/rbbt/rest/client/get.rb +15 -5
- data/lib/rbbt/rest/client/step.rb +8 -2
- data/lib/rbbt/tsv/util.rb +2 -0
- data/lib/rbbt/util/misc/concurrent_stream.rb +1 -1
- data/lib/rbbt/workflow.rb +5 -4
- data/lib/rbbt/workflow/accessor.rb +34 -35
- data/lib/rbbt/workflow/step/dependencies.rb +4 -3
- data/lib/rbbt/workflow/step/run.rb +4 -3
- data/lib/rbbt/workflow/task.rb +3 -1
- data/share/rbbt_commands/resource/produce +1 -1
- data/share/rbbt_commands/tsv/info +1 -1
- data/share/rbbt_commands/tsv/query +59 -0
- data/share/rbbt_commands/workflow/init +57 -0
- data/share/rbbt_commands/workflow/server +4 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 215819a1374483cea2057ae08343d6f03b71d41f
|
4
|
+
data.tar.gz: a22977fcc3e07a9eac6f544d77a61be31caddc9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f74b31e23ec63d0f47998a144c0149109804a3421b6f35562769d1488f5fe78b7f8469933a2a8f207c9106b1a8b8daff0ecdd8b81ff6d21eece27d734ab6734
|
7
|
+
data.tar.gz: 833a891002ef13c8bd6e8655475843af420130b621ba15113e5cddb466f9118eb93e7b0628c2a2a0c97750e24ee37ad25f2a4c851b8618958f691b312420aaa0
|
data/lib/rbbt/resource/path.rb
CHANGED
@@ -96,7 +96,7 @@ module Path
|
|
96
96
|
rsearch_paths = (resource and resource.respond_to?(:search_paths)) ? resource.search_paths : nil
|
97
97
|
key_elems = [where, caller_lib, rsearch_paths, paths]
|
98
98
|
key = Misc.digest(key_elems.inspect)
|
99
|
-
self.sub!('~/', Etc.getpwuid.dir + '/')
|
99
|
+
self.sub!('~/', Etc.getpwuid.dir + '/') if self.include? "~"
|
100
100
|
@path[key] ||= begin
|
101
101
|
paths = [paths, rsearch_paths, self.search_paths, SEARCH_PATHS].reverse.compact.inject({}){|acc,h| acc.merge! h; acc }
|
102
102
|
where = paths[:default] if where == :default
|
data/lib/rbbt/rest/client/get.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
class WorkflowRESTClient
|
2
|
+
def self.encode(url)
|
3
|
+
begin
|
4
|
+
URI.encode(url)
|
5
|
+
rescue
|
6
|
+
Log.warn $!.message
|
7
|
+
url
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
2
11
|
def self.fix_hash(hash, fix_values = false)
|
3
12
|
fixed = {}
|
4
13
|
hash.each do |key, value|
|
@@ -59,7 +68,7 @@ class WorkflowRESTClient
|
|
59
68
|
res = capture_exception do
|
60
69
|
Misc.insist(2, 0.5) do
|
61
70
|
Log.debug{ "RestClient clean: #{ url } - #{Misc.fingerprint params}" }
|
62
|
-
res = RestClient.get(
|
71
|
+
res = RestClient.get(self.encode(url), :params => params)
|
63
72
|
raise TryAgain if res.code == 202
|
64
73
|
res
|
65
74
|
end
|
@@ -73,7 +82,8 @@ class WorkflowRESTClient
|
|
73
82
|
res = capture_exception do
|
74
83
|
Misc.insist(2, 0.5) do
|
75
84
|
Log.debug{ "RestClient get_raw: #{ url } - #{Misc.fingerprint params}" }
|
76
|
-
|
85
|
+
raise "No url" if url.nil?
|
86
|
+
res = RestClient.get(self.encode(url), :params => params)
|
77
87
|
raise TryAgain if res.code == 202
|
78
88
|
res
|
79
89
|
end
|
@@ -88,7 +98,7 @@ class WorkflowRESTClient
|
|
88
98
|
|
89
99
|
res = capture_exception do
|
90
100
|
Misc.insist(2, 0.5) do
|
91
|
-
RestClient.get(
|
101
|
+
RestClient.get(self.encode(url), :params => params)
|
92
102
|
end
|
93
103
|
end
|
94
104
|
|
@@ -106,7 +116,7 @@ class WorkflowRESTClient
|
|
106
116
|
|
107
117
|
WorkflowRESTClient.__prepare_inputs_for_restclient(params)
|
108
118
|
name = capture_exception do
|
109
|
-
RestClient.post(
|
119
|
+
RestClient.post(self.encode(url), params)
|
110
120
|
end
|
111
121
|
|
112
122
|
Log.debug{ "RestClient jobname returned for #{ url } - #{Misc.fingerprint params}: #{name}" }
|
@@ -122,7 +132,7 @@ class WorkflowRESTClient
|
|
122
132
|
params = fix_params params
|
123
133
|
|
124
134
|
res = capture_exception do
|
125
|
-
RestClient.post(
|
135
|
+
RestClient.post(self.encode(url), params)
|
126
136
|
end
|
127
137
|
|
128
138
|
begin
|
@@ -204,7 +204,7 @@ class WorkflowRESTClient
|
|
204
204
|
end
|
205
205
|
|
206
206
|
|
207
|
-
def fork
|
207
|
+
def fork(noload=false, semaphore=nil)
|
208
208
|
init_job(:asynchronous)
|
209
209
|
end
|
210
210
|
|
@@ -233,6 +233,7 @@ class WorkflowRESTClient
|
|
233
233
|
end
|
234
234
|
|
235
235
|
def join
|
236
|
+
init_job unless @url
|
236
237
|
Log.debug{ "Joining RestClient: #{path}" }
|
237
238
|
if IO === @result
|
238
239
|
res = @result
|
@@ -258,6 +259,7 @@ class WorkflowRESTClient
|
|
258
259
|
params = params.merge(:_format => [:string, :boolean, :tsv, :annotations,:array].include?(result_type.to_sym) ? :raw : :json )
|
259
260
|
Misc.insist 3, rand(2) + 1 do
|
260
261
|
begin
|
262
|
+
init_job if url.nil?
|
261
263
|
WorkflowRESTClient.get_raw(url, params)
|
262
264
|
rescue
|
263
265
|
Log.exception $!
|
@@ -291,7 +293,11 @@ class WorkflowRESTClient
|
|
291
293
|
(stream ? res.read : res).split("\n")
|
292
294
|
res.split("\n")
|
293
295
|
else
|
294
|
-
|
296
|
+
if IO === res
|
297
|
+
JSON.parse res.read
|
298
|
+
else
|
299
|
+
JSON.parse res
|
300
|
+
end
|
295
301
|
end
|
296
302
|
end
|
297
303
|
|
data/lib/rbbt/tsv/util.rb
CHANGED
@@ -94,7 +94,7 @@ module ConcurrentStream
|
|
94
94
|
@pids.each do |pid|
|
95
95
|
begin
|
96
96
|
Process.waitpid(pid, Process::WUNTRACED)
|
97
|
-
raise ProcessFailed.new "Error joining process #{pid} in #{self.inspect}" unless $?.success? or no_fail
|
97
|
+
raise ProcessFailed.new "Error joining process #{pid} in #{self.filename || self.inspect}" unless $?.success? or no_fail
|
98
98
|
rescue Errno::ECHILD
|
99
99
|
end
|
100
100
|
end
|
data/lib/rbbt/workflow.rb
CHANGED
@@ -326,10 +326,11 @@ module Workflow
|
|
326
326
|
|
327
327
|
inputs.each do |k,v|
|
328
328
|
default = defaults[k]
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
329
|
+
next unless (task_inputs.include?(k.to_sym) or task_inputs.include?(k.to_s))
|
330
|
+
next if default == v
|
331
|
+
next if (String === default and Symbol === v and v.to_s == default)
|
332
|
+
next if (Symbol === default and String === v and v == default.to_s)
|
333
|
+
real_inputs[k] = v
|
333
334
|
end
|
334
335
|
|
335
336
|
if real_inputs.empty?
|
@@ -373,13 +373,17 @@ class Step
|
|
373
373
|
end
|
374
374
|
end
|
375
375
|
|
376
|
+
def stalled?
|
377
|
+
! (done? || error? || aborted?) && ! running?
|
378
|
+
end
|
379
|
+
|
376
380
|
def error?
|
377
381
|
status == :error
|
378
382
|
end
|
379
383
|
|
380
384
|
def nopid?
|
381
385
|
pid = info[:pid]
|
382
|
-
pid.nil? && ! (status == :aborted || status == :done || status == :error)
|
386
|
+
pid.nil? && ! (status.nil? || status.nil? || status == :aborted || status == :done || status == :error)
|
383
387
|
end
|
384
388
|
|
385
389
|
def aborted?
|
@@ -542,46 +546,41 @@ module Workflow
|
|
542
546
|
else
|
543
547
|
all_deps << dep unless Proc === dep
|
544
548
|
end
|
545
|
-
case dep
|
546
|
-
when Array
|
547
|
-
wf, t, o = dep
|
548
|
-
|
549
|
-
wf.rec_dependencies(t).each do |d|
|
550
|
-
if Array === d
|
551
|
-
new = d.dup
|
552
|
-
else
|
553
|
-
new = [dep.first, d]
|
554
|
-
end
|
555
549
|
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
550
|
+
begin
|
551
|
+
case dep
|
552
|
+
when Array
|
553
|
+
wf, t, o = dep
|
554
|
+
|
555
|
+
wf.rec_dependencies(t).each do |d|
|
556
|
+
if Array === d
|
557
|
+
new = d.dup
|
561
558
|
else
|
562
|
-
new.
|
559
|
+
new = [dep.first, d]
|
563
560
|
end
|
564
|
-
end
|
565
561
|
|
566
|
-
|
567
|
-
|
562
|
+
if Hash === o and not o.empty?
|
563
|
+
if Hash === new.last
|
564
|
+
hash = new.last.dup
|
565
|
+
o.each{|k,v| hash[k] ||= v}
|
566
|
+
new[new.length-1] = hash
|
567
|
+
else
|
568
|
+
new.push o.dup
|
569
|
+
end
|
570
|
+
end
|
568
571
|
|
569
|
-
|
570
|
-
rec_deps = rec_dependencies(dep.to_sym)
|
571
|
-
all_deps.concat rec_deps
|
572
|
-
when DependencyBlock
|
573
|
-
all_deps << dep.dependency if dep.dependency
|
574
|
-
case dep.dependency
|
575
|
-
when Array
|
576
|
-
dep_wf, dep_task, dep_options = dep.dependency
|
577
|
-
if dep_task === Symbol
|
578
|
-
dep_rec_dependencies = dep_wf.rec_dependencies(dep_task.to_sym)
|
579
|
-
dep_rec_dependencies.collect!{|d| Array === d ? d : [dep_wf, d]}
|
580
|
-
all_deps.concat dep_rec_dependencies
|
572
|
+
all_deps << new
|
581
573
|
end
|
582
|
-
|
583
|
-
|
574
|
+
|
575
|
+
when String, Symbol
|
576
|
+
rec_deps = rec_dependencies(dep.to_sym)
|
577
|
+
all_deps.concat rec_deps
|
578
|
+
when DependencyBlock
|
579
|
+
dep = dep.dependency
|
580
|
+
raise TryAgain
|
584
581
|
end
|
582
|
+
rescue TryAgain
|
583
|
+
retry
|
585
584
|
end
|
586
585
|
end
|
587
586
|
all_deps.uniq
|
@@ -706,7 +705,7 @@ module Workflow
|
|
706
705
|
end
|
707
706
|
else
|
708
707
|
input_options = workflow.task_info(dep_task)[:input_options][i] || {}
|
709
|
-
if input_options[:stream]
|
708
|
+
if input_options[:stream] or true
|
710
709
|
#rec_dependency.run(true).grace unless rec_dependency.done? or rec_dependency.running?
|
711
710
|
_inputs[i] = rec_dependency
|
712
711
|
else
|
@@ -76,10 +76,11 @@ class Step
|
|
76
76
|
return if job.done? && ! job.dirty?
|
77
77
|
|
78
78
|
status = job.status.to_s
|
79
|
-
|
80
|
-
|
79
|
+
|
80
|
+
if defined?(WorkflowRESTClient) && WorkflowRESTClient::RemoteStep === job
|
81
|
+
return unless (status == 'done' or status == 'error' or status == 'aborted')
|
81
82
|
else
|
82
|
-
return if status == 'streaming'
|
83
|
+
return if status == 'streaming' and job.running?
|
83
84
|
end
|
84
85
|
|
85
86
|
if (status == 'error' || job.aborted?) && job.recoverable_error?
|
@@ -243,7 +243,8 @@ class Step
|
|
243
243
|
def produce(force=false, dofork=false)
|
244
244
|
return self if done? and not dirty?
|
245
245
|
|
246
|
-
if error? or aborted?
|
246
|
+
if error? or aborted? or stalled?
|
247
|
+
abort if stalled?
|
247
248
|
if force or aborted? or recoverable_error?
|
248
249
|
clean
|
249
250
|
else
|
@@ -253,13 +254,12 @@ class Step
|
|
253
254
|
|
254
255
|
clean if dirty? or (not running? and not done?)
|
255
256
|
|
256
|
-
no_load = :stream
|
257
257
|
if dofork
|
258
258
|
fork(true) unless started?
|
259
259
|
|
260
260
|
join unless done?
|
261
261
|
else
|
262
|
-
run(
|
262
|
+
run(true) unless started?
|
263
263
|
|
264
264
|
join unless done?
|
265
265
|
end
|
@@ -478,6 +478,7 @@ class Step
|
|
478
478
|
self
|
479
479
|
ensure
|
480
480
|
set_info :joined, true
|
481
|
+
@result = nil
|
481
482
|
end
|
482
483
|
end
|
483
484
|
end
|
data/lib/rbbt/workflow/task.rb
CHANGED
@@ -84,12 +84,14 @@ module Task
|
|
84
84
|
task_name, wf = wf, workflow if task_name.nil? and Symbol === wf or String === wf
|
85
85
|
next if task_name.nil?
|
86
86
|
task = wf.tasks[task_name.to_sym]
|
87
|
-
else
|
87
|
+
else
|
88
88
|
next
|
89
89
|
end
|
90
|
+
|
90
91
|
maps = (Array === dep and Hash === dep.last) ? dep.last.keys : []
|
91
92
|
raise "Dependency task not found: #{dep}" if task.nil?
|
92
93
|
next if seen.include? [wf, task.name]
|
94
|
+
|
93
95
|
seen << [wf, task.name]
|
94
96
|
new_inputs = task.inputs - maps
|
95
97
|
next unless new_inputs.any?
|
@@ -5,7 +5,7 @@ require 'rbbt/resource'
|
|
5
5
|
require 'rbbt/workflow'
|
6
6
|
|
7
7
|
options = SOPT.get <<EOF
|
8
|
-
-
|
8
|
+
-W--workflows* Workflows to use; 'all' for all in Rbbt.etc.workflows:
|
9
9
|
-r--requires* Files to require; 'all' for all in Rbbt.etc.requires:
|
10
10
|
-f--force Force the production if the file is already present
|
11
11
|
-h--help Help
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rbbt-util'
|
4
|
+
require 'rbbt/util/simpleopt'
|
5
|
+
|
6
|
+
$0 = "rbbt #{$previous_commands*""} #{ File.basename(__FILE__) }" if $previous_commands
|
7
|
+
|
8
|
+
options = SOPT.setup <<EOF
|
9
|
+
Query a TSV file
|
10
|
+
|
11
|
+
$ rbbt tsv query [options] <file.tsv> <key>
|
12
|
+
|
13
|
+
Display summary information for a TSV entry. Works with Tokyocabinet HDB and BDB.
|
14
|
+
|
15
|
+
-tch--tokyocabinet File is a TC HDB
|
16
|
+
-tcb--tokyocabinet_bd File is a TC BDB
|
17
|
+
-t--type* Type of tsv (single, list, double, flat)
|
18
|
+
-hh--header_hash* Change the character used to mark the header line (defaults to #)
|
19
|
+
-k--key_field* Change the key field
|
20
|
+
-f--field* Change the fields to display
|
21
|
+
-s--sep* Change the fields separator (default TAB)
|
22
|
+
-h--help Help
|
23
|
+
EOF
|
24
|
+
|
25
|
+
SOPT.usage if options[:help]
|
26
|
+
|
27
|
+
file, key = ARGV
|
28
|
+
|
29
|
+
file = STDIN if file == '-'
|
30
|
+
|
31
|
+
raise ParameterException, "Please specify the tsv file as argument" if file.nil?
|
32
|
+
|
33
|
+
options[:fields] = options[:fields].split(/[,\|]/) if options[:fields]
|
34
|
+
options[:header_hash] = options["header_hash"]
|
35
|
+
options[:sep] = options["sep"]
|
36
|
+
|
37
|
+
case
|
38
|
+
when options[:tokyocabinet]
|
39
|
+
tsv = Persist.open_tokyocabinet(file, false)
|
40
|
+
when options[:tokyocabinet_bd]
|
41
|
+
tsv = Persist.open_tokyocabinet(file, false, nil, TokyoCabinet::BDB)
|
42
|
+
else
|
43
|
+
tsv = TSV.open(file, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
values = tsv[key]
|
47
|
+
|
48
|
+
head = "#{Log.color :magenta, tsv.key_field}: " << Log.color(:yellow, key)
|
49
|
+
puts head
|
50
|
+
puts (["-"] * Log.uncolor(head).length) * ""
|
51
|
+
values.zip(tsv.fields) do |value,field|
|
52
|
+
if Array === value
|
53
|
+
values = value.collect{|v| Log.color(:yellow, v)} * ", "
|
54
|
+
puts "#{Log.color :magenta, field}: " << values
|
55
|
+
else
|
56
|
+
puts "#{Log.color :magenta, field}: " << Log.color(:yellow, value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rbbt-util'
|
4
|
+
require 'rbbt/workflow'
|
5
|
+
|
6
|
+
options = SOPT.setup <<EOF
|
7
|
+
|
8
|
+
Init a new workflow scaffold
|
9
|
+
|
10
|
+
$ rbbt workflow init <workflow>
|
11
|
+
EOF
|
12
|
+
|
13
|
+
workflow = ARGV.shift
|
14
|
+
if workflow.nil?
|
15
|
+
usage
|
16
|
+
puts
|
17
|
+
puts Log.color :magenta, "## Error"
|
18
|
+
puts
|
19
|
+
puts "No workflow name specified."
|
20
|
+
puts
|
21
|
+
exit -1
|
22
|
+
end
|
23
|
+
|
24
|
+
workflow_path = Path.setup(workflow) unless Path === workflow
|
25
|
+
lib_path = workflow_path + '/lib'
|
26
|
+
|
27
|
+
if Dir.exists?(workflow_path)
|
28
|
+
usage
|
29
|
+
puts
|
30
|
+
puts Log.color :magenta, "## Error"
|
31
|
+
puts
|
32
|
+
puts "The workflow '#{workflow}' already exists!"
|
33
|
+
puts
|
34
|
+
exit -1
|
35
|
+
end
|
36
|
+
|
37
|
+
template = <<-EOF
|
38
|
+
require 'rbbt/workflow'
|
39
|
+
|
40
|
+
module #{workflow}
|
41
|
+
extend Workflow
|
42
|
+
|
43
|
+
desc "Scaffold task"
|
44
|
+
task :scaffold_task => :string do
|
45
|
+
"Scaffold task"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
EOF
|
50
|
+
|
51
|
+
Dir.mkdir(workflow_path)
|
52
|
+
workflow_file = workflow_path + '/workflow.rb'
|
53
|
+
File.write(workflow_file, template)
|
54
|
+
|
55
|
+
Dir.mkdir(lib_path)
|
56
|
+
lib_file = lib_path + '/.keep'
|
57
|
+
File.write(lib_file, '')
|
@@ -19,6 +19,7 @@ $ rbbt workflow server [options] <Workflow>
|
|
19
19
|
-RS--Rserve_session* Rserve session to use, otherwise start new one
|
20
20
|
-wd--workdir* Change the working directory of the workflow
|
21
21
|
-W--workflows* List of additional workflows to load
|
22
|
+
-R--requires* Require a list of files
|
22
23
|
--views* Directory with view templates
|
23
24
|
--stream Activate streaming of workflow tasks
|
24
25
|
--export* Export workflow tasks (asynchronous)
|
@@ -49,6 +50,7 @@ options[:Bind] ||= "0.0.0.0"
|
|
49
50
|
|
50
51
|
workflow = ARGV.shift
|
51
52
|
workflows = options[:workflows] || ""
|
53
|
+
requires = options[:requires] || ""
|
52
54
|
|
53
55
|
workflow = File.expand_path(workflow) if File.exist?(workflow)
|
54
56
|
|
@@ -73,7 +75,8 @@ TmpFile.with_file do |app_dir|
|
|
73
75
|
Open.write(app_dir.etc.target_workflow_sync_exports.find, sync_exports * "\n") if sync_exports
|
74
76
|
Open.write(app_dir.etc.target_workflow_exec_exports.find, exec_exports * "\n") if exec_exports
|
75
77
|
|
76
|
-
Open.write(app_dir.etc.workflows.find, workflows.split(/,\s*/)*"\n") if workflows
|
78
|
+
Open.write(app_dir.etc.workflows.find, workflows.split(/,\s*/)*"\n") if workflows and not workflows.empty?
|
79
|
+
Open.write(app_dir.etc.requires.find, requires.split(/,\s*/)*"\n") if requires and not requires.empty?
|
77
80
|
|
78
81
|
require 'rack'
|
79
82
|
ENV["RBBT_FINDER"] = "true" if options.include?(:finder)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rbbt-util
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.21.
|
4
|
+
version: 5.21.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miguel Vazquez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -357,6 +357,7 @@ files:
|
|
357
357
|
- share/rbbt_commands/tsv/head
|
358
358
|
- share/rbbt_commands/tsv/info
|
359
359
|
- share/rbbt_commands/tsv/json
|
360
|
+
- share/rbbt_commands/tsv/query
|
360
361
|
- share/rbbt_commands/tsv/read
|
361
362
|
- share/rbbt_commands/tsv/slice
|
362
363
|
- share/rbbt_commands/tsv/sort
|
@@ -367,6 +368,7 @@ files:
|
|
367
368
|
- share/rbbt_commands/workflow/cmd
|
368
369
|
- share/rbbt_commands/workflow/example
|
369
370
|
- share/rbbt_commands/workflow/info
|
371
|
+
- share/rbbt_commands/workflow/init
|
370
372
|
- share/rbbt_commands/workflow/install
|
371
373
|
- share/rbbt_commands/workflow/jobs
|
372
374
|
- share/rbbt_commands/workflow/knowledge_base
|