tap 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. data/Basic Overview +151 -0
  2. data/Command Reference +99 -0
  3. data/History +24 -0
  4. data/MIT-LICENSE +1 -1
  5. data/README +29 -57
  6. data/Rakefile +30 -37
  7. data/Tutorial +243 -191
  8. data/bin/tap +66 -35
  9. data/lib/tap.rb +47 -29
  10. data/lib/tap/app.rb +700 -342
  11. data/lib/tap/{script → cmd}/console.rb +0 -0
  12. data/lib/tap/{script → cmd}/destroy.rb +0 -0
  13. data/lib/tap/{script → cmd}/generate.rb +0 -0
  14. data/lib/tap/cmd/run.rb +156 -0
  15. data/lib/tap/constants.rb +4 -0
  16. data/lib/tap/dump.rb +57 -0
  17. data/lib/tap/env.rb +316 -0
  18. data/lib/tap/file_task.rb +106 -109
  19. data/lib/tap/generator.rb +4 -1
  20. data/lib/tap/generator/generators/command/USAGE +6 -0
  21. data/lib/tap/generator/generators/command/command_generator.rb +17 -0
  22. data/lib/tap/generator/generators/{script/templates/script.erb → command/templates/command.erb} +10 -10
  23. data/lib/tap/generator/generators/config/USAGE +21 -0
  24. data/lib/tap/generator/generators/config/config_generator.rb +17 -7
  25. data/lib/tap/generator/generators/file_task/USAGE +3 -0
  26. data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -0
  27. data/lib/tap/generator/generators/file_task/templates/file.txt +2 -0
  28. data/lib/tap/generator/generators/file_task/templates/file.yml +3 -0
  29. data/lib/tap/generator/generators/file_task/templates/task.erb +26 -20
  30. data/lib/tap/generator/generators/file_task/templates/test.erb +20 -10
  31. data/lib/tap/generator/generators/generator/generator_generator.rb +1 -1
  32. data/lib/tap/generator/generators/generator/templates/generator.erb +21 -12
  33. data/lib/tap/generator/generators/root/templates/Rakefile +33 -24
  34. data/lib/tap/generator/generators/root/templates/tap.yml +28 -31
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +1 -0
  36. data/lib/tap/generator/generators/task/USAGE +3 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +18 -5
  38. data/lib/tap/generator/generators/task/templates/task.erb +7 -12
  39. data/lib/tap/generator/generators/task/templates/test.erb +10 -11
  40. data/lib/tap/generator/generators/workflow/templates/task.erb +1 -1
  41. data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
  42. data/lib/tap/patches/rake/rake_test_loader.rb +8 -0
  43. data/lib/tap/patches/rake/testtask.rb +55 -0
  44. data/lib/tap/patches/ruby19/backtrace_filter.rb +51 -0
  45. data/lib/tap/patches/ruby19/parsedate.rb +16 -0
  46. data/lib/tap/root.rb +172 -67
  47. data/lib/tap/script.rb +70 -336
  48. data/lib/tap/support/aggregator.rb +55 -0
  49. data/lib/tap/support/audit.rb +281 -280
  50. data/lib/tap/support/batchable.rb +59 -0
  51. data/lib/tap/support/class_configuration.rb +279 -0
  52. data/lib/tap/support/configurable.rb +92 -0
  53. data/lib/tap/support/configurable_methods.rb +296 -0
  54. data/lib/tap/support/executable.rb +98 -0
  55. data/lib/tap/support/executable_queue.rb +82 -0
  56. data/lib/tap/support/logger.rb +9 -15
  57. data/lib/tap/support/rake.rb +43 -54
  58. data/lib/tap/support/run_error.rb +32 -13
  59. data/lib/tap/support/shell_utils.rb +47 -0
  60. data/lib/tap/support/tdoc.rb +9 -8
  61. data/lib/tap/support/tdoc/config_attr.rb +40 -16
  62. data/lib/tap/support/validation.rb +77 -0
  63. data/lib/tap/support/versions.rb +36 -36
  64. data/lib/tap/task.rb +276 -482
  65. data/lib/tap/test.rb +20 -261
  66. data/lib/tap/test/env_vars.rb +7 -5
  67. data/lib/tap/test/file_methods.rb +126 -121
  68. data/lib/tap/test/subset_methods.rb +86 -45
  69. data/lib/tap/test/tap_methods.rb +271 -0
  70. data/lib/tap/workflow.rb +174 -46
  71. data/test/app/config/another/task.yml +1 -0
  72. data/test/app/config/erb.yml +2 -1
  73. data/test/app/config/some/task.yml +1 -0
  74. data/test/app/config/template.yml +2 -6
  75. data/test/app_test.rb +1241 -1008
  76. data/test/env/test_configure/recurse_a.yml +2 -0
  77. data/test/env/test_configure/recurse_b.yml +2 -0
  78. data/test/env/test_configure/tap.yml +23 -0
  79. data/test/env/test_load_env_config/dir/tap.yml +3 -0
  80. data/test/env/test_load_env_config/recurse_a.yml +2 -0
  81. data/test/env/test_load_env_config/recurse_b.yml +2 -0
  82. data/test/env/test_load_env_config/tap.yml +3 -0
  83. data/test/env_test.rb +198 -0
  84. data/test/file_task_test.rb +70 -53
  85. data/{lib/tap/generator/generators/package/USAGE → test/root/file.txt} +0 -0
  86. data/test/root_test.rb +621 -454
  87. data/test/script_test.rb +38 -174
  88. data/test/support/aggregator_test.rb +99 -0
  89. data/test/support/audit_test.rb +409 -416
  90. data/test/support/batchable_test.rb +74 -0
  91. data/test/support/{task_configuration_test.rb → class_configuration_test.rb} +106 -47
  92. data/test/{task/config/overriding.yml → support/configurable/config/configured.yml} +0 -0
  93. data/test/support/configurable_test.rb +295 -0
  94. data/test/support/executable_queue_test.rb +103 -0
  95. data/test/support/executable_test.rb +38 -0
  96. data/test/support/logger_test.rb +17 -17
  97. data/test/support/rake_test.rb +4 -2
  98. data/test/support/shell_utils_test.rb +24 -0
  99. data/test/support/tdoc_test.rb +265 -258
  100. data/test/support/validation_test.rb +54 -0
  101. data/test/support/versions_test.rb +38 -38
  102. data/test/tap_test_helper.rb +19 -5
  103. data/test/tap_test_suite.rb +5 -2
  104. data/test/task_base_test.rb +13 -104
  105. data/test/task_syntax_test.rb +300 -0
  106. data/test/task_test.rb +258 -381
  107. data/test/test/env_vars_test.rb +40 -40
  108. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/one.txt +0 -0
  109. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/two.txt +0 -0
  110. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/one.txt +0 -0
  111. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/two.txt +0 -0
  112. data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/one.txt +0 -0
  113. data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/two.txt +0 -0
  114. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +1 -0
  115. data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_different_content}/expected/two.txt +0 -0
  116. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +1 -0
  117. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +1 -0
  118. data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_missing_expected_file}/expected/one.txt +0 -0
  119. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +1 -0
  120. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +1 -0
  121. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +1 -0
  122. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +1 -0
  123. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +1 -0
  124. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +1 -0
  125. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +1 -0
  126. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +1 -0
  127. data/test/test/file_methods_doc/test_sub/expected/one.txt +1 -0
  128. data/test/test/file_methods_doc/test_sub/expected/two.txt +1 -0
  129. data/test/test/file_methods_doc/test_sub/input/one.txt +1 -0
  130. data/test/test/file_methods_doc/test_sub/input/two.txt +1 -0
  131. data/test/test/file_methods_doc_test.rb +29 -0
  132. data/test/test/file_methods_test.rb +214 -143
  133. data/test/test/subset_methods_test.rb +111 -115
  134. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/a.txt +0 -0
  135. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/b.txt +0 -0
  136. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/a.txt +0 -0
  137. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/b.txt +0 -0
  138. data/test/test/tap_methods_test.rb +399 -0
  139. data/test/workflow_test.rb +101 -91
  140. metadata +86 -70
  141. data/lib/tap/generator/generators/package/package_generator.rb +0 -38
  142. data/lib/tap/generator/generators/package/templates/package.erb +0 -186
  143. data/lib/tap/generator/generators/script/USAGE +0 -0
  144. data/lib/tap/generator/generators/script/script_generator.rb +0 -17
  145. data/lib/tap/script/run.rb +0 -154
  146. data/lib/tap/support/batch_queue.rb +0 -162
  147. data/lib/tap/support/combinator.rb +0 -114
  148. data/lib/tap/support/task_configuration.rb +0 -169
  149. data/lib/tap/support/template.rb +0 -81
  150. data/lib/tap/support/templater.rb +0 -155
  151. data/lib/tap/version.rb +0 -4
  152. data/test/app/config/addition_template.yml +0 -6
  153. data/test/app_class_test.rb +0 -33
  154. data/test/check/binding_eval.rb +0 -23
  155. data/test/check/define_method_check.rb +0 -22
  156. data/test/check/dependencies_check.rb +0 -175
  157. data/test/check/inheritance_check.rb +0 -22
  158. data/test/support/batch_queue_test.rb +0 -320
  159. data/test/support/combinator_test.rb +0 -249
  160. data/test/support/template_test.rb +0 -122
  161. data/test/support/templater/erb.txt +0 -2
  162. data/test/support/templater/erb.yml +0 -2
  163. data/test/support/templater/somefile.txt +0 -2
  164. data/test/support/templater_test.rb +0 -192
  165. data/test/task/config/template.yml +0 -4
  166. data/test/task_class_test.rb +0 -170
  167. data/test/task_execute_test.rb +0 -262
  168. data/test/test/file_methods/test_assert_expected/expected/file.txt +0 -1
  169. data/test/test/file_methods/test_assert_expected/expected/folder/file.txt +0 -1
  170. data/test/test/file_methods/test_assert_expected/input/file.txt +0 -1
  171. data/test/test/file_methods/test_assert_expected/input/folder/file.txt +0 -1
  172. data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
  173. data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
  174. data/test/test/file_methods/test_file_compare/expected/output_1.txt +0 -3
  175. data/test/test/file_methods/test_file_compare/expected/output_2.txt +0 -1
  176. data/test/test/file_methods/test_file_compare/input/input_1.txt +0 -3
  177. data/test/test/file_methods/test_file_compare/input/input_2.txt +0 -3
  178. data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
  179. data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
  180. data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
  181. data/test/test/file_methods/test_yml_compare/expected/output_1.yml +0 -6
  182. data/test/test/file_methods/test_yml_compare/expected/output_2.yml +0 -6
  183. data/test/test/file_methods/test_yml_compare/input/input_1.yml +0 -4
  184. data/test/test/file_methods/test_yml_compare/input/input_2.yml +0 -4
  185. data/test/test_test.rb +0 -373
@@ -1,24 +1,23 @@
1
1
  require File.join(File.dirname(__FILE__), '<%= '../' * class_nesting_depth %>tap_test_helper.rb')
2
2
  require '<%= class_path.empty? ? file_name : File.join(class_path, file_name) %>'
3
3
 
4
- class <%= class_name_without_nesting %>Test < Test::Unit::TestCase
4
+ class <%= class_name %>Test < Test::Unit::TestCase
5
5
  acts_as_tap_test
6
6
 
7
7
  def test_<%= file_name %>
8
- t = <%= class_name %>.new nil, :label => 'key'
8
+ t = <%= class_name %>.new nil, :key => 'value'
9
9
 
10
10
  # specify application options
11
- with_options(:quiet => true) do
11
+ with_options(:quiet => true, :debug => true) do
12
12
 
13
13
  # run the task with some inputs
14
- app.run(t, "one", "two")
15
-
16
- # check the configuration and outputs
17
- assert_equal({:label => 'key'}, t.config)
18
- assert_outputs(t => [
19
- "key: one was processed",
20
- "key: two was processed"])
21
-
14
+ t.enq("one")
15
+ app.run
16
+
17
+ # check the configuration and outputs
18
+ assert_equal({:key => 'value'}, t.config)
19
+ assert_audit_equal ExpAudit[[nil, "one"], [t, "one was processed with value"]], app._results(t).first
20
+
22
21
  end
23
22
  end
24
23
 
@@ -9,7 +9,7 @@ class <%= class_name_without_nesting %> < Tap::Workflow
9
9
  def workflow
10
10
  # Define the workflow entry and exit points,
11
11
  # as well as the workflow logic.
12
- self.entry_point = Task.new
12
+ self.entry_point = Tap::Task.new
13
13
 
14
14
  # app.sequence(entry_point, 'another/task')
15
15
  end
@@ -1,7 +1,7 @@
1
1
  require File.join(File.dirname(__FILE__), '<%= '../' * class_nesting_depth %>tap_test_helper.rb')
2
2
  require '<%= class_path.empty? ? file_name : File.join(class_path, file_name) %>'
3
3
 
4
- class <%= class_name_without_nesting %>Test < Test::Unit::TestCase
4
+ class <%= class_name %>Test < Test::Unit::TestCase
5
5
  acts_as_tap_test
6
6
 
7
7
  end
@@ -0,0 +1,8 @@
1
+ # this is the same code as in rake/rake_test_loader.rb
2
+ # except it duplicates ARGV before iterating over it.
3
+ #
4
+ # This prevents an error in Ruby 1.9 when one of the
5
+ # loaded files attempts to modify ARGV. In that case
6
+ # you get an error like: 'can't modify array during
7
+ # iteration (RuntimeError)'
8
+ ARGV.dup.each { |f| load f unless f =~ /^-/ }
@@ -0,0 +1,55 @@
1
+ # NO idea why this prevents an error with @ruby_opts=nil,
2
+ # or even how @ruby_opts could be nil, on ruby 1.9 with
3
+ # rake test and tdoc. It does, though.
4
+ require 'pp'
5
+
6
+ module Rake # :nodoc:
7
+
8
+ class TestTask < TaskLib # :nodoc:
9
+
10
+ # Patch for TestTask#define in 'rake\testtask.rb'
11
+ #
12
+ # This patch lets you specify Windows-style paths in lib
13
+ # (ie with spaces and slashes) and to do something like:
14
+ #
15
+ # Rake::TestTask.new(:test) do |t|
16
+ # t.libs = $: << 'lib'
17
+ # end
18
+ #
19
+ # Using this patch you can specify additional load paths
20
+ # for the test from the command line using --lib-dir
21
+ #
22
+ def define
23
+ lib_opt = @libs.collect {|f| "-I\"#{File.expand_path(f)}\""}.join(' ')
24
+ desc "Run tests" + (@name==:test ? "" : " for #{@name}")
25
+ task @name do
26
+ run_code = ''
27
+ RakeFileUtils.verbose(@verbose) do
28
+ run_code =
29
+ case @loader
30
+ when :direct
31
+ "-e 'ARGV.each{|f| load f}'"
32
+ when :testrb
33
+ "-S testrb #{fix}"
34
+ when :rake
35
+ rake_loader
36
+ end
37
+ @ruby_opts.unshift(lib_opt)
38
+ @ruby_opts.unshift( "-w" ) if @warning
39
+ ruby @ruby_opts.join(" ") +
40
+ " \"#{run_code}\" " +
41
+ file_list.collect { |fn| "\"#{fn}\"" }.join(' ') +
42
+ " #{option_list}"
43
+ end
44
+ end
45
+ self
46
+ end
47
+
48
+ # Loads in the patched rake_test_loader to avoid the ARGV
49
+ # modification error, which arises within TDoc.
50
+ def rake_loader # :nodoc:
51
+ File.expand_path(File.join(File.dirname(__FILE__), 'rake_test_loader.rb'))
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,51 @@
1
+ module Test
2
+ module Unit
3
+ module Util # :nodoc:
4
+ module BacktraceFilter # :nodoc:
5
+
6
+ if method_defined?(:filter_backtrace)
7
+ alias :tap_original_filter_backtrace :filter_backtrace
8
+ end
9
+
10
+ # This is a slightly-modified version of the default BacktraceFilter
11
+ # provided in the Ruby 1.9 distribution. It solves the issue documented
12
+ # below, and hopefully will not be necessary when Ruby 1.9 is stable.
13
+ #
14
+ def filter_backtrace(backtrace, prefix=nil)
15
+ return ["No backtrace"] unless(backtrace)
16
+ split_p = if(prefix)
17
+ prefix.split(TESTUNIT_FILE_SEPARATORS)
18
+ else
19
+ TESTUNIT_PREFIX
20
+ end
21
+ match = proc do |e|
22
+ split_e = e.split(TESTUNIT_FILE_SEPARATORS)[0, split_p.size]
23
+ next false unless(split_e[0..-2] == split_p[0..-2])
24
+ split_e[-1].sub(TESTUNIT_RB_FILE, '') == split_p[-1]
25
+ end
26
+
27
+ # The Ruby 1.9 issue is that sometimes backtrace is a String
28
+ # and String is no longer Enumerable (hence it doesn't respond
29
+ # respond to detect). Arrayify to solve.
30
+ backtrace = [backtrace] unless backtrace.kind_of?(Array)
31
+
32
+ return backtrace unless(backtrace.detect(&match))
33
+ found_prefix = false
34
+ new_backtrace = backtrace.reverse.reject do |e|
35
+ if(match[e])
36
+ found_prefix = true
37
+ true
38
+ elsif(found_prefix)
39
+ false
40
+ else
41
+ true
42
+ end
43
+ end.reverse
44
+ new_backtrace = (new_backtrace.empty? ? backtrace : new_backtrace)
45
+ new_backtrace = new_backtrace.reject(&match)
46
+ new_backtrace.empty? ? backtrace : new_backtrace
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,16 @@
1
+ require 'date/format'
2
+
3
+ # Taken directly from the Ruby 1.8.6 standard library. Ruby 1.9 has apparently
4
+ # eliminated this class, but it is currently (2008-02-08) required by ActiveSupport.
5
+ #
6
+ # Making this file available to ActiveSupport allows testing under Ruby 1.9,
7
+ # but this patch should be unnecessary when ActiveSupport upgrades and becomes
8
+ # compatible with Ruby 1.9
9
+ module ParseDate # :nodoc:
10
+ def parsedate(str, comp=false)
11
+ Date._parse(str, comp).
12
+ values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday)
13
+ end
14
+
15
+ module_function :parsedate
16
+ end
@@ -1,4 +1,5 @@
1
1
  require 'tap/support/versions'
2
+ autoload(:FileUtils, 'fileutils')
2
3
 
3
4
  module Tap
4
5
 
@@ -10,14 +11,17 @@ module Tap
10
11
  # r = Root.new '/root_dir', :input => 'in', :output => 'out'
11
12
  #
12
13
  # # work with directories
13
- # r[:in] # => '/root_dir/in'
14
- # r[:out] # => '/root_dir/out'
15
- # r[:implicit] # => '/root_dir/implicit'
14
+ # r[:input] # => '/root_dir/in'
15
+ # r[:output] # => '/root_dir/out'
16
+ # r['implicit'] # => '/root_dir/implicit'
17
+ #
18
+ # # expanded paths are returned unchanged
19
+ # r[File.expand_path('expanded')] # => File.expand_path('expanded')
16
20
  #
17
21
  # # work with filepaths
18
- # fp = r.filepath(:in, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
19
- # r.relative_filepath(:in, fp) # => 'path/to/file.txt'
20
- # r.translate(fp, :in, :out) # => '/root_dir/out/path/to/file.txt'
22
+ # fp = r.filepath(:input, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
23
+ # r.relative_filepath(:input, fp) # => 'path/to/file.txt'
24
+ # r.translate(fp, :input, :output) # => '/root_dir/out/path/to/file.txt'
21
25
  #
22
26
  # # version filepaths
23
27
  # r.version('path/to/config.yml', 1.0) # => 'path/to/config-1.0.yml'
@@ -29,50 +33,52 @@ module Tap
29
33
  # r.filepath(:abs, "to", "file.txt") # => '/absolute/path/to/file.txt'
30
34
  #
31
35
  # By default, Roots are initialized to the present working directory (Dir.pwd).
32
- # As in the ':implicit' example, whenever Root needs to resolve an alias that is
33
- # not explicitly set, Root infers a path relative to the root directory.
36
+ # As in the 'implicit' example, Root infers a path relative to the root directory
37
+ # whenever it needs to resolve an alias that is not explicitly set. The only
38
+ # exceptions to this are fully expanded paths. These are returned unchanged.
34
39
  #
35
40
  # === Implementation Notes
36
41
  #
37
- # Internally Root stores expanded paths for both subdirectories and absolute paths in a
38
- # hash, 'paths', for quick lookup using the da (directory alias) keys. Expanding paths
39
- # ensures they remain constant even when the present working directory (Dir.pwd) changes.
42
+ # Internally Root stores expanded paths all aliased paths in the 'paths' hash.
43
+ # Expanding paths ensures they remain constant even when the present working
44
+ # directory (Dir.pwd) changes.
40
45
  #
41
- # Root keeps a separate hash mapping aliases to the subdirectory paths, 'directories'.
42
- # These relative filepaths allow reassignment of the expanded paths if and when the
43
- # root directory changes. By contrast, there is no separate data structure storing the
44
- # absolute paths. An absolute path can be distinguished from a subdirectory path by
45
- # aliases present in 'paths' but not 'directories'.
46
+ # Root keeps a separate 'directories' hash mapping aliases to their subdirectory paths.
47
+ # This hash allow reassignment if and when the root directory changes. By contrast,
48
+ # there is no separate data structure storing the absolute paths. An absolute path
49
+ # thus has an alias in 'paths' but not 'directories', whereas subdirectory paths
50
+ # have aliases in both.
46
51
  #
47
52
  # These features may be important to note when subclassing Root:
48
53
  # - root and all filepaths in 'paths' are expanded
49
- # - directory aliases and subdirectory paths are stored in 'directories'
54
+ # - subdirectory paths are stored in 'directories'
50
55
  # - absolute paths are present in 'paths' but not in 'directories'
51
56
  #
52
- class Root
57
+ class Root
58
+ # Regexp to match a windows-style root filepath.
59
+ WIN_ROOT_PATTERN = /^[A-z]:\//
60
+
53
61
  class << self
54
62
  include Support::Versions
55
63
 
56
64
  # Returns the filepath of path relative to dir. Both dir and path are
57
- # expanded before the relative filepath is determined. If strict=true
58
- # (the default), then relative_filepath raises an error if the path is
59
- # not relative to dir. Otherwise, the expanded path will be returned.
65
+ # expanded before the relative filepath is determined. An error is
66
+ # raised if the path is not relative to dir.
60
67
  #
61
68
  # Root.relative_filepath('dir', "dir/path/to/file.txt") # => "path/to/file.txt"
62
69
  #
63
- def relative_filepath(dir, path, strict=true)
64
- dir = File.expand_path(dir)
65
- expanded = File.expand_path(path)
70
+ def relative_filepath(dir, path, dir_string=Dir.pwd)
71
+ expanded_dir = File.expand_path(dir, dir_string)
72
+ expanded_path = File.expand_path(path, dir_string)
66
73
 
67
- unless expanded[0...dir.length] == dir
68
- raise "\n#{expanded}\nis not relative to:\n#{dir}" if strict
69
- return expanded
74
+ unless expanded_path.index(expanded_dir) == 0
75
+ raise "\n#{expanded_path}\nis not relative to:\n#{expanded_dir}"
70
76
  end
71
77
 
72
78
  # use dir.length + 1 to remove a leading '/'. If dir.length + 1 >= expanded.length
73
79
  # as in: relative_filepath('/path', '/path') then the first arg returns nil, and an
74
80
  # empty string is returned
75
- expanded[(dir.length+1)..-1] || ""
81
+ expanded_path[( expanded_dir.chomp("/").length + 1)..-1] || ""
76
82
  end
77
83
 
78
84
  # Lists all unique paths matching the input glob patterns.
@@ -94,14 +100,86 @@ module Tap
94
100
  results
95
101
  end.flatten.uniq
96
102
  end
103
+
104
+ # Executes the block in the specified directory. Makes the directory, if
105
+ # necessary when mkdir is specified. Otherwise, indir raises an error
106
+ # for non-existant directories, as well as non-directory inputs.
107
+ def indir(dir, mkdir=false)
108
+ unless File.directory?(dir)
109
+ if !File.exists?(dir) && mkdir
110
+ FileUtils.mkdir_p(dir)
111
+ else
112
+ raise "non a directory: #{dir}"
113
+ end
114
+ end
115
+
116
+ pwd = Dir.pwd
117
+ begin
118
+ Dir.chdir(dir)
119
+ yield
120
+ ensure
121
+ Dir.chdir(pwd)
122
+ end
123
+ end
124
+
125
+ # The path root type indicating windows, *nix, or some unknown
126
+ # style of filepaths (:win, :nix, :unknown).
127
+ def path_root_type
128
+ @path_root_type ||= case
129
+ when RUBY_PLATFORM =~ /mswin/ && File.expand_path(".") =~ WIN_ROOT_PATTERN then :win
130
+ when File.expand_path(".")[0] == ?/ then :nix
131
+ else :unknown
132
+ end
133
+ end
134
+
135
+ # Returns true if the input path appears to be an expanded path,
136
+ # based on Root.path_root_type.
137
+ #
138
+ # If root_type == :win returns true if the path matches
139
+ # WIN_ROOT_PATTERN.
140
+ #
141
+ # Root.expanded_path?('C:/path') # => true
142
+ # Root.expanded_path?('c:/path') # => true
143
+ # Root.expanded_path?('D:/path') # => true
144
+ # Root.expanded_path?('path') # => false
145
+ #
146
+ # If root_type == :nix, then expanded? returns true if
147
+ # the path begins with '/'.
148
+ #
149
+ # Root.expanded_path?('/path') # => true
150
+ # Root.expanded_path?('path') # => false
151
+ #
152
+ # Otherwise expanded_path? always returns nil.
153
+ def expanded_path?(path, root_type=path_root_type)
154
+ case root_type
155
+ when :win
156
+ path =~ WIN_ROOT_PATTERN ? true : false
157
+ when :nix
158
+ path[0] == ?/
159
+ else
160
+ nil
161
+ end
162
+ end
97
163
  end
98
164
 
99
165
  include Support::Versions
100
- attr_reader :root, :directories, :paths
101
166
 
102
- # Creates a new Root with the given root, directories, and absolute
103
- # paths. By default root is the current working directory and no
104
- # directories or absolute paths are specified.
167
+ # The root directory
168
+ attr_reader :root
169
+
170
+ # A hash of (alias, relative path) pairs for aliased subdirectories
171
+ attr_reader :directories
172
+
173
+ # A hash of (alias, expanded path) pairs for aliased subdirectories and absolute paths.
174
+ attr_reader :paths
175
+
176
+ # The filesystem root, inferred from self.root
177
+ # (ex '/' on *nix or something like 'C:/' on Windows).
178
+ attr_reader :path_root
179
+
180
+ # Creates a new Root with the given root directory, aliased directories
181
+ # and absolute paths. By default root is the present working directory
182
+ # and no aliased directories or absolute paths are specified.
105
183
  def initialize(root=Dir.pwd, directories={}, absolute_paths={})
106
184
  assign_paths(root, directories, absolute_paths)
107
185
  end
@@ -112,13 +190,21 @@ module Tap
112
190
  end
113
191
 
114
192
  # Sets the directories to those provided. 'root' and :root are reserved
115
- # directory keys and cannot be set using this method (use root= instead).
193
+ # and cannot be set using this method (use root= instead).
194
+ #
195
+ # r['alt'] # => File.join(r.root, 'alt')
196
+ # r.directories = {'alt' => 'dir'}
197
+ # r['alt'] # => File.join(r.root, 'dir')
116
198
  def directories=(dirs)
117
199
  assign_paths(root, dirs, absolute_paths)
118
200
  end
119
201
 
120
202
  # Sets the absolute paths to those provided. 'root' and :root are reserved
121
203
  # directory keys and cannot be set using this method (use root= instead).
204
+ #
205
+ # r['abs'] # => File.join(r.root, 'abs')
206
+ # r.absolute_paths = {'abs' => '/path/to/dir'}
207
+ # r['abs'] # => '/path/to/dir'
122
208
  def absolute_paths=(paths)
123
209
  assign_paths(root, directories, paths)
124
210
  end
@@ -132,9 +218,10 @@ module Tap
132
218
  abs_paths
133
219
  end
134
220
 
135
- # Sets a directory alias (da) for the path relative to the root directory.
136
- # The root directory cannot be set with this method (use root= instead).
137
- # Absolute filepaths can be set using the second syntax.
221
+ # Sets an alias for the subdirectory relative to the root directory.
222
+ # The aliases 'root' and :root cannot be set with this method
223
+ # (use root= instead). Absolute filepaths can be set using the
224
+ # second syntax.
138
225
  #
139
226
  # r = Root.new '/root_dir'
140
227
  # r[:dir] = 'path/to/dir'
@@ -144,13 +231,14 @@ module Tap
144
231
  # r[:abs] # => '/abs/path/to/dir'
145
232
  #
146
233
  #--
234
+ # Implementation Notes:
147
235
  # The syntax for setting an absolute filepath requires an odd use []=.
148
236
  # In fact the method recieves the arguments (:dir, true, '/abs/path/to/dir')
149
237
  # rather than (:dir, '/abs/path/to/dir', true), meaning that internally path
150
238
  # and absolute are switched when setting an absolute filepath.
151
239
  #++
152
- def []=(da, path, absolute=false)
153
- raise ArgumentError, "The directory key '#{da}' is reserved." if da.to_s == 'root'
240
+ def []=(dir, path, absolute=false)
241
+ raise ArgumentError, "The directory key '#{dir}' is reserved." if dir.to_s == 'root'
154
242
 
155
243
  # switch the paths if absolute was provided
156
244
  unless absolute == false
@@ -158,42 +246,49 @@ module Tap
158
246
  path = absolute
159
247
  absolute = switch
160
248
  end
161
-
249
+
162
250
  case
163
251
  when path.nil?
164
- @directories.delete(da)
165
- @paths.delete(da)
252
+ @directories.delete(dir)
253
+ @paths.delete(dir)
166
254
  when absolute
167
- @directories.delete(da)
168
- @paths[da] = File.expand_path(path)
255
+ @directories.delete(dir)
256
+ @paths[dir] = File.expand_path(path)
169
257
  else
170
- @directories[da] = path
171
- @paths[da] = File.expand_path(File.join(root, path))
172
- end
258
+ @directories[dir] = path
259
+ @paths[dir] = File.expand_path(File.join(root, path))
260
+ end
173
261
  end
174
262
 
175
263
  # Returns the expanded path for the specified alias. If the alias
176
- # has not been set, then the path is inferred to be 'root/da'.
264
+ # has not been set, then the path is inferred to be 'root/dir' unless
265
+ # the path is relative to path_root. These paths are returned
266
+ # directly.
177
267
  #
178
- # r = Root.new '/root_dir'
179
- # r[:dir] = 'path/to/dir'
180
- # r[:dir] # => '/root_dir/path/to/dir'
268
+ # r = Root.new '/root_dir', :dir => 'path/to/dir'
269
+ # r[:dir] # => '/root_dir/path/to/dir'
181
270
  #
182
- # r[:unset] = nil
183
- # r[:unset] # => '/root_dir/unset'
271
+ # r.path_root # => '/'
272
+ # r['relative/path'] # => '/root_dir/relative/path'
273
+ # r['/expanded/path'] # => '/expanded/path'
184
274
  #
185
- def [](da)
186
- self.paths[da] || File.expand_path(File.join(root, da.to_s))
275
+ def [](dir)
276
+ path = self.paths[dir]
277
+ return path unless path == nil
278
+
279
+ dir = dir.to_s
280
+ Root.expanded_path?(dir) ? dir : File.expand_path(File.join(root, dir))
187
281
  end
188
282
 
189
283
  # Constructs expanded filepaths relative to the path of the specified alias.
190
- def filepath(da, *filename)
191
- File.expand_path(File.join(self[da], *filename))
284
+ def filepath(dir, *filename)
285
+ # TODO - consider filename.compact so nils will not raise errors
286
+ File.expand_path(File.join(self[dir], *filename))
192
287
  end
193
288
 
194
289
  # Retrieves the filepath relative to the path of the specified alias.
195
- def relative_filepath(da, filepath, strict=true)
196
- Root.relative_filepath(self[da], filepath, strict)
290
+ def relative_filepath(dir, filepath)
291
+ Root.relative_filepath(self[dir], filepath)
197
292
  end
198
293
 
199
294
  # Generates a target filepath translated from the aliased input dir to
@@ -202,23 +297,28 @@ module Tap
202
297
  #
203
298
  # fp = r.filepath(:in, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
204
299
  # r.translate(fp, :in, :out) # => '/root_dir/out/path/to/file.txt'
205
- def translate(filepath, input_da, output_da)
206
- filepath(output_da, relative_filepath(input_da, filepath))
300
+ def translate(filepath, input_dir, output_dir)
301
+ filepath(output_dir, relative_filepath(input_dir, filepath))
207
302
  end
208
303
 
209
304
  # Lists all files in the aliased dir matching the input patterns. Patterns
210
305
  # should be valid inputs for +Dir.glob+. If no patterns are specified, lists
211
306
  # all files/folders matching '**/*'.
212
- def glob(da, *patterns)
307
+ def glob(dir, *patterns)
213
308
  patterns << "**/*" if patterns.empty?
214
- patterns.collect! {|pattern| filepath(da, pattern)}
309
+ patterns.collect! {|pattern| filepath(dir, pattern)}
215
310
  Root.glob(*patterns)
216
311
  end
217
312
 
218
313
  # Lists all versions of filename in the aliased dir matching the version patterns.
219
314
  # If no patterns are specified, then all versions of filename will be returned.
220
- def vglob(da, filename, *vpatterns)
221
- Root.vglob(filepath(da, filename), *vpatterns)
315
+ def vglob(dir, filename, *vpatterns)
316
+ Root.vglob(filepath(dir, filename), *vpatterns)
317
+ end
318
+
319
+ # Executes the provided block in the specified directory using Root.indir.
320
+ def indir(dir, mkdir=false)
321
+ Root.indir(self[dir], mkdir) { yield }
222
322
  end
223
323
 
224
324
  private
@@ -228,9 +328,14 @@ module Tap
228
328
  @root = File.expand_path(root)
229
329
  @directories = {}
230
330
  @paths = {'root' => @root, :root => @root}
231
-
232
- directories.each_pair {|da, path| self[da] = path }
233
- absolute_paths.each_pair {|da, path| self[da, true] = path }
331
+
332
+ @path_root = File.dirname(@root)
333
+ while @path_root != (parent = File.dirname(@path_root))
334
+ @path_root = parent
335
+ end
336
+
337
+ directories.each_pair {|dir, path| self[dir] = path }
338
+ absolute_paths.each_pair {|dir, path| self[dir, true] = path }
234
339
  end
235
340
 
236
341
  end