tap-tasks 0.2.0 → 0.3.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.
data/History CHANGED
@@ -1,3 +1,11 @@
1
+ == 0.3.0 / 2009-06-17
2
+
3
+ * added load/dump csv tasks
4
+ * updated glob task with include/exclude filters
5
+ * updated load/yaml with stream loading
6
+ * removed argv task
7
+ * removed FileTask
8
+
1
9
  == 0.2.0 / 2009-05-25
2
10
 
3
11
  Updates for use with Tap-0.17.0
@@ -0,0 +1,30 @@
1
+ require 'tap/tasks/dump'
2
+ require 'csv'
3
+
4
+ module Tap
5
+ module Tasks
6
+ class Dump
7
+
8
+ # :startdoc::task dumps data as csv
9
+ #
10
+ # Dumps arrays as CSV data. Each array passed to dump will be formatted
11
+ # into a single line of csv, ie multiple dumps build the csv results.
12
+ # Non-array objects are converted to arrays using to_ary.
13
+ #
14
+ # % tap run -- load/yaml ["a", "b", "c"] --: dump/csv
15
+ # a,b,c
16
+ #
17
+ class Csv < Dump
18
+
19
+ config :col_sep, ",", &c.string # The column separator (",")
20
+ config :row_sep, "\n", &c.string # The row separator ("\n")
21
+
22
+ # Dumps the data to io as CSV. Data is converted to an array using
23
+ # to_ary.
24
+ def dump(data, io)
25
+ io << CSV.generate_line(data.to_ary, col_sep) + row_sep
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,7 +2,7 @@ require 'tap/tasks/dump'
2
2
 
3
3
  module Tap
4
4
  module Tasks
5
- class Dump < Tap::Task
5
+ class Dump
6
6
 
7
7
  # :startdoc::task inspect and dump an object
8
8
  #
@@ -2,7 +2,7 @@ require 'tap/tasks/dump'
2
2
 
3
3
  module Tap
4
4
  module Tasks
5
- class Dump < Tap::Task
5
+ class Dump
6
6
 
7
7
  # :startdoc::task dumps data as YAML
8
8
  #
@@ -9,23 +9,39 @@ module Tap
9
9
  #
10
10
  # % tap run -- glob * --: dump/yaml
11
11
  #
12
+ # A variety of filters are available as configurations.
13
+ #
14
+ # == Glob Expansion
15
+ #
16
+ # NOTE that glob patterns are normally expanded on the command line,
17
+ # meaning the task will receive an array of files and not glob patterns.
18
+ # Usually this doesn't make a difference in the task results, but it can
19
+ # slow down launch times.
20
+ #
21
+ # To glob within the task and not the command line, quote the glob.
22
+ #
23
+ # % tap run -- glob '*' --: dump/yaml
24
+ #
12
25
  class Glob < Tap::Task
13
26
 
14
- config :filters, [], &c.list(&c.regexp) # regexp filters for results
15
- config :unique, true, &c.switch # ensure results are unique
16
- config :files, true, &c.switch # glob for files
17
- config :dirs, false, &c.switch # glob for directories
27
+ config :includes, [/./], :long => :include, &c.list(&c.regexp) # Regexp include filters
28
+ config :excludes, [], :long => :exclude, &c.list(&c.regexp) # Regexp exclude filters
29
+ config :unique, true, &c.switch # Ensure results are unique
30
+ config :files, true, &c.switch # Glob for files
31
+ config :dirs, false, &c.switch # Glob for directories
18
32
 
19
33
  def process(*patterns)
20
34
  results = []
21
35
  patterns.each do |pattern|
22
36
  Dir[pattern].each do |path|
23
- next if !files && File.file?(path)
24
- next if !dirs && File.directory?(path)
37
+ next if files == false && File.file?(path)
38
+ next if dirs == false && File.directory?(path)
25
39
 
26
40
  case path
27
- when *filters then next
28
- else results << path
41
+ when *excludes
42
+ next
43
+ when *includes
44
+ results << path
29
45
  end
30
46
  end
31
47
  end
@@ -0,0 +1,48 @@
1
+ require 'tap/tasks/load'
2
+ require 'csv'
3
+
4
+ module Tap
5
+ module Tasks
6
+ class Load
7
+
8
+ # :startdoc::task reads csv data
9
+ #
10
+ # Load CSV data as an array of arrays, selecting the specified rows and
11
+ # columns.
12
+ #
13
+ # % tap run -- load/csv 'a,b,c.d,e,f' --row-sep '.' --: inspect
14
+ # [["a", "b", "c"], ["d", "e", "f"]]
15
+ #
16
+ # Note this task is quite inefficient in that it will load all data
17
+ # before making a selection; large files or edge selections may benefit
18
+ # from an alternate task.
19
+ #
20
+ class Csv < Load
21
+
22
+ config :columns, nil, &c.range_or_nil # Specify a range of columns
23
+ config :rows, nil, &c.range_or_nil # Specify a range of rows
24
+
25
+ config :col_sep, nil, &c.string_or_nil # The column separator (",")
26
+ config :row_sep, nil, &c.string_or_nil # The row separator ("\r\n" or "\n")
27
+
28
+ # Loads the io data as CSV, into an array of arrays.
29
+ def load(io)
30
+ data = CSV.parse(io.read, col_sep, row_sep)
31
+
32
+ if rows
33
+ data = data[rows]
34
+ end
35
+
36
+ if columns
37
+ data.collect! do |cols|
38
+ cols[columns]
39
+ end
40
+ end
41
+
42
+ data
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -2,7 +2,7 @@ require 'tap/tasks/load'
2
2
 
3
3
  module Tap
4
4
  module Tasks
5
- class Load < Tap::Task
5
+ class Load
6
6
 
7
7
  # :startdoc::task loads data as YAML
8
8
  #
@@ -14,10 +14,37 @@ module Tap
14
14
  #
15
15
  class Yaml < Load
16
16
 
17
+ config :stream, false, &c.flag # Load documents from a stream
18
+
17
19
  # Loads data from io as YAML.
18
20
  def load(io)
19
- YAML.load(io)
21
+ if stream
22
+ load_stream(io)
23
+ else
24
+ YAML.load(io)
25
+ end
26
+ end
27
+
28
+ def load_stream(io)
29
+ lines = []
30
+ while !io.eof?
31
+ line = io.readline
32
+
33
+ if line =~ /^---/ && !lines.empty?
34
+ io.pos = io.pos - line.length
35
+ break
36
+ else
37
+ lines << line
38
+ end
39
+ end
40
+
41
+ YAML.load(lines.join)
42
+ end
43
+
44
+ def complete?(io, last)
45
+ !stream || io.eof?
20
46
  end
47
+
21
48
  end
22
49
  end
23
50
  end
@@ -0,0 +1,42 @@
1
+ require 'tap/tasks/load'
2
+
3
+ module Tap
4
+ module Tasks
5
+ # :startdoc::task an input prompt
6
+ #
7
+ # Prompt reads lines from the input until the exit sequence is reached
8
+ # or the source io is closed. This is effectively an echo:
9
+ #
10
+ # % tap run -- prompt --: dump
11
+ # >
12
+ #
13
+ class Prompt < Load
14
+ config :prompt, "> ", &c.string_or_nil # The prompt sequence
15
+ config :exit_seq, "\n", &c.string_or_nil # The prompt exit sequence
16
+ config :terminal, $stdout, &c.io_or_nil # The terminal IO
17
+
18
+ configurations[:use_close].default = true
19
+
20
+ def load(io)
21
+ open_io(terminal) do |terminal|
22
+ terminal.print prompt
23
+ end if prompt
24
+
25
+ if io.eof?
26
+ nil
27
+ else
28
+ io.readline
29
+ end
30
+ end
31
+
32
+ def complete?(io, line)
33
+ line == nil || line == exit_seq
34
+ end
35
+
36
+ def close(io)
37
+ super
38
+ app.terminate
39
+ end
40
+ end
41
+ end
42
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tap-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Chiang
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-25 00:00:00 -06:00
12
+ date: 2009-06-17 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.17.0
23
+ version: 0.18.0
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: tap-test
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.1.0
33
+ version: 0.2.0
34
34
  version:
35
35
  description:
36
36
  email: simon.a.chiang@gmail.com
@@ -43,13 +43,13 @@ extra_rdoc_files:
43
43
  - README
44
44
  - MIT-LICENSE
45
45
  files:
46
- - lib/tap/tasks/file_task/shell_utils.rb
47
- - lib/tap/tasks/argv.rb
46
+ - lib/tap/tasks/dump/csv.rb
48
47
  - lib/tap/tasks/dump/inspect.rb
49
48
  - lib/tap/tasks/dump/yaml.rb
50
49
  - lib/tap/tasks/load/yaml.rb
50
+ - lib/tap/tasks/load/csv.rb
51
51
  - lib/tap/tasks/glob.rb
52
- - lib/tap/tasks/file_task.rb
52
+ - lib/tap/tasks/prompt.rb
53
53
  - tap.yml
54
54
  - History
55
55
  - README
@@ -1,30 +0,0 @@
1
- require 'tap/task'
2
-
3
- module Tap
4
- module Tasks
5
- # :startdoc::task provides a handle to ARGV
6
- #
7
- # Simply returns ARGV. This task can be a useful hook when executing
8
- # saved workflows via run (given that all arguments after the workflow
9
- # file are preserved in ARGV).
10
- #
11
- # # [workflow.yml]
12
- # # - - argv
13
- # # - - dump/yaml
14
- # # - 0[1]
15
- #
16
- # % tap run -w workflow.yml a b c
17
- # ---
18
- # - a
19
- # - b
20
- # - c
21
- #
22
- class Argv < Tap::Task
23
-
24
- # Simply returns ARGV.
25
- def process
26
- ARGV
27
- end
28
- end
29
- end
30
- end
@@ -1,383 +0,0 @@
1
- require 'tap/tasks/file_task/shell_utils'
2
-
3
- module Tap
4
- module Tasks
5
- # FileTask is a base class for tasks that work with a file system. FileTask
6
- # tracks changes it makes so they may be rolled back to their original state.
7
- # Rollback automatically occurs on an execute error.
8
- #
9
- # File.open("file.txt", "w") {|file| file << "original content"}
10
- #
11
- # t = FileTask.intern do |task, raise_error|
12
- # task.mkdir_p("some/dir") # marked for rollback
13
- # task.prepare("file.txt") do |file| # marked for rollback
14
- # file << "new content"
15
- # end
16
- #
17
- # # raise an error to start rollback
18
- # raise "error!" if raise_error
19
- # end
20
- #
21
- # begin
22
- # t.execute(true)
23
- # rescue
24
- # $!.message # => "error!"
25
- # File.exists?("some/dir") # => false
26
- # File.read("file.txt") # => "original content"
27
- # end
28
- #
29
- # t.execute(false)
30
- # File.exists?("some/dir") # => true
31
- # File.read("file.txt") # => "new content"
32
- #
33
- class FileTask < Task
34
- include ShellUtils
35
-
36
- # The backup directory
37
- config_attr :backup_dir, 'backup' # The backup directory
38
-
39
- # A flag indicating whether or track changes
40
- # for rollback on execution error
41
- config :rollback_on_error, true, &c.switch # Rollback changes on error
42
-
43
- def initialize(config={}, app=Tap::App.instance)
44
- super
45
- @actions = []
46
- end
47
-
48
- # Initializes a copy that will rollback independent of self.
49
- def initialize_copy(orig)
50
- super
51
- @actions = []
52
- end
53
-
54
- # Returns the path, exchanging the extension with extname.
55
- # A false or nil extname removes the extension, while true
56
- # preserves the existing extension (and effectively does
57
- # nothing).
58
- #
59
- # t = FileTask.new
60
- # t.basepath('path/to/file.txt') # => 'path/to/file'
61
- # t.basepath('path/to/file.txt', '.html') # => 'path/to/file.html'
62
- #
63
- # t.basepath('path/to/file.txt', false) # => 'path/to/file'
64
- # t.basepath('path/to/file.txt', true) # => 'path/to/file.txt'
65
- #
66
- # Compare to basename.
67
- def basepath(path, extname=false)
68
- case extname
69
- when false, nil then path.chomp(File.extname(path))
70
- when true then path
71
- else Root::Utils.exchange(path, extname)
72
- end
73
- end
74
-
75
- # Returns the basename of path, exchanging the extension
76
- # with extname. A false or nil extname removes the
77
- # extension, while true preserves the existing extension.
78
- #
79
- # t = FileTask.new
80
- # t.basename('path/to/file.txt') # => 'file.txt'
81
- # t.basename('path/to/file.txt', '.html') # => 'file.html'
82
- #
83
- # t.basename('path/to/file.txt', false) # => 'file'
84
- # t.basename('path/to/file.txt', true) # => 'file.txt'
85
- #
86
- # Compare to basepath.
87
- def basename(path, extname=true)
88
- basepath(File.basename(path), extname)
89
- end
90
-
91
- # Constructs a filepath using the dir and the specified paths.
92
- #
93
- # t = FileTask.new
94
- # t.filepath('data', "result.txt") # => File.expand_path("data/tap/tasks/file_task/result.txt")
95
- #
96
- def filepath(dir, *paths)
97
- File.expand_path(File.join(dir, *paths))
98
- end
99
-
100
- # Makes a backup filepath relative to backup_dir by using name, the
101
- # basename of filepath, and an index.
102
- #
103
- # t = FileTask.new({:backup_dir => "/backup"}, "name")
104
- # t.backup_filepath("path/to/file.txt", time) # => "/backup/name/file.0.txt"
105
- #
106
- def backup_filepath(path)
107
- extname = File.extname(path)
108
- backup_path = filepath(backup_dir, File.basename(path).chomp(extname))
109
- next_indexed_path(backup_path, 0, extname)
110
- end
111
-
112
- # Returns true if all of the targets are up to date relative to all of the
113
- # listed sources. Single values or arrays can be provided for both targets
114
- # and sources.
115
- #
116
- # Returns false (ie 'not up to date') if app.force is true.
117
- #
118
- #--
119
- # TODO: add check vs date reference (ex config_file date)
120
- def uptodate?(targets, sources=[])
121
- if app && app.force
122
- log_basename(:force, targets)
123
- false
124
- else
125
- targets = [targets] unless targets.kind_of?(Array)
126
- sources = [sources] unless sources.kind_of?(Array)
127
-
128
- targets.each do |target|
129
- return false unless FileUtils.uptodate?(target, sources)
130
- end
131
- true
132
- end
133
- end
134
-
135
- # Makes a backup of path to backup_filepath(path) and returns the backup path.
136
- # If backup_using_copy is true, the backup is a copy of path, otherwise the
137
- # file or directory at path is moved to the backup path. Raises an error if
138
- # the backup path already exists.
139
- #
140
- # Backups are restored on rollback.
141
- #
142
- # file = "file.txt"
143
- # File.open(file, "w") {|f| f << "file content"}
144
- #
145
- # t = FileTask.new
146
- # backup_file = t.backup(file)
147
- #
148
- # File.exists?(file) # => false
149
- # File.exists?(backup_file) # => true
150
- # File.read(backup_file) # => "file content"
151
- #
152
- # File.open(file, "w") {|f| f << "new content"}
153
- # t.rollback
154
- #
155
- # File.exists?(file) # => true
156
- # File.exists?(backup_file ) # => false
157
- # File.read(file) # => "file content"
158
- #
159
- def backup(path, backup_using_copy=false)
160
- return nil unless File.exists?(path)
161
-
162
- source = File.expand_path(path)
163
- target = backup_filepath(source)
164
- raise "backup already exists: #{target}" if File.exists?(target)
165
-
166
- mkdir_p File.dirname(target)
167
-
168
- log :backup, "#{source} to #{target}", Logger::DEBUG
169
- if backup_using_copy
170
- FileUtils.cp(source, target)
171
- else
172
- FileUtils.mv(source, target)
173
- end
174
-
175
- actions << [:backup, source, target]
176
- target
177
- end
178
-
179
- # Creates a directory and all its parent directories. Directories created
180
- # by mkdir_p removed on rollback.
181
- def mkdir_p(dir)
182
- dir = File.expand_path(dir)
183
-
184
- dirs = []
185
- while !File.exists?(dir)
186
- dirs.unshift(dir)
187
- dir = File.dirname(dir)
188
- end
189
-
190
- dirs.each {|d| mkdir(d) }
191
- end
192
-
193
- # Creates a directory. Directories created by mkdir removed on rollback.
194
- def mkdir(dir)
195
- dir = File.expand_path(dir)
196
-
197
- unless File.exists?(dir)
198
- log :mkdir, dir, Logger::DEBUG
199
- FileUtils.mkdir(dir)
200
- actions << [:make, dir]
201
- end
202
- end
203
-
204
- # Prepares the path by backing up any existing file and ensuring that
205
- # the parent directory for path exists. If a block is given, a file
206
- # is opened and yielded to it (as in File.open). Prepared paths are
207
- # removed and the backups restored on rollback.
208
- #
209
- # Returns the expanded path.
210
- def prepare(path, backup_using_copy=false)
211
- raise "not a file: #{path}" if File.directory?(path)
212
- path = File.expand_path(path)
213
-
214
- if File.exists?(path)
215
- # backup or remove existing files
216
- backup(path, backup_using_copy)
217
- else
218
- # ensure the parent directory exists
219
- # for non-existant files
220
- mkdir_p File.dirname(path)
221
- end
222
- log :prepare, path, Logger::DEBUG
223
- actions << [:make, path]
224
-
225
- if block_given?
226
- File.open(path, "w") {|file| yield(file) }
227
- end
228
-
229
- path
230
- end
231
-
232
- # Removes a file. If a directory is provided, it's contents are removed
233
- # recursively. Files and directories removed by rm_r are restored
234
- # upon an execution error.
235
- def rm_r(path)
236
- path = File.expand_path(path)
237
-
238
- backup(path, false)
239
- log :rm_r, path, Logger::DEBUG
240
- end
241
-
242
- # Removes an empty directory. Directories removed by rmdir are restored
243
- # upon an execution error.
244
- def rmdir(dir)
245
- dir = File.expand_path(dir)
246
-
247
- unless Root::Utils.empty?(dir)
248
- raise "not an empty directory: #{dir}"
249
- end
250
-
251
- backup(dir, false)
252
- log :rmdir, dir, Logger::DEBUG
253
- end
254
-
255
- # Removes a file. Directories cannot be removed by this method.
256
- # Files removed by rm are restored upon an execution error.
257
- def rm(path)
258
- path = File.expand_path(path)
259
-
260
- unless File.file?(path)
261
- raise "not a file: #{path}"
262
- end
263
-
264
- backup(path, false)
265
- log :rm, path, Logger::DEBUG
266
- end
267
-
268
- # Copies source to target. Files and directories copied by cp are
269
- # restored upon an execution error.
270
- def cp(source, target)
271
- target = File.join(target, File.basename(source)) if File.directory?(target)
272
- prepare(target)
273
-
274
- log :cp, "#{source} to #{target}", Logger::DEBUG
275
- FileUtils.cp(source, target)
276
- end
277
-
278
- # Copies source to target. If source is a directory, the contents
279
- # are copied recursively. If target is a directory, copies source
280
- # to target/source. Files and directories copied by cp are restored
281
- # upon an execution error.
282
- def cp_r(source, target)
283
- target = File.join(target, File.basename(source)) if File.directory?(target)
284
- prepare(target)
285
-
286
- log :cp_r, "#{source} to #{target}", Logger::DEBUG
287
- FileUtils.cp_r(source, target)
288
- end
289
-
290
- # Moves source to target. Files and directories moved by mv are
291
- # restored upon an execution error.
292
- def mv(source, target, backup_source=true)
293
- backup(source, true) if backup_source
294
- prepare(target)
295
-
296
- log :mv, "#{source} to #{target}", Logger::DEBUG
297
- FileUtils.mv(source, target)
298
- end
299
-
300
- # Rolls back any actions capable of being rolled back.
301
- #
302
- # Rollback is forceful. For instance if you make a folder using
303
- # mkdir, rollback will remove the folder and all files within it
304
- # even if they were not added by self.
305
- def rollback
306
- while !actions.empty?
307
- action, source, target = actions.pop
308
-
309
- case action
310
- when :make
311
- log :rollback, "#{source}", Logger::DEBUG
312
- FileUtils.rm_r(source)
313
- when :backup
314
- log :rollback, "#{target} to #{source}", Logger::DEBUG
315
- dir = File.dirname(source)
316
- FileUtils.mkdir_p(dir) unless File.exists?(dir)
317
- FileUtils.mv(target, source, :force => true)
318
- else
319
- raise "unknown action: #{[action, source, target].inspect}"
320
- end
321
- end
322
- end
323
-
324
- # Removes backup files. Cleanup cannot be rolled back and prevents
325
- # rollback of actions up to when cleanup is called. If cleanup_dirs
326
- # is true, empty directories containing the backup files will be
327
- # removed.
328
- def cleanup(cleanup_dirs=true)
329
- actions.each do |action, source, target|
330
- if action == :backup
331
- log :cleanup, target, Logger::DEBUG
332
- FileUtils.rm_r(target) if File.exists?(target)
333
- cleanup_dir(File.dirname(target)) if cleanup_dirs
334
- end
335
- end
336
- actions.clear
337
- end
338
-
339
- # Removes the directory if empty, and all empty parent directories. This
340
- # method cannot be rolled back.
341
- def cleanup_dir(dir)
342
- while Root::Utils.empty?(dir)
343
- log :rmdir, dir, Logger::DEBUG
344
- FileUtils.rmdir(dir)
345
- dir = File.dirname(dir)
346
- end
347
- end
348
-
349
- # Logs the given action, with the basenames of the input paths.
350
- def log_basename(action, paths, level=Logger::INFO)
351
- msg = [paths].flatten.collect {|path| File.basename(path) }.join(',')
352
- log(action, msg, level)
353
- end
354
-
355
- def call(*_inputs)
356
- actions.clear
357
-
358
- begin
359
- super
360
- rescue(Exception)
361
- rollback if rollback_on_error
362
- raise
363
- end
364
- end
365
-
366
- protected
367
-
368
- # An array tracking actions (backup, rm, mv, etc) performed by self,
369
- # allowing rollback on an execution error. Not intended to be
370
- # modified manually.
371
- attr_reader :actions
372
-
373
- private
374
-
375
- # utility method for backup_filepath; increments index until the
376
- # path base.indexext does not exist.
377
- def next_indexed_path(base, index, ext) # :nodoc:
378
- path = sprintf('%s.%d%s', base, index, ext)
379
- File.exists?(path) ? next_indexed_path(base, index + 1, ext) : path
380
- end
381
- end
382
- end
383
- end
@@ -1,71 +0,0 @@
1
- require 'tap/task'
2
- autoload(:Tempfile, 'tempfile')
3
-
4
- module Tap
5
- module Tasks
6
- class FileTask < Tap::Task
7
-
8
- # Provides several shell utility methods for calling programs.
9
- #
10
- # == Windows
11
- # MSDOS has command line length limits specific to the version of Windows being
12
- # run (from http://www.ss64.com/nt/cmd.html):
13
- #
14
- # Windows NT:: 256 characters
15
- # Windows 2000:: 2046 characters
16
- # Windows XP:: 8190 characters
17
- #
18
- # Commands longer than these limits fail, usually with something like: 'the input
19
- # line is too long'
20
- module ShellUtils
21
-
22
- # Run the system command +cmd+, passing the result to the block, if given.
23
- # Raises an error if the command fails. Uses the same semantics as
24
- # Kernel::exec and Kernel::system.
25
- #
26
- # Based on FileUtils#sh from Rake.
27
- def sh(*cmd) # :yields: ok, status
28
- ok = system(*cmd)
29
-
30
- if block_given?
31
- yield(ok, $?)
32
- else
33
- ok or raise "Command failed with status (#{$?.exitstatus}): [#{ cmd.join(' ')}]"
34
- end
35
- end
36
-
37
- # Runs the system command +cmd+ using sh, redirecting the output to the
38
- # specified file path. Uses the redirection command:
39
- #
40
- # "> \"#{path}\" 2>&1 #{cmd}"
41
- #
42
- # This redirection has been tested on Windows, OS X, and Fedora. See
43
- # http://en.wikipedia.org/wiki/Redirection_(Unix) for pointers on
44
- # redirection. This style of redirection SHOULD NOT be used with
45
- # commands that contain other redirections.
46
- def redirect_sh(cmd, path, &block) # :yields: ok, status
47
- sh( "> \"#{path}\" 2>&1 #{cmd}", &block)
48
- end
49
-
50
- # Runs the system command +cmd+ and returns the output as a string.
51
- def capture_sh(cmd, quiet=false, &block) # :yields: ok, status, tempfile_path
52
- tempfile = Tempfile.new('shell_utils')
53
- tempfile.close
54
- redirect_sh(cmd, tempfile.path) do |ok, status|
55
- if block_given?
56
- yield(ok, $?, tempfile.path)
57
- else
58
- ok or raise %Q{Command failed with status (#{$?.exitstatus}): [#{cmd}]
59
- -------------- command output -------------------
60
- #{File.read(tempfile.path)}
61
- -------------------------------------------------
62
- }
63
- end
64
- end
65
-
66
- quiet == true ? "" : File.read(tempfile.path)
67
- end
68
- end
69
- end
70
- end
71
- end