Class: Tap::Tasks::FileTask
- Inherits:
-
Tap::Task
- Object
- Tap::Task
- Tap::Tasks::FileTask
- Includes:
- ShellUtils
- Defined in:
- lib/tap/tasks/file_task.rb,
lib/tap/tasks/file_task/shell_utils.rb
Overview
FileTask is a base class for tasks that work with a file system. FileTask tracks changes it makes so they may be rolled back to their original state. Rollback automatically occurs on an execute error.
File.open("file.txt", "w") {|file| file << "original content"}
t = FileTask.intern do |task, raise_error|
task.mkdir_p("some/dir") # marked for rollback
task.prepare("file.txt") do |file| # marked for rollback
file << "new content"
end
# raise an error to start rollback
raise "error!" if raise_error
end
begin
t.execute(true)
rescue
$!. # => "error!"
File.exists?("some/dir") # => false
File.read("file.txt") # => "original content"
end
t.execute(false)
File.exists?("some/dir") # => true
File.read("file.txt") # => "new content"
Defined Under Namespace
Modules: ShellUtils
Instance Method Summary collapse
-
#backup(path, backup_using_copy = false) ⇒ Object
Makes a backup of path to backup_filepath(path) and returns the backup path.
-
#backup_filepath(path) ⇒ Object
Makes a backup filepath relative to backup_dir by using name, the basename of filepath, and an index.
-
#basename(path, extname = true) ⇒ Object
Returns the basename of path, exchanging the extension with extname.
-
#basepath(path, extname = false) ⇒ Object
Returns the path, exchanging the extension with extname.
- #call(*_inputs) ⇒ Object
-
#cleanup(cleanup_dirs = true) ⇒ Object
Removes backup files.
-
#cleanup_dir(dir) ⇒ Object
Removes the directory if empty, and all empty parent directories.
-
#cp(source, target) ⇒ Object
Copies source to target.
-
#cp_r(source, target) ⇒ Object
Copies source to target.
-
#filepath(dir, *paths) ⇒ Object
Constructs a filepath using the dir and the specified paths.
-
#initialize(config = {}, app = Tap::App.instance) ⇒ FileTask
constructor
A new instance of FileTask.
-
#initialize_copy(orig) ⇒ Object
Initializes a copy that will rollback independent of self.
-
#log_basename(action, paths, level = Logger::INFO) ⇒ Object
Logs the given action, with the basenames of the input paths.
-
#mkdir(dir) ⇒ Object
Creates a directory.
-
#mkdir_p(dir) ⇒ Object
Creates a directory and all its parent directories.
-
#mv(source, target, backup_source = true) ⇒ Object
Moves source to target.
-
#prepare(path, backup_using_copy = false) ⇒ Object
Prepares the path by backing up any existing file and ensuring that the parent directory for path exists.
-
#rm(path) ⇒ Object
Removes a file.
-
#rm_r(path) ⇒ Object
Removes a file.
-
#rmdir(dir) ⇒ Object
Removes an empty directory.
-
#rollback ⇒ Object
Rolls back any actions capable of being rolled back.
-
#uptodate?(targets, sources = []) ⇒ Boolean
Returns true if all of the targets are up to date relative to all of the listed sources.
Methods included from ShellUtils
#capture_sh, #redirect_sh, #sh
Constructor Details
#initialize(config = {}, app = Tap::App.instance) ⇒ FileTask
Returns a new instance of FileTask.
43 44 45 46 |
# File 'lib/tap/tasks/file_task.rb', line 43 def initialize(config={}, app=Tap::App.instance) super @actions = [] end |
Instance Method Details
#backup(path, backup_using_copy = false) ⇒ Object
Makes a backup of path to backup_filepath(path) and returns the backup path. If backup_using_copy is true, the backup is a copy of path, otherwise the file or directory at path is moved to the backup path. Raises an error if the backup path already exists.
Backups are restored on rollback.
file = "file.txt"
File.open(file, "w") {|f| f << "file content"}
t = FileTask.new
backup_file = t.backup(file)
File.exists?(file) # => false
File.exists?(backup_file) # => true
File.read(backup_file) # => "file content"
File.open(file, "w") {|f| f << "new content"}
t.rollback
File.exists?(file) # => true
File.exists?(backup_file ) # => false
File.read(file) # => "file content"
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/tap/tasks/file_task.rb', line 159 def backup(path, backup_using_copy=false) return nil unless File.exists?(path) source = File.(path) target = backup_filepath(source) raise "backup already exists: #{target}" if File.exists?(target) mkdir_p File.dirname(target) log :backup, "#{source} to #{target}", Logger::DEBUG if backup_using_copy FileUtils.cp(source, target) else FileUtils.mv(source, target) end actions << [:backup, source, target] target end |
#backup_filepath(path) ⇒ Object
106 107 108 109 110 |
# File 'lib/tap/tasks/file_task.rb', line 106 def backup_filepath(path) extname = File.extname(path) backup_path = filepath(backup_dir, File.basename(path).chomp(extname)) next_indexed_path(backup_path, 0, extname) end |
#basename(path, extname = true) ⇒ Object
Returns the basename of path, exchanging the extension with extname. A false or nil extname removes the extension, while true preserves the existing extension.
t = FileTask.new
t.basename('path/to/file.txt') # => 'file.txt'
t.basename('path/to/file.txt', '.html') # => 'file.html'
t.basename('path/to/file.txt', false) # => 'file'
t.basename('path/to/file.txt', true) # => 'file.txt'
Compare to basepath.
87 88 89 |
# File 'lib/tap/tasks/file_task.rb', line 87 def basename(path, extname=true) basepath(File.basename(path), extname) end |
#basepath(path, extname = false) ⇒ Object
Returns the path, exchanging the extension with extname.
A false or nil extname removes the extension, while true preserves the existing extension (and effectively does nothing).
t = FileTask.new
t.basepath('path/to/file.txt') # => 'path/to/file'
t.basepath('path/to/file.txt', '.html') # => 'path/to/file.html'
t.basepath('path/to/file.txt', false) # => 'path/to/file'
t.basepath('path/to/file.txt', true) # => 'path/to/file.txt'
Compare to basename.
67 68 69 70 71 72 73 |
# File 'lib/tap/tasks/file_task.rb', line 67 def basepath(path, extname=false) case extname when false, nil then path.chomp(File.extname(path)) when true then path else Root::Utils.exchange(path, extname) end end |
#call(*_inputs) ⇒ Object
355 356 357 358 359 360 361 362 363 364 |
# File 'lib/tap/tasks/file_task.rb', line 355 def call(*_inputs) actions.clear begin super rescue(Exception) rollback if rollback_on_error raise end end |
#cleanup(cleanup_dirs = true) ⇒ Object
Removes backup files. Cleanup cannot be rolled back and prevents rollback of actions up to when cleanup is called. If cleanup_dirs is true, empty directories containing the backup files will be removed.
328 329 330 331 332 333 334 335 336 337 |
# File 'lib/tap/tasks/file_task.rb', line 328 def cleanup(cleanup_dirs=true) actions.each do |action, source, target| if action == :backup log :cleanup, target, Logger::DEBUG FileUtils.rm_r(target) if File.exists?(target) cleanup_dir(File.dirname(target)) if cleanup_dirs end end actions.clear end |
#cleanup_dir(dir) ⇒ Object
Removes the directory if empty, and all empty parent directories. This method cannot be rolled back.
341 342 343 344 345 346 347 |
# File 'lib/tap/tasks/file_task.rb', line 341 def cleanup_dir(dir) while Root::Utils.empty?(dir) log :rmdir, dir, Logger::DEBUG FileUtils.rmdir(dir) dir = File.dirname(dir) end end |
#cp(source, target) ⇒ Object
Copies source to target. Files and directories copied by cp are restored upon an execution error.
270 271 272 273 274 275 276 |
# File 'lib/tap/tasks/file_task.rb', line 270 def cp(source, target) target = File.join(target, File.basename(source)) if File.directory?(target) prepare(target) log :cp, "#{source} to #{target}", Logger::DEBUG FileUtils.cp(source, target) end |
#cp_r(source, target) ⇒ Object
Copies source to target. If source is a directory, the contents are copied recursively. If target is a directory, copies source to target/source. Files and directories copied by cp are restored upon an execution error.
282 283 284 285 286 287 288 |
# File 'lib/tap/tasks/file_task.rb', line 282 def cp_r(source, target) target = File.join(target, File.basename(source)) if File.directory?(target) prepare(target) log :cp_r, "#{source} to #{target}", Logger::DEBUG FileUtils.cp_r(source, target) end |
#filepath(dir, *paths) ⇒ Object
96 97 98 |
# File 'lib/tap/tasks/file_task.rb', line 96 def filepath(dir, *paths) File.(File.join(dir, *paths)) end |
#initialize_copy(orig) ⇒ Object
Initializes a copy that will rollback independent of self.
49 50 51 52 |
# File 'lib/tap/tasks/file_task.rb', line 49 def initialize_copy(orig) super @actions = [] end |
#log_basename(action, paths, level = Logger::INFO) ⇒ Object
Logs the given action, with the basenames of the input paths.
350 351 352 353 |
# File 'lib/tap/tasks/file_task.rb', line 350 def log_basename(action, paths, level=Logger::INFO) msg = [paths].flatten.collect {|path| File.basename(path) }.join(',') log(action, msg, level) end |
#mkdir(dir) ⇒ Object
Creates a directory. Directories created by mkdir removed on rollback.
194 195 196 197 198 199 200 201 202 |
# File 'lib/tap/tasks/file_task.rb', line 194 def mkdir(dir) dir = File.(dir) unless File.exists?(dir) log :mkdir, dir, Logger::DEBUG FileUtils.mkdir(dir) actions << [:make, dir] end end |
#mkdir_p(dir) ⇒ Object
Creates a directory and all its parent directories. Directories created by mkdir_p removed on rollback.
181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/tap/tasks/file_task.rb', line 181 def mkdir_p(dir) dir = File.(dir) dirs = [] while !File.exists?(dir) dirs.unshift(dir) dir = File.dirname(dir) end dirs.each {|d| mkdir(d) } end |
#mv(source, target, backup_source = true) ⇒ Object
Moves source to target. Files and directories moved by mv are restored upon an execution error.
292 293 294 295 296 297 298 |
# File 'lib/tap/tasks/file_task.rb', line 292 def mv(source, target, backup_source=true) backup(source, true) if backup_source prepare(target) log :mv, "#{source} to #{target}", Logger::DEBUG FileUtils.mv(source, target) end |
#prepare(path, backup_using_copy = false) ⇒ Object
Prepares the path by backing up any existing file and ensuring that the parent directory for path exists. If a block is given, a file is opened and yielded to it (as in File.open). Prepared paths are removed and the backups restored on rollback.
Returns the expanded path.
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/tap/tasks/file_task.rb', line 210 def prepare(path, backup_using_copy=false) raise "not a file: #{path}" if File.directory?(path) path = File.(path) if File.exists?(path) # backup or remove existing files backup(path, backup_using_copy) else # ensure the parent directory exists # for non-existant files mkdir_p File.dirname(path) end log :prepare, path, Logger::DEBUG actions << [:make, path] if block_given? File.open(path, "w") {|file| yield(file) } end path end |
#rm(path) ⇒ Object
Removes a file. Directories cannot be removed by this method. Files removed by rm are restored upon an execution error.
257 258 259 260 261 262 263 264 265 266 |
# File 'lib/tap/tasks/file_task.rb', line 257 def rm(path) path = File.(path) unless File.file?(path) raise "not a file: #{path}" end backup(path, false) log :rm, path, Logger::DEBUG end |
#rm_r(path) ⇒ Object
Removes a file. If a directory is provided, it’s contents are removed recursively. Files and directories removed by rm_r are restored upon an execution error.
235 236 237 238 239 240 |
# File 'lib/tap/tasks/file_task.rb', line 235 def rm_r(path) path = File.(path) backup(path, false) log :rm_r, path, Logger::DEBUG end |
#rmdir(dir) ⇒ Object
Removes an empty directory. Directories removed by rmdir are restored upon an execution error.
244 245 246 247 248 249 250 251 252 253 |
# File 'lib/tap/tasks/file_task.rb', line 244 def rmdir(dir) dir = File.(dir) unless Root::Utils.empty?(dir) raise "not an empty directory: #{dir}" end backup(dir, false) log :rmdir, dir, Logger::DEBUG end |
#rollback ⇒ Object
Rolls back any actions capable of being rolled back.
Rollback is forceful. For instance if you make a folder using mkdir, rollback will remove the folder and all files within it even if they were not added by self.
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/tap/tasks/file_task.rb', line 305 def rollback while !actions.empty? action, source, target = actions.pop case action when :make log :rollback, "#{source}", Logger::DEBUG FileUtils.rm_r(source) when :backup log :rollback, "#{target} to #{source}", Logger::DEBUG dir = File.dirname(source) FileUtils.mkdir_p(dir) unless File.exists?(dir) FileUtils.mv(target, source, :force => true) else raise "unknown action: #{[action, source, target].inspect}" end end end |
#uptodate?(targets, sources = []) ⇒ Boolean
Returns true if all of the targets are up to date relative to all of the listed sources. Single values or arrays can be provided for both targets and sources.
Returns false (ie ‘not up to date’) if app.force is true.
– TODO: add check vs date reference (ex config_file date)
120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/tap/tasks/file_task.rb', line 120 def uptodate?(targets, sources=[]) if app && app.force log_basename(:force, targets) false else targets = [targets] unless targets.kind_of?(Array) sources = [sources] unless sources.kind_of?(Array) targets.each do |target| return false unless FileUtils.uptodate?(target, sources) end true end end |