tap 0.7.9 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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)