shell_helpers 0.1.0 → 0.6.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.
- checksums.yaml +5 -5
- data/.gitignore +6 -2
- data/.travis.yml +10 -0
- data/.yardopts +6 -1
- data/Gemfile +8 -0
- data/LICENSE.txt +1 -1
- data/README.md +27 -3
- data/Rakefile +7 -12
- data/TODO +2 -0
- data/bin/abs_to_rel.rb +42 -0
- data/bin/mv_and_ln.rb +54 -0
- data/gemspec.yml +5 -4
- data/lib/shell_helpers.rb +38 -11
- data/lib/shell_helpers/export.rb +169 -0
- data/lib/shell_helpers/logger.rb +83 -28
- data/lib/shell_helpers/options.rb +28 -0
- data/lib/shell_helpers/pathname.rb +583 -110
- data/lib/shell_helpers/run.rb +115 -29
- data/lib/shell_helpers/sh.rb +188 -39
- data/lib/shell_helpers/sysutils.rb +427 -0
- data/lib/shell_helpers/utils.rb +216 -119
- data/lib/shell_helpers/version.rb +1 -1
- data/shell_helpers.gemspec +13 -1
- data/test/helper.rb +12 -1
- data/test/test_export.rb +77 -0
- metadata +33 -8
- data/.document +0 -3
data/lib/shell_helpers/run.rb
CHANGED
@@ -2,35 +2,94 @@
|
|
2
2
|
require 'open3'
|
3
3
|
require 'shellwords'
|
4
4
|
|
5
|
-
module
|
5
|
+
module ShellHelpers
|
6
6
|
module Run #{{{
|
7
7
|
extend(self)
|
8
|
+
RunError=Class.new(StandardError)
|
9
|
+
|
10
|
+
def sudo_args(sudoarg)
|
11
|
+
return "" unless sudoarg
|
12
|
+
return sudoarg.shellsplit if sudoarg.is_a?(String)
|
13
|
+
["sudo"]
|
14
|
+
end
|
15
|
+
|
8
16
|
#the run_* commands here capture all the output
|
9
17
|
def run_command(*command)
|
18
|
+
#stdout, stderr, status
|
10
19
|
return Open3.capture3(*command)
|
11
20
|
end
|
12
21
|
|
13
|
-
|
14
|
-
|
22
|
+
#Only capture output
|
23
|
+
def output_of(*command)
|
24
|
+
stdout,status = Open3.capture2(*command)
|
25
|
+
yield stdout, status if block_given?
|
15
26
|
return stdout
|
16
27
|
end
|
17
28
|
|
18
|
-
def
|
19
|
-
|
29
|
+
def status_of(*command)
|
30
|
+
stdout,stderr,status = run_command(*command)
|
31
|
+
yield stdout, stderr, status if block_given?
|
20
32
|
return status.success?
|
33
|
+
#system(*command)
|
34
|
+
#return $?.dup
|
21
35
|
end
|
22
36
|
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
37
|
+
#wrap the output of the command in an enumerator
|
38
|
+
#allows to lazily parse the result
|
39
|
+
def run_lazy(*command)
|
40
|
+
r=nil
|
41
|
+
IO.popen(command) do |f|
|
42
|
+
r=f.each_line.lazy
|
43
|
+
end
|
44
|
+
r
|
45
|
+
end
|
46
|
+
|
47
|
+
def process_command(*args, **opts)
|
48
|
+
spawn_opts={}
|
49
|
+
if args.last.kind_of?(Hash)
|
50
|
+
#we may have no symbol keywords
|
51
|
+
*args,spawn_opts=*args
|
52
|
+
end
|
53
|
+
sudo=opts.delete(:sudo)
|
54
|
+
env={}
|
55
|
+
if args.first.kind_of?(Hash)
|
56
|
+
env,*args=*args
|
57
|
+
end
|
58
|
+
env.merge!(opts.delete(:env)||{})
|
59
|
+
args=args.map {|arg| arg.to_s} if args.length > 1
|
60
|
+
spawn_opts.merge!(opts)
|
61
|
+
if sudo
|
62
|
+
if args.length > 1
|
63
|
+
args.unshift(*Run.sudo_args(sudo))
|
64
|
+
else
|
65
|
+
args="#{Run.sudo_args(sudo).shelljoin} #{args.first}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
return env, args, spawn_opts
|
69
|
+
end
|
70
|
+
|
71
|
+
#by default capture stdout and status
|
72
|
+
def run(*args, output: :capture, error: nil, fail_mode: :error, chomp: false, sudo: false, error_mode: nil, expected: nil, on_success: nil, quiet: nil, **opts)
|
73
|
+
env, args, spawn_opts=Run.process_command(*args, **opts)
|
74
|
+
|
75
|
+
if args.length > 1
|
76
|
+
launch=args.shelljoin
|
27
77
|
else
|
28
|
-
launch=
|
78
|
+
launch=args.first #assume it has already been escaped
|
29
79
|
end
|
30
|
-
launch+=" 2>/dev/null" if quiet
|
80
|
+
launch+=" 2>/dev/null" if error==:quiet or quiet
|
81
|
+
launch+=" >/dev/null" if output==:quiet
|
82
|
+
out=error=nil
|
83
|
+
|
31
84
|
begin
|
32
|
-
|
33
|
-
|
85
|
+
if error==:capture
|
86
|
+
out, error, status=Open3.capture3(env, launch, spawn_opts)
|
87
|
+
elsif output==:capture
|
88
|
+
out, status=Open3.capture2(env, launch, spawn_opts)
|
89
|
+
else
|
90
|
+
system(env, launch, spawn_opts)
|
91
|
+
status=$?
|
92
|
+
end
|
34
93
|
rescue => e
|
35
94
|
status=false
|
36
95
|
case fail_mode
|
@@ -38,24 +97,49 @@ module SH
|
|
38
97
|
raise e
|
39
98
|
when :empty
|
40
99
|
out=""
|
100
|
+
when :nil
|
101
|
+
out=nil
|
102
|
+
when Proc
|
103
|
+
fail_mode.call(e)
|
41
104
|
end
|
42
105
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
out
|
52
|
-
|
106
|
+
status=ProcessStatus.new(status, expected) if expected
|
107
|
+
yield status.success?, out, err, status if block_given?
|
108
|
+
if status.success?
|
109
|
+
# this block is called in case of success
|
110
|
+
on_success.call(status, out, err) if on_success.is_a?(Proc)
|
111
|
+
else # the command failed
|
112
|
+
case error_mode
|
113
|
+
when :nil
|
114
|
+
out=nil
|
115
|
+
when :empty
|
116
|
+
out=""
|
117
|
+
when :error
|
118
|
+
raise RunError.new("Error running command '#{launch}': #{status}")
|
119
|
+
when Proc
|
120
|
+
error_mode.call(status, out, error)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
if chomp and out
|
124
|
+
case chomp
|
125
|
+
when :line, :lines
|
126
|
+
#out is now an array
|
127
|
+
out=out.each_line.map {|l| l.chomp}
|
53
128
|
else
|
54
|
-
out
|
55
|
-
status=$?
|
56
|
-
return out, status.success?
|
129
|
+
out.chomp!
|
57
130
|
end
|
58
131
|
end
|
132
|
+
|
133
|
+
return out, error, status if error
|
134
|
+
return out, status
|
135
|
+
end
|
136
|
+
|
137
|
+
#a simple wrapper for %x//
|
138
|
+
def run_simple(*command, **opts, &b)
|
139
|
+
# here the block is called in case of failure
|
140
|
+
opts[:error_mode]=b if b
|
141
|
+
out, *_rest = run(*command, **opts)
|
142
|
+
return out
|
59
143
|
end
|
60
144
|
|
61
145
|
#same as Run, but if we get interrupted once, we don't want to launch any more commands
|
@@ -65,7 +149,7 @@ module SH
|
|
65
149
|
def run_command(*args)
|
66
150
|
if !@interrupted
|
67
151
|
begin
|
68
|
-
|
152
|
+
Run.run_command(*args)
|
69
153
|
rescue Interrupt #interruption
|
70
154
|
@interrupted=true
|
71
155
|
return "", "", false
|
@@ -75,10 +159,12 @@ module SH
|
|
75
159
|
end
|
76
160
|
end
|
77
161
|
|
162
|
+
#TODO: handle non default options, 'error: :capture' would imply we
|
163
|
+
#need to return "", "", false
|
78
164
|
def run(*command)
|
79
165
|
if !@interrupted
|
80
166
|
begin
|
81
|
-
return
|
167
|
+
return Run.run(*command)
|
82
168
|
rescue Interrupt #interruption
|
83
169
|
@interrupted=true
|
84
170
|
return "", false
|
@@ -126,7 +212,7 @@ module SH
|
|
126
212
|
else
|
127
213
|
status
|
128
214
|
end
|
129
|
-
if status.kind_of?
|
215
|
+
if status.kind_of? Integer
|
130
216
|
status
|
131
217
|
elsif status
|
132
218
|
0
|
data/lib/shell_helpers/sh.rb
CHANGED
@@ -1,14 +1,25 @@
|
|
1
1
|
# vim: foldmethod=marker
|
2
|
-
#from methadone (error.rb, exit_now.rb, process_status.rb, run.rb;
|
3
|
-
#import
|
4
|
-
|
5
|
-
|
6
|
-
require '
|
2
|
+
#from methadone (error.rb, exit_now.rb, process_status.rb, run.rb;
|
3
|
+
#last import 4626a2bca9b6e54077a06a0f8e11a04fadc6e7ae, 2017-01-19
|
4
|
+
require 'shell_helpers/logger'
|
5
|
+
require 'shell_helpers/run'
|
6
|
+
require 'forwardable'
|
7
7
|
|
8
|
-
|
8
|
+
begin
|
9
|
+
require 'simplecolor'
|
10
|
+
rescue LoadError
|
11
|
+
#fallback, don't colorize
|
12
|
+
module SimpleColor
|
13
|
+
def self.color(s,**opts)
|
14
|
+
puts s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ShellHelpers
|
9
20
|
# ExitNow {{{
|
10
21
|
# Standard exception you can throw to exit with a given status code.
|
11
|
-
# Generally, you should prefer
|
22
|
+
# Generally, you should prefer SH::ExitNow.exit_now! over using this
|
12
23
|
# directly, however you may wish to create a rich hierarchy of exceptions
|
13
24
|
# that extend from this in your app, so this is provided if you wish to
|
14
25
|
# do so.
|
@@ -102,10 +113,50 @@ module SH
|
|
102
113
|
extend self
|
103
114
|
attr_writer :default_sh_options
|
104
115
|
def default_sh_options
|
105
|
-
@default_sh_options||={log:
|
106
|
-
|
116
|
+
@default_sh_options||={log: true, capture: false, on_success: nil, on_failure: nil, expected:0, dryrun: false, escape: false,
|
117
|
+
log_level_execute_debug: :debug,
|
118
|
+
log_level_execute: :info, log_level_error: :error,
|
107
119
|
log_level_stderr: :error, log_level_stdout_success: :info,
|
108
|
-
log_level_stdout_fail: :warn}
|
120
|
+
log_level_stdout_fail: :warn, detach: false}
|
121
|
+
end
|
122
|
+
|
123
|
+
attr_writer :spawned
|
124
|
+
def spawned
|
125
|
+
@spawned||=[]
|
126
|
+
end
|
127
|
+
def wait_spawned
|
128
|
+
spawned.each {|c| Process.waitpid(c)}
|
129
|
+
end
|
130
|
+
|
131
|
+
# callback called by sh to select the exec mode
|
132
|
+
# mode: :system,:spawn,:exec,:capture
|
133
|
+
# opts: sudo, env
|
134
|
+
def shrun(*args,mode: :system, **opts)
|
135
|
+
env, args, spawn_opts=Run.process_command(*args, **opts)
|
136
|
+
# p env, args, spawn_opts
|
137
|
+
case mode
|
138
|
+
when :system
|
139
|
+
system(env,*args,spawn_opts)
|
140
|
+
when :spawn, :detach
|
141
|
+
pid=spawn(env,*args,spawn_opts)
|
142
|
+
if mode==:detach
|
143
|
+
Process.detach(pid)
|
144
|
+
else
|
145
|
+
spawned << pid
|
146
|
+
if block_given?
|
147
|
+
yield pid
|
148
|
+
Process.wait(pid)
|
149
|
+
else
|
150
|
+
pid
|
151
|
+
end
|
152
|
+
end
|
153
|
+
when :exec
|
154
|
+
exec(env,*args,spawn_opts)
|
155
|
+
when :capture
|
156
|
+
Run.run_command(env,*args,spawn_opts)
|
157
|
+
when :run
|
158
|
+
Run.run(env,*args,spawn_opts)
|
159
|
+
end
|
109
160
|
end
|
110
161
|
|
111
162
|
# Run a shell command, capturing and logging its output.
|
@@ -117,7 +168,7 @@ module SH
|
|
117
168
|
# name: pretty name of command
|
118
169
|
# on_success,on_failure: blocks to call on success/failure
|
119
170
|
# block:: if provided, will be called if the command exited nonzero. The block may take 0, 1, 2, or 3 arguments.
|
120
|
-
# The arguments provided are the standard output as a string, standard error as a string, and the processstatus as
|
171
|
+
# The arguments provided are the standard output as a string, standard error as a string, and the processstatus as SH::ProcessStatus
|
121
172
|
# You should be safe to pass in a lambda instead of a block, as long as your lambda doesn't take more than three arguments
|
122
173
|
#
|
123
174
|
# Example
|
@@ -130,41 +181,48 @@ module SH
|
|
130
181
|
# end
|
131
182
|
#
|
132
183
|
# Returns the exit status of the command. Note that if the command doesn't exist, this returns 127.
|
184
|
+
|
133
185
|
def sh(*command, **opts, &block)
|
134
186
|
defaults=default_sh_options
|
135
187
|
curopts=defaults.dup
|
136
188
|
defaults.keys.each do |k|
|
137
189
|
v=opts.delete(k)
|
138
|
-
curopts[k]=v
|
190
|
+
curopts[k]=v unless v.nil?
|
139
191
|
end
|
192
|
+
|
140
193
|
log=curopts[:log]
|
141
|
-
command=command.first if command.length==1 and command.first.kind_of?(Array)
|
142
|
-
command_name = curopts[:name] || command_name(command)
|
194
|
+
# command=command.first if command.length==1 and command.first.kind_of?(Array) #so that sh(["ls", "-a"]) works
|
195
|
+
command_name = curopts[:name] || command_name(command) #this keep the options
|
143
196
|
command=command.shelljoin if curopts[:escape]
|
144
|
-
|
197
|
+
if log
|
198
|
+
sh_logger.send(curopts[:log_level_execute], SimpleColor.color("Executing '#{command_name}'",:bold))
|
199
|
+
p_env, p_args, p_opts= Run.process_command(*command, **opts)
|
200
|
+
sh_logger.send(curopts[:log_level_execute_debug], SimpleColor.color("Debug execute: '#{[p_env, *p_args, p_opts]}'", :bold))
|
201
|
+
end
|
145
202
|
|
146
203
|
if !curopts[:dryrun]
|
147
|
-
if curopts[:capture]
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
204
|
+
if curopts[:capture] || curopts[:mode]==:capture
|
205
|
+
stdout,stderr,status = shrun(*command,**opts,mode: :capture)
|
206
|
+
elsif curopts[:detach] || curopts[:mode]==:spawn || curopts[:mode]==:detach
|
207
|
+
mode = curopts[:detach] ? :detach : curops[:mode]
|
208
|
+
_pid = shrun(*command,**opts, mode: mode)
|
209
|
+
status=0; stdout=nil; stderr=nil
|
210
|
+
elsif curopts[:mode]==:run
|
211
|
+
*res=shrun(*command,**opts,mode: :capture)
|
212
|
+
case res.length
|
213
|
+
when 2
|
214
|
+
stdout, status=res; stderr=nil
|
215
|
+
when 3
|
216
|
+
stdout, stderr, status=res
|
153
217
|
end
|
154
218
|
else
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
else
|
159
|
-
system(command.to_s,**opts)
|
160
|
-
end
|
161
|
-
status=$?
|
162
|
-
stdout=nil; stderr=nil
|
219
|
+
mode=curopts[:mode]||:system
|
220
|
+
shrun(*command,mode: mode, **opts)
|
221
|
+
status=$?; stdout=nil; stderr=nil
|
163
222
|
end
|
164
223
|
else
|
165
|
-
|
166
|
-
status=0
|
167
|
-
stdout=nil; stderr=nil
|
224
|
+
sh_logger.info command.to_s
|
225
|
+
status=0; stdout=nil; stderr=nil
|
168
226
|
end
|
169
227
|
process_status = ProcessStatus.new(status,curopts[:expected])
|
170
228
|
|
@@ -187,7 +245,7 @@ module SH
|
|
187
245
|
|
188
246
|
# Run a command, throwing an exception if the command exited nonzero.
|
189
247
|
# Otherwise, behaves exactly like #sh.
|
190
|
-
# Raises
|
248
|
+
# Raises SH::FailedCommandError if the command exited nonzero.
|
191
249
|
# Examples:
|
192
250
|
#
|
193
251
|
# sh!("rsync foo bar")
|
@@ -197,7 +255,7 @@ module SH
|
|
197
255
|
def sh!(*args,failure_msg: nil,**opts, &block)
|
198
256
|
on_failure=Proc.new do |*blockargs|
|
199
257
|
process_status=blockargs.last
|
200
|
-
raise
|
258
|
+
raise FailedCommandError.new(process_status.exitstatus,command_name(args),failure_msg: failure_msg)
|
201
259
|
end
|
202
260
|
sh(*args,**opts,on_failure: on_failure,&block)
|
203
261
|
end
|
@@ -212,18 +270,27 @@ module SH
|
|
212
270
|
@sh_logger = logger
|
213
271
|
end
|
214
272
|
|
273
|
+
#split commands on newlines and run sh on each line
|
274
|
+
def sh_commands(com, **opts)
|
275
|
+
com.each_line do |line|
|
276
|
+
sh(line.chomp,**opts)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
215
280
|
private
|
216
281
|
def command_name(command)
|
217
282
|
if command.size == 1
|
218
|
-
|
283
|
+
command.first.to_s
|
219
284
|
else
|
220
|
-
|
285
|
+
#command.to_s
|
286
|
+
#command.map {|i| i.to_s}.to_s
|
287
|
+
command.shelljoin
|
221
288
|
end
|
222
289
|
end
|
223
290
|
|
224
291
|
def sh_logger
|
225
292
|
@sh_logger ||= begin
|
226
|
-
raise StandardError, "No logger set! Please include
|
293
|
+
raise StandardError, "No logger set! Please include SH::CLILogging
|
227
294
|
ng or provide your own via #change_sh_logger." unless self.respond_to?(:logger)
|
228
295
|
self.logger
|
229
296
|
end
|
@@ -231,8 +298,8 @@ ng or provide your own via #change_sh_logger." unless self.respond_to?(:logger)
|
|
231
298
|
|
232
299
|
end
|
233
300
|
|
234
|
-
#SH::ShLog.sh is like SH::Sh.sh
|
235
|
-
#
|
301
|
+
#SH::ShLog.sh is by default like SH::Sh.sh.
|
302
|
+
# It is easy to change it to be more verbose though
|
236
303
|
module ShLog
|
237
304
|
include Sh
|
238
305
|
extend self
|
@@ -240,5 +307,87 @@ ng or provide your own via #change_sh_logger." unless self.respond_to?(:logger)
|
|
240
307
|
@default_sh_options[:log]=true
|
241
308
|
@default_sh_options[:log_level_execute]=:info
|
242
309
|
end
|
310
|
+
|
311
|
+
# Do not log execution
|
312
|
+
module ShQuiet
|
313
|
+
include Sh
|
314
|
+
extend self
|
315
|
+
@default_sh_options=default_sh_options
|
316
|
+
@default_sh_options[:log]=true
|
317
|
+
@default_sh_options[:log_level_execute]=:debug
|
318
|
+
end
|
319
|
+
|
320
|
+
# Completely silent
|
321
|
+
module ShSilent
|
322
|
+
include Sh
|
323
|
+
extend self
|
324
|
+
@default_sh_options=default_sh_options
|
325
|
+
@default_sh_options[:log]=false
|
326
|
+
end
|
327
|
+
|
328
|
+
module ShDryRun
|
329
|
+
include Sh
|
330
|
+
extend self
|
331
|
+
@default_sh_options=default_sh_options
|
332
|
+
@default_sh_options[:log]=true
|
333
|
+
@default_sh_options[:log_level_execute]=:info
|
334
|
+
@default_sh_options[:dryrun]=true
|
335
|
+
end
|
336
|
+
|
337
|
+
# this modules deal with default options, paths and arg handling for
|
338
|
+
# different programs, while letting the user control
|
339
|
+
# Exemples:
|
340
|
+
# ShConfig.launch(:ls, "/", config: {ls: {default_opts: ["-l"]}})
|
341
|
+
# => ["ls", "-l", "/", {}]
|
342
|
+
# ShConfig.launch(:ls, "/", config: {ls: {default_opts: ["-l"]}}, method: :sh)
|
343
|
+
# ShConfig.launch(:ls, "/", config: {ls: {default_opts: ["-l"]}}) { |*args| SH.sh(*args) }
|
344
|
+
# ShConfig.launch(:ls, "/", config: {ls: {wrap: ->(cmd,*args, &b) { b.call(cmd, '-l', *args) } }}, method: :sh)
|
345
|
+
|
346
|
+
module ShConfig
|
347
|
+
extend self
|
348
|
+
def launch(*args, opts: [], cmd_prepend: [], cmd_postpone: [], config: self.sh_config, default_opts: true, method: nil, **keywords, &b)
|
349
|
+
if args.length == 1 and (arg=args.first).is_a?(String)
|
350
|
+
args=arg.shellsplit
|
351
|
+
args[0]=args[0][1..-1].to_sym if args[0][0]==':'
|
352
|
+
end
|
353
|
+
opts=opts.shellsplit if opts.is_a?(String)
|
354
|
+
default_opts=default_opts.shellsplit if default_opts.is_a?(String)
|
355
|
+
cmd_prepend=cmd_prepend.shellsplit if cmd_prepend.is_a?(String)
|
356
|
+
cmd_postpone=cmd_postpone.shellsplit if cmd_postpone.is_a?(String)
|
357
|
+
dopts = default_opts.is_a?(Array) ? default_opts : []
|
358
|
+
cmd, *args=args
|
359
|
+
if cmd.is_a?(Symbol)
|
360
|
+
if config.key?(cmd)
|
361
|
+
c=config[cmd]
|
362
|
+
cmd=c[:bin] || cmd.to_s
|
363
|
+
dopts += (Array(c[:default_opts])||[]) if default_opts
|
364
|
+
wrap=c[:wrap]
|
365
|
+
else
|
366
|
+
cmd=cmd.to_s
|
367
|
+
end
|
368
|
+
end
|
369
|
+
cargs=Array(cmd_prepend) + [cmd] + dopts + Array(opts) + args + Array(cmd_postpone)
|
370
|
+
if !b
|
371
|
+
if method
|
372
|
+
b=lambda do |*args|
|
373
|
+
SH.public_send(method, *args)
|
374
|
+
end
|
375
|
+
else
|
376
|
+
b=lambda do |*args|
|
377
|
+
return *args
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
if wrap
|
382
|
+
wrap.call(*cargs, **keywords, &b)
|
383
|
+
else
|
384
|
+
b.call(*cargs, **keywords)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
def sh_config
|
389
|
+
{}
|
390
|
+
end
|
391
|
+
end
|
243
392
|
# }}}
|
244
393
|
end
|