mushy 0.21.2 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/mushy +5 -9
- data/lib/mushy/builder/api.rb +146 -152
- data/lib/mushy/builder/index.rb +1 -0
- data/lib/mushy/flux.rb +2 -1
- data/lib/mushy/fluxs/bash.rb +50 -58
- data/lib/mushy/fluxs/browser.rb +232 -240
- data/lib/mushy/fluxs/build_csv.rb +60 -68
- data/lib/mushy/fluxs/cli.rb +22 -28
- data/lib/mushy/fluxs/document.rb +33 -39
- data/lib/mushy/fluxs/environment.rb +23 -36
- data/lib/mushy/fluxs/file_watch.rb +114 -77
- data/lib/mushy/fluxs/write_json.rb +30 -40
- data/lib/mushy.rb +6 -6
- data/mushy.gemspec +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e76c4fac239938383546eb9dcec5faa82b28dc94663343c3c16345ddf45ee5f
|
4
|
+
data.tar.gz: d5a6e435fa286e0cba5e52bc43752c7a2723d629c15566c214dfab368de00d06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06ed9957d991a0158ddd30b62cf5c2652ec1271f117479e0f4f240bb8b24f0d735e921b9be32f373e5d96f614e8623a1d8e924c52e919173a7e4c8da60446bac
|
7
|
+
data.tar.gz: e1d42f2ed6a29433a0c802d46a840e13f5516d607436a2044c8da8265fcc465db29a8aa081c787409ab296f590f5eb045e32126e9050c8f4a08485a19408052e
|
data/bin/mushy
CHANGED
@@ -4,7 +4,6 @@ require 'thor'
|
|
4
4
|
require 'mushy'
|
5
5
|
|
6
6
|
class MushyCLI < Thor
|
7
|
-
|
8
7
|
argument :file, optional: true, type: :string
|
9
8
|
argument :values, optional: true, type: :hash
|
10
9
|
|
@@ -16,29 +15,26 @@ class MushyCLI < Thor
|
|
16
15
|
|
17
16
|
desc "build FILE", 'Build a flow.'
|
18
17
|
def build
|
19
|
-
|
20
|
-
MushyCLI.set_special( { method: 'build', file: file } )
|
21
|
-
|
18
|
+
MushyCLI.set_special_values( { method: 'build', file: file } )
|
22
19
|
end
|
23
20
|
|
24
|
-
def self.
|
21
|
+
def self.set_special_values data
|
25
22
|
@special = data
|
26
23
|
end
|
27
24
|
|
28
|
-
def self.
|
25
|
+
def self.get_special_values
|
29
26
|
@special
|
30
27
|
end
|
31
|
-
|
32
28
|
end
|
33
29
|
|
34
30
|
MushyCLI.start(ARGV)
|
35
31
|
|
36
|
-
exit unless MushyCLI.
|
32
|
+
exit unless MushyCLI.get_special_values
|
37
33
|
|
38
34
|
require 'sinatra'
|
39
35
|
enable :run
|
40
36
|
|
41
|
-
the_file = MushyCLI.
|
37
|
+
the_file = MushyCLI.get_special_values[:file]
|
42
38
|
|
43
39
|
get '/' do
|
44
40
|
Mushy::Builder::Index.file
|
data/lib/mushy/builder/api.rb
CHANGED
@@ -1,191 +1,185 @@
|
|
1
1
|
require 'daemons'
|
2
2
|
|
3
|
-
module Mushy
|
3
|
+
module Mushy::Builder
|
4
|
+
end
|
4
5
|
|
5
|
-
|
6
|
+
module Mushy::Builder::Api
|
7
|
+
def self.run(data)
|
8
|
+
data = SymbolizedHash.new JSON.parse(data)
|
6
9
|
|
7
|
-
|
10
|
+
event = SymbolizedHash.new JSON.parse(data[:setup][:event].to_json)
|
8
11
|
|
9
|
-
|
12
|
+
config = SymbolizedHash.new data[:config]
|
10
13
|
|
11
|
-
|
14
|
+
flux = Mushy::Flow.build_flux({ type: data[:setup][:flux], config: config })
|
12
15
|
|
13
|
-
|
16
|
+
result = flux.execute event
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
flux = Mushy::Flow.build_flux( { type: data[:setup][:flux], config: config } )
|
18
|
-
|
19
|
-
result = flux.execute event
|
20
|
-
|
21
|
-
[result].flatten
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.save file, data
|
25
|
-
|
26
|
-
file = "#{file}.mushy" unless file.downcase.end_with?('.mushy')
|
27
|
-
|
28
|
-
data = SymbolizedHash.new JSON.parse(data)
|
29
|
-
Mushy::WriteFile.new.process( {}, { name: file, data: JSON.pretty_generate(data) })
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.start file, event
|
34
|
-
original_file = file
|
35
|
-
file = [file, "#{Dir.home}/.mushy/#{file}"]
|
36
|
-
.map { |x| (x.downcase.end_with?('.mushy') ? x : "#{x}.mushy") }
|
37
|
-
.select { |x| File.exist?(x) }
|
38
|
-
.first
|
39
|
-
|
40
|
-
unless file
|
41
|
-
puts "#{original_file} does not exist."
|
42
|
-
return
|
43
|
-
end
|
44
|
-
|
45
|
-
flow = File.open(file).read
|
46
|
-
flow = Mushy::Flow.parse flow
|
18
|
+
[result].flatten
|
19
|
+
end
|
47
20
|
|
48
|
-
|
21
|
+
def self.save(file, data)
|
22
|
+
file = "#{file}.mushy" unless file.downcase.end_with?('.mushy')
|
49
23
|
|
50
|
-
|
24
|
+
data = SymbolizedHash.new JSON.parse(data)
|
25
|
+
Mushy::WriteFile.new.process({}, { name: file, data: JSON.pretty_generate(data) })
|
26
|
+
end
|
51
27
|
|
52
|
-
|
28
|
+
def self.start(file, event)
|
29
|
+
original_file = file
|
30
|
+
file = [file, "#{Dir.home}/.mushy/#{file}"]
|
31
|
+
.map { |x| (x.downcase.end_with?('.mushy') ? x : "#{x}.mushy") }
|
32
|
+
.select { |x| File.exist?(x) }
|
33
|
+
.first
|
53
34
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end,
|
59
|
-
run_method: (s.config[:run_strategy] == 'daemon' ? :run_this_as_a_daemon : :run_this_inline),
|
60
|
-
}
|
61
|
-
}.group_by { |x| x[:run_method] }
|
35
|
+
unless file
|
36
|
+
puts "#{original_file} does not exist."
|
37
|
+
return
|
38
|
+
end
|
62
39
|
|
63
|
-
|
64
|
-
|
65
|
-
.map { |x| ->() { loop &x } }
|
66
|
-
.map { |x| run_this_as_a_daemon &x }
|
40
|
+
flow = File.open(file).read
|
41
|
+
flow = Mushy::Flow.parse flow
|
67
42
|
|
68
|
-
|
69
|
-
.map { |p| ->() { p[:flux].loop &p[:proc] } }
|
70
|
-
.map { |x| ->() { loop &x } }
|
71
|
-
.map { |x| run_this_inline &x }
|
43
|
+
service_fluxes = flow.fluxs.select { |x| x.respond_to? :loop }
|
72
44
|
|
73
|
-
|
74
|
-
end
|
45
|
+
pwd = Dir.pwd
|
75
46
|
|
76
|
-
|
47
|
+
if service_fluxes.any?
|
77
48
|
|
78
|
-
|
79
|
-
|
49
|
+
things = service_fluxes
|
50
|
+
.map do |s|
|
51
|
+
{
|
52
|
+
flux: s,
|
53
|
+
proc: lambda do |e|
|
54
|
+
Dir.chdir(pwd)
|
55
|
+
Mushy::Runner.new.start(e, s, flow)
|
56
|
+
end,
|
57
|
+
run_method: (s.config[:run_strategy] == 'daemon' ? :run_this_as_a_daemon : :run_this_inline)
|
58
|
+
}
|
59
|
+
end.group_by { |x| x[:run_method] }
|
80
60
|
|
81
|
-
|
82
|
-
|
83
|
-
|
61
|
+
(things[:run_this_as_a_daemon] || [])
|
62
|
+
.map { |p| -> { p[:flux].loop(&p[:proc]) } }
|
63
|
+
.map { |x| -> { loop(&x) } }
|
64
|
+
.map { |x| run_this_as_a_daemon(&x) }
|
84
65
|
|
85
|
-
|
86
|
-
|
87
|
-
|
66
|
+
(things[:run_this_inline] || [])
|
67
|
+
.map { |p| -> { p[:flux].loop(&p[:proc]) } }
|
68
|
+
.map { |x| -> { loop(&x) } }
|
69
|
+
.map { |x| run_this_inline(&x) }
|
88
70
|
|
89
|
-
|
90
|
-
|
91
|
-
data = JSON.parse File.open(file).read
|
71
|
+
exit
|
72
|
+
end
|
92
73
|
|
93
|
-
|
94
|
-
data['fluxs'] = organize_as_a_flattened_tree_based_on_parents data['fluxs']
|
74
|
+
cli_flux = flow.fluxs.select { |x| x.is_a?(Mushy::Cli) }.first
|
95
75
|
|
96
|
-
|
97
|
-
|
98
|
-
{ fluxs: [] }
|
99
|
-
end
|
76
|
+
Mushy::Runner.new.start event, cli_flux, flow
|
77
|
+
end
|
100
78
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
.each { |x| x['parents'] = [x['parent']].select { |y| y } }
|
105
|
-
fluxs
|
106
|
-
.select { |x| x['parent'] }
|
107
|
-
.each { |x| x.delete 'parent' }
|
108
|
-
fluxs
|
109
|
-
.select { |x| x['parents'] }
|
110
|
-
.each { |x| x['parents'] = x['parents'].select { |y| y } }
|
111
|
-
|
112
|
-
fluxs
|
113
|
-
end
|
79
|
+
def self.run_this_inline(&block)
|
80
|
+
block.call
|
81
|
+
end
|
114
82
|
|
115
|
-
|
116
|
-
|
83
|
+
def self.run_this_as_a_daemon(&block)
|
84
|
+
Daemons.call(&block).pid.pid
|
85
|
+
end
|
117
86
|
|
118
|
-
|
87
|
+
def self.get_flow(file)
|
88
|
+
file = "#{file}.mushy" unless file.downcase.end_with?('.mushy')
|
89
|
+
data = JSON.parse File.open(file).read
|
119
90
|
|
120
|
-
|
91
|
+
data['fluxs'] = standardize_these data['fluxs']
|
92
|
+
data['fluxs'] = organize_as_a_flattened_tree_based_on_parents data['fluxs']
|
121
93
|
|
122
|
-
|
94
|
+
data
|
95
|
+
rescue
|
96
|
+
{ fluxs: [] }
|
97
|
+
end
|
123
98
|
|
124
|
-
|
125
|
-
|
126
|
-
|
99
|
+
def self.standardize_these(fluxs)
|
100
|
+
fluxs
|
101
|
+
.reject { |x| x['parents'] }
|
102
|
+
.each { |x| x['parents'] = [x['parent']].select { |y| y } }
|
103
|
+
fluxs
|
104
|
+
.select { |x| x['parent'] }
|
105
|
+
.each { |x| x.delete 'parent' }
|
106
|
+
fluxs
|
107
|
+
.select { |x| x['parents'] }
|
108
|
+
.each { |x| x['parents'] = x['parents'].select { |y| y } }
|
109
|
+
|
110
|
+
fluxs
|
111
|
+
end
|
127
112
|
|
128
|
-
|
113
|
+
def self.organize_as_a_flattened_tree_based_on_parents(fluxs)
|
114
|
+
fluxs = fluxs.sort_by { |x| x['parents'].count }
|
129
115
|
|
130
|
-
|
116
|
+
new_fluxs = [fluxs.first]
|
131
117
|
|
132
|
-
|
118
|
+
loop do
|
119
|
+
next_fluxs = fluxs.select { |x| x['parents'].include? new_fluxs[-1]['id'] }
|
133
120
|
|
134
|
-
|
121
|
+
unless next_fluxs.any?
|
122
|
+
next_fluxs = [fluxs.reject { |x| new_fluxs.map { |y| y['id'] }.include?(x['id']) }[0]].select { |x| x }
|
135
123
|
end
|
136
124
|
|
137
|
-
|
138
|
-
{
|
139
|
-
fluxs: Mushy::Flux.all.select { |x| x.respond_to? :details }.select { |x| x.details }.map do |flux|
|
140
|
-
details = flux.details
|
141
|
-
|
142
|
-
details[:documentation] = Documentation.build_from details
|
143
|
-
|
144
|
-
details[:config][:incoming_split] = { type: 'text', shrink: true, description: 'Split an incoming event into multiple events by this key, an each event will be processed independently.', default: '' }
|
145
|
-
details[:config][:outgoing_split] = { type: 'text', shrink: true, description: 'Split an outgoing event into multiple events by this key.', default: '' }
|
146
|
-
details[:config][:merge] = { type: 'text', shrink: true, description: 'A comma-delimited list of fields from the event to carry through. Use * to merge all fields.', default: '' }
|
147
|
-
details[:config][:group] = { type: 'text', shrink: true, description: 'Group events by this key, with the value as the key. If a group key is provided like group_by|group_key, then multiple events with the results under group_key will be returned.', default: '' }
|
148
|
-
details[:config][:limit] = { type: 'integer', shrink: true, description: 'Limit the number of events to this number.', default: '' }
|
149
|
-
details[:config][:join] = { type: 'text', shrink: true, description: 'Join all of the events from this flux into one event, under this name.', default: '' }
|
150
|
-
details[:config][:sort] = { type: 'text', shrink: true, description: 'Sort by this key.', default: '' }
|
151
|
-
details[:config][:ignore] = { type: 'text', shrink: true, description: 'Ignore these keys.', value: '', default: '' }
|
152
|
-
details[:config][:model] = { type: 'keyvalue', shrink: true, description: 'Reshape the outgoing events.', value: {}, default: {} }
|
153
|
-
|
154
|
-
details[:config][:error_strategy] = {
|
155
|
-
description: 'Error strategy. (return to return an event with "exception" returning the error, or ignore to ignore the exception)',
|
156
|
-
type: 'select',
|
157
|
-
options: ['', 'return', 'ignore'],
|
158
|
-
value: '',
|
159
|
-
shrink: true,
|
160
|
-
}
|
161
|
-
|
162
|
-
if flux.new.respond_to? :loop
|
163
|
-
details[:config][:run_strategy] = {
|
164
|
-
description: 'Run this using this strategy. (select "daemon" if this should be run in the background)',
|
165
|
-
type: 'select',
|
166
|
-
options: ['', 'inline', 'daemon'],
|
167
|
-
value: '',
|
168
|
-
shrink: true,
|
169
|
-
}
|
170
|
-
end
|
171
|
-
|
172
|
-
details[:config]
|
173
|
-
.select { |_, v| v[:type] == 'keyvalue' }
|
174
|
-
.select { |_, v| v[:editors].nil? }
|
175
|
-
.each do |_, v|
|
176
|
-
v[:editors] = [
|
177
|
-
{ id: 'new_key', target: 'key', field: { type: 'text', value: '', default: '' } },
|
178
|
-
{ id: 'new_value', target: 'value', field: { type: 'text', value: '', default: '' } }
|
179
|
-
]
|
180
|
-
end
|
181
|
-
|
182
|
-
details
|
183
|
-
end.sort_by { |x| x[:name] }
|
184
|
-
}
|
185
|
-
end
|
125
|
+
new_fluxs = [new_fluxs, next_fluxs].flatten
|
186
126
|
|
127
|
+
break unless next_fluxs.any?
|
187
128
|
end
|
188
129
|
|
130
|
+
new_fluxs
|
189
131
|
end
|
190
132
|
|
191
|
-
|
133
|
+
def self.get_fluxs
|
134
|
+
{
|
135
|
+
fluxs: Mushy::Flux.all
|
136
|
+
.select { |x| x.respond_to? :details }
|
137
|
+
.select(&:details)
|
138
|
+
.map do |flux|
|
139
|
+
details = flux.details
|
140
|
+
|
141
|
+
details[:documentation] = Mushy::Builder::Documentation.build_from details
|
142
|
+
|
143
|
+
details[:config][:incoming_split] = { type: 'text', shrink: true, description: 'Split an incoming event into multiple events by this key, an each event will be processed independently.', default: '' }
|
144
|
+
details[:config][:outgoing_split] = { type: 'text', shrink: true, description: 'Split an outgoing event into multiple events by this key.', default: '' }
|
145
|
+
details[:config][:merge] = { type: 'text', shrink: true, description: 'A comma-delimited list of fields from the event to carry through. Use * to merge all fields.', default: '' }
|
146
|
+
details[:config][:group] = { type: 'text', shrink: true, description: 'Group events by this key, with the value as the key. If a group key is provided like group_by|group_key, then multiple events with the results under group_key will be returned.', default: '' }
|
147
|
+
details[:config][:limit] = { type: 'integer', shrink: true, description: 'Limit the number of events to this number.', default: '' }
|
148
|
+
details[:config][:join] = { type: 'text', shrink: true, description: 'Join all of the events from this flux into one event, under this name.', default: '' }
|
149
|
+
details[:config][:sort] = { type: 'text', shrink: true, description: 'Sort by this key.', default: '' }
|
150
|
+
details[:config][:ignore] = { type: 'text', shrink: true, description: 'Ignore these keys.', value: '', default: '' }
|
151
|
+
details[:config][:model] = { type: 'keyvalue', shrink: true, description: 'Reshape the outgoing events.', value: {}, default: {} }
|
152
|
+
|
153
|
+
details[:config][:error_strategy] = {
|
154
|
+
description: 'Error strategy. (return to return an event with "exception" returning the error, or ignore to ignore the exception)',
|
155
|
+
type: 'select',
|
156
|
+
options: ['', 'return', 'ignore'],
|
157
|
+
value: '',
|
158
|
+
shrink: true
|
159
|
+
}
|
160
|
+
|
161
|
+
if flux.new.respond_to? :loop
|
162
|
+
details[:config][:run_strategy] = {
|
163
|
+
description: 'Run this using this strategy. (select "daemon" if this should be run in the background)',
|
164
|
+
type: 'select',
|
165
|
+
options: ['', 'inline', 'daemon'],
|
166
|
+
value: '',
|
167
|
+
shrink: true
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
details[:config]
|
172
|
+
.select { |_, v| v[:type] == 'keyvalue' }
|
173
|
+
.select { |_, v| v[:editors].nil? }
|
174
|
+
.each do |_, v|
|
175
|
+
v[:editors] = [
|
176
|
+
{ id: 'new_key', target: 'key', field: { type: 'text', value: '', default: '' } },
|
177
|
+
{ id: 'new_value', target: 'value', field: { type: 'text', value: '', default: '' } }
|
178
|
+
]
|
179
|
+
end
|
180
|
+
|
181
|
+
details
|
182
|
+
end.sort_by { |x| x[:name] }
|
183
|
+
}
|
184
|
+
end
|
185
|
+
end
|
data/lib/mushy/builder/index.rb
CHANGED
@@ -479,6 +479,7 @@ module Mushy
|
|
479
479
|
Vue.set(app.results, 'errorMessage', '');
|
480
480
|
var the_setup = thingToData(app.setup);
|
481
481
|
the_setup.event = c.test_event;
|
482
|
+
c['_test_mode'] = true;
|
482
483
|
axios.post('/run', { config: c, setup: the_setup })
|
483
484
|
.then(function(r){
|
484
485
|
Vue.set(app.setup.testResultModal, 'is-active', true);
|
data/lib/mushy/flux.rb
CHANGED
@@ -63,7 +63,8 @@ module Mushy
|
|
63
63
|
the_original_join = mashed_config[:join]
|
64
64
|
mashed_config[:join] = nil if mashed_config[:incoming_split]
|
65
65
|
|
66
|
-
|
66
|
+
method = config[:_test_mode] && respond_to?(:test) ? :test : :process
|
67
|
+
results = send(method, event, mashed_config)
|
67
68
|
|
68
69
|
returned_one_result = results.is_a?(Hash)
|
69
70
|
|
data/lib/mushy/fluxs/bash.rb
CHANGED
@@ -1,66 +1,58 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
1
|
+
class Mushy::Bash < Mushy::Flux
|
2
|
+
def self.details
|
3
|
+
{
|
4
|
+
name: 'Bash',
|
5
|
+
title: 'Execute a command via bash',
|
6
|
+
description: 'Run a bash command.',
|
7
|
+
fluxGroup: { name: 'Execute' },
|
8
|
+
config: {
|
9
|
+
command: {
|
10
|
+
description: 'The command to run in bash.',
|
11
|
+
type: 'text',
|
12
|
+
value: '{{command}}'
|
13
|
+
},
|
14
|
+
directory: {
|
15
|
+
description: 'The working directory in which the command will be run.',
|
16
|
+
type: 'text',
|
17
|
+
shrink: true,
|
18
|
+
value: ''
|
19
|
+
}
|
20
|
+
},
|
21
|
+
examples: {
|
22
|
+
'Successful Call' => {
|
23
|
+
description: 'This will run the ls command and return the full bash result.',
|
24
|
+
input: { command: 'ls' },
|
25
|
+
result: {
|
26
|
+
text: "bin\nblue_heart.png\nthe_output.txt\n",
|
27
|
+
success: true,
|
28
|
+
exit_code: 0
|
29
|
+
}
|
23
30
|
},
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
"text": "bin\nblue_heart.png\nthe_output.txt\n",
|
32
|
-
"success": true,
|
33
|
-
"exit_code": 0
|
34
|
-
}
|
35
|
-
},
|
36
|
-
"Failed Call" => {
|
37
|
-
description: 'This is an example of what happens when the command fails.',
|
38
|
-
input: { command: 'rm file_that_does_not_exist.txt' },
|
39
|
-
result: {
|
40
|
-
"text": "",
|
41
|
-
"success": false,
|
42
|
-
"exit_code": 256
|
43
|
-
}
|
44
|
-
},
|
31
|
+
'Failed Call' => {
|
32
|
+
description: 'This is an example of what happens when the command fails.',
|
33
|
+
input: { command: 'rm file_that_does_not_exist.txt' },
|
34
|
+
result: {
|
35
|
+
text: '',
|
36
|
+
success: false,
|
37
|
+
exit_code: 256
|
45
38
|
}
|
39
|
+
}
|
46
40
|
}
|
47
|
-
|
48
|
-
|
49
|
-
def process event, config
|
50
|
-
command = config[:command]
|
41
|
+
}
|
42
|
+
end
|
51
43
|
|
52
|
-
|
44
|
+
def process(_, config)
|
45
|
+
command = config[:command]
|
53
46
|
|
54
|
-
|
47
|
+
command = "cd #{config[:directory]};#{command}" if config[:directory]
|
55
48
|
|
56
|
-
|
57
|
-
{
|
58
|
-
text: text,
|
59
|
-
success: result.success?,
|
60
|
-
exit_code: result.to_i,
|
61
|
-
}
|
62
|
-
end
|
49
|
+
text = `#{command}`
|
63
50
|
|
51
|
+
result = $?
|
52
|
+
{
|
53
|
+
text: text,
|
54
|
+
success: result.success?,
|
55
|
+
exit_code: result.to_i
|
56
|
+
}
|
64
57
|
end
|
65
|
-
|
66
|
-
end
|
58
|
+
end
|