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 +8 -0
- data/lib/tap/tasks/dump/csv.rb +30 -0
- data/lib/tap/tasks/dump/inspect.rb +1 -1
- data/lib/tap/tasks/dump/yaml.rb +1 -1
- data/lib/tap/tasks/glob.rb +24 -8
- data/lib/tap/tasks/load/csv.rb +48 -0
- data/lib/tap/tasks/load/yaml.rb +29 -2
- data/lib/tap/tasks/prompt.rb +42 -0
- metadata +7 -7
- data/lib/tap/tasks/argv.rb +0 -30
- data/lib/tap/tasks/file_task.rb +0 -383
- data/lib/tap/tasks/file_task/shell_utils.rb +0 -71
data/History
CHANGED
@@ -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
|
data/lib/tap/tasks/dump/yaml.rb
CHANGED
data/lib/tap/tasks/glob.rb
CHANGED
@@ -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 :
|
15
|
-
config :
|
16
|
-
config :
|
17
|
-
config :
|
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
|
24
|
-
next if
|
37
|
+
next if files == false && File.file?(path)
|
38
|
+
next if dirs == false && File.directory?(path)
|
25
39
|
|
26
40
|
case path
|
27
|
-
when *
|
28
|
-
|
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
|
data/lib/tap/tasks/load/yaml.rb
CHANGED
@@ -2,7 +2,7 @@ require 'tap/tasks/load'
|
|
2
2
|
|
3
3
|
module Tap
|
4
4
|
module Tasks
|
5
|
-
class Load
|
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
|
-
|
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.
|
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-
|
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.
|
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.
|
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/
|
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/
|
52
|
+
- lib/tap/tasks/prompt.rb
|
53
53
|
- tap.yml
|
54
54
|
- History
|
55
55
|
- README
|
data/lib/tap/tasks/argv.rb
DELETED
@@ -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
|
data/lib/tap/tasks/file_task.rb
DELETED
@@ -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
|