tap 0.7.9 → 0.8.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 (149) hide show
  1. data/History +28 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README +71 -43
  4. data/Rakefile +81 -64
  5. data/Tutorial +235 -0
  6. data/bin/tap +80 -44
  7. data/lib/tap.rb +41 -12
  8. data/lib/tap/app.rb +243 -246
  9. data/lib/tap/file_task.rb +357 -118
  10. data/lib/tap/generator.rb +88 -29
  11. data/lib/tap/generator/generators/config/config_generator.rb +4 -2
  12. data/lib/tap/generator/generators/config/templates/config.erb +1 -2
  13. data/lib/tap/generator/generators/file_task/file_task_generator.rb +3 -18
  14. data/lib/tap/generator/generators/file_task/templates/task.erb +22 -15
  15. data/lib/tap/generator/generators/file_task/templates/test.erb +13 -2
  16. data/{test/test/inference_methods/test_assert_files_exist/input/input_1.txt → lib/tap/generator/generators/generator/USAGE} +0 -0
  17. data/lib/tap/generator/generators/generator/generator_generator.rb +21 -0
  18. data/lib/tap/generator/generators/generator/templates/generator.erb +23 -0
  19. data/lib/tap/generator/generators/generator/templates/usage.erb +1 -0
  20. data/{test/test/inference_methods/test_assert_files_exist/input/input_2.txt → lib/tap/generator/generators/package/USAGE} +0 -0
  21. data/lib/tap/generator/generators/package/package_generator.rb +38 -0
  22. data/lib/tap/generator/generators/package/templates/package.erb +186 -0
  23. data/lib/tap/generator/generators/root/root_generator.rb +14 -9
  24. data/lib/tap/generator/generators/root/templates/Rakefile +20 -14
  25. data/{test/test/inference_methods/test_infer_glob/expected/file.yml → lib/tap/generator/generators/root/templates/ReadMe.txt} +0 -0
  26. data/lib/tap/generator/generators/root/templates/tap.yml +82 -0
  27. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -1
  28. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +2 -1
  29. data/{test/test/inference_methods/test_infer_glob/expected/file_1.txt → lib/tap/generator/generators/script/USAGE} +0 -0
  30. data/lib/tap/generator/generators/script/script_generator.rb +17 -0
  31. data/lib/tap/generator/generators/script/templates/script.erb +42 -0
  32. data/lib/tap/generator/generators/task/task_generator.rb +1 -1
  33. data/lib/tap/generator/generators/task/templates/task.erb +24 -16
  34. data/lib/tap/generator/generators/task/templates/test.erb +13 -17
  35. data/lib/tap/generator/generators/workflow/templates/task.erb +10 -10
  36. data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
  37. data/lib/tap/generator/generators/workflow/workflow_generator.rb +3 -18
  38. data/lib/tap/root.rb +108 -146
  39. data/lib/tap/script.rb +362 -0
  40. data/lib/tap/script/console.rb +28 -0
  41. data/lib/tap/script/destroy.rb +13 -1
  42. data/lib/tap/script/generate.rb +13 -1
  43. data/lib/tap/script/run.rb +100 -57
  44. data/lib/tap/support/batch_queue.rb +0 -3
  45. data/lib/tap/support/logger.rb +6 -3
  46. data/lib/tap/support/rake.rb +54 -0
  47. data/lib/tap/support/task_configuration.rb +169 -0
  48. data/lib/tap/support/tdoc.rb +198 -0
  49. data/lib/tap/support/tdoc/config_attr.rb +338 -0
  50. data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
  51. data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
  52. data/lib/tap/support/versions.rb +33 -1
  53. data/lib/tap/task.rb +339 -227
  54. data/lib/tap/test.rb +86 -128
  55. data/lib/tap/test/env_vars.rb +16 -5
  56. data/lib/tap/test/file_methods.rb +373 -0
  57. data/lib/tap/test/subset_methods.rb +299 -180
  58. data/lib/tap/version.rb +2 -1
  59. data/lib/tap/workflow.rb +2 -0
  60. data/test/app/lib/app_test_task.rb +1 -0
  61. data/test/app_test.rb +327 -83
  62. data/test/check/binding_eval.rb +23 -0
  63. data/test/check/define_method_check.rb +22 -0
  64. data/test/check/dependencies_check.rb +175 -0
  65. data/test/check/inheritance_check.rb +22 -0
  66. data/test/file_task_test.rb +524 -291
  67. data/test/{test/inference_methods/test_infer_glob/expected/file_2.txt → root/glob/one.txt} +0 -0
  68. data/test/root/glob/two.txt +0 -0
  69. data/test/root_test.rb +330 -262
  70. data/test/script_test.rb +194 -0
  71. data/test/support/audit_test.rb +5 -2
  72. data/test/support/combinator_test.rb +10 -10
  73. data/test/support/rake_test.rb +35 -0
  74. data/test/support/task_configuration_test.rb +272 -0
  75. data/test/support/tdoc_test.rb +363 -0
  76. data/test/support/templater_test.rb +2 -2
  77. data/test/support/versions_test.rb +32 -0
  78. data/test/tap_test_helper.rb +39 -0
  79. data/test/task_base_test.rb +115 -0
  80. data/test/task_class_test.rb +56 -4
  81. data/test/task_execute_test.rb +29 -0
  82. data/test/task_test.rb +89 -70
  83. data/test/test/env_vars_test.rb +48 -0
  84. data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/file.txt +0 -0
  85. data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/folder/file.txt +0 -0
  86. data/test/test/{inference_methods → file_methods}/test_assert_expected/input/file.txt +0 -0
  87. data/test/test/{inference_methods → file_methods}/test_assert_expected/input/folder/file.txt +0 -0
  88. data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
  89. data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
  90. data/test/test/file_methods/test_assert_output_files_equal/expected/one.txt +1 -0
  91. data/test/test/file_methods/test_assert_output_files_equal/expected/two.txt +1 -0
  92. data/test/test/file_methods/test_assert_output_files_equal/input/one.txt +1 -0
  93. data/test/test/file_methods/test_assert_output_files_equal/input/two.txt +1 -0
  94. data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_1.txt +0 -0
  95. data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_2.txt +0 -0
  96. data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_1.txt +0 -0
  97. data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_2.txt +0 -0
  98. data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
  99. data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
  100. data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
  101. data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
  102. data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
  103. data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
  104. data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_1.yml +0 -0
  105. data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_2.yml +0 -0
  106. data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_1.yml +0 -0
  107. data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_2.yml +0 -0
  108. data/test/test/file_methods_test.rb +204 -0
  109. data/test/test/subset_methods_test.rb +93 -33
  110. data/test/test/test_assert_expected_result_files/expected/task/name/a.txt +1 -0
  111. data/test/test/test_assert_expected_result_files/expected/task/name/b.txt +1 -0
  112. data/test/test/test_assert_expected_result_files/input/a.txt +1 -0
  113. data/test/test/test_assert_expected_result_files/input/b.txt +1 -0
  114. data/test/test/test_file_task_test/expected/one.txt +1 -0
  115. data/test/test/test_file_task_test/expected/two.txt +1 -0
  116. data/test/test/test_file_task_test/input/one.txt +1 -0
  117. data/test/test/test_file_task_test/input/two.txt +1 -0
  118. data/test/test_test.rb +143 -3
  119. data/test/workflow_test.rb +2 -0
  120. data/vendor/rails_generator.rb +56 -0
  121. data/vendor/rails_generator/base.rb +263 -0
  122. data/vendor/rails_generator/commands.rb +581 -0
  123. data/vendor/rails_generator/generated_attribute.rb +42 -0
  124. data/vendor/rails_generator/lookup.rb +209 -0
  125. data/vendor/rails_generator/manifest.rb +53 -0
  126. data/vendor/rails_generator/options.rb +143 -0
  127. data/vendor/rails_generator/scripts.rb +83 -0
  128. data/vendor/rails_generator/scripts/destroy.rb +7 -0
  129. data/vendor/rails_generator/scripts/generate.rb +7 -0
  130. data/vendor/rails_generator/scripts/update.rb +12 -0
  131. data/vendor/rails_generator/simple_logger.rb +46 -0
  132. data/vendor/rails_generator/spec.rb +44 -0
  133. metadata +180 -196
  134. data/lib/tap/generator/generators/root/templates/app.yml +0 -19
  135. data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +0 -4
  136. data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +0 -26
  137. data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
  138. data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +0 -57
  139. data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +0 -108
  140. data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +0 -40
  141. data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +0 -21
  142. data/lib/tap/generator/generators/root/templates/server/config/environment.rb +0 -60
  143. data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +0 -5
  144. data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +0 -53
  145. data/lib/tap/script/server.rb +0 -12
  146. data/lib/tap/support/rap.rb +0 -38
  147. data/lib/tap/test/inference_methods.rb +0 -298
  148. data/test/task/config/task_with_config.yml +0 -1
  149. data/test/test/inference_methods_test.rb +0 -311
data/lib/tap/file_task.rb CHANGED
@@ -1,22 +1,111 @@
1
1
  module Tap
2
2
 
3
- # FileTask provides methods for making files
4
-
5
- # FileTask process adds error handling code to restore any files marked
6
- # for backup or files that were made (using make or mkdir) if an unhandled
7
- # error occurs during processing. Set +restore_on_error+ to false to
8
- # turn off automatic restore. In addtion, backed-up files can be removed
9
- # when process completes by setting +remove_backed_up_files+ to true.
3
+ # FileTask provides methods for creating/modifying files such that you can
4
+ # rollback changes if an error occurs. In addition, FileTask provides a
5
+ # method to infer filepaths within the standard Tap directory structure.
6
+ #
7
+ # == Creating Files/Rolling Back Changes
8
+ #
9
+ # FileTask tracks which files to roll back using the added_files array
10
+ # and the backed_up_files hash. On an execute error, all added files are
11
+ # removed and then all backed up files (backed_up_files keys) are restored
12
+ # using the corrsponding backup files (backed_up_files values).
13
+ #
14
+ # For consistency, all filepaths in added_files and backed_up_files should
15
+ # be expanded using File.expand_path. The easiest way to ensure files are
16
+ # properly set up for rollback is to use prepare before working with files
17
+ # and to create directories with mkdir.
18
+ #
19
+ # # this file will be backed up and restored
20
+ # File.open("file.txt", "w") {|f| f << "original content"}
21
+ #
22
+ # t = FileTask.new do |task, inputs|
23
+ # task.mkdir("some/dir") # marked for rollback
24
+ # task.prepare("file.txt", "path/to/file.txt") # marked for rollback
25
+ #
26
+ # File.open("file.txt", "w") {|f| f << "new content"}
27
+ # File.touch("path/to/file.txt")
28
+ #
29
+ # # raise an error to start rollback
30
+ # raise "error!"
31
+ # end
32
+ #
33
+ # begin
34
+ # File.exists?("some/dir") # => false
35
+ # File.exists?("path/to/file.txt") # => false
36
+ # t.execute(nil)
37
+ # rescue
38
+ # $!.message # => "error!"
39
+ # File.exists?("some/dir") # => false
40
+ # File.exists?("path/to/file.txt") # => false
41
+ # File.read("file.txt") # => "original content"
42
+ # end
43
+ #
44
+ #--
45
+ # TODO - make cleanup into remove_backup_files and simply remove this
46
+ # configuration. People will figure it out themselves if they want that to happen.
47
+ #
48
+ # The FileTask configurations modify the default backup and restore behavior.
49
+ #
50
+ # backup_dir:: The app directory alias for backups (default :backup)
51
+ # backup_timestamp:: A strftime format string used as a timestamp for backup
52
+ # files (default "%Y%m%d_%H%M%S")
53
+ # rollback_on_error:: Controls whether or not added and backed up files are
54
+ # rolled back on error (default true)
55
+ # cleanup_after_execute:: If true, backed up files will be removed after a
56
+ # successful execution (default false)
57
+ #++
10
58
  class FileTask < Task
11
- set_default_config({
12
- :backup_dir => :backup,
13
- :backup_timestamp => "%Y%m%d_%H%M%S",
14
- :restore_on_error => true,
15
- :remove_backed_up_files => false},
16
- :make_accessors => true)
59
+ autoload(:FileUtils, "fileutils")
60
+
61
+ class << self
17
62
 
18
- attr_reader :inference_block, :backed_up_files, :made_files
19
- attr_accessor :dirname
63
+ # A batch File.open method. If a block is given, each file in the list will be
64
+ # opened the open files passed to the block. Files are automatically closed when
65
+ # the block returns. If no block is given, the open files are returned.
66
+ #
67
+ # FileTask.open(["one.txt", "two.txt"], "w") do |one, two|
68
+ # one << "one"
69
+ # two << "two"
70
+ # end
71
+ #
72
+ # File.read("one.txt") # => "one"
73
+ # File.read("two.txt") # => "two"
74
+ #
75
+ # Note that open normally takes and passes a list (ie an Array). If you provide
76
+ # a single argument, it will be translated into an Array, and passed AS AN ARRAY
77
+ # to the block.
78
+ #
79
+ # FileTask.open("file.txt", "w") do |array|
80
+ # array.first << "content"
81
+ # end
82
+ #
83
+ # File.read("file.txt") # => "content"
84
+ def open(list, mode="rb")
85
+ open_files = []
86
+ begin
87
+ [list].flatten.map {|path| path.to_str }.each do |filepath|
88
+ open_files << File.open(filepath, mode)
89
+ end
90
+
91
+ block_given? ? yield(open_files) : open_files
92
+ ensure
93
+ open_files.each {|file| file.close } if block_given?
94
+ end
95
+ end
96
+ end
97
+
98
+ write_inheritable_attribute(:backup_dir, :backup)
99
+ class_inheritable_accessor(:backup_dir)
100
+
101
+ write_inheritable_attribute(:backup_timestamp, "%Y%m%d_%H%M%S")
102
+ class_inheritable_accessor(:backup_timestamp)
103
+
104
+ write_inheritable_attribute(:rollback_on_error, true)
105
+ class_inheritable_accessor(:rollback_on_error)
106
+
107
+ attr_reader :inference_block, :backed_up_files, :added_files
108
+ attr_accessor :dirname, :backup_dir, :backup_timestamp, :rollback_on_error
20
109
 
21
110
  def initialize(*args)
22
111
  super
@@ -24,7 +113,10 @@ module Tap
24
113
  batch.each do |task|
25
114
  task.dirname = task.default_dirname
26
115
  task.backed_up_files = {}
27
- task.made_files = []
116
+ task.added_files = []
117
+ task.backup_dir = self.class.backup_dir
118
+ task.backup_timestamp = self.class.backup_timestamp
119
+ task.rollback_on_error = self.class.rollback_on_error
28
120
  end
29
121
  end
30
122
 
@@ -43,32 +135,36 @@ module Tap
43
135
  # t = FileTask.new
44
136
  # t.app[:data] # => "/data"
45
137
  # t.dirname # => "tap/file_task"
46
- # t.infer_filepath(:data, "result.txt") # => "/data/tap/file_task/result.txt"
138
+ # t.filepath(:data, "result.txt") # => "/data/tap/file_task/result.txt"
47
139
  #
48
140
  # t.inference do |root, dir, path|
49
141
  # File.join(root, dir, path.chomp(".txt") + ".yml")
50
142
  # end
51
143
  #
52
- # t.infer_filepath(:data, "result.txt") # => "/data/tap/file_task/result.yml"
144
+ # t.filepath(:data, "result.txt") # => "/data/tap/file_task/result.yml"
53
145
  #
54
- def infer_filepath(dir, *paths) # :yields: app[dir], dirname, *paths
146
+ def filepath(dir, *paths)
55
147
  inference_block ?
56
148
  inference_block.call(app[dir], dirname, *paths) :
57
149
  app.filepath(dir, dirname, *paths)
58
150
  end
59
151
 
60
- # Makes a backup filepath for input filepath by doing the following:
61
- # - insert a timestamp between the basename and extname of the filepath
62
- # - identify a backup path as the part of the filepath relative to
63
- # dirname, or the basename if the filepath is not relative to dirname
64
- # - infer_filepath using the backup_dir and this backup path
152
+ # Makes a backup filepath relative to backup_dir by translating the input
153
+ # filepath and inserting a timestamp formatted using backup_timestamp.
154
+ # The filepath used during translation will be the filepath relative
155
+ # to dirname (if the input filepath is relative to dirname) or just
156
+ # the basename of the filepath.
65
157
  #
66
158
  # t = FileTask.new("dir/name", :backup_dir => :backup, :backup_timestamp => "%Y%m%d")
67
- # t.app[:backup] # => "/backup"
68
- # Date.today.to_s # => "2007-08-08"
159
+ # t.dirname # => "dir/name"
160
+ # t.app[:backup] # => "/backup"
161
+ # Date.today.to_s # => "2007-08-08"
69
162
  #
70
- # t.backup_filepath("path/to/folder/file.txt") # => "/backup/file_20070808.txt"
71
- # t.backup_filepath("dir/name/folder/file.txt") # => "/backup/folder/file_20070808.txt"
163
+ # # uses path relative to dirname, if possible
164
+ # t.backup_filepath("dir/name/folder/file.txt") # => "/backup/folder/file_20070808.txt"
165
+ #
166
+ # # otherwise uses basename
167
+ # t.backup_filepath("path/to/folder/file.txt") # => "/backup/file_20070808.txt"
72
168
  #
73
169
  def backup_filepath(filepath)
74
170
  extname = File.extname(filepath)
@@ -79,33 +175,18 @@ module Tap
79
175
  backup_path[(split_index + dirname.length + 1)..-1] :
80
176
  File.basename(backup_path)
81
177
 
82
- infer_filepath(backup_dir, backup_path)
83
- end
84
-
85
- # A batch File.open method. If a block is given, each file in the list will be
86
- # opened the open files passed to the block. Files are automatically closed when
87
- # the block returns. If no block is given, the open files are returned.
88
- def open(list, mode="rb")
89
- open_files = []
90
- begin
91
- fu_list(list).each do |filepath|
92
- open_files << File.open(filepath, mode)
93
- end
94
-
95
- block_given? ? yield(open_files) : open_files
96
- ensure
97
- open_files.each {|file| file.close } if block_given?
98
- end
178
+ filepath(backup_dir, backup_path)
99
179
  end
100
180
 
101
181
  # Returns true if all of the targets are up to date relative to all of the sources
102
- # AND the task config_file (if it exists). Single values or arrays can be provided
103
- # for both targets and sources.
182
+ # AND the task config_file, if it exists. Single values or arrays can be provided
183
+ # for both targets and sources. Used to check if any work needs to be done for
184
+ # a given set of sources and configurations.
104
185
  #
105
- # Returns false if +force?+ is true.
186
+ # Returns false (ie 'not up to date') if +force?+ is true.
106
187
  def uptodate?(targets, sources=[])
107
188
  if app.options.force
108
- log_filepaths(:force, *targets)
189
+ log_basename(:force, *targets)
109
190
  false
110
191
  else
111
192
  targets = [targets] unless targets.kind_of?(Array)
@@ -119,12 +200,31 @@ module Tap
119
200
  end
120
201
 
121
202
  # Makes a backup of each file in list to backup_filepath(file) and registers
122
- # the files so that they can be restored using restore. If copy is true, the
123
- # files will be copied to backup_filepath, otherwise the file is moved to
124
- # backup_filepath. Raises an error if the file is already backed up.
203
+ # the files so that they can be restored using restore. If backup_using_copy
204
+ # is true, the files will be copied to backup_filepath, otherwise the file is
205
+ # moved to backup_filepath. Raises an error if the file is already listed
206
+ # in backed_up_files.
125
207
  #
126
208
  # Returns a list of the backup_filepaths.
127
- def backup(list, copy=true)
209
+ #
210
+ # file = "file.txt"
211
+ # File.open(file, "w") {|f| f << "file content"}
212
+ #
213
+ # t = FileTask.new
214
+ # backed_up_file = t.backup(file).first
215
+ #
216
+ # File.exists?(file) # => false
217
+ # File.exists?(backed_up_file) # => true
218
+ # File.read(backed_up_file) # => "file content"
219
+ #
220
+ # File.open(file, "w") {|f| f << "new content"}
221
+ # t.restore(file)
222
+ #
223
+ # File.exists?(file) # => true
224
+ # File.exists?(backed_up_file) # => false
225
+ # File.read(file) # => "file content"
226
+ #
227
+ def backup(list, backup_using_copy=false)
128
228
  fu_list(list).collect do |filepath|
129
229
  next unless File.exists?(filepath)
130
230
 
@@ -137,7 +237,7 @@ module Tap
137
237
  dir = File.dirname(target)
138
238
  mkdir(dir)
139
239
 
140
- if copy
240
+ if backup_using_copy
141
241
  log :cp, "#{filepath} to #{target}", Logger::DEBUG
142
242
  FileUtils.cp(filepath, target)
143
243
  else
@@ -151,13 +251,33 @@ module Tap
151
251
  end
152
252
  end
153
253
 
154
- # Restores a backed-up file to its original location and removes the
155
- # directory of the backup if it is empty. Returns the restored files.
254
+ # Restores each file in the input list using the backup file from
255
+ # backed_up_files. The backup directory is removed if it is empty.
256
+ #
257
+ # Returns a list of the restored files.
258
+ #
259
+ # file = "file.txt"
260
+ # File.open(file, "w") {|f| f << "file content"}
261
+ #
262
+ # t = FileTask.new
263
+ # backed_up_file = t.backup(file).first
264
+ #
265
+ # File.exists?(file) # => true
266
+ # File.exists?(backed_up_file) # => true
267
+ # File.read(backed_up_file) # => "file content"
268
+ #
269
+ # File.open(file, "w") {|f| f << "new content"}
270
+ # t.restore(file)
271
+ #
272
+ # File.exists?(file) # => true
273
+ # File.exists?(backed_up_file) # => false
274
+ # File.read(file) # => "file content"
275
+ #
156
276
  def restore(list)
157
277
  fu_list(list).collect do |filepath|
158
278
  filepath = File.expand_path(filepath)
159
- raise "No backed up file for: #{filepath}" unless backed_up_files.has_key?(filepath)
160
-
279
+ next unless backed_up_files.has_key?(filepath)
280
+
161
281
  target = backed_up_files.delete(filepath)
162
282
 
163
283
  dir = File.dirname(filepath)
@@ -170,13 +290,36 @@ module Tap
170
290
  rmdir(dir)
171
291
 
172
292
  filepath
173
- end
293
+ end.compact
174
294
  end
175
295
 
176
296
  # Creates the directories in list if they do not exist and adds
177
- # them to made_files so they can be removed using rmdir.
297
+ # them to added_files so they can be removed using rmdir. Creating
298
+ # directories in this way causes them to be rolled back upon an
299
+ # execution error.
178
300
  #
179
301
  # Returns the made directories.
302
+ #
303
+ # t = FileTask.new do |task, inputs|
304
+ # File.exists?("path") # => false
305
+ #
306
+ # task.mkdir("path/to/dir") # will be rolled back
307
+ # File.exists?("path/to/dir") # => true
308
+ #
309
+ # FileUtils.mkdir("path/to/another") # will not be rolled back
310
+ # File.exists?("path/to/another") # => true
311
+ #
312
+ # raise "error!"
313
+ # end
314
+ #
315
+ # begin
316
+ # t.execute(nil)
317
+ # rescue
318
+ # $!.message # => "error!"
319
+ # File.exists?("path/to/dir") # => false
320
+ # File.exists?("path/to/another") # => true
321
+ # end
322
+ #
180
323
  def mkdir(list)
181
324
  fu_list(list).each do |dir|
182
325
  dir = File.expand_path(dir)
@@ -190,17 +333,28 @@ module Tap
190
333
  make_paths.reverse_each do |dir|
191
334
  log :mkdir, dir, Logger::DEBUG
192
335
  FileUtils.mkdir(dir)
193
- made_files << dir
336
+ added_files << dir
194
337
  end
195
338
  end
196
339
  end
197
340
 
198
341
  # Removes each directory in the input list, provided the directory is in
199
- # made_files and the directory is empty. When checking if the directory
342
+ # added_files and the directory is empty. When checking if the directory
200
343
  # is empty, rmdir checks for regular files and hidden files. Removed
201
- # directories are removed from made_files.
344
+ # directories are removed from added_files.
202
345
  #
203
346
  # Returns a list of the removed directories.
347
+ #
348
+ # t = FileTask.new
349
+ # File.exists?("path") # => false
350
+ # FileUtils.mkdir("path") # will not be removed
351
+ #
352
+ # t.mkdir("path/to/dir")
353
+ # File.exists?("path/to/dir") # => true
354
+ #
355
+ # t.rmdir("path/to/dir")
356
+ # File.exists?("path") # => true
357
+ # File.exists?("path/to") # => false
204
358
  def rmdir(list)
205
359
  removed = []
206
360
  fu_list(list).each do |dir|
@@ -208,7 +362,7 @@ module Tap
208
362
 
209
363
  # remove directories and parents until the
210
364
  # directory was not made by the task
211
- while made_files.include?(dir)
365
+ while added_files.include?(dir)
212
366
  break unless Dir.entries(dir).delete_if {|d| d == "." || d == ".."}.empty?
213
367
 
214
368
  if File.exists?(dir)
@@ -216,20 +370,45 @@ module Tap
216
370
  FileUtils.rmdir(dir)
217
371
  end
218
372
 
219
- removed << made_files.delete(dir)
373
+ removed << added_files.delete(dir)
220
374
  dir = File.dirname(dir)
221
375
  end
222
376
  end
223
377
  removed
224
378
  end
225
379
 
226
- # Make prepares the input list of files by backing them up (if they exist),
227
- # ensuring that the parent directory for the file exists, and adds each file
228
- # to made_files. As a result the files can be removed using rm, and
229
- # the original files restored using restore.
380
+ # Prepares the input list of files by backing them up (if they exist),
381
+ # ensuring that the parent directory for the file exists, and adding
382
+ # each file to added_files. As a result the files can be removed
383
+ # using rm, restored using restore, and will be rolled back upon an
384
+ # execution error.
385
+ #
386
+ # Returns the prepared files.
387
+ #
388
+ # File.open("file.txt", "w") {|f| f << "original content"}
230
389
  #
231
- # Returns the made files.
232
- def make(list) # :yields: list
390
+ # t = FileTask.new do |task, inputs|
391
+ # File.exists?("path") # => false
392
+ #
393
+ # # backup... make parent dirs... prepare for restore
394
+ # task.prepare(["file.txt", "path/to/file.txt"])
395
+ #
396
+ # File.open("file.txt", "w") {|f| f << "new content"}
397
+ # File.touch("path/to/file.txt")
398
+ #
399
+ # raise "error!"
400
+ # end
401
+ #
402
+ # begin
403
+ # t.execute(nil)
404
+ # rescue
405
+ # $!.message # => "error!"
406
+ # File.exists?("file.txt") # => true
407
+ # File.read("file.txt") # => "original content"
408
+ # File.exists?("path") # => false
409
+ # end
410
+ #
411
+ def prepare(list, backup_using_copy=false)
233
412
  list = fu_list(list)
234
413
  existing_files, non_existant_files = list.partition do |filepath|
235
414
  File.exists?(filepath)
@@ -237,8 +416,7 @@ module Tap
237
416
 
238
417
  # backup existing files
239
418
  existing_files.each do |filepath|
240
- backup(filepath, false)
241
- made_files << File.expand_path(filepath)
419
+ backup(filepath, backup_using_copy)
242
420
  end
243
421
 
244
422
  # ensure the parent directory exists
@@ -246,24 +424,37 @@ module Tap
246
424
  non_existant_files.each do |filepath|
247
425
  dir = File.dirname(filepath)
248
426
  mkdir(dir)
249
- made_files << File.expand_path(filepath)
250
427
  end
251
-
252
- yield list if block_given?
253
428
 
429
+ list.each do |filepath|
430
+ added_files << File.expand_path(filepath)
431
+ end
432
+
254
433
  list
255
434
  end
256
435
 
257
- # Removes each file in the input list, provided the file is in made_files.
436
+ # Removes each file in the input list, provided the file is in added_files.
258
437
  # The parent directory of each file is removed using rmdir. Removed files
259
- # are removed from made_files.
438
+ # are removed from added_files.
260
439
  #
261
440
  # Returns the removed files and directories.
441
+ #
442
+ # t = FileTask.new
443
+ # File.exists?("path") # => false
444
+ # FileUtils.mkdir("path") # will not be removed
445
+ #
446
+ # t.make("path/to/file.txt")
447
+ # FileUtils.touch("path/to/file.txt")
448
+ # File.exists?("path/to/file.txt") # => true
449
+ #
450
+ # t.rm("path/to/file.txt")
451
+ # File.exists?("path") # => true
452
+ # File.exists?("path/to") # => false
262
453
  def rm(list)
263
454
  removed = []
264
455
  fu_list(list).each do |filepath|
265
456
  filepath = File.expand_path(filepath)
266
- next unless made_files.include?(filepath)
457
+ next unless added_files.include?(filepath)
267
458
 
268
459
  # if the file exists, remove it
269
460
  if File.exists?(filepath)
@@ -271,21 +462,87 @@ module Tap
271
462
  FileUtils.rm(filepath, :force => true)
272
463
  end
273
464
 
274
- removed << made_files.delete(filepath)
465
+ removed << added_files.delete(filepath)
275
466
  removed.concat rmdir(File.dirname(filepath))
276
467
  end
277
468
  removed
278
469
  end
470
+
471
+ def rollback
472
+ added_files.dup.each do |filepath|
473
+ begin
474
+ case
475
+ when File.file?(filepath)
476
+ rm(filepath)
477
+ when File.directory?(filepath)
478
+ rmdir(filepath)
479
+ else
480
+ # assures non-existant files are cleared from added_files
481
+ # this is automatically done by rm and rmdir for existing files
482
+ added_files.delete(filepath)
483
+ end
484
+ rescue
485
+ yield $!
486
+ end
487
+ end
488
+
489
+ backed_up_files.keys.each do |filepath|
490
+ begin
491
+ restore(filepath)
492
+ rescue
493
+ yield $!
494
+ end
495
+ end
496
+ end
497
+
498
+ def cleanup(pattern=nil)
499
+ backed_up_files.each do |filepath, target|
500
+ next unless pattern == nil || target =~ pattern
501
+
502
+ # the filepath needs to be added to added_files
503
+ # before it can be removed by rm
504
+ added_files << target
505
+ rm(target)
506
+ backed_up_files.delete(filepath)
507
+ end
508
+ end
509
+
510
+ # Run the system command +cmd+. If multiple arguments are given the command
511
+ # is not run with the shell (same semantics as Kernel::exec and Kernel::system).
512
+ #
513
+ # Example:
514
+ # sh %{ls -ltr}
515
+ #
516
+ # sh 'ls', 'file with spaces'
517
+ #
518
+ # # check exit status after command runs
519
+ # sh %{grep pattern file} do |ok, res|
520
+ # if ! ok
521
+ # puts "pattern not found (status = #{res.exitstatus})"
522
+ # end
523
+ # end
524
+ #
525
+ def sh(*cmd, &block)
526
+ # based on sh from Rake
527
+ unless block_given?
528
+ block = lambda { |ok, status|
529
+ ok or raise "Command failed with status (#{status.exitstatus}): [#{cmd.join(" ")}]"
530
+ }
531
+ end
532
+ log :sh, cmd.join(" ")
533
+ res = system(*cmd)
534
+ block.call(res, $?)
535
+ end
279
536
 
280
537
  # Logs the given action, with the basenames of the input filepaths.
281
- def log_filepaths(action, *filepaths)
538
+ def log_basename(action, filepaths, level=Logger::INFO)
282
539
  msg = filepaths.collect {|filepath| File.basename(filepath) }.join(',')
283
- log(action , msg)
540
+ log(action, msg, level)
284
541
  end
285
542
 
286
543
  protected
287
544
 
288
- attr_writer :inference_block, :backed_up_files, :made_files
545
+ attr_writer :inference_block, :backed_up_files, :added_files
289
546
 
290
547
  # The default_dirname is based on the name of the task, and the
291
548
  # index of the task in batch (if the task is batched):
@@ -300,51 +557,33 @@ module Tap
300
557
  batched? ? "#{name}_#{batch_index}" : name
301
558
  end
302
559
 
303
- def after_execute
304
- backed_up_files.values.each do |filepath|
305
- # the filepath needs to be added to made_files
306
- # before it can be removed by rm
307
- made_files << filepath
308
- rm(filepath)
309
- end if remove_backed_up_files
560
+ # Clears added_files and backed_up_files so that
561
+ # a failure will not affect previous executions
562
+ def before_execute
563
+ added_files.clear
564
+ backed_up_files.clear
310
565
  end
311
566
 
567
+ # Removes made files/dirs and restores backed-up files upon
568
+ # an execute error. Collects any errors raised along the way
569
+ # and raises them in a Tap::Support::RunError.
312
570
  def on_execute_error(original_error)
313
- restore_errors = []
314
-
315
- if restore_on_error
316
- made_files.dup.each do |filepath|
317
- begin
318
- case
319
- when File.file?(filepath)
320
- rm(filepath)
321
- when File.directory?(filepath)
322
- rmdir(filepath)
323
- end
324
- rescue
325
- restore_errors << $!
326
- end
327
- end
328
-
329
- backed_up_files.keys.each do |filepath|
330
- begin
331
- restore(filepath)
332
- rescue
333
- restore_errors << $!
334
- end
335
- end
571
+ rollback_errors = []
572
+ if rollback_on_error
573
+ rollback {|error| rollback_errors << error}
336
574
  end
337
575
 
338
- # Re-raise an error unless the original error was due
339
- # to task termination and no restore errors occured
340
- if restore_errors.empty?
576
+ # Re-raise the error if no rollback errors occured,
577
+ # otherwise, raise a RunError tracking the restore
578
+ # errors.
579
+ if rollback_errors.empty?
341
580
  raise original_error
342
581
  else
343
- raise Support::RunError.new(original_error, restore_errors)
582
+ raise Support::RunError.new(original_error, rollback_errors)
344
583
  end
345
584
  end
346
585
 
347
- private
586
+ protected
348
587
 
349
588
  # Lifted from FileUtils
350
589
  def fu_list(arg)