tap 0.7.9

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 (146) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/README +71 -0
  3. data/Rakefile +117 -0
  4. data/bin/tap +63 -0
  5. data/lib/tap.rb +15 -0
  6. data/lib/tap/app.rb +739 -0
  7. data/lib/tap/file_task.rb +354 -0
  8. data/lib/tap/generator.rb +29 -0
  9. data/lib/tap/generator/generators/config/USAGE +0 -0
  10. data/lib/tap/generator/generators/config/config_generator.rb +23 -0
  11. data/lib/tap/generator/generators/config/templates/config.erb +2 -0
  12. data/lib/tap/generator/generators/file_task/USAGE +0 -0
  13. data/lib/tap/generator/generators/file_task/file_task_generator.rb +21 -0
  14. data/lib/tap/generator/generators/file_task/templates/task.erb +27 -0
  15. data/lib/tap/generator/generators/file_task/templates/test.erb +12 -0
  16. data/lib/tap/generator/generators/root/USAGE +0 -0
  17. data/lib/tap/generator/generators/root/root_generator.rb +36 -0
  18. data/lib/tap/generator/generators/root/templates/Rakefile +48 -0
  19. data/lib/tap/generator/generators/root/templates/app.yml +19 -0
  20. data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +4 -0
  21. data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +26 -0
  22. data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
  23. data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +57 -0
  24. data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +108 -0
  25. data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +40 -0
  26. data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +21 -0
  27. data/lib/tap/generator/generators/root/templates/server/config/environment.rb +60 -0
  28. data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +5 -0
  29. data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +53 -0
  30. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +3 -0
  31. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +4 -0
  32. data/lib/tap/generator/generators/task/USAGE +0 -0
  33. data/lib/tap/generator/generators/task/task_generator.rb +21 -0
  34. data/lib/tap/generator/generators/task/templates/task.erb +21 -0
  35. data/lib/tap/generator/generators/task/templates/test.erb +29 -0
  36. data/lib/tap/generator/generators/workflow/USAGE +0 -0
  37. data/lib/tap/generator/generators/workflow/templates/task.erb +16 -0
  38. data/lib/tap/generator/generators/workflow/templates/test.erb +7 -0
  39. data/lib/tap/generator/generators/workflow/workflow_generator.rb +21 -0
  40. data/lib/tap/generator/options.rb +26 -0
  41. data/lib/tap/generator/usage.rb +26 -0
  42. data/lib/tap/root.rb +275 -0
  43. data/lib/tap/script/console.rb +7 -0
  44. data/lib/tap/script/destroy.rb +8 -0
  45. data/lib/tap/script/generate.rb +8 -0
  46. data/lib/tap/script/run.rb +111 -0
  47. data/lib/tap/script/server.rb +12 -0
  48. data/lib/tap/support/audit.rb +415 -0
  49. data/lib/tap/support/batch_queue.rb +165 -0
  50. data/lib/tap/support/combinator.rb +114 -0
  51. data/lib/tap/support/logger.rb +91 -0
  52. data/lib/tap/support/rap.rb +38 -0
  53. data/lib/tap/support/run_error.rb +20 -0
  54. data/lib/tap/support/template.rb +81 -0
  55. data/lib/tap/support/templater.rb +155 -0
  56. data/lib/tap/support/versions.rb +63 -0
  57. data/lib/tap/task.rb +448 -0
  58. data/lib/tap/test.rb +320 -0
  59. data/lib/tap/test/env_vars.rb +16 -0
  60. data/lib/tap/test/inference_methods.rb +298 -0
  61. data/lib/tap/test/subset_methods.rb +260 -0
  62. data/lib/tap/version.rb +3 -0
  63. data/lib/tap/workflow.rb +73 -0
  64. data/test/app/config/addition_template.yml +6 -0
  65. data/test/app/config/batch.yml +2 -0
  66. data/test/app/config/empty.yml +0 -0
  67. data/test/app/config/erb.yml +1 -0
  68. data/test/app/config/template.yml +6 -0
  69. data/test/app/config/version-0.1.yml +1 -0
  70. data/test/app/config/version.yml +1 -0
  71. data/test/app/lib/app_test_task.rb +2 -0
  72. data/test/app_class_test.rb +33 -0
  73. data/test/app_test.rb +1372 -0
  74. data/test/file_task/config/batch.yml +2 -0
  75. data/test/file_task/config/configured.yml +1 -0
  76. data/test/file_task/old_file_one.txt +0 -0
  77. data/test/file_task/old_file_two.txt +0 -0
  78. data/test/file_task_test.rb +1041 -0
  79. data/test/root/alt_lib/alt_module.rb +4 -0
  80. data/test/root/lib/absolute_alt_filepath.rb +2 -0
  81. data/test/root/lib/alternative_filepath.rb +2 -0
  82. data/test/root/lib/another_module.rb +2 -0
  83. data/test/root/lib/nested/some_module.rb +4 -0
  84. data/test/root/lib/no_module_included.rb +0 -0
  85. data/test/root/lib/some/module.rb +4 -0
  86. data/test/root/lib/some_class.rb +2 -0
  87. data/test/root/lib/some_module.rb +3 -0
  88. data/test/root/load_path/load_path_module.rb +2 -0
  89. data/test/root/load_path/skip_module.rb +2 -0
  90. data/test/root/mtime/older.txt +0 -0
  91. data/test/root/unload/full_path.rb +2 -0
  92. data/test/root/unload/loaded_by_nested.rb +2 -0
  93. data/test/root/unload/nested/nested_load.rb +6 -0
  94. data/test/root/unload/nested/nested_with_ext.rb +4 -0
  95. data/test/root/unload/nested/relative_path.rb +4 -0
  96. data/test/root/unload/older.rb +2 -0
  97. data/test/root/unload/unload_base.rb +9 -0
  98. data/test/root/versions/another.yml +0 -0
  99. data/test/root/versions/file-0.1.2.yml +0 -0
  100. data/test/root/versions/file-0.1.yml +0 -0
  101. data/test/root/versions/file.yml +0 -0
  102. data/test/root_test.rb +483 -0
  103. data/test/support/audit_test.rb +449 -0
  104. data/test/support/batch_queue_test.rb +320 -0
  105. data/test/support/combinator_test.rb +249 -0
  106. data/test/support/logger_test.rb +31 -0
  107. data/test/support/template_test.rb +122 -0
  108. data/test/support/templater/erb.txt +2 -0
  109. data/test/support/templater/erb.yml +2 -0
  110. data/test/support/templater/somefile.txt +2 -0
  111. data/test/support/templater_test.rb +192 -0
  112. data/test/support/versions_test.rb +71 -0
  113. data/test/tap_test_helper.rb +4 -0
  114. data/test/tap_test_suite.rb +4 -0
  115. data/test/task/config/batch.yml +2 -0
  116. data/test/task/config/batched.yml +2 -0
  117. data/test/task/config/configured.yml +1 -0
  118. data/test/task/config/example.yml +1 -0
  119. data/test/task/config/overriding.yml +2 -0
  120. data/test/task/config/task_with_config.yml +1 -0
  121. data/test/task/config/template.yml +4 -0
  122. data/test/task_class_test.rb +118 -0
  123. data/test/task_execute_test.rb +233 -0
  124. data/test/task_test.rb +424 -0
  125. data/test/test/inference_methods/test_assert_expected/expected/file.txt +1 -0
  126. data/test/test/inference_methods/test_assert_expected/expected/folder/file.txt +1 -0
  127. data/test/test/inference_methods/test_assert_expected/input/file.txt +1 -0
  128. data/test/test/inference_methods/test_assert_expected/input/folder/file.txt +1 -0
  129. data/test/test/inference_methods/test_assert_files_exist/input/input_1.txt +0 -0
  130. data/test/test/inference_methods/test_assert_files_exist/input/input_2.txt +0 -0
  131. data/test/test/inference_methods/test_file_compare/expected/output_1.txt +3 -0
  132. data/test/test/inference_methods/test_file_compare/expected/output_2.txt +1 -0
  133. data/test/test/inference_methods/test_file_compare/input/input_1.txt +3 -0
  134. data/test/test/inference_methods/test_file_compare/input/input_2.txt +3 -0
  135. data/test/test/inference_methods/test_infer_glob/expected/file.yml +0 -0
  136. data/test/test/inference_methods/test_infer_glob/expected/file_1.txt +0 -0
  137. data/test/test/inference_methods/test_infer_glob/expected/file_2.txt +0 -0
  138. data/test/test/inference_methods/test_yml_compare/expected/output_1.yml +6 -0
  139. data/test/test/inference_methods/test_yml_compare/expected/output_2.yml +6 -0
  140. data/test/test/inference_methods/test_yml_compare/input/input_1.yml +4 -0
  141. data/test/test/inference_methods/test_yml_compare/input/input_2.yml +4 -0
  142. data/test/test/inference_methods_test.rb +311 -0
  143. data/test/test/subset_methods_test.rb +115 -0
  144. data/test/test_test.rb +233 -0
  145. data/test/workflow_test.rb +108 -0
  146. metadata +274 -0
@@ -0,0 +1,5 @@
1
+ # This clears the built-in prerequisite 'db:test:prepare' from all the test tasks, allowing
2
+ # rails to run without a database (updated from "Rails Recipies":http://media.pragprog.com/titles/fr_rr/NoDatabase.pdf)
3
+ ['test:units', 'test:functionals', 'test:recent', 'test:integration', 'test:uncommitted'].each do |name|
4
+ Rake::Task[name].prerequisites.clear
5
+ end
@@ -0,0 +1,53 @@
1
+ ENV["RAILS_ENV" ] = "test"
2
+ require File.expand_path(File.dirname(__FILE__) + "/../config/environment" )
3
+
4
+ #################
5
+ # Here I've copied the contents of 'test_help' so I could remove the dependencies
6
+ # causing rails to raise errors if running without a database.
7
+ #
8
+ # require 'test_help'
9
+ require_dependency 'application'
10
+
11
+ # Make double-sure the RAILS_ENV is set to test,
12
+ # so fixtures are loaded to the right database
13
+ silence_warnings { RAILS_ENV = "test" }
14
+
15
+ require 'test/unit'
16
+ #require 'active_record/fixtures'
17
+ require 'action_controller/test_process'
18
+ require 'action_controller/integration'
19
+ #require 'action_web_service/test_invoke'
20
+ require 'breakpoint'
21
+
22
+ #Test::Unit::TestCase.fixture_path = RAILS_ROOT + "/test/fixtures/"
23
+ #ActionController::IntegrationTest.fixture_path = Test::Unit::TestCase.fixture_path
24
+
25
+ #def create_fixtures(*table_names)
26
+ # Fixtures.create_fixtures(RAILS_ROOT + "/test/fixtures", table_names)
27
+ #end
28
+ #################
29
+
30
+ class Test::Unit::TestCase
31
+ # Transactional fixtures accelerate your tests by wrapping each test method
32
+ # in a transaction that's rolled back on completion. This ensures that the
33
+ # test database remains unchanged so your fixtures don't have to be reloaded
34
+ # between every test method. Fewer database queries means faster tests.
35
+ #
36
+ # Read Mike Clark's excellent walkthrough at
37
+ # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
38
+ #
39
+ # Every Active Record database supports transactions except MyISAM tables
40
+ # in MySQL. Turn off transactional fixtures in this case; however, if you
41
+ # don't care one way or the other, switching from MyISAM to InnoDB tables
42
+ # is recommended.
43
+ #self.use_transactional_fixtures = true
44
+
45
+ # Instantiated fixtures are slow, but give you @david where otherwise you
46
+ # would need people(:david). If you don't want to migrate your existing
47
+ # test cases which use the @david style and don't mind the speed hit (each
48
+ # instantiated fixtures translates to a database query per test method),
49
+ # then set this back to true.
50
+ #self.use_instantiated_fixtures = false
51
+
52
+ # Add more helper methods to be used by all tests here...
53
+ end
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'tap'
3
+ require 'tap/test'
@@ -0,0 +1,4 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '../lib')
2
+
3
+ ENV["ALL"] = true # runs all subsets (see gemdev)
4
+ Dir.glob("./**/*_test.rb").each {|test| require test}
File without changes
@@ -0,0 +1,21 @@
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")
14
+
15
+ test_path = @app.relative_filepath(:root, @app[:test])
16
+ m.directory File.join(test_path, class_path)
17
+ m.template "test.erb", File.join(test_path, class_name.underscore + "_test.rb")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # == Documentation
2
+ #
3
+ # === Command Line Usage
4
+ # Replace with your command line usage instructions
5
+ #
6
+ class <%= class_name %> < Tap::Task
7
+
8
+ def process(input)
9
+ # The process logic goes here.
10
+
11
+ # You can access configurations as symbols within the config hash
12
+ message = "#{config[:greeting]} #{input}!"
13
+
14
+ # Use log to record information
15
+ log :message, message
16
+
17
+ # The return of process is the task output
18
+ message
19
+ end
20
+
21
+ end
@@ -0,0 +1,29 @@
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) %>'
3
+
4
+ class <%= class_name %>Test < Test::Unit::TestCase
5
+ acts_as_tap_test
6
+
7
+ def test_greeting
8
+ t = <%= class_name %>.new nil, :greeting => 'hello'
9
+
10
+ with_options(:quiet => true) do
11
+ t.execute("from me", "to you")
12
+
13
+ assert_equal({:greeting => 'hello'}, t.config)
14
+ assert_outputs(t => ["hello from me!", "hello to you!"])
15
+
16
+ # check the audit trail for each input
17
+ assert_audits(t.results,
18
+ 0 => [[nil, 'from me'], [t, 'hello from me!']],
19
+ 1 => [[nil, 'to you'], [t, 'hello to you!']])
20
+
21
+ # an alternate way of checking audits
22
+ check_audit(t.results[1]) do
23
+ 'to you'._from nil
24
+ 'hello to you!'._from t
25
+ end
26
+ end
27
+ end
28
+
29
+ end
File without changes
@@ -0,0 +1,16 @@
1
+ # == Documentation
2
+ #
3
+ # === Command Line Usage
4
+ # Replace with your command line usage instructions
5
+ #
6
+ class <%= class_name %> < Tap::Workflow
7
+ protected
8
+
9
+ def workflow
10
+ # Define the workflow entry and exit points,
11
+ # as well as the workflow logic.
12
+ self.entry_point = Task.new
13
+
14
+ # app.sequence(entry_point, 'another/task')
15
+ end
16
+ end
@@ -0,0 +1,7 @@
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) %>'
3
+
4
+ class <%= class_name %>Test < Test::Unit::TestCase
5
+ acts_as_tap_test
6
+
7
+ end
@@ -0,0 +1,21 @@
1
+ module Tap::Generator::Generators
2
+ class WorkflowGenerator < 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")
14
+
15
+ test_path = @app.relative_filepath(:root, @app[:test])
16
+ m.directory File.join(test_path, class_path)
17
+ m.template "test.erb", File.join(test_path, class_name.underscore + "_test.rb")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module Tap
2
+ module Generator
3
+ module Options # :nodoc:
4
+ protected
5
+
6
+ # Adapted from code in 'rails/rails_generator/options.rb'
7
+ def add_general_options!(opt)
8
+ opt.separator ''
9
+ opt.separator 'General Options:'
10
+
11
+ opt.on('-h', '--help', 'Show this help message and quit.') { |v| options[:help] = v }
12
+ opt.on('-p', '--pretend', 'Run but do not make any changes.') { |v| options[:pretend] = v }
13
+ opt.on('-f', '--force', 'Overwrite files that already exist.') { options[:collision] = :force }
14
+ opt.on('-s', '--skip', 'Skip files that already exist.') { options[:collision] = :skip }
15
+ opt.on('-q', '--quiet', 'Suppress normal output.') { |v| options[:quiet] = v }
16
+ opt.on('-t', '--backtrace', 'Debugging: show backtrace on errors.') { |v| options[:backtrace] = v }
17
+ opt.on('-c', '--svn', 'Modify files with subversion. (Note: svn must be in path)') do
18
+ options[:svn] = `svn status`.inject({}) do |opt, e|
19
+ opt[e.chomp[7..-1]] = true
20
+ opt
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ require 'tap/generator/options'
2
+
3
+ module Tap
4
+ module Generator
5
+ module Usage # :nodoc:
6
+ include Options
7
+
8
+ protected
9
+
10
+ # Adapted from code in 'rails/rails_generator/scripts.rb'
11
+ def usage_message
12
+ usage = "\nInstalled Generators\n"
13
+ Rails::Generator::Base.sources.each do |source|
14
+ label = source.label.to_s.capitalize
15
+ names = source.names
16
+ usage << " #{label}: #{names.join(', ')}\n" unless names.empty?
17
+ end
18
+
19
+ usage << <<end_blurb
20
+
21
+ end_blurb
22
+ return usage
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/tap/root.rb ADDED
@@ -0,0 +1,275 @@
1
+ require 'fileutils'
2
+ require 'tap/support/versions'
3
+
4
+ # string.rb is required for camelizing and underscoring strings
5
+ # requiring just this file rather than the full gem results in
6
+ # noticably faster startup
7
+ require 'active_support/core_ext/string'
8
+
9
+ module Tap
10
+
11
+ # Most applications that utilize files allow the definition of a root directory,
12
+ # allowing you to place the application directory wherever you like. Root takes
13
+ # this concept further, allowing you to define subdirectories, as well as manipulate
14
+ # files and filepaths within these directories. For example:
15
+ #
16
+ # root = Root.new 'root_dir', :input => 'in', :output => 'out'
17
+ #
18
+ # # work with directories
19
+ # root[:in] # => 'root_dir/in'
20
+ # root[:out] # => 'root_dir/out'
21
+ # root[:implicit] # => 'root_dir/implicit'
22
+ #
23
+ # # work with filepaths
24
+ # fp = root.filepath(:in, 'path/to/file.txt') # => 'root_dir/in/path/to/file.txt'
25
+ # root.relative_filepath(:in, fp) # => 'path/to/file.txt'
26
+ # root.translate(fp, :in, :out) # => 'root_dir/out/path/to/file.txt'
27
+ #
28
+ # # version filepaths
29
+ # root.version('path/to/config.yml', 1.0) # => 'path/to/config-1.0.yml'
30
+ # root.increment('path/to/config-1.0.yml', 0.1) # => 'path/to/config-1.1.yml'
31
+ # root.deversion('path/to/config-1.1.yml') # => ['path/to/config.yml', "1.1"]
32
+ #
33
+ # # work with modules...
34
+ # mp = root.module_filepath(:lib, 'Some::Module') # => 'root_dir/lib/some/module.rb'
35
+ #
36
+ # # loading them...
37
+ # Object.const_defined?(:Some) # => false
38
+ # root.lookup_module('Some::Module') # => Some::Module
39
+ # Some.const_defined?(:Module) # => true
40
+ #
41
+ # # and unloading them!
42
+ # root.unload_modules # => ["Some::Module"]
43
+ # Object.const_defined?(:Some) # => false
44
+ #
45
+ # === Notes
46
+ #
47
+ # Root internally stores paths for both relative directories and absolute paths in a
48
+ # hash for quick lookup using the dir keys. Root keeps a separate hash for the
49
+ # original directories used to create the paths, but this is not necessary for
50
+ # absolute paths. An absolute path can be distinguished from a directory by having
51
+ # an entry in paths but not directories.
52
+ class Root
53
+ class << self
54
+ include Support::Versions
55
+
56
+ # Returns the filepath of path relative to dir. If strict=true (the default) then
57
+ # relative_filepath raises an error if the path is not relative to dir.
58
+ #
59
+ # Root.relative_filepath('dir', "dir/path/to/file.txt") # => "path/to/file.txt"
60
+ #
61
+ def relative_filepath(dir, path, strict=true)
62
+ dir = File.expand_path(dir)
63
+ expanded = File.expand_path(path)
64
+
65
+ unless expanded[0...dir.length] == dir
66
+ raise "\n#{expanded}\nis not relative to:\n#{dir}" if strict
67
+ return path
68
+ end
69
+
70
+ # use dir.length + 1 to remove a leading '/'. If dir.length + 1 >= expanded.length
71
+ # as in: relative_filepath('/path', '/path') then the first arg returns nil, and an
72
+ # empty string is returned
73
+ expanded[(dir.length+1)..-1] || ""
74
+ end
75
+
76
+ # Lists all unique paths matching the input glob patterns.
77
+ def glob(*patterns)
78
+ patterns.collect do |pattern|
79
+ Dir.glob(pattern)
80
+ end.flatten.uniq
81
+ end
82
+
83
+ # Lists all unique versions of path matching the glob version patterns. If no patterns
84
+ # are specified, then all versions of path will be returned.
85
+ def vglob(path, *vpatterns)
86
+ vpatterns << "*" if vpatterns.empty?
87
+ vpatterns.collect do |vpattern|
88
+ results = Dir.glob(version(path, vpattern))
89
+
90
+ # extra work to include the default version path for any version
91
+ results << path if vpattern == "*" && File.exists?(path)
92
+ results
93
+ end.flatten.uniq
94
+ end
95
+
96
+
97
+
98
+ #def package(target, *filepaths)
99
+ # options = filepaths.pop if filepaths.last.kind_of?(Hash)
100
+ #end
101
+
102
+ # glob for dirs, remove if empty. removes dir if empty on complete
103
+ # OR
104
+ # remove files older than a certain date
105
+ # remove all files
106
+ #
107
+ #def cleanup(dir)
108
+ #end
109
+
110
+ # iotask?
111
+ def parse_by_line(path, options={}, &block)
112
+ str = read(path, options.merge(:as => :txt))
113
+ lines = str.split(/\r?\n/)
114
+ lines.each do |line|
115
+ # don't yield empty lines because they split to [] (ie [nil, nil] for the block inputs)
116
+ yield(line.split(/#/, 2)) unless line.empty?
117
+ end
118
+ end
119
+ end
120
+
121
+ include Support::Versions
122
+ attr_reader :root, :directories, :paths
123
+
124
+ # Creates a new Root with the given root, directories, and absolute
125
+ # paths. By default root is the current working directory and no
126
+ # directories or absolute paths are specified.
127
+ def initialize(root=Dir.pwd, directories={}, absolute_paths={})
128
+ assign_paths(root, directories, absolute_paths)
129
+ end
130
+
131
+ # Sets the root directory. All paths are reassigned accordingly.
132
+ def root=(path)
133
+ assign_paths(path, directories, absolute_paths)
134
+ end
135
+
136
+ # Sets the directories to those provided. 'root' and :root are reserved directory
137
+ # keys and cannot be set using this method (use root= instead).
138
+ def directories=(dirs)
139
+ assign_paths(root, dirs, absolute_paths)
140
+ end
141
+
142
+ # Sets the absolute paths to those provided. 'root' and :root are reserved directory
143
+ # keys and cannot be set using this method (use root= instead).
144
+ def absolute_paths=(paths)
145
+ assign_paths(root, directories, paths)
146
+ end
147
+
148
+ # Returns the absolute paths registered with the Root.
149
+ def absolute_paths
150
+ abs_paths = {}
151
+ paths.each do |dir, path|
152
+ abs_paths[dir] = path unless directories.include?(dir) || dir.to_s == 'root'
153
+ end
154
+ abs_paths
155
+ end
156
+
157
+ # Sets the path to the specified directory, relative to the root directory.
158
+ # The root directory cannot be set with this method (use root= instead).
159
+ # Absolute filepaths can be set by specifying the absolute parameter.
160
+ #
161
+ # root[:dir] = 'path/to/dir'
162
+ # root[:root] # => 'root_dir'
163
+ # root[:dir] # => 'root_dir/path/to/dir'
164
+ #
165
+ # root[:abs, true] = '/abs/path/to/dir'
166
+ # root[:abs] # => '/abs/path/to/dir'
167
+ #
168
+ # Note - the syntax for setting an absolute filepath requires an odd use []=.
169
+ # In fact the method recieves the arguments (:dir, true, '/abs/path/to/dir')
170
+ # rather than (:dir, '/abs/path/to/dir', true), meaning that internally path
171
+ # and absolute are switched when setting an absolute filepath.
172
+ def []=(dir, path, absolute=false)
173
+ raise ArgumentError, "The directory key '#{dir}' is reserved." if dir.to_s == 'root'
174
+
175
+ # switch the paths if absolute was provided
176
+ unless absolute == false
177
+ switch = path
178
+ path = absolute
179
+ absolute = switch
180
+ end
181
+
182
+ case
183
+ when path.nil?
184
+ @directories.delete(dir)
185
+ @paths.delete(dir)
186
+ when absolute
187
+ @directories.delete(dir)
188
+ @paths[dir] = path
189
+ else
190
+ @directories[dir] = path
191
+ @paths[dir] = File.join(root, path)
192
+ end
193
+ end
194
+
195
+ # Returns the path to the specified directory. If the path has not been set,
196
+ # then the path is inferred to be 'root/dir'.
197
+ def [](dir)
198
+ self.paths[dir] || File.join(root, dir.to_s)
199
+ end
200
+
201
+ # Constructs filepaths relative to the path of the specfied directory.
202
+ def filepath(dir, *filename)
203
+ File.join(self[dir], *filename)
204
+ end
205
+
206
+ # Retrieves filepath relative to the path of the specified directory.
207
+ def relative_filepath(dir, filepath, strict=true)
208
+ Root.relative_filepath(self[dir], filepath, strict)
209
+ end
210
+
211
+ # Generates a target filepath translated from the input directory to the output directory.
212
+ #
213
+ # translate("/input/dir/path/to/input.txt") # => "/output/dir/path/to/input.txt"
214
+ def translate(filepath, input_dir, output_dir)
215
+ filepath(output_dir, relative_filepath(input_dir, filepath))
216
+ end
217
+
218
+ # Lists all files in the specified dir matching the input patterns. Patterns should be valid inputs
219
+ # for +Dir.glob+. If no patterns are specified, lists all files/folders matching '**/*'.
220
+ def glob(dir, *patterns)
221
+ patterns << "**/*" if patterns.empty?
222
+ patterns.collect! {|pattern| filepath(dir, pattern)}
223
+ Root.glob(*patterns)
224
+ end
225
+
226
+ # Lists all versions of filename in the specified dir matching the version patterns.
227
+ # If no patterns are specified, then all versions of filename will be returned.
228
+ def vglob(dir, filename, *vpatterns)
229
+ Root.vglob(filepath(dir, filename), *vpatterns)
230
+ end
231
+
232
+ # Opens the file specfied by dir and filename, then passes the open file to the input block
233
+ def open(dir, filename, mode_string="r", &block)
234
+ File.open(filepath(dir, filename), mode_string, &block)
235
+ end
236
+
237
+ # Reads the specififed file
238
+ def read(dir, filename)
239
+ File.read( filepath(dir, filename) )
240
+ end
241
+
242
+ #
243
+ def parse_by_line(dir, filename, options={}, &block)
244
+ Root.parse_by_line( filepath(dir, filename), options, &block)
245
+ end
246
+
247
+ # The timestamp of the most recently modified file under dir. Returns nil
248
+ # if none of the listed files are relative to dir.
249
+ def mtime(dir, *filenames)
250
+ filenames.collect do |fname|
251
+ fname = relative_filepath(dir, fname, false)
252
+ path = filepath(dir, fname)
253
+
254
+ File.exists?(path) ? File.mtime(path) : nil
255
+ end.compact.max
256
+ end
257
+
258
+ # Makes all the directories specified in paths. Parent directories are created as needed.
259
+ def mkpaths
260
+ paths.values.each { |dir| FileUtils.mkdir_p(dir) }
261
+ end
262
+
263
+ private
264
+
265
+ def assign_paths(root, directories, absolute_paths)
266
+ @root = root
267
+ @directories = {}
268
+ @paths = {'root' => root, :root => root}
269
+
270
+ directories.each_pair {|dir, path| self[dir] = path }
271
+ absolute_paths.each_pair {|dir, path| self[dir, true] = path }
272
+ end
273
+
274
+ end
275
+ end