bahuvrihi-tap 0.11.2 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rap +2 -3
- data/bin/tap +1 -1
- data/cmd/console.rb +2 -2
- data/cmd/manifest.rb +2 -2
- data/cmd/run.rb +7 -9
- data/cmd/server.rb +5 -5
- data/doc/Class Reference +17 -20
- data/doc/Tutorial +5 -7
- data/lib/tap.rb +2 -0
- data/lib/tap/app.rb +21 -31
- data/lib/tap/constants.rb +2 -2
- data/lib/tap/declarations.rb +85 -97
- data/lib/tap/declarations/declaration_task.rb +58 -0
- data/lib/tap/declarations/description.rb +24 -0
- data/lib/tap/env.rb +20 -16
- data/lib/tap/exe.rb +2 -2
- data/lib/tap/file_task.rb +224 -410
- data/lib/tap/generator/arguments.rb +9 -0
- data/lib/tap/generator/base.rb +105 -28
- data/lib/tap/generator/destroy.rb +29 -12
- data/lib/tap/generator/generate.rb +55 -39
- data/lib/tap/generator/generators/command/templates/command.erb +3 -3
- data/lib/tap/generator/generators/config/config_generator.rb +34 -3
- data/lib/tap/generator/generators/root/root_generator.rb +6 -9
- data/lib/tap/generator/generators/root/templates/Rakefile +4 -4
- data/lib/tap/generator/generators/task/templates/test.erb +1 -1
- data/lib/tap/root.rb +211 -156
- data/lib/tap/support/aggregator.rb +6 -9
- data/lib/tap/support/audit.rb +278 -357
- data/lib/tap/support/constant_manifest.rb +24 -21
- data/lib/tap/support/dependency.rb +1 -1
- data/lib/tap/support/executable.rb +26 -48
- data/lib/tap/support/join.rb +44 -19
- data/lib/tap/support/joins/sync_merge.rb +3 -5
- data/lib/tap/support/parser.rb +1 -1
- data/lib/tap/task.rb +195 -150
- data/lib/tap/tasks/dump.rb +2 -2
- data/lib/tap/test/extensions.rb +11 -13
- data/lib/tap/test/file_test.rb +71 -129
- data/lib/tap/test/file_test_class.rb +4 -1
- data/lib/tap/test/tap_test.rb +26 -154
- metadata +15 -22
- 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/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/instance_configuration.rb +0 -173
- data/lib/tap/support/lazydoc.rb +0 -386
- data/lib/tap/support/lazydoc/attributes.rb +0 -48
- 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
@@ -0,0 +1,58 @@
|
|
1
|
+
autoload(:OpenStruct, 'ostruct')
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Declarations
|
5
|
+
# Dependency tasks are a singleton version of tasks. Dependency tasks only
|
6
|
+
# have one instance (DeclarationTask.instance) and the instance is
|
7
|
+
# registered as a dependency, so it will only execute once.
|
8
|
+
class DeclarationTask < Tap::Task
|
9
|
+
class << self
|
10
|
+
attr_writer :blocks
|
11
|
+
|
12
|
+
def blocks
|
13
|
+
@blocks ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_writer :arg_names
|
17
|
+
|
18
|
+
def arg_names
|
19
|
+
@arg_names ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Initializes instance and registers it as a dependency.
|
23
|
+
def new(*args)
|
24
|
+
@instance ||= super
|
25
|
+
@instance.app.dependencies.register(@instance)
|
26
|
+
@instance
|
27
|
+
end
|
28
|
+
|
29
|
+
def args
|
30
|
+
args = Lazydoc::Arguments.new
|
31
|
+
arg_names.each {|name| args.arguments << name.to_s }
|
32
|
+
args
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def process(*inputs)
|
37
|
+
# collect inputs to make a rakish-args object
|
38
|
+
args = {}
|
39
|
+
self.class.arg_names.each do |arg_name|
|
40
|
+
break if inputs.empty?
|
41
|
+
args[arg_name] = inputs.shift
|
42
|
+
end
|
43
|
+
args = OpenStruct.new(args)
|
44
|
+
|
45
|
+
# execute each block assciated with this task
|
46
|
+
self.class.blocks.each do |task_block|
|
47
|
+
case task_block.arity
|
48
|
+
when 0 then task_block.call()
|
49
|
+
when 1 then task_block.call(self)
|
50
|
+
else task_block.call(self, args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Tap
|
2
|
+
module Declarations
|
3
|
+
|
4
|
+
# A special type of Lazydoc::Comment designed to handle the comment syntax
|
5
|
+
# for task declarations.
|
6
|
+
class Description < Lazydoc::Comment
|
7
|
+
attr_accessor :desc
|
8
|
+
|
9
|
+
def prepend(line)
|
10
|
+
if line =~ /::desc\s+(.*?)\s*$/
|
11
|
+
self.desc = $1
|
12
|
+
false
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
resolve
|
20
|
+
desc.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/tap/env.rb
CHANGED
@@ -8,7 +8,7 @@ module Tap
|
|
8
8
|
# whenever these configs are reset.
|
9
9
|
class Env
|
10
10
|
include Enumerable
|
11
|
-
include
|
11
|
+
include Configurable
|
12
12
|
include Support::Minimap
|
13
13
|
|
14
14
|
class << self
|
@@ -38,16 +38,17 @@ module Tap
|
|
38
38
|
# # File.expand_path("./path/to/config.yml") => e1,
|
39
39
|
# # File.expand_path("./path/to/dir/#{Tap::Env::DEFAULT_CONFIG_FILE}") => e2 }
|
40
40
|
#
|
41
|
-
# The Env is initialized using configurations read from the env config
|
42
|
-
#
|
43
|
-
# will be initialized regardless of whether the config file or directory
|
41
|
+
# The Env is initialized using configurations read from the env config
|
42
|
+
# file, and a Root initialized to the config file directory. An instance
|
43
|
+
# will be initialized regardless of whether the config file or directory
|
44
|
+
# exists.
|
44
45
|
def instantiate(path_or_root, default_config={}, logger=nil, &block)
|
45
46
|
path = path_or_root.kind_of?(Root) ? path_or_root.root : path_or_root
|
46
47
|
path = pathify(path)
|
47
48
|
|
48
49
|
begin
|
49
50
|
root = path_or_root.kind_of?(Root) ? path_or_root : Root.new(File.dirname(path))
|
50
|
-
config = default_config.merge(
|
51
|
+
config = default_config.merge(load_file(path))
|
51
52
|
|
52
53
|
# note the assignment of env to instances MUST occur before
|
53
54
|
# reconfigure to prevent infinite looping
|
@@ -102,6 +103,12 @@ module Tap
|
|
102
103
|
instance_variable_set(instance_variable, [*input].compact.collect {|path| root[path]}.uniq)
|
103
104
|
end
|
104
105
|
end
|
106
|
+
|
107
|
+
# helper to load path as YAML. load_file returns a hash if the path
|
108
|
+
# loads to nil or false (as happens for empty files)
|
109
|
+
def load_file(path) # :nodoc:
|
110
|
+
Root.trivial?(path) ? {} : (YAML.load_file(path) || {})
|
111
|
+
end
|
105
112
|
end
|
106
113
|
|
107
114
|
@@instance = nil
|
@@ -375,9 +382,8 @@ module Tap
|
|
375
382
|
|
376
383
|
# freeze array configs like load_paths
|
377
384
|
config.each_pair do |key, value|
|
378
|
-
|
379
|
-
|
380
|
-
end
|
385
|
+
next unless value.kind_of?(Array)
|
386
|
+
value.freeze
|
381
387
|
end
|
382
388
|
|
383
389
|
# activate nested envs
|
@@ -418,18 +424,16 @@ module Tap
|
|
418
424
|
$LOAD_PATH.delete(path)
|
419
425
|
end
|
420
426
|
|
421
|
-
# unfreeze array configs by duplicating
|
422
|
-
self.config.class_config.each_pair do |key, value|
|
423
|
-
value = send(key)
|
424
|
-
case value
|
425
|
-
when Array then instance_variable_set("@#{key}", value.dup)
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
427
|
@active = false
|
430
428
|
@manifests.clear
|
431
429
|
@@instance = nil if @@instance == self
|
432
430
|
|
431
|
+
# unfreeze array configs by duplicating
|
432
|
+
config.each_pair do |key, value|
|
433
|
+
next unless value.kind_of?(Array)
|
434
|
+
instance_variable_set("@#{key}", value.dup)
|
435
|
+
end
|
436
|
+
|
433
437
|
# dectivate nested envs
|
434
438
|
envs.reverse_each do |env|
|
435
439
|
env.deactivate
|
data/lib/tap/exe.rb
CHANGED
@@ -8,7 +8,7 @@ module Tap
|
|
8
8
|
class << self
|
9
9
|
def instantiate(path=Dir.pwd, logger=Tap::App::DEFAULT_LOGGER, &block)
|
10
10
|
app = Tap::App.instance = Tap::App.new({:root => path}, logger)
|
11
|
-
exe = super(app,
|
11
|
+
exe = super(app, load_file(GLOBAL_CONFIG_FILE), app.logger, &block)
|
12
12
|
|
13
13
|
# add all gems if no gems are specified (Note this is VERY SLOW ~ 1/3 the overhead for tap)
|
14
14
|
if !File.exists?(Tap::Env::DEFAULT_CONFIG_FILE)
|
@@ -68,7 +68,7 @@ module Tap
|
|
68
68
|
load path # run the command, if it exists
|
69
69
|
else
|
70
70
|
puts "Unknown command: '#{command}'"
|
71
|
-
puts "Type 'tap help' for usage information."
|
71
|
+
puts "Type 'tap --help' for usage information."
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
data/lib/tap/file_task.rb
CHANGED
@@ -3,116 +3,53 @@ autoload(:FileUtils, "fileutils")
|
|
3
3
|
|
4
4
|
module Tap
|
5
5
|
|
6
|
-
# FileTask
|
7
|
-
#
|
6
|
+
# FileTask is a base class for tasks that work with a file system. FileTask
|
7
|
+
# tracks changes it makes so they may be rolled back to their original state.
|
8
|
+
# Rollback automatically occurs on an execute error.
|
8
9
|
#
|
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"}
|
10
|
+
# File.open("file.txt", "w") {|file| file << "original content"}
|
23
11
|
#
|
24
|
-
# t = FileTask.intern do |task|
|
25
|
-
# task.
|
26
|
-
# task.prepare("file.txt"
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# File.touch("path/to/file.txt")
|
12
|
+
# t = FileTask.intern do |task, raise_error|
|
13
|
+
# task.mkdir_p("some/dir") # marked for rollback
|
14
|
+
# task.prepare("file.txt") do |file| # marked for rollback
|
15
|
+
# file << "new content"
|
16
|
+
# end
|
30
17
|
#
|
31
18
|
# # raise an error to start rollback
|
32
|
-
# raise "error!"
|
19
|
+
# raise "error!" if raise_error
|
33
20
|
# end
|
34
21
|
#
|
35
22
|
# begin
|
36
|
-
#
|
37
|
-
# File.exists?("path/to/file.txt") # => false
|
38
|
-
# t.execute(nil)
|
23
|
+
# t.execute(true)
|
39
24
|
# rescue
|
40
25
|
# $!.message # => "error!"
|
41
26
|
# File.exists?("some/dir") # => false
|
42
|
-
# File.exists?("path/to/file.txt") # => false
|
43
27
|
# File.read("file.txt") # => "original content"
|
44
28
|
# end
|
45
29
|
#
|
30
|
+
# t.execute(false)
|
31
|
+
# File.exists?("some/dir") # => true
|
32
|
+
# File.read("file.txt") # => "new content"
|
33
|
+
#
|
46
34
|
class FileTask < Task
|
47
35
|
include Tap::Support::ShellUtils
|
48
36
|
|
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
|
37
|
+
# The backup directory
|
59
38
|
config_attr :backup_dir, 'backup' # the backup directory
|
60
39
|
|
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
|
40
|
+
# A flag indicating whether or track changes
|
41
|
+
# for rollback on execution error
|
67
42
|
config :rollback_on_error, true, &c.switch # rollback changes on error
|
68
43
|
|
69
44
|
def initialize(config={}, name=nil, app=App.instance)
|
70
45
|
super
|
71
|
-
|
72
|
-
@backed_up_files = {}
|
73
|
-
@added_files = []
|
46
|
+
@actions = []
|
74
47
|
end
|
75
48
|
|
49
|
+
# Initializes a copy that will rollback independent of self.
|
76
50
|
def initialize_copy(orig)
|
77
51
|
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
|
52
|
+
@actions = []
|
116
53
|
end
|
117
54
|
|
118
55
|
# Returns the path, exchanging the extension with extname.
|
@@ -130,13 +67,9 @@ module Tap
|
|
130
67
|
# Compare to basename.
|
131
68
|
def basepath(path, extname=false)
|
132
69
|
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}"
|
70
|
+
when false, nil then path.chomp(File.extname(path))
|
71
|
+
when true then path
|
72
|
+
else Root.exchange(path, extname)
|
140
73
|
end
|
141
74
|
end
|
142
75
|
|
@@ -167,27 +100,26 @@ module Tap
|
|
167
100
|
app.filepath(dir, name, *paths)
|
168
101
|
end
|
169
102
|
|
170
|
-
# Makes a backup filepath relative to backup_dir by using
|
171
|
-
# basename of filepath
|
103
|
+
# Makes a backup filepath relative to backup_dir by using name, the
|
104
|
+
# basename of filepath, and an index.
|
172
105
|
#
|
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"
|
106
|
+
# t = FileTask.new({:backup_dir => "/backup"}, "name")
|
107
|
+
# t.backup_filepath("path/to/file.txt", time) # => "/backup/name/file.0.txt"
|
178
108
|
#
|
179
|
-
def backup_filepath(
|
180
|
-
extname = File.extname(
|
181
|
-
backup_path =
|
182
|
-
|
109
|
+
def backup_filepath(path)
|
110
|
+
extname = File.extname(path)
|
111
|
+
backup_path = filepath(backup_dir, File.basename(path).chomp(extname))
|
112
|
+
next_indexed_path(backup_path, 0, extname)
|
183
113
|
end
|
184
114
|
|
185
|
-
# Returns true if all of the targets are up to date relative to all of the
|
186
|
-
# sources
|
187
|
-
#
|
115
|
+
# Returns true if all of the targets are up to date relative to all of the
|
116
|
+
# listed sources. Single values or arrays can be provided for both targets
|
117
|
+
# and sources.
|
118
|
+
#
|
119
|
+
# Returns false (ie 'not up to date') if app.force is true.
|
188
120
|
#
|
189
|
-
|
190
|
-
#
|
121
|
+
#--
|
122
|
+
# TODO: add check vs date reference (ex config_file date)
|
191
123
|
def uptodate?(targets, sources=[])
|
192
124
|
if app.force
|
193
125
|
log_basename(:force, *targets)
|
@@ -196,9 +128,6 @@ module Tap
|
|
196
128
|
targets = [targets] unless targets.kind_of?(Array)
|
197
129
|
sources = [sources] unless sources.kind_of?(Array)
|
198
130
|
|
199
|
-
# should be able to specify this somehow, externally set
|
200
|
-
# sources << config_file unless config_file == nil
|
201
|
-
|
202
131
|
targets.each do |target|
|
203
132
|
return false unless FileUtils.uptodate?(target, sources)
|
204
133
|
end
|
@@ -206,367 +135,252 @@ module Tap
|
|
206
135
|
end
|
207
136
|
end
|
208
137
|
|
209
|
-
# Makes a backup of
|
210
|
-
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
# in backed_up_files.
|
138
|
+
# Makes a backup of path to backup_filepath(path) and returns the backup path.
|
139
|
+
# If backup_using_copy is true, the backup is a copy of path, otherwise the
|
140
|
+
# file or directory at path is moved to the backup path. Raises an error if
|
141
|
+
# the backup file already exists.
|
214
142
|
#
|
215
|
-
#
|
143
|
+
# Backups are restored on rollback.
|
216
144
|
#
|
217
145
|
# file = "file.txt"
|
218
146
|
# File.open(file, "w") {|f| f << "file content"}
|
219
147
|
#
|
220
148
|
# t = FileTask.new
|
221
|
-
#
|
149
|
+
# backup_file = t.backup(file)
|
222
150
|
#
|
223
151
|
# File.exists?(file) # => false
|
224
|
-
# File.exists?(
|
225
|
-
# File.read(
|
152
|
+
# File.exists?(backup_file) # => true
|
153
|
+
# File.read(backup_file) # => "file content"
|
226
154
|
#
|
227
155
|
# File.open(file, "w") {|f| f << "new content"}
|
228
|
-
# t.
|
156
|
+
# t.rollback
|
229
157
|
#
|
230
158
|
# File.exists?(file) # => true
|
231
|
-
# File.exists?(
|
159
|
+
# File.exists?(backup_file ) # => false
|
232
160
|
# File.read(file) # => "file content"
|
233
161
|
#
|
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
|
162
|
+
def backup(path, backup_using_copy=false)
|
163
|
+
return nil unless File.exists?(path)
|
242
164
|
|
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
|
165
|
+
source = File.expand_path(path)
|
166
|
+
target = backup_filepath(source)
|
167
|
+
raise "backup file already exists: #{target}" if File.exists?(target)
|
168
|
+
|
169
|
+
mkdir_p File.dirname(target)
|
170
|
+
|
171
|
+
log :backup, "#{source} to #{target}", Logger::DEBUG
|
172
|
+
if backup_using_copy
|
173
|
+
FileUtils.cp(source, target)
|
174
|
+
else
|
175
|
+
FileUtils.mv(source, target)
|
258
176
|
end
|
177
|
+
|
178
|
+
actions << [:backup, source, target]
|
179
|
+
target
|
259
180
|
end
|
260
181
|
|
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)
|
182
|
+
# Creates a directory and all its parent directories. Directories created
|
183
|
+
# by mkdir_p removed on rollback.
|
184
|
+
def mkdir_p(dir)
|
185
|
+
dir = File.expand_path(dir)
|
292
186
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
dir = File.dirname(
|
297
|
-
|
187
|
+
dirs = []
|
188
|
+
while !File.exists?(dir)
|
189
|
+
dirs.unshift(dir)
|
190
|
+
dir = File.dirname(dir)
|
191
|
+
end
|
298
192
|
|
299
|
-
|
300
|
-
end.compact
|
193
|
+
dirs.each {|dir| mkdir(dir) }
|
301
194
|
end
|
302
195
|
|
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
|
196
|
+
# Creates a directory. Directories created by mkdir removed on rollback.
|
197
|
+
def mkdir(dir)
|
198
|
+
dir = File.expand_path(dir)
|
339
199
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
end
|
200
|
+
unless File.exists?(dir)
|
201
|
+
log :mkdir, dir, Logger::DEBUG
|
202
|
+
FileUtils.mkdir(dir)
|
203
|
+
actions << [:make, dir]
|
345
204
|
end
|
346
205
|
end
|
347
206
|
|
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)
|
207
|
+
# Prepares the path by backing up any existing file and ensuring that
|
208
|
+
# the parent directory for path exists. If a block is given, a file
|
209
|
+
# is opened and yielded to it (as in File.open). Prepared paths are
|
210
|
+
# removed and the backups restored on rollback.
|
211
|
+
#
|
212
|
+
# Returns the expanded path.
|
213
|
+
def prepare(path, backup_using_copy=false)
|
214
|
+
raise "not a file: #{path}" if File.directory?(path)
|
215
|
+
path = File.expand_path(path)
|
216
|
+
|
217
|
+
if File.exists?(path)
|
218
|
+
# backup or remove existing files
|
219
|
+
backup(path, backup_using_copy)
|
220
|
+
else
|
221
|
+
# ensure the parent directory exists
|
222
|
+
# for non-existant files
|
223
|
+
mkdir_p File.dirname(path)
|
224
|
+
end
|
225
|
+
log :prepare, path, Logger::DEBUG
|
226
|
+
actions << [:make, path]
|
369
227
|
|
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
|
228
|
+
if block_given?
|
229
|
+
File.open(path, "w") {|file| yield(file) }
|
383
230
|
end
|
384
|
-
|
231
|
+
|
232
|
+
path
|
385
233
|
end
|
386
234
|
|
387
|
-
|
388
|
-
|
235
|
+
# Removes a file. If a directory is provided, it's contents are removed
|
236
|
+
# recursively. Files and directories removed by rm_r are restored
|
237
|
+
# upon an execution error.
|
238
|
+
def rm_r(path)
|
239
|
+
path = File.expand_path(path)
|
240
|
+
|
241
|
+
backup(path, false)
|
242
|
+
log :rm_r, path, Logger::DEBUG
|
389
243
|
end
|
390
244
|
|
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
|
245
|
+
# Removes an empty directory. Directories removed by rmdir are restored
|
246
|
+
# upon an execution error.
|
247
|
+
def rmdir(dir)
|
248
|
+
dir = File.expand_path(dir)
|
427
249
|
|
428
|
-
|
429
|
-
|
430
|
-
backup(filepath, backup_using_copy)
|
250
|
+
unless Root.empty?(dir)
|
251
|
+
raise "not an empty directory: #{dir}"
|
431
252
|
end
|
432
253
|
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
254
|
+
backup(dir, false)
|
255
|
+
log :rmdir, dir, Logger::DEBUG
|
256
|
+
end
|
257
|
+
|
258
|
+
# Removes a file. Directories cannot be removed by this method.
|
259
|
+
# Files removed by rm are restored upon an execution error.
|
260
|
+
def rm(path)
|
261
|
+
path = File.expand_path(path)
|
439
262
|
|
440
|
-
|
441
|
-
|
263
|
+
unless File.file?(path)
|
264
|
+
raise "not a file: #{path}"
|
442
265
|
end
|
443
|
-
|
444
|
-
|
266
|
+
|
267
|
+
backup(path, false)
|
268
|
+
log :rm, path, Logger::DEBUG
|
445
269
|
end
|
446
270
|
|
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
|
271
|
+
# Copies source to target. Files and directories copied by cp are
|
272
|
+
# restored upon an execution error.
|
273
|
+
def cp(source, target)
|
274
|
+
target = File.join(target, File.basename(source)) if File.directory?(target)
|
275
|
+
prepare(target)
|
276
|
+
|
277
|
+
log :cp, "#{source} to #{target}", Logger::DEBUG
|
278
|
+
FileUtils.cp(source, target)
|
480
279
|
end
|
481
280
|
|
482
|
-
#
|
483
|
-
#
|
484
|
-
#
|
485
|
-
#
|
486
|
-
def
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
281
|
+
# Copies source to target. If source is a directory, the contents
|
282
|
+
# are copied recursively. If target is a directory, copies source
|
283
|
+
# to target/source. Files and directories copied by cp are restored
|
284
|
+
# upon an execution error.
|
285
|
+
def cp_r(source, target)
|
286
|
+
target = File.join(target, File.basename(source)) if File.directory?(target)
|
287
|
+
prepare(target)
|
288
|
+
|
289
|
+
log :cp_r, "#{source} to #{target}", Logger::DEBUG
|
290
|
+
FileUtils.cp_r(source, target)
|
291
|
+
end
|
292
|
+
|
293
|
+
# Moves source to target. Files and directories moved by mv are
|
294
|
+
# restored upon an execution error.
|
295
|
+
def mv(source, target, backup_source=true)
|
296
|
+
backup(source, true) if backup_source
|
297
|
+
prepare(target)
|
298
|
+
|
299
|
+
log :mv, "#{source} to #{target}", Logger::DEBUG
|
300
|
+
FileUtils.mv(source, target)
|
301
|
+
end
|
302
|
+
|
303
|
+
# Rolls back any actions capable of being rolled back. Rollback
|
304
|
+
# is forceful; for instance if you make a folder using mkdir
|
305
|
+
# rollback removes that directory using FileUtils.rm_r. Any
|
306
|
+
# files added to the folder will be removed even if they were
|
307
|
+
# not added by self.
|
308
|
+
def rollback
|
309
|
+
while !actions.empty?
|
310
|
+
action, source, target = actions.pop
|
311
|
+
|
312
|
+
case action
|
313
|
+
when :make
|
314
|
+
log :rollback, "#{source}", Logger::DEBUG
|
315
|
+
FileUtils.rm_r(source)
|
316
|
+
when :backup
|
317
|
+
log :rollback, "#{target} to #{source}", Logger::DEBUG
|
318
|
+
dir = File.dirname(source)
|
319
|
+
FileUtils.mkdir_p(dir) unless File.exists?(dir)
|
320
|
+
FileUtils.mv(target, source, :force => true)
|
321
|
+
else
|
322
|
+
raise "unknown action: #{[action, source, target].inspect}"
|
501
323
|
end
|
502
324
|
end
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
325
|
+
end
|
326
|
+
|
327
|
+
# Removes backup files. Cleanup cannot be rolled back and prevents
|
328
|
+
# rollback of actions up to when cleanup is called. If cleanup_dirs
|
329
|
+
# is true, empty directories containing the backup files will be
|
330
|
+
# removed.
|
331
|
+
def cleanup(cleanup_dirs=true)
|
332
|
+
actions.each do |action, source, target|
|
333
|
+
if action == :backup
|
334
|
+
log :cleanup, target, Logger::DEBUG
|
335
|
+
FileUtils.rm_r(target) if File.exists?(target)
|
336
|
+
cleanup_dir(File.dirname(target)) if cleanup_dirs
|
509
337
|
end
|
510
338
|
end
|
339
|
+
actions.clear
|
511
340
|
end
|
512
341
|
|
513
|
-
# Removes
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
rm(target)
|
522
|
-
backed_up_files.delete(filepath)
|
523
|
-
end
|
342
|
+
# Removes the directory if empty, and all empty parent directories. This
|
343
|
+
# method cannot be rolled back.
|
344
|
+
def cleanup_dir(dir)
|
345
|
+
while Root.empty?(dir)
|
346
|
+
log :rmdir, dir, Logger::DEBUG
|
347
|
+
FileUtils.rmdir(dir)
|
348
|
+
dir = File.dirname(dir)
|
349
|
+
end
|
524
350
|
end
|
525
351
|
|
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
|
-
|
352
|
+
# Logs the given action, with the basenames of the input paths.
|
353
|
+
def log_basename(action, paths, level=Logger::INFO)
|
354
|
+
msg = [paths].flatten.collect {|path| File.basename(path) }.join(',')
|
534
355
|
log(action, msg, level)
|
535
356
|
end
|
536
357
|
|
537
358
|
protected
|
538
|
-
|
539
|
-
attr_writer :backed_up_files, :added_files
|
540
359
|
|
541
|
-
#
|
542
|
-
#
|
360
|
+
# An array tracking actions (backup, rm, mv, etc) performed by self,
|
361
|
+
# allowing rollback on an execution error. Not intended to be
|
362
|
+
# modified manually.
|
363
|
+
attr_reader :actions
|
364
|
+
|
365
|
+
# Clears actions so that a failure will not affect previous executions
|
543
366
|
def before_execute
|
544
|
-
|
545
|
-
backed_up_files.clear
|
367
|
+
actions.clear
|
546
368
|
end
|
547
369
|
|
548
370
|
# Removes made files/dirs and restores backed-up files upon
|
549
|
-
# an execute error.
|
550
|
-
# and raises them in a Tap::Support::RunError.
|
371
|
+
# an execute error.
|
551
372
|
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
|
373
|
+
rollback if rollback_on_error
|
374
|
+
raise original_error
|
565
375
|
end
|
566
376
|
|
567
|
-
|
568
|
-
|
569
|
-
|
377
|
+
private
|
378
|
+
|
379
|
+
# utility method for backup_filepath; increments index until the
|
380
|
+
# path base.indexext does not exist.
|
381
|
+
def next_indexed_path(base, index, ext) # :nodoc:
|
382
|
+
path = sprintf('%s.%d%s', base, index, ext)
|
383
|
+
File.exists?(path) ? next_indexed_path(base, index + 1, ext) : path
|
570
384
|
end
|
571
385
|
end
|
572
386
|
end
|