tap 0.11.1 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +35 -1
- data/MIT-LICENSE +1 -1
- data/README +16 -15
- data/bin/tap +1 -1
- data/cmd/console.rb +4 -3
- data/cmd/manifest.rb +2 -2
- data/cmd/run.rb +12 -15
- data/doc/Class Reference +120 -117
- data/doc/Command Reference +27 -27
- data/doc/Syntax Reference +55 -111
- data/doc/Tutorial +69 -26
- data/lib/tap.rb +3 -8
- data/lib/tap/app.rb +122 -146
- data/lib/tap/constants.rb +2 -2
- data/lib/tap/env.rb +178 -252
- data/lib/tap/exe.rb +67 -30
- data/lib/tap/file_task.rb +224 -411
- data/lib/tap/generator/arguments.rb +13 -0
- data/lib/tap/generator/base.rb +112 -30
- data/lib/tap/generator/destroy.rb +36 -13
- data/lib/tap/generator/generate.rb +69 -48
- data/lib/tap/generator/generators/command/templates/command.erb +3 -3
- data/lib/tap/generator/generators/config/config_generator.rb +82 -10
- data/lib/tap/generator/generators/generator/generator_generator.rb +16 -6
- data/lib/tap/generator/generators/generator/templates/task.erb +2 -2
- data/lib/tap/generator/generators/generator/templates/test.erb +26 -0
- data/lib/tap/generator/generators/root/root_generator.rb +24 -13
- data/lib/tap/generator/generators/root/templates/Rakefile +4 -4
- data/lib/tap/generator/generators/root/templates/{tapfile → Rapfile} +6 -6
- data/lib/tap/generator/generators/root/templates/gemspec +0 -1
- data/lib/tap/generator/generators/task/task_generator.rb +3 -3
- data/lib/tap/generator/generators/task/templates/test.erb +1 -1
- data/lib/tap/generator/manifest.rb +7 -1
- data/lib/tap/generator/preview.rb +76 -0
- data/lib/tap/root.rb +222 -156
- data/lib/tap/spec.rb +41 -0
- data/lib/tap/support/aggregator.rb +25 -28
- data/lib/tap/support/audit.rb +278 -357
- data/lib/tap/support/constant.rb +2 -1
- data/lib/tap/support/constant_manifest.rb +28 -25
- data/lib/tap/support/dependency.rb +1 -1
- data/lib/tap/support/executable.rb +52 -183
- data/lib/tap/support/executable_queue.rb +50 -20
- data/lib/tap/support/gems.rb +1 -1
- data/lib/tap/support/intern.rb +0 -6
- data/lib/tap/support/join.rb +49 -83
- data/lib/tap/support/joins.rb +0 -3
- data/lib/tap/support/joins/switch.rb +13 -11
- data/lib/tap/support/joins/sync_merge.rb +25 -50
- data/lib/tap/support/manifest.rb +1 -0
- data/lib/tap/support/node.rb +140 -20
- data/lib/tap/support/parser.rb +56 -42
- data/lib/tap/support/schema.rb +183 -157
- data/lib/tap/support/templater.rb +9 -1
- data/lib/tap/support/versions.rb +39 -0
- data/lib/tap/task.rb +150 -177
- data/lib/tap/tasks/dump.rb +4 -4
- data/lib/tap/tasks/load.rb +29 -29
- data/lib/tap/test.rb +66 -53
- data/lib/tap/test/env_vars.rb +3 -3
- data/lib/tap/test/extensions.rb +11 -17
- data/lib/tap/test/file_test.rb +74 -132
- data/lib/tap/test/file_test_class.rb +4 -1
- data/lib/tap/test/regexp_escape.rb +2 -2
- data/lib/tap/test/script_test.rb +2 -2
- data/lib/tap/test/subset_test.rb +6 -6
- data/lib/tap/test/tap_test.rb +28 -154
- metadata +30 -51
- data/bin/rap +0 -118
- data/cgi/run.rb +0 -97
- data/lib/tap/declarations.rb +0 -229
- data/lib/tap/generator/generators/config/templates/doc.erb +0 -12
- data/lib/tap/generator/generators/config/templates/nodoc.erb +0 -8
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +0 -27
- data/lib/tap/generator/generators/file_task/templates/file.txt +0 -11
- data/lib/tap/generator/generators/file_task/templates/result.yml +0 -6
- data/lib/tap/generator/generators/file_task/templates/task.erb +0 -33
- data/lib/tap/generator/generators/file_task/templates/test.erb +0 -29
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +0 -5
- data/lib/tap/patches/optparse/summarize.rb +0 -62
- data/lib/tap/support/assignments.rb +0 -173
- data/lib/tap/support/class_configuration.rb +0 -182
- data/lib/tap/support/combinator.rb +0 -125
- data/lib/tap/support/configurable.rb +0 -113
- data/lib/tap/support/configurable_class.rb +0 -271
- data/lib/tap/support/configuration.rb +0 -170
- data/lib/tap/support/gems/rake.rb +0 -111
- data/lib/tap/support/instance_configuration.rb +0 -173
- data/lib/tap/support/joins/fork.rb +0 -19
- data/lib/tap/support/joins/merge.rb +0 -22
- data/lib/tap/support/joins/sequence.rb +0 -21
- data/lib/tap/support/lazy_attributes.rb +0 -45
- data/lib/tap/support/lazydoc.rb +0 -386
- data/lib/tap/support/lazydoc/comment.rb +0 -503
- data/lib/tap/support/lazydoc/config.rb +0 -17
- data/lib/tap/support/lazydoc/definition.rb +0 -36
- data/lib/tap/support/lazydoc/document.rb +0 -152
- data/lib/tap/support/lazydoc/method.rb +0 -24
- data/lib/tap/support/tdoc.rb +0 -409
- data/lib/tap/support/tdoc/tdoc_html_generator.rb +0 -38
- data/lib/tap/support/tdoc/tdoc_html_template.rb +0 -42
- data/lib/tap/support/validation.rb +0 -479
- data/lib/tap/tasks/rake.rb +0 -57
data/lib/tap/exe.rb
CHANGED
@@ -4,44 +4,89 @@ require 'tap/support/schema'
|
|
4
4
|
|
5
5
|
module Tap
|
6
6
|
class Exe < Env
|
7
|
-
|
8
7
|
class << self
|
9
|
-
def instantiate(path=Dir.pwd
|
10
|
-
app =
|
11
|
-
exe = super(app
|
8
|
+
def instantiate(path=Dir.pwd)
|
9
|
+
app = App.instance.reconfigure(:root => path)
|
10
|
+
exe = super(app)
|
12
11
|
|
13
12
|
# add all gems if no gems are specified (Note this is VERY SLOW ~ 1/3 the overhead for tap)
|
14
|
-
if !File.exists?(Tap::Env::DEFAULT_CONFIG_FILE)
|
15
|
-
|
16
|
-
end
|
17
|
-
|
13
|
+
exe.gems = :all if !File.exists?(Tap::Env::DEFAULT_CONFIG_FILE)
|
14
|
+
|
18
15
|
# add the default tap instance
|
19
|
-
|
20
|
-
# tap.tasks.paths = tap.root.glob(:lib, "tap/tasks/*").collect do |task_path|
|
21
|
-
# [tap.root[:lib], task_path]
|
22
|
-
# end
|
23
|
-
exe.push(tap)
|
16
|
+
exe.push Env.instantiate("#{File.dirname(__FILE__)}/../..")
|
24
17
|
exe
|
25
18
|
end
|
26
19
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
20
|
+
def load_config(path)
|
21
|
+
super(GLOBAL_CONFIG_FILE).merge super(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adapted from Gem.find_home
|
25
|
+
def user_home
|
26
|
+
['HOME', 'USERPROFILE'].each do |homekey|
|
27
|
+
return ENV[homekey] if ENV[homekey]
|
28
|
+
end
|
29
|
+
|
30
|
+
if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
|
31
|
+
return "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}"
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
File.expand_path("~")
|
36
|
+
rescue
|
37
|
+
File::ALT_SEPARATOR ? "C:/" : "/"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# The Root directory structure for self.
|
43
|
+
nest(:root, Tap::App) do |config|
|
44
|
+
case config
|
45
|
+
when App then config
|
46
|
+
when String then App.new(:root => config)
|
47
|
+
else App.new(config)
|
48
|
+
end
|
31
49
|
end
|
32
50
|
|
33
51
|
config :before, nil
|
34
52
|
config :after, nil
|
53
|
+
# Specify files to require when self is activated.
|
54
|
+
config :requires, [], &c.array_or_nil
|
55
|
+
|
56
|
+
# Specify files to load when self is activated.
|
57
|
+
config :loads, [], &c.array_or_nil
|
35
58
|
config :aliases, {}, &c.hash_or_nil
|
36
59
|
|
60
|
+
# The global home directory
|
61
|
+
GLOBAL_HOME = File.join(Exe.user_home, ".tap")
|
62
|
+
|
37
63
|
# The global config file path
|
38
|
-
GLOBAL_CONFIG_FILE = File.join(
|
39
|
-
|
64
|
+
GLOBAL_CONFIG_FILE = File.join(GLOBAL_HOME, "tap.yml")
|
65
|
+
|
66
|
+
def initialize(app=App.instance)
|
67
|
+
super(app)
|
68
|
+
end
|
69
|
+
|
40
70
|
# Alias for root (Exe should have a Tap::App as root)
|
41
71
|
def app
|
42
72
|
root
|
43
73
|
end
|
44
74
|
|
75
|
+
def activate
|
76
|
+
if super
|
77
|
+
|
78
|
+
# perform requires
|
79
|
+
requires.each do |path|
|
80
|
+
require path
|
81
|
+
end
|
82
|
+
|
83
|
+
# perform loads
|
84
|
+
loads.each do |path|
|
85
|
+
load path
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
45
90
|
def handle_error(err)
|
46
91
|
case
|
47
92
|
when $DEBUG
|
@@ -68,14 +113,14 @@ module Tap
|
|
68
113
|
load path # run the command, if it exists
|
69
114
|
else
|
70
115
|
puts "Unknown command: '#{command}'"
|
71
|
-
puts "Type 'tap help' for usage information."
|
116
|
+
puts "Type 'tap --help' for usage information."
|
72
117
|
end
|
73
118
|
end
|
74
119
|
end
|
75
120
|
|
76
|
-
def build(argv=ARGV)
|
121
|
+
def build(argv=ARGV, app=app)
|
77
122
|
schema = argv.kind_of?(Support::Schema) ? argv : Support::Schema.parse(argv)
|
78
|
-
schema.
|
123
|
+
schema.build(app) do |args|
|
79
124
|
task = args.shift
|
80
125
|
const = tasks.search(task)
|
81
126
|
|
@@ -131,13 +176,5 @@ module Tap
|
|
131
176
|
end
|
132
177
|
end
|
133
178
|
end
|
134
|
-
|
135
|
-
def run(queues)
|
136
|
-
queues.each_with_index do |queue, i|
|
137
|
-
app.queue.concat(queue)
|
138
|
-
app.run
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
179
|
end
|
143
180
|
end
|
data/lib/tap/file_task.rb
CHANGED
@@ -1,118 +1,54 @@
|
|
1
1
|
require 'tap/support/shell_utils'
|
2
|
-
autoload(:FileUtils, "fileutils")
|
3
2
|
|
4
3
|
module Tap
|
5
4
|
|
6
|
-
# FileTask
|
7
|
-
#
|
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
8
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# FileTask tracks which files to roll back using the added_files array
|
12
|
-
# and the backed_up_files hash. On an execute error, all added files are
|
13
|
-
# removed and then all backed up files (backed_up_files.keys) are restored
|
14
|
-
# using the corresponding backup files (backed_up_files.values).
|
15
|
-
#
|
16
|
-
# For consistency, all filepaths in added_files and backed_up_files should
|
17
|
-
# be expanded using File.expand_path. The easiest way to ensure files are
|
18
|
-
# properly set up for rollback is to use prepare before working with files
|
19
|
-
# and to create directories with mkdir.
|
20
|
-
#
|
21
|
-
# # this file will be backed up and restored
|
22
|
-
# File.open("file.txt", "w") {|f| f << "original content"}
|
9
|
+
# File.open("file.txt", "w") {|file| file << "original content"}
|
23
10
|
#
|
24
|
-
# t = FileTask.intern do |task|
|
25
|
-
# task.
|
26
|
-
# task.prepare("file.txt"
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# File.touch("path/to/file.txt")
|
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
|
30
16
|
#
|
31
17
|
# # raise an error to start rollback
|
32
|
-
# raise "error!"
|
18
|
+
# raise "error!" if raise_error
|
33
19
|
# end
|
34
20
|
#
|
35
21
|
# begin
|
36
|
-
#
|
37
|
-
# File.exists?("path/to/file.txt") # => false
|
38
|
-
# t.execute(nil)
|
22
|
+
# t.execute(true)
|
39
23
|
# rescue
|
40
24
|
# $!.message # => "error!"
|
41
25
|
# File.exists?("some/dir") # => false
|
42
|
-
# File.exists?("path/to/file.txt") # => false
|
43
26
|
# File.read("file.txt") # => "original content"
|
44
27
|
# end
|
45
28
|
#
|
29
|
+
# t.execute(false)
|
30
|
+
# File.exists?("some/dir") # => true
|
31
|
+
# File.read("file.txt") # => "new content"
|
32
|
+
#
|
46
33
|
class FileTask < Task
|
47
34
|
include Tap::Support::ShellUtils
|
48
35
|
|
49
|
-
#
|
50
|
-
# backed-up files are backed_up_files.keys and the actual
|
51
|
-
# backup files are backed_up_files.values. All filepaths
|
52
|
-
# in backed_up_files should be expanded.
|
53
|
-
attr_reader :backed_up_files
|
54
|
-
|
55
|
-
# An array of files added during task execution.
|
56
|
-
attr_reader :added_files
|
57
|
-
|
58
|
-
# The backup directory, defaults to the class backup_dir
|
36
|
+
# The backup directory
|
59
37
|
config_attr :backup_dir, 'backup' # the backup directory
|
60
38
|
|
61
|
-
# A
|
62
|
-
#
|
63
|
-
config :timestamp, "%Y%m%d_%H%M%S" # the backup timestamp format
|
64
|
-
|
65
|
-
# A flag indicating whether or not to rollback changes on
|
66
|
-
# error, defaults to the class rollback_on_error
|
39
|
+
# A flag indicating whether or track changes
|
40
|
+
# for rollback on execution error
|
67
41
|
config :rollback_on_error, true, &c.switch # rollback changes on error
|
68
42
|
|
69
43
|
def initialize(config={}, name=nil, app=App.instance)
|
70
44
|
super
|
71
|
-
|
72
|
-
@backed_up_files = {}
|
73
|
-
@added_files = []
|
45
|
+
@actions = []
|
74
46
|
end
|
75
47
|
|
48
|
+
# Initializes a copy that will rollback independent of self.
|
76
49
|
def initialize_copy(orig)
|
77
50
|
super
|
78
|
-
@
|
79
|
-
@added_files = []
|
80
|
-
end
|
81
|
-
|
82
|
-
# A batch File.open method. If a block is given, each file in the list will be
|
83
|
-
# opened the open files passed to the block. Files are automatically closed when
|
84
|
-
# the block returns. If no block is given, the open files are returned.
|
85
|
-
#
|
86
|
-
# t = FileTask.new
|
87
|
-
# t.open(["one.txt", "two.txt"], "w") do |one, two|
|
88
|
-
# one << "one"
|
89
|
-
# two << "two"
|
90
|
-
# end
|
91
|
-
#
|
92
|
-
# File.read("one.txt") # => "one"
|
93
|
-
# File.read("two.txt") # => "two"
|
94
|
-
#
|
95
|
-
# Note that open normally takes and passes a list (ie an Array). If you provide
|
96
|
-
# a single argument, it will be translated into an Array, and passed AS AN ARRAY
|
97
|
-
# to the block.
|
98
|
-
#
|
99
|
-
# t.open("file.txt", "w") do |array|
|
100
|
-
# array.first << "content"
|
101
|
-
# end
|
102
|
-
#
|
103
|
-
# File.read("file.txt") # => "content"
|
104
|
-
#
|
105
|
-
def open(list, mode="rb")
|
106
|
-
open_files = []
|
107
|
-
begin
|
108
|
-
[list].flatten.map {|path| path.to_str }.each do |filepath|
|
109
|
-
open_files << File.open(filepath, mode)
|
110
|
-
end
|
111
|
-
|
112
|
-
block_given? ? yield(open_files) : open_files
|
113
|
-
ensure
|
114
|
-
open_files.each {|file| file.close } if block_given?
|
115
|
-
end
|
51
|
+
@actions = []
|
116
52
|
end
|
117
53
|
|
118
54
|
# Returns the path, exchanging the extension with extname.
|
@@ -130,13 +66,9 @@ module Tap
|
|
130
66
|
# Compare to basename.
|
131
67
|
def basepath(path, extname=false)
|
132
68
|
case extname
|
133
|
-
when false, nil
|
134
|
-
|
135
|
-
|
136
|
-
path
|
137
|
-
else
|
138
|
-
extname = extname[1, extname.length-1] if extname[0] == ?.
|
139
|
-
"#{path.chomp(File.extname(path))}.#{extname}"
|
69
|
+
when false, nil then path.chomp(File.extname(path))
|
70
|
+
when true then path
|
71
|
+
else Root.exchange(path, extname)
|
140
72
|
end
|
141
73
|
end
|
142
74
|
|
@@ -167,27 +99,26 @@ module Tap
|
|
167
99
|
app.filepath(dir, name, *paths)
|
168
100
|
end
|
169
101
|
|
170
|
-
# Makes a backup filepath relative to backup_dir by using
|
171
|
-
# basename of filepath
|
102
|
+
# Makes a backup filepath relative to backup_dir by using name, the
|
103
|
+
# basename of filepath, and an index.
|
172
104
|
#
|
173
|
-
# t = FileTask.new({:
|
174
|
-
# t.
|
175
|
-
# time = Time.utc(2008,8,8)
|
176
|
-
#
|
177
|
-
# t.backup_filepath("path/to/file.txt", time) # => "/backup/name/file_20080808.txt"
|
105
|
+
# t = FileTask.new({:backup_dir => "/backup"}, "name")
|
106
|
+
# t.backup_filepath("path/to/file.txt", time) # => "/backup/name/file.0.txt"
|
178
107
|
#
|
179
|
-
def backup_filepath(
|
180
|
-
extname = File.extname(
|
181
|
-
backup_path =
|
182
|
-
|
108
|
+
def backup_filepath(path)
|
109
|
+
extname = File.extname(path)
|
110
|
+
backup_path = filepath(backup_dir, File.basename(path).chomp(extname))
|
111
|
+
next_indexed_path(backup_path, 0, extname)
|
183
112
|
end
|
184
113
|
|
185
|
-
# Returns true if all of the targets are up to date relative to all of the
|
186
|
-
# sources
|
187
|
-
#
|
114
|
+
# Returns true if all of the targets are up to date relative to all of the
|
115
|
+
# listed sources. Single values or arrays can be provided for both targets
|
116
|
+
# and sources.
|
117
|
+
#
|
118
|
+
# Returns false (ie 'not up to date') if app.force is true.
|
188
119
|
#
|
189
|
-
|
190
|
-
#
|
120
|
+
#--
|
121
|
+
# TODO: add check vs date reference (ex config_file date)
|
191
122
|
def uptodate?(targets, sources=[])
|
192
123
|
if app.force
|
193
124
|
log_basename(:force, *targets)
|
@@ -196,9 +127,6 @@ module Tap
|
|
196
127
|
targets = [targets] unless targets.kind_of?(Array)
|
197
128
|
sources = [sources] unless sources.kind_of?(Array)
|
198
129
|
|
199
|
-
# should be able to specify this somehow, externally set
|
200
|
-
# sources << config_file unless config_file == nil
|
201
|
-
|
202
130
|
targets.each do |target|
|
203
131
|
return false unless FileUtils.uptodate?(target, sources)
|
204
132
|
end
|
@@ -206,367 +134,252 @@ module Tap
|
|
206
134
|
end
|
207
135
|
end
|
208
136
|
|
209
|
-
# Makes a backup of
|
210
|
-
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
# in backed_up_files.
|
137
|
+
# Makes a backup of path to backup_filepath(path) and returns the backup path.
|
138
|
+
# If backup_using_copy is true, the backup is a copy of path, otherwise the
|
139
|
+
# file or directory at path is moved to the backup path. Raises an error if
|
140
|
+
# the backup path already exists.
|
214
141
|
#
|
215
|
-
#
|
142
|
+
# Backups are restored on rollback.
|
216
143
|
#
|
217
144
|
# file = "file.txt"
|
218
145
|
# File.open(file, "w") {|f| f << "file content"}
|
219
146
|
#
|
220
147
|
# t = FileTask.new
|
221
|
-
#
|
148
|
+
# backup_file = t.backup(file)
|
222
149
|
#
|
223
150
|
# File.exists?(file) # => false
|
224
|
-
# File.exists?(
|
225
|
-
# File.read(
|
151
|
+
# File.exists?(backup_file) # => true
|
152
|
+
# File.read(backup_file) # => "file content"
|
226
153
|
#
|
227
154
|
# File.open(file, "w") {|f| f << "new content"}
|
228
|
-
# t.
|
155
|
+
# t.rollback
|
229
156
|
#
|
230
157
|
# File.exists?(file) # => true
|
231
|
-
# File.exists?(
|
158
|
+
# File.exists?(backup_file ) # => false
|
232
159
|
# File.read(file) # => "file content"
|
233
160
|
#
|
234
|
-
def backup(
|
235
|
-
|
236
|
-
next unless File.exists?(filepath)
|
237
|
-
|
238
|
-
filepath = File.expand_path(filepath)
|
239
|
-
if backed_up_files.include?(filepath)
|
240
|
-
raise "Backup for #{filepath} already exists."
|
241
|
-
end
|
161
|
+
def backup(path, backup_using_copy=false)
|
162
|
+
return nil unless File.exists?(path)
|
242
163
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
# track the target for restores
|
256
|
-
backed_up_files[filepath] = target
|
257
|
-
target
|
164
|
+
source = File.expand_path(path)
|
165
|
+
target = backup_filepath(source)
|
166
|
+
raise "backup already exists: #{target}" if File.exists?(target)
|
167
|
+
|
168
|
+
mkdir_p File.dirname(target)
|
169
|
+
|
170
|
+
log :backup, "#{source} to #{target}", Logger::DEBUG
|
171
|
+
if backup_using_copy
|
172
|
+
FileUtils.cp(source, target)
|
173
|
+
else
|
174
|
+
FileUtils.mv(source, target)
|
258
175
|
end
|
176
|
+
|
177
|
+
actions << [:backup, source, target]
|
178
|
+
target
|
259
179
|
end
|
260
180
|
|
261
|
-
#
|
262
|
-
#
|
263
|
-
|
264
|
-
|
265
|
-
#
|
266
|
-
# file = "file.txt"
|
267
|
-
# File.open(file, "w") {|f| f << "file content"}
|
268
|
-
#
|
269
|
-
# t = FileTask.new
|
270
|
-
# backed_up_file = t.backup(file).first
|
271
|
-
#
|
272
|
-
# File.exists?(file) # => true
|
273
|
-
# File.exists?(backed_up_file) # => true
|
274
|
-
# File.read(backed_up_file) # => "file content"
|
275
|
-
#
|
276
|
-
# File.open(file, "w") {|f| f << "new content"}
|
277
|
-
# t.restore(file)
|
278
|
-
#
|
279
|
-
# File.exists?(file) # => true
|
280
|
-
# File.exists?(backed_up_file) # => false
|
281
|
-
# File.read(file) # => "file content"
|
282
|
-
#
|
283
|
-
def restore(list)
|
284
|
-
fu_list(list).collect do |filepath|
|
285
|
-
filepath = File.expand_path(filepath)
|
286
|
-
next unless backed_up_files.has_key?(filepath)
|
287
|
-
|
288
|
-
target = backed_up_files.delete(filepath)
|
289
|
-
|
290
|
-
dir = File.dirname(filepath)
|
291
|
-
mkdir(dir)
|
181
|
+
# Creates a directory and all its parent directories. Directories created
|
182
|
+
# by mkdir_p removed on rollback.
|
183
|
+
def mkdir_p(dir)
|
184
|
+
dir = File.expand_path(dir)
|
292
185
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
dir = File.dirname(
|
297
|
-
|
186
|
+
dirs = []
|
187
|
+
while !File.exists?(dir)
|
188
|
+
dirs.unshift(dir)
|
189
|
+
dir = File.dirname(dir)
|
190
|
+
end
|
298
191
|
|
299
|
-
|
300
|
-
end.compact
|
192
|
+
dirs.each {|d| mkdir(d) }
|
301
193
|
end
|
302
194
|
|
303
|
-
# Creates
|
304
|
-
|
305
|
-
|
306
|
-
# execution error.
|
307
|
-
#
|
308
|
-
# Returns the made directories.
|
309
|
-
#
|
310
|
-
# t = FileTask.new do |task, inputs|
|
311
|
-
# File.exists?("path") # => false
|
312
|
-
#
|
313
|
-
# task.mkdir("path/to/dir") # will be rolled back
|
314
|
-
# File.exists?("path/to/dir") # => true
|
315
|
-
#
|
316
|
-
# FileUtils.mkdir("path/to/another") # will not be rolled back
|
317
|
-
# File.exists?("path/to/another") # => true
|
318
|
-
#
|
319
|
-
# raise "error!"
|
320
|
-
# end
|
321
|
-
#
|
322
|
-
# begin
|
323
|
-
# t.execute(nil)
|
324
|
-
# rescue
|
325
|
-
# $!.message # => "error!"
|
326
|
-
# File.exists?("path/to/dir") # => false
|
327
|
-
# File.exists?("path/to/another") # => true
|
328
|
-
# end
|
329
|
-
#
|
330
|
-
def mkdir(list)
|
331
|
-
fu_list(list).each do |dir|
|
332
|
-
dir = File.expand_path(dir)
|
333
|
-
|
334
|
-
make_paths = []
|
335
|
-
while !File.exists?(dir)
|
336
|
-
make_paths << dir
|
337
|
-
dir = File.dirname(dir)
|
338
|
-
end
|
195
|
+
# Creates a directory. Directories created by mkdir removed on rollback.
|
196
|
+
def mkdir(dir)
|
197
|
+
dir = File.expand_path(dir)
|
339
198
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
end
|
199
|
+
unless File.exists?(dir)
|
200
|
+
log :mkdir, dir, Logger::DEBUG
|
201
|
+
FileUtils.mkdir(dir)
|
202
|
+
actions << [:make, dir]
|
345
203
|
end
|
346
204
|
end
|
347
205
|
|
348
|
-
#
|
349
|
-
#
|
350
|
-
# is
|
351
|
-
#
|
352
|
-
#
|
353
|
-
# Returns
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
dir = File.expand_path(dir)
|
206
|
+
# Prepares the path by backing up any existing file and ensuring that
|
207
|
+
# the parent directory for path exists. If a block is given, a file
|
208
|
+
# is opened and yielded to it (as in File.open). Prepared paths are
|
209
|
+
# removed and the backups restored on rollback.
|
210
|
+
#
|
211
|
+
# Returns the expanded path.
|
212
|
+
def prepare(path, backup_using_copy=false)
|
213
|
+
raise "not a file: #{path}" if File.directory?(path)
|
214
|
+
path = File.expand_path(path)
|
215
|
+
|
216
|
+
if File.exists?(path)
|
217
|
+
# backup or remove existing files
|
218
|
+
backup(path, backup_using_copy)
|
219
|
+
else
|
220
|
+
# ensure the parent directory exists
|
221
|
+
# for non-existant files
|
222
|
+
mkdir_p File.dirname(path)
|
223
|
+
end
|
224
|
+
log :prepare, path, Logger::DEBUG
|
225
|
+
actions << [:make, path]
|
369
226
|
|
370
|
-
|
371
|
-
|
372
|
-
while added_files.include?(dir)
|
373
|
-
break unless dir_empty?(dir)
|
374
|
-
|
375
|
-
if File.exists?(dir)
|
376
|
-
log :rmdir, dir, Logger::DEBUG
|
377
|
-
FileUtils.rmdir(dir)
|
378
|
-
end
|
379
|
-
|
380
|
-
removed << added_files.delete(dir)
|
381
|
-
dir = File.dirname(dir)
|
382
|
-
end
|
227
|
+
if block_given?
|
228
|
+
File.open(path, "w") {|file| yield(file) }
|
383
229
|
end
|
384
|
-
|
230
|
+
|
231
|
+
path
|
385
232
|
end
|
386
233
|
|
387
|
-
|
388
|
-
|
234
|
+
# Removes a file. If a directory is provided, it's contents are removed
|
235
|
+
# recursively. Files and directories removed by rm_r are restored
|
236
|
+
# upon an execution error.
|
237
|
+
def rm_r(path)
|
238
|
+
path = File.expand_path(path)
|
239
|
+
|
240
|
+
backup(path, false)
|
241
|
+
log :rm_r, path, Logger::DEBUG
|
389
242
|
end
|
390
243
|
|
391
|
-
#
|
392
|
-
#
|
393
|
-
|
394
|
-
|
395
|
-
# execution error.
|
396
|
-
#
|
397
|
-
# Returns the prepared files.
|
398
|
-
#
|
399
|
-
# File.open("file.txt", "w") {|f| f << "original content"}
|
400
|
-
#
|
401
|
-
# t = FileTask.new do |task, inputs|
|
402
|
-
# File.exists?("path") # => false
|
403
|
-
#
|
404
|
-
# # backup... make parent dirs... prepare for restore
|
405
|
-
# task.prepare(["file.txt", "path/to/file.txt"])
|
406
|
-
#
|
407
|
-
# File.open("file.txt", "w") {|f| f << "new content"}
|
408
|
-
# File.touch("path/to/file.txt")
|
409
|
-
#
|
410
|
-
# raise "error!"
|
411
|
-
# end
|
412
|
-
#
|
413
|
-
# begin
|
414
|
-
# t.execute(nil)
|
415
|
-
# rescue
|
416
|
-
# $!.message # => "error!"
|
417
|
-
# File.exists?("file.txt") # => true
|
418
|
-
# File.read("file.txt") # => "original content"
|
419
|
-
# File.exists?("path") # => false
|
420
|
-
# end
|
421
|
-
#
|
422
|
-
def prepare(list, backup_using_copy=false)
|
423
|
-
list = fu_list(list)
|
424
|
-
existing_files, non_existant_files = list.partition do |filepath|
|
425
|
-
File.exists?(filepath)
|
426
|
-
end
|
244
|
+
# Removes an empty directory. Directories removed by rmdir are restored
|
245
|
+
# upon an execution error.
|
246
|
+
def rmdir(dir)
|
247
|
+
dir = File.expand_path(dir)
|
427
248
|
|
428
|
-
|
429
|
-
|
430
|
-
backup(filepath, backup_using_copy)
|
249
|
+
unless Root.empty?(dir)
|
250
|
+
raise "not an empty directory: #{dir}"
|
431
251
|
end
|
432
252
|
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
253
|
+
backup(dir, false)
|
254
|
+
log :rmdir, dir, Logger::DEBUG
|
255
|
+
end
|
256
|
+
|
257
|
+
# Removes a file. Directories cannot be removed by this method.
|
258
|
+
# Files removed by rm are restored upon an execution error.
|
259
|
+
def rm(path)
|
260
|
+
path = File.expand_path(path)
|
439
261
|
|
440
|
-
|
441
|
-
|
262
|
+
unless File.file?(path)
|
263
|
+
raise "not a file: #{path}"
|
442
264
|
end
|
443
|
-
|
444
|
-
|
265
|
+
|
266
|
+
backup(path, false)
|
267
|
+
log :rm, path, Logger::DEBUG
|
445
268
|
end
|
446
269
|
|
447
|
-
#
|
448
|
-
#
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
# FileUtils.mkdir("path") # will not be removed
|
456
|
-
#
|
457
|
-
# t.prepare("path/to/file.txt")
|
458
|
-
# FileUtils.touch("path/to/file.txt")
|
459
|
-
# File.exists?("path/to/file.txt") # => true
|
460
|
-
#
|
461
|
-
# t.rm("path/to/file.txt")
|
462
|
-
# File.exists?("path") # => true
|
463
|
-
# File.exists?("path/to") # => false
|
464
|
-
def rm(list)
|
465
|
-
removed = []
|
466
|
-
fu_list(list).each do |filepath|
|
467
|
-
filepath = File.expand_path(filepath)
|
468
|
-
next unless added_files.include?(filepath)
|
469
|
-
|
470
|
-
# if the file exists, remove it
|
471
|
-
if File.exists?(filepath)
|
472
|
-
log :rm, filepath, Logger::DEBUG
|
473
|
-
FileUtils.rm(filepath, :force => true)
|
474
|
-
end
|
475
|
-
|
476
|
-
removed << added_files.delete(filepath)
|
477
|
-
removed.concat rmdir(File.dirname(filepath))
|
478
|
-
end
|
479
|
-
removed
|
270
|
+
# Copies source to target. Files and directories copied by cp are
|
271
|
+
# restored upon an execution error.
|
272
|
+
def cp(source, target)
|
273
|
+
target = File.join(target, File.basename(source)) if File.directory?(target)
|
274
|
+
prepare(target)
|
275
|
+
|
276
|
+
log :cp, "#{source} to #{target}", Logger::DEBUG
|
277
|
+
FileUtils.cp(source, target)
|
480
278
|
end
|
481
279
|
|
482
|
-
#
|
483
|
-
#
|
484
|
-
#
|
485
|
-
#
|
486
|
-
def
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
280
|
+
# Copies source to target. If source is a directory, the contents
|
281
|
+
# are copied recursively. If target is a directory, copies source
|
282
|
+
# to target/source. Files and directories copied by cp are restored
|
283
|
+
# upon an execution error.
|
284
|
+
def cp_r(source, target)
|
285
|
+
target = File.join(target, File.basename(source)) if File.directory?(target)
|
286
|
+
prepare(target)
|
287
|
+
|
288
|
+
log :cp_r, "#{source} to #{target}", Logger::DEBUG
|
289
|
+
FileUtils.cp_r(source, target)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Moves source to target. Files and directories moved by mv are
|
293
|
+
# restored upon an execution error.
|
294
|
+
def mv(source, target, backup_source=true)
|
295
|
+
backup(source, true) if backup_source
|
296
|
+
prepare(target)
|
297
|
+
|
298
|
+
log :mv, "#{source} to #{target}", Logger::DEBUG
|
299
|
+
FileUtils.mv(source, target)
|
300
|
+
end
|
301
|
+
|
302
|
+
# Rolls back any actions capable of being rolled back.
|
303
|
+
#
|
304
|
+
# Rollback is forceful. For instance if you make a folder using
|
305
|
+
# mkdir, rollback will remove the folder and all files within it
|
306
|
+
# even if they were not added by self.
|
307
|
+
def rollback
|
308
|
+
while !actions.empty?
|
309
|
+
action, source, target = actions.pop
|
310
|
+
|
311
|
+
case action
|
312
|
+
when :make
|
313
|
+
log :rollback, "#{source}", Logger::DEBUG
|
314
|
+
FileUtils.rm_r(source)
|
315
|
+
when :backup
|
316
|
+
log :rollback, "#{target} to #{source}", Logger::DEBUG
|
317
|
+
dir = File.dirname(source)
|
318
|
+
FileUtils.mkdir_p(dir) unless File.exists?(dir)
|
319
|
+
FileUtils.mv(target, source, :force => true)
|
320
|
+
else
|
321
|
+
raise "unknown action: #{[action, source, target].inspect}"
|
501
322
|
end
|
502
323
|
end
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
324
|
+
end
|
325
|
+
|
326
|
+
# Removes backup files. Cleanup cannot be rolled back and prevents
|
327
|
+
# rollback of actions up to when cleanup is called. If cleanup_dirs
|
328
|
+
# is true, empty directories containing the backup files will be
|
329
|
+
# removed.
|
330
|
+
def cleanup(cleanup_dirs=true)
|
331
|
+
actions.each do |action, source, target|
|
332
|
+
if action == :backup
|
333
|
+
log :cleanup, target, Logger::DEBUG
|
334
|
+
FileUtils.rm_r(target) if File.exists?(target)
|
335
|
+
cleanup_dir(File.dirname(target)) if cleanup_dirs
|
509
336
|
end
|
510
337
|
end
|
338
|
+
actions.clear
|
511
339
|
end
|
512
340
|
|
513
|
-
# Removes
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
rm(target)
|
522
|
-
backed_up_files.delete(filepath)
|
523
|
-
end
|
341
|
+
# Removes the directory if empty, and all empty parent directories. This
|
342
|
+
# method cannot be rolled back.
|
343
|
+
def cleanup_dir(dir)
|
344
|
+
while Root.empty?(dir)
|
345
|
+
log :rmdir, dir, Logger::DEBUG
|
346
|
+
FileUtils.rmdir(dir)
|
347
|
+
dir = File.dirname(dir)
|
348
|
+
end
|
524
349
|
end
|
525
350
|
|
526
|
-
# Logs the given action, with the basenames of the input
|
527
|
-
def log_basename(action,
|
528
|
-
msg =
|
529
|
-
when Array then filepaths.collect {|filepath| File.basename(filepath) }.join(',')
|
530
|
-
else
|
531
|
-
File.basename(filepaths)
|
532
|
-
end
|
533
|
-
|
351
|
+
# Logs the given action, with the basenames of the input paths.
|
352
|
+
def log_basename(action, paths, level=Logger::INFO)
|
353
|
+
msg = [paths].flatten.collect {|path| File.basename(path) }.join(',')
|
534
354
|
log(action, msg, level)
|
535
355
|
end
|
536
356
|
|
537
357
|
protected
|
538
|
-
|
539
|
-
attr_writer :backed_up_files, :added_files
|
540
358
|
|
541
|
-
#
|
542
|
-
#
|
359
|
+
# An array tracking actions (backup, rm, mv, etc) performed by self,
|
360
|
+
# allowing rollback on an execution error. Not intended to be
|
361
|
+
# modified manually.
|
362
|
+
attr_reader :actions
|
363
|
+
|
364
|
+
# Clears actions so that a failure will not affect previous executions
|
543
365
|
def before_execute
|
544
|
-
|
545
|
-
backed_up_files.clear
|
366
|
+
actions.clear
|
546
367
|
end
|
547
368
|
|
548
369
|
# Removes made files/dirs and restores backed-up files upon
|
549
|
-
# an execute error.
|
550
|
-
# and raises them in a Tap::Support::RunError.
|
370
|
+
# an execute error.
|
551
371
|
def on_execute_error(original_error)
|
552
|
-
|
553
|
-
|
554
|
-
rollback {|error| rollback_errors << error}
|
555
|
-
end
|
556
|
-
|
557
|
-
# Re-raise the error if no rollback errors occured,
|
558
|
-
# otherwise, raise a RunError tracking the errors.
|
559
|
-
if rollback_errors.empty?
|
560
|
-
raise original_error
|
561
|
-
else
|
562
|
-
rollback_errors.unshift(original_error)
|
563
|
-
raise Support::RunError.new(rollback_errors)
|
564
|
-
end
|
372
|
+
rollback if rollback_on_error
|
373
|
+
raise original_error
|
565
374
|
end
|
566
375
|
|
567
|
-
|
568
|
-
|
569
|
-
|
376
|
+
private
|
377
|
+
|
378
|
+
# utility method for backup_filepath; increments index until the
|
379
|
+
# path base.indexext does not exist.
|
380
|
+
def next_indexed_path(base, index, ext) # :nodoc:
|
381
|
+
path = sprintf('%s.%d%s', base, index, ext)
|
382
|
+
File.exists?(path) ? next_indexed_path(base, index + 1, ext) : path
|
570
383
|
end
|
571
384
|
end
|
572
385
|
end
|