lib/tap/test/utils.rb in bahuvrihi-tap-0.10.4 vs lib/tap/test/utils.rb in bahuvrihi-tap-0.10.5

- old
+ new

@@ -1,24 +1,33 @@ require 'tap/root' require 'fileutils' +require 'tempfile' module Tap + module Support + autoload(:Templater, 'tap/support/templater') + end + module Test module Utils module_function # Generates an array of [source, reference] pairs mapping source - # files to reference files under the source_dir and reference_dir, - # respectively. Only files under source dir with the reference_extname - # will be mapped; mappings are either: + # files to reference files under the source and reference dirs, + # respectively. Only files under source dir matching the pattern + # will be mapped. Mappings are either (in this order): # - # - a direct translation from source_dir to reference_dir, minus - # the extname - # - a path under reference_dir with a matching basename, minus - # the extname, so long as the matched path is unique + # - the path under reference_dir contained in the source file + # - a direct translation of the source file from the source to + # the reference dir, minus the extname # - # If a mapped path cannot be found, dereference raises an ArgumentError. + # Notes: + # - Source files may contain comments but should otherwise + # consist only of indentation (which is stripped) and + # the reference path. + # - If a mapped path cannot be found, dereference raises + # a DereferenceError. # # === example # # root # |- input @@ -31,59 +40,122 @@ # |- one.txt # `- path # `- to # `- two.txt # + # The 'two.txt.ref' file contains a reference path: + # + # File.read('/root/input/two.txt.ref') # => 'path/to/two.txt' + # + # Now: + # # reference_map('/root/input', '/root/ref') # # => [ # # ['/root/input/dir.ref', '/root/ref/dir'], # # ['/root/input/one.txt.ref', '/root/ref/one.txt'], # # ['/root/input/two.txt.ref', '/root/ref/path/to/two.txt']] # - # reference_map(:input, :ref, ".txt") # !> ArgumentError + # And since no path matches 'ignored.txt': # - def reference_map(source_dir, reference_dir, reference_extname='.ref') - Dir.glob(File.join(source_dir, "**/*#{reference_extname}")).collect do |path| - relative_path = Tap::Root.relative_filepath(source_dir, path).chomp(reference_extname) - reference_path = File.join(reference_dir, relative_path) - - unless File.exists?(reference_path) - matching_paths = Dir.glob(File.join(reference_dir, "**/#{File.basename(relative_path)}")) - - reference_path = case matching_paths.length - when 0 then raise ArgumentError, "no reference found for: #{path}" - when 1 then matching_paths[0] - else raise ArgumentError, "multiple references found for: #{path} [#{matching_paths.join(', ')}]" - end + # reference_map('/root/input', '/root/ref', '**/*.txt') + # # !> DereferenceError + # + def reference_map(source_dir, reference_dir, pattern='**/*.ref') + Dir.glob(File.join(source_dir, pattern)).collect do |source| + # use the path specified in the source file + relative_path = File.read(source).gsub(/#.*$/, "").strip + + # use the relative filepath of the source file to the + # source dir (minus the extname) if no path is specified + if relative_path.empty? + relative_path = Tap::Root.relative_filepath(source_dir, source).chomp(File.extname(source)) end + + reference = File.join(reference_dir, relative_path) + + # raise an error if no reference file is found + unless File.exists?(reference) + raise DereferenceError, "no reference found for: #{source}" + end - [path, reference_path] + [source, reference] end end - def dereference(source_dirs, reference_dir, extname='.ref') + # Dereferences source files with reference files for the duration + # of the block. The mappings of source to reference files are + # determined using reference_map; dereferenced files are at the + # same location as the source files, but with the '.ref' extname + # removed. + # + # Notes: + # - The reference extname is implicitly specified in pattern; + # the final extname of the source file is removed during + # dereferencing regardless of what it is. + # + def dereference(source_dirs, reference_dir, pattern='**/*.ref', tempdir=Dir::tmpdir) mapped_paths = [] begin [*source_dirs].each do |source_dir| - reference_map(source_dir, reference_dir, extname).each do |path, source| - FileUtils.rm_r(path) - target = path.chomp(extname) - FileUtils.cp_r(source, target) - mapped_paths << target + reference_map(source_dir, reference_dir, pattern).each do |source, reference| + + # move the source file to a temporary location + tempfile = Tempfile.new(File.basename(source), tempdir) + tempfile.close + FileUtils.mv(source, tempfile.path) + + # copy the reference to the target + target = source.chomp(File.extname(source)) + FileUtils.cp_r(reference, target) + + mapped_paths << [target, source, tempfile] end end unless reference_dir == nil yield ensure - mapped_paths.each do |path| - FileUtils.rm_r(path) if File.exists?(path) - FileUtils.touch(path + extname) + mapped_paths.each do |target, source, tempfile| + # remove the target and restore the original source file + FileUtils.rm_r(target) if File.exists?(target) + FileUtils.mv(tempfile.path, source) end end end + # Uses a Tap::Support::Templater to template and replace the contents of path, + # for the duration of the block. The attributes will be available in the + # template context. + def template(paths, attributes={}, tempdir=Dir::tmpdir) + mapped_paths = [] + begin + [*paths].each do |path| + # move the source file to a temporary location + tempfile = Tempfile.new(File.basename(path), tempdir) + tempfile.close + FileUtils.cp(path, tempfile.path) + + # template the source file + content = File.read(path) + File.open(path, "wb") do |file| + file << Support::Templater.new(content, attributes).build + end + + mapped_paths << [path, tempfile] + end + + yield + + ensure + mapped_paths.each do |path, tempfile| + # restore the original source file + FileUtils.rm(path) if File.exists?(path) + FileUtils.mv(tempfile.path, path) + end + end + end + # Yields to the input block for each pair of entries in the input # arrays. An error is raised if the input arrays do not have equal # numbers of entries. def each_pair(a, b, &block) # :yields: entry_a, entry_b, each_pair_with_index(a,b) do |entry_a, entry_b, index| @@ -100,19 +172,57 @@ 0.upto(a.length-1) do |index| yield(a[index], b[index], index) end end + # Attempts to recursively remove the specified method directory and all + # files within it. Raises an error if the removal does not succeed. + def clear_dir(dir) + # clear out the folder if it exists + FileUtils.rm_r(dir) if File.exists?(dir) + end + # Attempts to remove the specified directory. The root # will not be removed if the directory does not exist, or # is not empty. def try_remove_dir(dir) # Remove the directory if possible begin FileUtils.rmdir(dir) if File.exists?(dir) && Dir.glob(File.join(dir, "*")).empty? rescue # rescue cases where there is a hidden file, for example .svn end + end + + # Sets ARGV to the input argv for the duration of the block. + def with_argv(argv=[]) + current_argv = ARGV.dup + begin + ARGV.clear + ARGV.concat(argv) + + yield + + ensure + ARGV.clear + ARGV.concat(current_argv) + end + end + + def whitespace_escape(str) + str.to_s.gsub(/\s/) do |match| + case match + when "\n" then "\\n\n" + when "\t" then "\\t" + when "\r" then "\\r" + when "\f" then "\\f" + else match + end + end + end + + # Raised when no reference can be found for a reference path. + class DereferenceError < StandardError end end end end \ No newline at end of file