tap-tasks 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|