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