tap 0.9.1 → 0.10.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 (244) hide show
  1. data/History +37 -30
  2. data/MIT-LICENSE +1 -1
  3. data/README +92 -44
  4. data/bin/tap +62 -75
  5. data/cmd/console.rb +42 -0
  6. data/cmd/destroy.rb +16 -0
  7. data/cmd/generate.rb +16 -0
  8. data/cmd/run.rb +126 -0
  9. data/doc/Class Reference +362 -0
  10. data/doc/Command Reference +153 -0
  11. data/doc/Tutorial +237 -0
  12. data/lib/tap.rb +6 -45
  13. data/lib/tap/app.rb +126 -500
  14. data/lib/tap/constants.rb +2 -29
  15. data/lib/tap/env.rb +555 -250
  16. data/lib/tap/file_task.rb +60 -103
  17. data/lib/tap/generator/base.rb +109 -0
  18. data/lib/tap/generator/destroy.rb +37 -0
  19. data/lib/tap/generator/generate.rb +61 -0
  20. data/lib/tap/generator/generators/command/command_generator.rb +16 -12
  21. data/lib/tap/generator/generators/command/templates/command.erb +13 -19
  22. data/lib/tap/generator/generators/config/config_generator.rb +18 -27
  23. data/lib/tap/generator/generators/config/templates/doc.erb +12 -0
  24. data/lib/tap/generator/generators/config/templates/nodoc.erb +8 -0
  25. data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -11
  26. data/lib/tap/generator/generators/file_task/templates/file.txt +11 -2
  27. data/lib/tap/generator/generators/file_task/templates/result.yml +6 -0
  28. data/lib/tap/generator/generators/file_task/templates/task.erb +24 -31
  29. data/lib/tap/generator/generators/file_task/templates/test.erb +18 -22
  30. data/lib/tap/generator/generators/root/root_generator.rb +45 -31
  31. data/lib/tap/generator/generators/root/templates/Rakefile +64 -41
  32. data/lib/tap/generator/generators/root/templates/gemspec +27 -0
  33. data/lib/tap/generator/generators/root/templates/tapfile +8 -0
  34. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -0
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  36. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +15 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +21 -28
  38. data/lib/tap/generator/generators/task/templates/task.erb +13 -23
  39. data/lib/tap/generator/generators/task/templates/test.erb +15 -18
  40. data/lib/tap/generator/manifest.rb +14 -0
  41. data/lib/tap/patches/rake/rake_test_loader.rb +0 -0
  42. data/lib/tap/patches/rake/testtask.rb +0 -0
  43. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -0
  44. data/lib/tap/patches/ruby19/parsedate.rb +0 -0
  45. data/lib/tap/root.rb +260 -21
  46. data/lib/tap/support/aggregator.rb +11 -11
  47. data/lib/tap/support/assignments.rb +172 -0
  48. data/lib/tap/support/audit.rb +20 -18
  49. data/lib/tap/support/batchable.rb +21 -10
  50. data/lib/tap/support/batchable_class.rb +107 -0
  51. data/lib/tap/support/class_configuration.rb +154 -239
  52. data/lib/tap/support/command_line.rb +97 -102
  53. data/lib/tap/support/comment.rb +270 -0
  54. data/lib/tap/support/configurable.rb +86 -65
  55. data/lib/tap/support/configurable_class.rb +296 -0
  56. data/lib/tap/support/configuration.rb +122 -0
  57. data/lib/tap/support/constant.rb +70 -0
  58. data/lib/tap/support/constant_utils.rb +127 -0
  59. data/lib/tap/support/declarations.rb +111 -0
  60. data/lib/tap/support/executable.rb +30 -17
  61. data/lib/tap/support/executable_queue.rb +0 -0
  62. data/lib/tap/support/framework.rb +71 -0
  63. data/lib/tap/support/framework_class.rb +199 -0
  64. data/lib/tap/support/instance_configuration.rb +147 -0
  65. data/lib/tap/support/lazydoc.rb +428 -0
  66. data/lib/tap/support/manifest.rb +89 -0
  67. data/lib/tap/support/run_error.rb +0 -0
  68. data/lib/tap/support/shell_utils.rb +33 -9
  69. data/lib/tap/support/summary.rb +30 -0
  70. data/lib/tap/support/tdoc.rb +339 -134
  71. data/lib/tap/support/tdoc/tdoc_html_generator.rb +0 -0
  72. data/lib/tap/support/tdoc/tdoc_html_template.rb +0 -0
  73. data/lib/tap/support/templater.rb +180 -0
  74. data/lib/tap/support/validation.rb +409 -76
  75. data/lib/tap/support/versions.rb +5 -3
  76. data/lib/tap/task.rb +78 -174
  77. data/lib/tap/tasks/dump.rb +56 -0
  78. data/lib/tap/tasks/rake.rb +93 -0
  79. data/lib/tap/test.rb +3 -3
  80. data/lib/tap/test/env_vars.rb +2 -2
  81. data/lib/tap/test/file_methods.rb +19 -20
  82. data/lib/tap/test/script_methods.rb +144 -0
  83. data/lib/tap/test/subset_methods.rb +1 -1
  84. data/lib/tap/test/tap_methods.rb +28 -62
  85. data/lib/tap/workflow.rb +22 -39
  86. metadata +48 -179
  87. data/Basic Overview +0 -151
  88. data/Command Reference +0 -99
  89. data/Rakefile +0 -127
  90. data/Tutorial +0 -287
  91. data/lib/tap/cmd/console.rb +0 -31
  92. data/lib/tap/cmd/destroy.rb +0 -20
  93. data/lib/tap/cmd/generate.rb +0 -20
  94. data/lib/tap/cmd/run.rb +0 -151
  95. data/lib/tap/dump.rb +0 -57
  96. data/lib/tap/generator.rb +0 -91
  97. data/lib/tap/generator/generators/command/USAGE +0 -6
  98. data/lib/tap/generator/generators/config/USAGE +0 -21
  99. data/lib/tap/generator/generators/config/templates/config.erb +0 -1
  100. data/lib/tap/generator/generators/file_task/USAGE +0 -3
  101. data/lib/tap/generator/generators/file_task/templates/file.yml +0 -3
  102. data/lib/tap/generator/generators/generator/USAGE +0 -0
  103. data/lib/tap/generator/generators/generator/generator_generator.rb +0 -21
  104. data/lib/tap/generator/generators/generator/templates/generator.erb +0 -32
  105. data/lib/tap/generator/generators/generator/templates/usage.erb +0 -1
  106. data/lib/tap/generator/generators/root/USAGE +0 -0
  107. data/lib/tap/generator/generators/root/templates/ReadMe.txt +0 -0
  108. data/lib/tap/generator/generators/root/templates/tap.yml +0 -80
  109. data/lib/tap/generator/generators/task/USAGE +0 -3
  110. data/lib/tap/generator/generators/workflow/USAGE +0 -0
  111. data/lib/tap/generator/generators/workflow/templates/task.erb +0 -16
  112. data/lib/tap/generator/generators/workflow/templates/test.erb +0 -7
  113. data/lib/tap/generator/generators/workflow/workflow_generator.rb +0 -6
  114. data/lib/tap/generator/options.rb +0 -26
  115. data/lib/tap/generator/usage.rb +0 -26
  116. data/lib/tap/support/batchable_methods.rb +0 -34
  117. data/lib/tap/support/command_line_methods.rb +0 -76
  118. data/lib/tap/support/configurable_methods.rb +0 -224
  119. data/lib/tap/support/logger.rb +0 -88
  120. data/lib/tap/support/rake.rb +0 -43
  121. data/lib/tap/support/tdoc/config_attr.rb +0 -362
  122. data/test/app/config/another/task.yml +0 -1
  123. data/test/app/config/batch.yml +0 -2
  124. data/test/app/config/empty.yml +0 -0
  125. data/test/app/config/erb.yml +0 -2
  126. data/test/app/config/some/task.yml +0 -1
  127. data/test/app/config/template.yml +0 -2
  128. data/test/app/config/version-0.1.yml +0 -1
  129. data/test/app/config/version.yml +0 -1
  130. data/test/app/lib/app_test_task.rb +0 -3
  131. data/test/app_test.rb +0 -1849
  132. data/test/env/test_configure/recurse_a.yml +0 -2
  133. data/test/env/test_configure/recurse_b.yml +0 -2
  134. data/test/env/test_configure/tap.yml +0 -23
  135. data/test/env/test_load_env_config/dir/tap.yml +0 -3
  136. data/test/env/test_load_env_config/recurse_a.yml +0 -2
  137. data/test/env/test_load_env_config/recurse_b.yml +0 -2
  138. data/test/env/test_load_env_config/tap.yml +0 -3
  139. data/test/env_test.rb +0 -198
  140. data/test/file_task/config/batch.yml +0 -2
  141. data/test/file_task/config/configured.yml +0 -1
  142. data/test/file_task/old_file_one.txt +0 -0
  143. data/test/file_task/old_file_two.txt +0 -0
  144. data/test/file_task_test.rb +0 -1291
  145. data/test/root/alt_lib/alt_module.rb +0 -4
  146. data/test/root/file.txt +0 -0
  147. data/test/root/glob/one.txt +0 -0
  148. data/test/root/glob/two.txt +0 -0
  149. data/test/root/lib/absolute_alt_filepath.rb +0 -2
  150. data/test/root/lib/alternative_filepath.rb +0 -2
  151. data/test/root/lib/another_module.rb +0 -2
  152. data/test/root/lib/nested/some_module.rb +0 -4
  153. data/test/root/lib/no_module_included.rb +0 -0
  154. data/test/root/lib/some/module.rb +0 -4
  155. data/test/root/lib/some_class.rb +0 -2
  156. data/test/root/lib/some_module.rb +0 -3
  157. data/test/root/load_path/load_path_module.rb +0 -2
  158. data/test/root/load_path/skip_module.rb +0 -2
  159. data/test/root/mtime/older.txt +0 -0
  160. data/test/root/unload/full_path.rb +0 -2
  161. data/test/root/unload/loaded_by_nested.rb +0 -2
  162. data/test/root/unload/nested/nested_load.rb +0 -6
  163. data/test/root/unload/nested/nested_with_ext.rb +0 -4
  164. data/test/root/unload/nested/relative_path.rb +0 -4
  165. data/test/root/unload/older.rb +0 -2
  166. data/test/root/unload/unload_base.rb +0 -9
  167. data/test/root/versions/another.yml +0 -0
  168. data/test/root/versions/file-0.1.2.yml +0 -0
  169. data/test/root/versions/file-0.1.yml +0 -0
  170. data/test/root/versions/file.yml +0 -0
  171. data/test/root_test.rb +0 -718
  172. data/test/support/aggregator_test.rb +0 -99
  173. data/test/support/audit_test.rb +0 -445
  174. data/test/support/batchable_test.rb +0 -74
  175. data/test/support/class_configuration_test.rb +0 -331
  176. data/test/support/command_line_test.rb +0 -58
  177. data/test/support/configurable/config/configured.yml +0 -2
  178. data/test/support/configurable_test.rb +0 -295
  179. data/test/support/executable_queue_test.rb +0 -103
  180. data/test/support/executable_test.rb +0 -38
  181. data/test/support/logger_test.rb +0 -31
  182. data/test/support/rake_test.rb +0 -37
  183. data/test/support/shell_utils_test.rb +0 -24
  184. data/test/support/tdoc_test.rb +0 -370
  185. data/test/support/validation_test.rb +0 -54
  186. data/test/support/versions_test.rb +0 -103
  187. data/test/tap_test_helper.rb +0 -57
  188. data/test/tap_test_suite.rb +0 -7
  189. data/test/task/config/batch.yml +0 -2
  190. data/test/task/config/batched.yml +0 -2
  191. data/test/task/config/configured.yml +0 -1
  192. data/test/task/config/example.yml +0 -1
  193. data/test/task_base_test.rb +0 -24
  194. data/test/task_syntax_test.rb +0 -300
  195. data/test/task_test.rb +0 -320
  196. data/test/test/env_vars_test.rb +0 -48
  197. data/test/test/file_methods/test_assert_files/expected/one.txt +0 -1
  198. data/test/test/file_methods/test_assert_files/expected/two.txt +0 -1
  199. data/test/test/file_methods/test_assert_files/input/one.txt +0 -1
  200. data/test/test/file_methods/test_assert_files/input/two.txt +0 -1
  201. data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/one.txt +0 -1
  202. data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/two.txt +0 -1
  203. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +0 -1
  204. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/two.txt +0 -1
  205. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +0 -1
  206. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +0 -1
  207. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/expected/one.txt +0 -1
  208. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +0 -1
  209. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +0 -1
  210. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +0 -1
  211. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +0 -1
  212. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +0 -1
  213. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +0 -1
  214. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +0 -1
  215. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +0 -1
  216. data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
  217. data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
  218. data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
  219. data/test/test/file_methods_doc/test_sub/expected/one.txt +0 -1
  220. data/test/test/file_methods_doc/test_sub/expected/two.txt +0 -1
  221. data/test/test/file_methods_doc/test_sub/input/one.txt +0 -1
  222. data/test/test/file_methods_doc/test_sub/input/two.txt +0 -1
  223. data/test/test/file_methods_doc_test.rb +0 -29
  224. data/test/test/file_methods_test.rb +0 -275
  225. data/test/test/subset_methods_test.rb +0 -171
  226. data/test/test/tap_methods/test_assert_files/expected/task/name/a.txt +0 -1
  227. data/test/test/tap_methods/test_assert_files/expected/task/name/b.txt +0 -1
  228. data/test/test/tap_methods/test_assert_files/input/a.txt +0 -1
  229. data/test/test/tap_methods/test_assert_files/input/b.txt +0 -1
  230. data/test/test/tap_methods_test.rb +0 -399
  231. data/test/workflow_test.rb +0 -120
  232. data/vendor/rails_generator.rb +0 -56
  233. data/vendor/rails_generator/base.rb +0 -263
  234. data/vendor/rails_generator/commands.rb +0 -581
  235. data/vendor/rails_generator/generated_attribute.rb +0 -42
  236. data/vendor/rails_generator/lookup.rb +0 -209
  237. data/vendor/rails_generator/manifest.rb +0 -53
  238. data/vendor/rails_generator/options.rb +0 -143
  239. data/vendor/rails_generator/scripts.rb +0 -83
  240. data/vendor/rails_generator/scripts/destroy.rb +0 -7
  241. data/vendor/rails_generator/scripts/generate.rb +0 -7
  242. data/vendor/rails_generator/scripts/update.rb +0 -12
  243. data/vendor/rails_generator/simple_logger.rb +0 -46
  244. data/vendor/rails_generator/spec.rb +0 -44
@@ -0,0 +1,27 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "<%= project_name %>"
3
+ s.version = "0.0.1"
4
+ #s.author = "Your Name Here"
5
+ #s.email = "your.email@pubfactory.edu"
6
+ #s.homepage = "http://rubyforge.org/projects/<%= project_name %>/"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = "<%= project_name %> task library"
9
+ s.require_path = "lib"
10
+ s.test_file = "test/tap_test_suite.rb"
11
+ #s.rubyforge_project = "<%= project_name %>"
12
+ #s.has_rdoc = true
13
+ s.add_dependency("tap", "~> <%= Tap::VERSION %>")
14
+
15
+ # list extra rdoc files like README here.
16
+ s.extra_rdoc_files = %W{
17
+ }
18
+
19
+ # list the files you want to include here. you can
20
+ # check this manifest using 'rake :print_manifest'
21
+ s.files = %W{
22
+ tapfile.rb
23
+ test/tap_test_helper.rb
24
+ test/tap_test_suite.rb
25
+ test/tapfile_test.rb
26
+ }
27
+ end
@@ -0,0 +1,8 @@
1
+ require 'tap'
2
+
3
+ # Goodnight::manifest your basic goodnight moon task
4
+ # Prints the input with a configurable message.
5
+ Tap.task('goodnight', :message => 'goodnight') do |task, name|
6
+ task.log task.message, name
7
+ "#{task.message} #{name}"
8
+ end
@@ -1,5 +1,5 @@
1
1
  $:.unshift File.join(File.dirname(__FILE__), '../lib')
2
2
 
3
3
  # runs all subsets (see Tap::Test::SubsetMethods)
4
- ENV["ALL"] = true
4
+ ENV["ALL"] = "true"
5
5
  Dir.glob("./**/*_test.rb").each {|test| require test}
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/tap_test_helper.rb'
2
+ require File.dirname(__FILE__) + '/../tapfile.rb'
3
+
4
+ class TapfileTest < Test::Unit::TestCase
5
+ acts_as_tap_test
6
+
7
+ def test_goodnight
8
+ task = Goodnight.new :message => "goodnight"
9
+
10
+ # a simple test
11
+ assert_equal({:message => 'goodnight'}, task.config)
12
+ assert_equal "goodnight moon", task.process("moon")
13
+ end
14
+
15
+ end
@@ -1,34 +1,27 @@
1
1
  module Tap::Generator::Generators
2
- class TaskGenerator < Rails::Generator::NamedBase # :nodoc:
3
- def initialize(*args)
4
- super(*args)
5
- @destination_root = Tap::App.instance[:root]
6
- @app = Tap::App.instance
7
- end
8
-
9
- def manifest
10
- record do |m|
11
- lib_path = @app.relative_filepath(:root, @app[:lib])
12
- m.directory File.join(lib_path, class_path)
13
- m.template "task.erb", File.join(lib_path, class_name.underscore + ".rb"), :class_nesting => class_nesting
14
-
15
- if options[:test]
16
- test_path = @app.relative_filepath(:root, @app[:test])
17
- m.directory File.join(test_path, class_path)
18
- m.template "test.erb", File.join(test_path, class_name.underscore + "_test.rb")
19
- end
20
-
21
- task_manifest(m)
2
+
3
+ # :startdoc::generator a task and test
4
+ #
5
+ # Generates a new Tap::Task and an associated test file.
6
+ class TaskGenerator < Tap::Generator::Base
7
+
8
+ config :test, true, &c.switch # Generates the task without test files.
9
+
10
+ def manifest(m, const_name)
11
+ const = Constant.new(const_name.camelize)
12
+
13
+ task_path = app.filepath('lib', "#{const.path}.rb")
14
+ m.directory File.dirname(task_path)
15
+ m.template task_path, "task.erb", :const => const
16
+
17
+ if test
18
+ test_path = app.filepath('test', "#{const.path}_test.rb")
19
+ m.directory File.dirname(test_path)
20
+ m.template test_path, "test.erb", :const => const
22
21
  end
22
+
23
+ const
23
24
  end
24
-
25
- def task_manifest(m)
26
- end
27
-
28
- def add_options!(opt)
29
- options[:test] = true
30
- opt.on(nil, '--[no-]test', 'Generates the task without test files.') { |value| options[:test] = value }
31
- end
32
25
 
33
26
  end
34
27
  end
@@ -1,24 +1,14 @@
1
- # == Description
2
- # Replace with a description. The default task simply
3
- # demonstrates the use of a config and logging.
4
- # === Usage
5
- # Replace with usage instructions
6
- #
7
- class <%= class_name_without_nesting %> < Tap::Task
8
- # use config to set task configurations
9
- # configs have accessors by default
10
-
11
- config :key, 'value' # a sample config
12
-
13
- # process defines what the task does; use the
14
- # same number of inputs to enque the task
15
- # as specified here
16
- def process(input)
17
- # use log to record information
18
- result = "#{input} was processed with #{key}"
19
- log self.name, result
20
-
21
- result
1
+ <% redirect do |target| %># <%= const.name %>::manifest <replace with manifest summary>
2
+ # <replace with command line description>
3
+
4
+ # <%= const.const_name %> Documentation
5
+ class <%= const.const_name %> < Tap::Task
6
+
7
+ # <config file documentation>
8
+ config :message, 'goodnight' # a sample config
9
+
10
+ def process(name)
11
+ log message, name
12
+ "#{message} #{name}"
22
13
  end
23
-
24
- end
14
+ end <% module_nest(const.nesting, ' ') { target } end %>
@@ -1,24 +1,21 @@
1
- require File.join(File.dirname(__FILE__), '<%= '../' * class_nesting_depth %>tap_test_helper.rb')
2
- require '<%= class_path.empty? ? file_name : File.join(class_path, file_name) %>'
1
+ require File.join(File.dirname(__FILE__), '<%= '../' * const.nesting_depth %>tap_test_helper.rb')
2
+ require '<%= const.path %>'
3
3
 
4
- class <%= class_name %>Test < Test::Unit::TestCase
4
+ class <%= const.name %>Test < Test::Unit::TestCase
5
5
  acts_as_tap_test
6
6
 
7
- def test_<%= file_name %>
8
- t = <%= class_name %>.new nil, :key => 'value'
7
+ def test_<%= const.basename %>
8
+ task = <%= const.name %>.new :message => "goodnight"
9
9
 
10
- # specify application options
11
- with_options(:quiet => true, :debug => true) do
12
-
13
- # run the task with some inputs
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
-
21
- end
10
+ # a simple test
11
+ assert_equal({:message => 'goodnight'}, task.config)
12
+ assert_equal "goodnight moon", task.process("moon")
13
+
14
+ # a more complex test
15
+ task.enq("moon")
16
+ app.run
17
+
18
+ assert_equal ["goodnight moon"], app.results(task)
19
+ assert_audit_equal ExpAudit[[nil, "moon"], [task, "goodnight moon"]], app._results(task)[0]
22
20
  end
23
-
24
21
  end
@@ -0,0 +1,14 @@
1
+ module Tap
2
+ module Generator
3
+ class Manifest
4
+ def initialize(actions)
5
+ @actions = actions
6
+ end
7
+
8
+ # Record an action.
9
+ def method_missing(action, *args, &block)
10
+ @actions << [action, args, block]
11
+ end
12
+ end
13
+ end
14
+ end
File without changes
File without changes
File without changes
File without changes
data/lib/tap/root.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'tap/support/versions'
2
+ require 'tap/support/configurable'
2
3
  autoload(:FileUtils, 'fileutils')
3
4
 
4
5
  module Tap
@@ -62,8 +63,8 @@ module Tap
62
63
  include Support::Versions
63
64
 
64
65
  # Returns the filepath of path relative to dir. Both dir and path are
65
- # expanded before the relative filepath is determined. An error is
66
- # raised if the path is not relative to dir.
66
+ # expanded before the relative filepath is determined. Returns nil if
67
+ # the path is not relative to dir.
67
68
  #
68
69
  # Root.relative_filepath('dir', "dir/path/to/file.txt") # => "path/to/file.txt"
69
70
  #
@@ -71,9 +72,7 @@ module Tap
71
72
  expanded_dir = File.expand_path(dir, dir_string)
72
73
  expanded_path = File.expand_path(path, dir_string)
73
74
 
74
- unless expanded_path.index(expanded_dir) == 0
75
- raise "\n#{expanded_path}\nis not relative to:\n#{expanded_dir}"
76
- end
75
+ return nil unless expanded_path.index(expanded_dir) == 0
77
76
 
78
77
  # use dir.length + 1 to remove a leading '/'. If dir.length + 1 >= expanded.length
79
78
  # as in: relative_filepath('/path', '/path') then the first arg returns nil, and an
@@ -101,6 +100,15 @@ module Tap
101
100
  end.flatten.uniq
102
101
  end
103
102
 
103
+ # Path suffix glob. Globs along the base paths for
104
+ # paths that match the specified suffix pattern.
105
+ def sglob(suffix_pattern, *base_paths)
106
+ base_paths.collect do |base|
107
+ base = File.expand_path(base)
108
+ Dir.glob(File.join(base, suffix_pattern))
109
+ end.flatten.uniq
110
+ end
111
+
104
112
  # Executes the block in the specified directory. Makes the directory, if
105
113
  # necessary when mkdir is specified. Otherwise, indir raises an error
106
114
  # for non-existant directories, as well as non-directory inputs.
@@ -109,7 +117,7 @@ module Tap
109
117
  if !File.exists?(dir) && mkdir
110
118
  FileUtils.mkdir_p(dir)
111
119
  else
112
- raise "non a directory: #{dir}"
120
+ raise "not a directory: #{dir}"
113
121
  end
114
122
  end
115
123
 
@@ -160,15 +168,242 @@ module Tap
160
168
  nil
161
169
  end
162
170
  end
171
+
172
+ # Minimizes a set of paths to the set of shortest basepaths that unqiuely
173
+ # identify the paths. The path extension and versions are removed from
174
+ # the basepath if possible. For example:
175
+ #
176
+ # Tap::Root.minimize ['path/to/a.rb', 'path/to/b.rb']
177
+ # # => ['a', 'b']
178
+ #
179
+ # Tap::Root.minimize ['path/to/a-0.1.0.rb', 'path/to/b-0.1.0.rb']
180
+ # # => ['a', 'b']
181
+ #
182
+ # Tap::Root.minimize ['path/to/file.rb', 'path/to/file.txt']
183
+ # # => ['file.rb', 'file.txt']
184
+ #
185
+ # Tap::Root.minimize ['path-0.1/to/file.rb', 'path-0.2/to/file.rb']
186
+ # # => ['path-0.1/to/file', 'path-0.2/to/file']
187
+ #
188
+ # Minimized paths that carry their extension will always carry
189
+ # their version as well, but the converse is not true; paths
190
+ # can be minimized to carry just the version and not the path
191
+ # extension.
192
+ #
193
+ # Tap::Root.minimize ['path/to/a-0.1.0.rb', 'path/to/a-0.1.0.txt']
194
+ # # => ['a-0.1.0.rb', 'a-0.1.0.txt']
195
+ #
196
+ # Tap::Root.minimize ['path/to/a-0.1.0.rb', 'path/to/a-0.2.0.rb']
197
+ # # => ['a-0.1.0', 'a-0.2.0']
198
+ #
199
+ # If a block is given, each (path, mini-path) pair will be passed
200
+ # to it after minimization.
201
+ def minimize(paths) # :yields: path, mini_path
202
+ unless block_given?
203
+ mini_paths = []
204
+ minimize(paths) {|p, mp| mini_paths << mp }
205
+ return mini_paths
206
+ end
207
+
208
+ splits = paths.uniq.collect do |path|
209
+ extname = File.extname(path)
210
+ extname = '' if extname =~ /^\.\d+$/
211
+ base = File.basename(path.chomp(extname))
212
+ version = base =~ /(-\d+(\.\d+)*)$/ ? $1 : ''
213
+
214
+ [dirname_or_array(path), base.chomp(version), extname, version, false, path]
215
+ end
216
+
217
+ while !splits.empty?
218
+ index = 0
219
+ splits = splits.collect do |(dir, base, extname, version, flagged, path)|
220
+ index += 1
221
+ case
222
+ when !flagged && just_one?(splits, index, base)
223
+
224
+ # found just one
225
+ yield(path, base)
226
+ nil
227
+ when dir.kind_of?(Array)
228
+
229
+ # no more path segments to use, try to add
230
+ # back version and extname
231
+ if dir.empty?
232
+ dir << File.dirname(base)
233
+ base = File.basename(base)
234
+ end
235
+
236
+ case
237
+ when !version.empty?
238
+ # add back version (occurs first)
239
+ [dir, "#{base}#{version}", extname, '', false, path]
240
+
241
+ when !extname.empty?
242
+
243
+ # add back extension (occurs second)
244
+ [dir, "#{base}#{extname}", '', version, false, path]
245
+ else
246
+
247
+ # nothing more to distinguish... path is minimized (occurs third)
248
+ yield(path, min_join(dir[0], base))
249
+ nil
250
+ end
251
+ else
252
+
253
+ # shift path segment. dirname_or_array returns an
254
+ # array if this is the last path segment to shift.
255
+ [dirname_or_array(dir), min_join(File.basename(dir), base), extname, version, false, path]
256
+ end
257
+ end.compact
258
+ end
259
+ end
260
+
261
+ # Returns true if the mini_path matches path. Matching logic
262
+ # reverses that of minimize:
263
+ # * a match occurs when path ends with mini_path
264
+ # * if mini_path doesn't specify an extension, then mini_path
265
+ # must only match path up to the path extension
266
+ # * if mini_path doesn't specify a version, then mini_path
267
+ # must only match path up to the path basename (minus the
268
+ # version and extname)
269
+ #
270
+ # For example:
271
+ #
272
+ # Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file') # => true
273
+ # Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'dir/file') # => true
274
+ # Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file-0.1.0') # => true
275
+ # Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file-0.1.0.rb') # => true
276
+ #
277
+ # Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file.rb') # => false
278
+ # Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'file-0.2.0') # => false
279
+ # Tap::Root.minimal_match?('dir/file-0.1.0.rb', 'another') # => false
280
+ #
281
+ # In matching, partial basenames are not allowed but partial directories
282
+ # are allowed. Hence:
283
+ #
284
+ # Tap::Root.minimal_match?('dir/file-0.1.0.txt', 'file') # => true
285
+ # Tap::Root.minimal_match?('dir/file-0.1.0.txt', 'ile') # => false
286
+ # Tap::Root.minimal_match?('dir/file-0.1.0.txt', 'r/file') # => true
287
+ #
288
+ def minimal_match?(path, mini_path)
289
+ extname = File.extname(mini_path)
290
+ extname = '' if extname =~ /^\.\d+$/
291
+ version = mini_path =~ /(-\d+(\.\d+)*)#{extname}$/ ? $1 : ''
292
+
293
+ match_path = case
294
+ when !extname.empty?
295
+ # force full match
296
+ path
297
+ when !version.empty?
298
+ # match up to version
299
+ path.chomp(File.extname(path))
300
+ else
301
+ # match up base
302
+ path.chomp(File.extname(path)).sub(/(-\d+(\.\d+)*)$/, '')
303
+ end
304
+
305
+ # key ends with pattern AND basenames of each are equal...
306
+ # the last check ensures that a full path segment has
307
+ # been specified
308
+ match_path[-mini_path.length, mini_path.length] == mini_path && File.basename(match_path) == File.basename(mini_path)
309
+ end
310
+
311
+ # Returns the path segments for the given path, splitting along the path
312
+ # divider. Root paths are always represented by a string, if only an
313
+ # empty string.
314
+ #
315
+ # os divider example
316
+ # windows '\' Root.split('C:\path\to\file') # => ["C:", "path", "to", "file"]
317
+ # *nix '/' Root.split('/path/to/file') # => ["", "path", "to", "file"]
318
+ #
319
+ # The path is always expanded relative to the expand_dir; so '.' and '..' are
320
+ # resolved. However, unless expand_path == true, only the segments relative
321
+ # to the expand_dir are returned.
322
+ #
323
+ # On windows (note that expanding paths allows the use of slashes or backslashes):
324
+ #
325
+ # Dir.pwd # => 'C:/'
326
+ # Root.split('path\to\..\.\to\file') # => ["C:", "path", "to", "file"]
327
+ # Root.split('path/to/.././to/file', false) # => ["path", "to", "file"]
328
+ #
329
+ # On *nix (or more generally systems with '/' roots):
330
+ #
331
+ # Dir.pwd # => '/'
332
+ # Root.split('path/to/.././to/file') # => ["", "path", "to", "file"]
333
+ # Root.split('path/to/.././to/file', false) # => ["path", "to", "file"]
334
+ #
335
+ def split(path, expand_path=true, expand_dir=Dir.pwd)
336
+ path = if expand_path
337
+ File.expand_path(path, expand_dir)
338
+ else
339
+ # normalize the path by expanding it, then
340
+ # work back to the relative filepath as needed
341
+ expanded_dir = File.expand_path(expand_dir)
342
+ expanded_path = File.expand_path(path, expand_dir)
343
+ expanded_path.index(expanded_dir) != 0 ? expanded_path : Tap::Root.relative_filepath(expanded_dir, expanded_path)
344
+ end
345
+
346
+ segments = path.scan(/[^\/]+/)
347
+
348
+ # add back the root filepath as needed on *nix
349
+ segments.unshift "" if path[0] == ?/
350
+ segments
351
+ end
352
+
353
+ private
354
+
355
+ # utility method for minimize -- joins the
356
+ # dir and path, preventing results like:
357
+ #
358
+ # "./path"
359
+ # "//path"
360
+ def min_join(dir, path) # :nodoc:
361
+ case dir
362
+ when "." then path
363
+ when "/" then "/#{path}"
364
+ else "#{dir}/#{path}"
365
+ end
366
+ end
367
+
368
+ # utility method for minimize -- returns the
369
+ # dirname of path, or an array if the dirname
370
+ # is effectively empty.
371
+ def dirname_or_array(path) # :nodoc:
372
+ dir = File.dirname(path)
373
+ case dir
374
+ when path, '.' then []
375
+ else dir
376
+ end
377
+ end
378
+
379
+ # utility method for minimize -- determines if there
380
+ # is just one of the base in splits, while flagging
381
+ # all matching entries.
382
+ def just_one?(splits, index, base) # :nodoc:
383
+ just_one = true
384
+ index.upto(splits.length-1) do |i|
385
+ if splits[i][1] == base
386
+ splits[i][4] = true
387
+ just_one = false
388
+ end
389
+ end
390
+
391
+ just_one
392
+ end
393
+
163
394
  end
164
395
 
165
396
  include Support::Versions
397
+ include Support::Configurable
398
+
399
+ # The root directory.
400
+ config_attr(:root, '.', :writer => false)
166
401
 
167
- # The root directory
168
- attr_reader :root
402
+ # A hash of (alias, relative path) pairs for aliased subdirectories.
403
+ config_attr(:directories, {}, :writer => false)
169
404
 
170
- # A hash of (alias, relative path) pairs for aliased subdirectories
171
- attr_reader :directories
405
+ # A hash of (alias, relative path) pairs for aliased absolute paths.
406
+ config_attr(:absolute_paths, {}, :reader => false, :writer => false)
172
407
 
173
408
  # A hash of (alias, expanded path) pairs for aliased subdirectories and absolute paths.
174
409
  attr_reader :paths
@@ -182,29 +417,30 @@ module Tap
182
417
  # and no aliased directories or absolute paths are specified.
183
418
  def initialize(root=Dir.pwd, directories={}, absolute_paths={})
184
419
  assign_paths(root, directories, absolute_paths)
420
+ @config = self.class.configurations.instance_config(self)
185
421
  end
186
-
187
- # Sets the root directory. All paths are reassigned accordingly.
422
+
423
+ # Sets the root directory. All paths are reassigned accordingly.
188
424
  def root=(path)
189
425
  assign_paths(path, directories, absolute_paths)
190
426
  end
191
427
 
192
- # Sets the directories to those provided. 'root' and :root are reserved
428
+ # Sets the directories to those provided. 'root' and :root are reserved
193
429
  # and cannot be set using this method (use root= instead).
194
430
  #
195
- # r['alt'] # => File.join(r.root, 'alt')
196
- # r.directories = {'alt' => 'dir'}
197
- # r['alt'] # => File.join(r.root, 'dir')
431
+ # r['alt'] # => File.join(r.root, 'alt')
432
+ # r.directories = {'alt' => 'dir'}
433
+ # r['alt'] # => File.join(r.root, 'dir')
198
434
  def directories=(dirs)
199
435
  assign_paths(root, dirs, absolute_paths)
200
436
  end
201
437
 
202
- # Sets the absolute paths to those provided. 'root' and :root are reserved
438
+ # Sets the absolute paths to those provided. 'root' and :root are reserved
203
439
  # directory keys and cannot be set using this method (use root= instead).
204
440
  #
205
- # r['abs'] # => File.join(r.root, 'abs')
206
- # r.absolute_paths = {'abs' => '/path/to/dir'}
207
- # r['abs'] # => '/path/to/dir'
441
+ # r['abs'] # => File.join(r.root, 'abs')
442
+ # r.absolute_paths = {'abs' => '/path/to/dir'}
443
+ # r['abs'] # => '/path/to/dir'
208
444
  def absolute_paths=(paths)
209
445
  assign_paths(root, directories, paths)
210
446
  end
@@ -298,7 +534,10 @@ module Tap
298
534
  # fp = r.filepath(:in, 'path/to/file.txt') # => '/root_dir/in/path/to/file.txt'
299
535
  # r.translate(fp, :in, :out) # => '/root_dir/out/path/to/file.txt'
300
536
  def translate(filepath, input_dir, output_dir)
301
- filepath(output_dir, relative_filepath(input_dir, filepath))
537
+ unless relative_path = relative_filepath(input_dir, filepath)
538
+ raise "\n#{filepath}\nis not relative to:\n#{input_dir}"
539
+ end
540
+ filepath(output_dir, relative_path)
302
541
  end
303
542
 
304
543
  # Lists all files in the aliased dir matching the input patterns. Patterns