scout-essentials 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.vimproject +78 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +18 -0
  7. data/Rakefile +47 -0
  8. data/VERSION +1 -0
  9. data/lib/scout/cmd.rb +348 -0
  10. data/lib/scout/concurrent_stream.rb +284 -0
  11. data/lib/scout/config.rb +168 -0
  12. data/lib/scout/exceptions.rb +77 -0
  13. data/lib/scout/indiferent_hash/case_insensitive.rb +30 -0
  14. data/lib/scout/indiferent_hash/options.rb +115 -0
  15. data/lib/scout/indiferent_hash.rb +96 -0
  16. data/lib/scout/log/color.rb +224 -0
  17. data/lib/scout/log/color_class.rb +269 -0
  18. data/lib/scout/log/fingerprint.rb +69 -0
  19. data/lib/scout/log/progress/report.rb +244 -0
  20. data/lib/scout/log/progress/util.rb +173 -0
  21. data/lib/scout/log/progress.rb +106 -0
  22. data/lib/scout/log/trap.rb +107 -0
  23. data/lib/scout/log.rb +441 -0
  24. data/lib/scout/meta_extension.rb +100 -0
  25. data/lib/scout/misc/digest.rb +63 -0
  26. data/lib/scout/misc/filesystem.rb +25 -0
  27. data/lib/scout/misc/format.rb +255 -0
  28. data/lib/scout/misc/helper.rb +31 -0
  29. data/lib/scout/misc/insist.rb +56 -0
  30. data/lib/scout/misc/monitor.rb +66 -0
  31. data/lib/scout/misc/system.rb +73 -0
  32. data/lib/scout/misc.rb +10 -0
  33. data/lib/scout/named_array.rb +138 -0
  34. data/lib/scout/open/lock/lockfile.rb +587 -0
  35. data/lib/scout/open/lock.rb +68 -0
  36. data/lib/scout/open/remote.rb +135 -0
  37. data/lib/scout/open/stream.rb +491 -0
  38. data/lib/scout/open/util.rb +244 -0
  39. data/lib/scout/open.rb +170 -0
  40. data/lib/scout/path/find.rb +204 -0
  41. data/lib/scout/path/tmpfile.rb +8 -0
  42. data/lib/scout/path/util.rb +127 -0
  43. data/lib/scout/path.rb +51 -0
  44. data/lib/scout/persist/open.rb +17 -0
  45. data/lib/scout/persist/path.rb +15 -0
  46. data/lib/scout/persist/serialize.rb +157 -0
  47. data/lib/scout/persist.rb +104 -0
  48. data/lib/scout/resource/open.rb +8 -0
  49. data/lib/scout/resource/path.rb +80 -0
  50. data/lib/scout/resource/produce/rake.rb +69 -0
  51. data/lib/scout/resource/produce.rb +151 -0
  52. data/lib/scout/resource/scout.rb +3 -0
  53. data/lib/scout/resource/software.rb +178 -0
  54. data/lib/scout/resource/util.rb +59 -0
  55. data/lib/scout/resource.rb +40 -0
  56. data/lib/scout/simple_opt/accessor.rb +54 -0
  57. data/lib/scout/simple_opt/doc.rb +126 -0
  58. data/lib/scout/simple_opt/get.rb +57 -0
  59. data/lib/scout/simple_opt/parse.rb +67 -0
  60. data/lib/scout/simple_opt/setup.rb +26 -0
  61. data/lib/scout/simple_opt.rb +5 -0
  62. data/lib/scout/tmpfile.rb +129 -0
  63. data/lib/scout-essentials.rb +10 -0
  64. data/scout-essentials.gemspec +143 -0
  65. data/share/color/color_names +507 -0
  66. data/share/color/diverging_colors.hex +12 -0
  67. data/share/software/install_helpers +523 -0
  68. data/test/scout/indiferent_hash/test_case_insensitive.rb +16 -0
  69. data/test/scout/indiferent_hash/test_options.rb +46 -0
  70. data/test/scout/log/test_color.rb +0 -0
  71. data/test/scout/log/test_progress.rb +108 -0
  72. data/test/scout/misc/test_digest.rb +30 -0
  73. data/test/scout/misc/test_filesystem.rb +30 -0
  74. data/test/scout/misc/test_insist.rb +13 -0
  75. data/test/scout/misc/test_system.rb +21 -0
  76. data/test/scout/open/test_lock.rb +52 -0
  77. data/test/scout/open/test_remote.rb +25 -0
  78. data/test/scout/open/test_stream.rb +676 -0
  79. data/test/scout/open/test_util.rb +73 -0
  80. data/test/scout/path/test_find.rb +110 -0
  81. data/test/scout/path/test_util.rb +22 -0
  82. data/test/scout/persist/test_open.rb +37 -0
  83. data/test/scout/persist/test_path.rb +37 -0
  84. data/test/scout/persist/test_serialize.rb +114 -0
  85. data/test/scout/resource/test_path.rb +58 -0
  86. data/test/scout/resource/test_produce.rb +94 -0
  87. data/test/scout/resource/test_software.rb +24 -0
  88. data/test/scout/resource/test_util.rb +38 -0
  89. data/test/scout/simple_opt/test_doc.rb +16 -0
  90. data/test/scout/simple_opt/test_get.rb +11 -0
  91. data/test/scout/simple_opt/test_parse.rb +10 -0
  92. data/test/scout/simple_opt/test_setup.rb +77 -0
  93. data/test/scout/test_cmd.rb +85 -0
  94. data/test/scout/test_concurrent_stream.rb +29 -0
  95. data/test/scout/test_config.rb +66 -0
  96. data/test/scout/test_indiferent_hash.rb +26 -0
  97. data/test/scout/test_log.rb +32 -0
  98. data/test/scout/test_meta_extension.rb +80 -0
  99. data/test/scout/test_misc.rb +6 -0
  100. data/test/scout/test_named_array.rb +43 -0
  101. data/test/scout/test_open.rb +146 -0
  102. data/test/scout/test_path.rb +54 -0
  103. data/test/scout/test_persist.rb +186 -0
  104. data/test/scout/test_resource.rb +26 -0
  105. data/test/scout/test_tmpfile.rb +53 -0
  106. data/test/test_helper.rb +50 -0
  107. metadata +247 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a6c2774d7bf55f2336b53d962d7f471a18d415bf4c8734c32abe669d0214d6c8
4
+ data.tar.gz: ded38f35d4277f86af691c301113c464587093db4925294bdb46c76c9ff16b3b
5
+ SHA512:
6
+ metadata.gz: '0128a95c09d025382f373b3aef9ceaba29845eccac7270bfcf9ac9513b90a2d22b5fc3a096a3e0f516748c03e201d91f6969a79c6fd2fcecb2000ab2eae9f6bc'
7
+ data.tar.gz: a76999c366fc0e18c95d2874c19067472eeb6801199afb276a07887f94ab2cbe4b0ad20a457d475264cd4bca01ad8efa5bac167ebfa5b3ac20e6f074c0a6304d
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.vimproject ADDED
@@ -0,0 +1,78 @@
1
+ scout-essentials=/$PWD filter="*.rb *.txt *.md *.conf *.yaml" {
2
+ LICENSE.txt
3
+ Rakefile
4
+ lib=lib {
5
+ scout-essentials.rb
6
+ scout=scout{
7
+ exceptions.rb
8
+ meta_extension.rb
9
+ misc.rb
10
+ misc=misc{
11
+ digest.rb
12
+ filesystem.rb
13
+ format.rb
14
+ helper.rb
15
+ insist.rb
16
+ monitor.rb
17
+ system.rb
18
+ }
19
+ named_array.rb
20
+ indiferent_hash.rb
21
+ indiferent_hash=indiferent_hash{
22
+ case_insensitive.rb
23
+ options.rb
24
+ }
25
+ log.rb
26
+ log=log{
27
+ color.rb
28
+ color_class.rb
29
+ fingerprint.rb
30
+ progress.rb
31
+ trap.rb
32
+ }
33
+ tmpfile.rb
34
+ simple_opt.rb
35
+ simple_opt=simple_opt{
36
+ accessor.rb
37
+ doc.rb
38
+ get.rb
39
+ parse.rb
40
+ setup.rb
41
+ }
42
+ path.rb
43
+ path=path{
44
+ find.rb
45
+ tmpfile.rb
46
+ util.rb
47
+ }
48
+ concurrent_stream.rb
49
+ cmd.rb
50
+ open.rb
51
+ open=open{
52
+ lock.rb
53
+ remote.rb
54
+ stream.rb
55
+ util.rb
56
+ }
57
+ resource.rb
58
+ resource=resource{
59
+ open.rb
60
+ path.rb
61
+ produce.rb
62
+ scout.rb
63
+ software.rb
64
+ util.rb
65
+ }
66
+ config.rb
67
+ persist.rb
68
+ persist=persist{
69
+ open.rb
70
+ path.rb
71
+ serialize.rb
72
+ }
73
+ }
74
+ }
75
+ test=test{
76
+ test_helper.rb
77
+ }
78
+ }
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "https://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "rdoc", "~> 3.12"
11
+ gem "bundler", "~> 1.0"
12
+ gem "juwelier", "~> 2.1.0"
13
+ gem "simplecov", ">= 0"
14
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2023 Miguel Vazquez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,18 @@
1
+ = scout-essentials
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to scout-essentials
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2023 Miguel Vazquez. See LICENSE.txt for
18
+ further details.
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ ENV["BRANCH"] = 'main'
4
+
5
+ require 'rubygems'
6
+ require 'rake'
7
+ require 'juwelier'
8
+ Juwelier::Tasks.new do |gem|
9
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
10
+ gem.name = "scout-essentials"
11
+ gem.homepage = "http://github.com/mikisvaz/scout-essentials"
12
+ gem.license = "MIT"
13
+ gem.summary = %Q{Scout essential tools}
14
+ gem.description = %Q{Things a scout can use anywhere}
15
+ gem.email = "mikisvaz@gmail.com"
16
+ gem.authors = ["Miguel Vazquez"]
17
+
18
+ # dependencies defined in Gemfile
19
+
20
+ gem.add_runtime_dependency 'term-ansicolor'
21
+ gem.add_runtime_dependency 'yaml'
22
+ end
23
+ Juwelier::RubygemsDotOrgTasks.new
24
+ require 'rake/testtask'
25
+ Rake::TestTask.new(:test) do |test|
26
+ test.libs << 'lib' << 'test'
27
+ test.pattern = 'test/**/test_*.rb'
28
+ test.verbose = true
29
+ end
30
+
31
+ desc "Code coverage detail"
32
+ task :simplecov do
33
+ ENV['COVERAGE'] = "true"
34
+ Rake::Task['test'].execute
35
+ end
36
+
37
+ task :default => :test
38
+
39
+ require 'rdoc/task'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "scout-essentials #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/lib/scout/cmd.rb ADDED
@@ -0,0 +1,348 @@
1
+ require_relative 'indiferent_hash'
2
+ require_relative 'concurrent_stream'
3
+ require_relative 'log'
4
+ require_relative 'open/stream'
5
+ require 'stringio'
6
+ require 'open3'
7
+
8
+ module CMD
9
+
10
+ TOOLS = IndiferentHash.setup({})
11
+ def self.tool(tool, claim = nil, test = nil, cmd = nil, &block)
12
+ TOOLS[tool] = [claim, test, block, cmd]
13
+ end
14
+
15
+ def self.conda(tool, env = nil, channel = 'bioconda')
16
+ if env
17
+ CMD.cmd("bash -l -c '(conda activate #{env} && conda install #{tool} -c #{channel})'")
18
+ else
19
+ CMD.cmd("bash -l -c 'conda install #{tool} -c #{channel}'")
20
+ end
21
+ end
22
+
23
+
24
+ def self.get_tool(tool)
25
+ return tool.to_s unless TOOLS[tool]
26
+
27
+ @@init_cmd_tool ||= IndiferentHash.setup({})
28
+
29
+ claim, test, block, cmd = TOOLS[tool]
30
+ cmd = tool.to_s if cmd.nil?
31
+
32
+ if !@@init_cmd_tool[tool]
33
+
34
+ begin
35
+ if test
36
+ CMD.cmd(test + " ")
37
+ else
38
+ CMD.cmd("#{cmd} --help")
39
+ end
40
+ rescue
41
+ if claim
42
+ claim.produce
43
+ else
44
+ block.call
45
+ end
46
+ end
47
+ version_txt = ""
48
+ version = nil
49
+ ["--version", "-version", "--help", ""].each do |f|
50
+ begin
51
+ version_txt += CMD.cmd("#{cmd} #{f} 2>&1", :nofail => true).read
52
+ version = CMD.scan_version_text(version_txt, tool)
53
+ break if version
54
+ rescue
55
+ Log.exception $!
56
+ end
57
+ end
58
+
59
+ @@init_cmd_tool[tool] = version || true
60
+
61
+ return cmd if cmd
62
+ end
63
+
64
+ cmd
65
+ end
66
+
67
+ def self.scan_version_text(text, cmd = nil)
68
+ cmd = "NOCMDGIVE" if cmd.nil? || cmd.empty?
69
+ text = Misc.fixutf8 text
70
+ text.split("\n").each do |line|
71
+ next unless line =~ /\W#{cmd}\W/i
72
+ m = line.match(/(v(?:\d+\.)*\d+(?:-[a-z_]+)?)/i)
73
+ return m[1] if m
74
+ m = line.match(/((?:\d+\.)*\d+(?:-[a-z_]+)?v)/i)
75
+ return m[1] if m
76
+ next unless line =~ /\Wversion\W/i
77
+ m = line.match(/((?:\d+\.)*\d+(?:-[a-z_]+)?)/i)
78
+ return m[1] if m
79
+ end
80
+ m = text.match(/(?:version.*?|#{cmd}.*?|#{cmd.to_s.split(/[-_.]/).first}.*?|v)((?:\d+\.)*\d+(?:-[a-z_]+)?)/i)
81
+ return m[1] if m
82
+ m = text.match(/(?:#{cmd}.*(v.*|.*v))/i)
83
+ return m[1] if m
84
+ nil
85
+ end
86
+ def self.versions
87
+ return {} unless defined? @@init_cmd_tool
88
+ @@init_cmd_tool.select{|k,v| v =~ /\d+\./ }
89
+ end
90
+
91
+ def self.bash(cmd)
92
+ cmd = %Q(bash <<EOF\n#{cmd}\nEOF\n)
93
+ CMD.cmd(cmd, :autojoin => true)
94
+ end
95
+
96
+ def self.process_cmd_options(options = {})
97
+ add_dashes = IndiferentHash.process_options options, :add_option_dashes
98
+
99
+ string = ""
100
+ options.each do |option, value|
101
+ raise "Invalid option key: #{option.inspect}" if option.to_s !~ /^[a-z_0-9\-=.]+$/i
102
+ #raise "Invalid option value: #{value.inspect}" if value.to_s.include? "'"
103
+ value = value.gsub("'","\\'") if value.to_s.include? "'"
104
+
105
+ option = "--" << option.to_s if add_dashes and option.to_s[0] != '-'
106
+
107
+ case
108
+ when value.nil? || FalseClass === value
109
+ next
110
+ when TrueClass === value
111
+ string << "#{option} "
112
+ else
113
+ if option.to_s.chars.to_a.last == "="
114
+ string << "#{option}'#{value}' "
115
+ else
116
+ string << "#{option} '#{value}' "
117
+ end
118
+ end
119
+ end
120
+
121
+ string.strip
122
+ end
123
+
124
+ def self.cmd(tool, cmd = nil, options = {}, &block)
125
+ options, cmd = cmd, nil if Hash === cmd
126
+
127
+ options = IndiferentHash.add_defaults options, :stderr => Log::DEBUG
128
+ in_content = options.delete(:in)
129
+ stderr = options.delete(:stderr)
130
+ post = options.delete(:post)
131
+ pipe = options.delete(:pipe)
132
+ log = options.delete(:log)
133
+ no_fail = options.delete(:no_fail)
134
+ no_fail = options.delete(:nofail) if no_fail.nil?
135
+ no_wait = options.delete(:no_wait)
136
+ xvfb = options.delete(:xvfb)
137
+ bar = options.delete(:progress_bar)
138
+ save_stderr = options.delete(:save_stderr)
139
+ autojoin = options.delete(:autojoin)
140
+ autojoin = no_wait if autojoin.nil?
141
+
142
+ dont_close_in = options.delete(:dont_close_in)
143
+
144
+ log = true if log.nil?
145
+
146
+ if cmd.nil? && ! Symbol === tool
147
+ cmd = tool
148
+ else
149
+ tool = get_tool(tool)
150
+ if cmd.nil?
151
+ cmd = tool
152
+ else
153
+ cmd = tool + ' ' + cmd
154
+ end
155
+
156
+ end
157
+
158
+ case xvfb
159
+ when TrueClass
160
+ cmd = "xvfb-run --server-args='-screen 0 1024x768x24' --auto-servernum #{cmd}"
161
+ when String
162
+ cmd = "xvfb-run --server-args='#{xvfb}' --auto-servernum --server-num=1 #{cmd}"
163
+ when String
164
+ end
165
+
166
+ if stderr == true
167
+ stderr = Log::HIGH
168
+ end
169
+
170
+ cmd_options = process_cmd_options options
171
+ if cmd =~ /'\{opt\}'/
172
+ cmd.sub!('\'{opt}\'', cmd_options)
173
+ else
174
+ cmd << " " << cmd_options
175
+ end
176
+
177
+ in_content = StringIO.new in_content if String === in_content
178
+
179
+ sin, sout, serr, wait_thr = begin
180
+ Open3.popen3(ENV, cmd)
181
+ rescue
182
+ Log.warn $!.message
183
+ raise ProcessFailed, nil, cmd unless no_fail
184
+ return
185
+ end
186
+ pid = wait_thr.pid
187
+
188
+ Log.debug{"CMD: [#{pid}] #{cmd}".strip if log}
189
+
190
+ if in_content.respond_to?(:read)
191
+ in_thread = Thread.new(Thread.current) do |parent|
192
+ begin
193
+ Thread.current.report_on_exception = false if no_fail
194
+ Thread.current["name"] = "CMD in"
195
+ while c = in_content.read(Open::BLOCK_SIZE)
196
+ sin << c
197
+ end
198
+ sin.close unless sin.closed?
199
+
200
+ unless dont_close_in
201
+ in_content.close unless in_content.closed?
202
+ in_content.join if in_content.respond_to? :join
203
+ end
204
+ rescue
205
+ Log.error "Error in CMD [#{pid}] #{cmd}: #{$!.message}" unless no_fail
206
+ raise $!
207
+ end
208
+ end
209
+ Thread.pass until in_thread["name"]
210
+ else
211
+ in_thread = nil
212
+ sin.close
213
+ end
214
+
215
+ pids = [pid]
216
+
217
+ if pipe
218
+
219
+ ConcurrentStream.setup sout, :pids => pids, :autojoin => autojoin, :no_fail => no_fail
220
+
221
+ sout.callback = post if post
222
+
223
+ if (Integer === stderr and log) || bar
224
+ err_thread = Thread.new do
225
+ Thread.current["name"] = "Error log: [#{pid}] #{ cmd }"
226
+ begin
227
+ while line = serr.gets
228
+ bar.process(line) if bar
229
+ sout.log = line
230
+ sout.std_err << line if save_stderr
231
+ Log.log "STDERR [#{pid}]: " + line, stderr if log
232
+ end
233
+ serr.close
234
+ rescue
235
+ Log.exception $!
236
+ raise $!
237
+ end
238
+ end
239
+ else
240
+ err_thread = Open.consume_stream(serr, true)
241
+ end
242
+
243
+ sout.threads = [in_thread, err_thread, wait_thr].compact
244
+
245
+ sout
246
+ else
247
+
248
+ if bar
249
+ err = ""
250
+ err_thread = Thread.new do
251
+ while not serr.eof?
252
+ line = serr.gets
253
+ bar.process(line)
254
+ err << line if Integer === stderr and log
255
+ end
256
+ serr.close
257
+ end
258
+ elsif log and Integer === stderr
259
+ err = ""
260
+ err_thread = Thread.new do
261
+ while not serr.eof?
262
+ err << serr.gets
263
+ end
264
+ serr.close
265
+ end
266
+ else
267
+ Open.consume_stream(serr, true)
268
+ err_thread = nil
269
+ err = ""
270
+ end
271
+
272
+ ConcurrentStream.setup sout, :pids => pids, :threads => [in_thread, err_thread].compact, :autojoin => autojoin, :no_fail => no_fail
273
+
274
+ begin
275
+ out = StringIO.new sout.read
276
+ sout.close unless sout.closed?
277
+
278
+ status = wait_thr.value
279
+ if status && ! status.success? && ! no_fail
280
+ if !err.empty?
281
+ raise ProcessFailed.new pid, "#{cmd} failed with error status #{status.exitstatus}.\n#{err}"
282
+ else
283
+ raise ProcessFailed.new pid, "#{cmd} failed with error status #{status.exitstatus}"
284
+ end
285
+ else
286
+ Log.log err, stderr if Integer === stderr and log
287
+ end
288
+ out
289
+ ensure
290
+ post.call if post
291
+ end
292
+ end
293
+ end
294
+
295
+ def self.cmd_pid(*args)
296
+ all_args = *args
297
+
298
+ bar = all_args.last[:progress_bar] if Hash === all_args.last
299
+
300
+ all_args << {} unless Hash === all_args.last
301
+
302
+ level = all_args.last[:log] || 0
303
+ level = 0 if TrueClass === level
304
+ level = 10 if FalseClass === level
305
+ level = level.to_i
306
+
307
+ all_args.last[:log] = true
308
+ all_args.last[:pipe] = true
309
+
310
+ io = cmd(*all_args)
311
+ pid = io.pids.first
312
+
313
+ line = "" if bar
314
+ starting = true
315
+ while c = io.getc
316
+ if starting
317
+ if pid
318
+ Log.logn "STDOUT [#{pid}]: ", level
319
+ else
320
+ Log.logn "STDOUT: ", level
321
+ end
322
+ starting = false
323
+ end
324
+ STDERR << c if Log.severity <= level
325
+ line << c if bar
326
+ if c == "\n"
327
+ bar.process(line) if bar
328
+ starting = true
329
+ line = "" if bar
330
+ end
331
+ end
332
+ begin
333
+ io.join
334
+ bar.remove if bar
335
+ rescue
336
+ bar.remove(true) if bar
337
+ raise $!
338
+ end
339
+
340
+ nil
341
+ end
342
+
343
+ def self.cmd_log(*args)
344
+ cmd_pid(*args)
345
+ nil
346
+ end
347
+
348
+ end