tap 0.12.4 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +34 -0
- data/README +62 -41
- data/bin/tap +36 -40
- data/cmd/console.rb +14 -6
- data/cmd/manifest.rb +62 -58
- data/cmd/run.rb +49 -31
- data/doc/API +84 -0
- data/doc/Class Reference +83 -115
- data/doc/Examples/Command Line +36 -0
- data/doc/Examples/Workflow +40 -0
- data/lib/tap/app.rb +293 -214
- data/lib/tap/app/node.rb +43 -0
- data/lib/tap/app/queue.rb +77 -0
- data/lib/tap/app/stack.rb +16 -0
- data/lib/tap/app/state.rb +22 -0
- data/lib/tap/constants.rb +2 -2
- data/lib/tap/env.rb +400 -314
- data/lib/tap/env/constant.rb +227 -0
- data/lib/tap/env/gems.rb +63 -0
- data/lib/tap/env/manifest.rb +89 -0
- data/lib/tap/env/minimap.rb +292 -0
- data/lib/tap/{support → env}/string_ext.rb +2 -2
- data/lib/tap/exe.rb +113 -125
- data/lib/tap/join.rb +175 -0
- data/lib/tap/joins.rb +9 -0
- data/lib/tap/joins/switch.rb +44 -0
- data/lib/tap/joins/sync.rb +99 -0
- data/lib/tap/root.rb +100 -491
- data/lib/tap/root/utils.rb +220 -0
- data/lib/tap/{support → root}/versions.rb +31 -29
- data/lib/tap/schema.rb +248 -0
- data/lib/tap/schema/parser.rb +413 -0
- data/lib/tap/schema/utils.rb +82 -0
- data/lib/tap/support/intern.rb +19 -6
- data/lib/tap/support/templater.rb +8 -3
- data/lib/tap/task.rb +175 -171
- data/lib/tap/tasks/dump.rb +58 -0
- data/lib/tap/tasks/load.rb +62 -0
- metadata +30 -73
- data/cmd/destroy.rb +0 -27
- data/cmd/generate.rb +0 -27
- data/doc/Command Reference +0 -105
- data/doc/Syntax Reference +0 -234
- data/doc/Tutorial +0 -348
- data/lib/tap/dump.rb +0 -142
- data/lib/tap/file_task.rb +0 -384
- data/lib/tap/generator/arguments.rb +0 -13
- data/lib/tap/generator/base.rb +0 -176
- data/lib/tap/generator/destroy.rb +0 -60
- data/lib/tap/generator/generate.rb +0 -93
- data/lib/tap/generator/generators/command/command_generator.rb +0 -21
- data/lib/tap/generator/generators/command/templates/command.erb +0 -32
- data/lib/tap/generator/generators/config/config_generator.rb +0 -98
- data/lib/tap/generator/generators/generator/generator_generator.rb +0 -37
- data/lib/tap/generator/generators/generator/templates/task.erb +0 -27
- data/lib/tap/generator/generators/generator/templates/test.erb +0 -26
- data/lib/tap/generator/generators/root/root_generator.rb +0 -84
- data/lib/tap/generator/generators/root/templates/MIT-LICENSE +0 -22
- data/lib/tap/generator/generators/root/templates/README +0 -14
- data/lib/tap/generator/generators/root/templates/Rakefile +0 -84
- data/lib/tap/generator/generators/root/templates/Rapfile +0 -11
- data/lib/tap/generator/generators/root/templates/gemspec +0 -27
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -3
- data/lib/tap/generator/generators/task/task_generator.rb +0 -25
- data/lib/tap/generator/generators/task/templates/task.erb +0 -14
- data/lib/tap/generator/generators/task/templates/test.erb +0 -19
- data/lib/tap/generator/manifest.rb +0 -20
- data/lib/tap/generator/preview.rb +0 -69
- data/lib/tap/load.rb +0 -64
- data/lib/tap/spec.rb +0 -41
- data/lib/tap/support/aggregator.rb +0 -65
- data/lib/tap/support/audit.rb +0 -333
- data/lib/tap/support/constant.rb +0 -143
- data/lib/tap/support/constant_manifest.rb +0 -126
- data/lib/tap/support/dependencies.rb +0 -54
- data/lib/tap/support/dependency.rb +0 -44
- data/lib/tap/support/executable.rb +0 -198
- data/lib/tap/support/executable_queue.rb +0 -125
- data/lib/tap/support/gems.rb +0 -43
- data/lib/tap/support/join.rb +0 -144
- data/lib/tap/support/joins.rb +0 -12
- data/lib/tap/support/joins/switch.rb +0 -27
- data/lib/tap/support/joins/sync_merge.rb +0 -38
- data/lib/tap/support/manifest.rb +0 -171
- data/lib/tap/support/minimap.rb +0 -90
- data/lib/tap/support/node.rb +0 -176
- data/lib/tap/support/parser.rb +0 -450
- data/lib/tap/support/schema.rb +0 -385
- data/lib/tap/support/shell_utils.rb +0 -67
- data/lib/tap/test.rb +0 -77
- data/lib/tap/test/assertions.rb +0 -38
- data/lib/tap/test/env_vars.rb +0 -29
- data/lib/tap/test/extensions.rb +0 -73
- data/lib/tap/test/file_test.rb +0 -362
- data/lib/tap/test/file_test_class.rb +0 -15
- data/lib/tap/test/regexp_escape.rb +0 -87
- data/lib/tap/test/script_test.rb +0 -46
- data/lib/tap/test/script_tester.rb +0 -115
- data/lib/tap/test/subset_test.rb +0 -260
- data/lib/tap/test/subset_test_class.rb +0 -99
- data/lib/tap/test/tap_test.rb +0 -109
- data/lib/tap/test/utils.rb +0 -231
data/lib/tap/file_task.rb
DELETED
@@ -1,384 +0,0 @@
|
|
1
|
-
require 'tap/support/shell_utils'
|
2
|
-
|
3
|
-
module Tap
|
4
|
-
|
5
|
-
# FileTask is a base class for tasks that work with a file system. FileTask
|
6
|
-
# tracks changes it makes so they may be rolled back to their original state.
|
7
|
-
# Rollback automatically occurs on an execute error.
|
8
|
-
#
|
9
|
-
# File.open("file.txt", "w") {|file| file << "original content"}
|
10
|
-
#
|
11
|
-
# t = FileTask.intern do |task, raise_error|
|
12
|
-
# task.mkdir_p("some/dir") # marked for rollback
|
13
|
-
# task.prepare("file.txt") do |file| # marked for rollback
|
14
|
-
# file << "new content"
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# # raise an error to start rollback
|
18
|
-
# raise "error!" if raise_error
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# begin
|
22
|
-
# t.execute(true)
|
23
|
-
# rescue
|
24
|
-
# $!.message # => "error!"
|
25
|
-
# File.exists?("some/dir") # => false
|
26
|
-
# File.read("file.txt") # => "original content"
|
27
|
-
# end
|
28
|
-
#
|
29
|
-
# t.execute(false)
|
30
|
-
# File.exists?("some/dir") # => true
|
31
|
-
# File.read("file.txt") # => "new content"
|
32
|
-
#
|
33
|
-
class FileTask < Task
|
34
|
-
include Tap::Support::ShellUtils
|
35
|
-
|
36
|
-
# The backup directory
|
37
|
-
config_attr :backup_dir, 'backup' # The backup directory
|
38
|
-
|
39
|
-
# A flag indicating whether or track changes
|
40
|
-
# for rollback on execution error
|
41
|
-
config :rollback_on_error, true, &c.switch # Rollback changes on error
|
42
|
-
|
43
|
-
def initialize(config={}, name=nil, app=App.instance)
|
44
|
-
super
|
45
|
-
@actions = []
|
46
|
-
end
|
47
|
-
|
48
|
-
# Initializes a copy that will rollback independent of self.
|
49
|
-
def initialize_copy(orig)
|
50
|
-
super
|
51
|
-
@actions = []
|
52
|
-
end
|
53
|
-
|
54
|
-
# Returns the path, exchanging the extension with extname.
|
55
|
-
# A false or nil extname removes the extension, while true
|
56
|
-
# preserves the existing extension (and effectively does
|
57
|
-
# nothing).
|
58
|
-
#
|
59
|
-
# t = FileTask.new
|
60
|
-
# t.basepath('path/to/file.txt') # => 'path/to/file'
|
61
|
-
# t.basepath('path/to/file.txt', '.html') # => 'path/to/file.html'
|
62
|
-
#
|
63
|
-
# t.basepath('path/to/file.txt', false) # => 'path/to/file'
|
64
|
-
# t.basepath('path/to/file.txt', true) # => 'path/to/file.txt'
|
65
|
-
#
|
66
|
-
# Compare to basename.
|
67
|
-
def basepath(path, extname=false)
|
68
|
-
case extname
|
69
|
-
when false, nil then path.chomp(File.extname(path))
|
70
|
-
when true then path
|
71
|
-
else Root.exchange(path, extname)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Returns the basename of path, exchanging the extension
|
76
|
-
# with extname. A false or nil extname removes the
|
77
|
-
# extension, while true preserves the existing extension.
|
78
|
-
#
|
79
|
-
# t = FileTask.new
|
80
|
-
# t.basename('path/to/file.txt') # => 'file.txt'
|
81
|
-
# t.basename('path/to/file.txt', '.html') # => 'file.html'
|
82
|
-
#
|
83
|
-
# t.basename('path/to/file.txt', false) # => 'file'
|
84
|
-
# t.basename('path/to/file.txt', true) # => 'file.txt'
|
85
|
-
#
|
86
|
-
# Compare to basepath.
|
87
|
-
def basename(path, extname=true)
|
88
|
-
basepath(File.basename(path), extname)
|
89
|
-
end
|
90
|
-
|
91
|
-
# Constructs a filepath using the dir, name, and the specified paths.
|
92
|
-
#
|
93
|
-
# t = FileTask.new
|
94
|
-
# t.name # => "tap/file_task"
|
95
|
-
# t.filepath('data', "result.txt") # => File.expand_path("data/tap/file_task/result.txt")
|
96
|
-
#
|
97
|
-
def filepath(dir, *paths)
|
98
|
-
File.expand_path(File.join(dir, name, *paths))
|
99
|
-
end
|
100
|
-
|
101
|
-
# Makes a backup filepath relative to backup_dir by using name, the
|
102
|
-
# basename of filepath, and an index.
|
103
|
-
#
|
104
|
-
# t = FileTask.new({:backup_dir => "/backup"}, "name")
|
105
|
-
# t.backup_filepath("path/to/file.txt", time) # => "/backup/name/file.0.txt"
|
106
|
-
#
|
107
|
-
def backup_filepath(path)
|
108
|
-
extname = File.extname(path)
|
109
|
-
backup_path = filepath(backup_dir, File.basename(path).chomp(extname))
|
110
|
-
next_indexed_path(backup_path, 0, extname)
|
111
|
-
end
|
112
|
-
|
113
|
-
# Returns true if all of the targets are up to date relative to all of the
|
114
|
-
# listed sources. Single values or arrays can be provided for both targets
|
115
|
-
# and sources.
|
116
|
-
#
|
117
|
-
# Returns false (ie 'not up to date') if app.force is true.
|
118
|
-
#
|
119
|
-
#--
|
120
|
-
# TODO: add check vs date reference (ex config_file date)
|
121
|
-
def uptodate?(targets, sources=[])
|
122
|
-
if app.force
|
123
|
-
log_basename(:force, *targets)
|
124
|
-
false
|
125
|
-
else
|
126
|
-
targets = [targets] unless targets.kind_of?(Array)
|
127
|
-
sources = [sources] unless sources.kind_of?(Array)
|
128
|
-
|
129
|
-
targets.each do |target|
|
130
|
-
return false unless FileUtils.uptodate?(target, sources)
|
131
|
-
end
|
132
|
-
true
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# Makes a backup of path to backup_filepath(path) and returns the backup path.
|
137
|
-
# If backup_using_copy is true, the backup is a copy of path, otherwise the
|
138
|
-
# file or directory at path is moved to the backup path. Raises an error if
|
139
|
-
# the backup path already exists.
|
140
|
-
#
|
141
|
-
# Backups are restored on rollback.
|
142
|
-
#
|
143
|
-
# file = "file.txt"
|
144
|
-
# File.open(file, "w") {|f| f << "file content"}
|
145
|
-
#
|
146
|
-
# t = FileTask.new
|
147
|
-
# backup_file = t.backup(file)
|
148
|
-
#
|
149
|
-
# File.exists?(file) # => false
|
150
|
-
# File.exists?(backup_file) # => true
|
151
|
-
# File.read(backup_file) # => "file content"
|
152
|
-
#
|
153
|
-
# File.open(file, "w") {|f| f << "new content"}
|
154
|
-
# t.rollback
|
155
|
-
#
|
156
|
-
# File.exists?(file) # => true
|
157
|
-
# File.exists?(backup_file ) # => false
|
158
|
-
# File.read(file) # => "file content"
|
159
|
-
#
|
160
|
-
def backup(path, backup_using_copy=false)
|
161
|
-
return nil unless File.exists?(path)
|
162
|
-
|
163
|
-
source = File.expand_path(path)
|
164
|
-
target = backup_filepath(source)
|
165
|
-
raise "backup already exists: #{target}" if File.exists?(target)
|
166
|
-
|
167
|
-
mkdir_p File.dirname(target)
|
168
|
-
|
169
|
-
log :backup, "#{source} to #{target}", Logger::DEBUG
|
170
|
-
if backup_using_copy
|
171
|
-
FileUtils.cp(source, target)
|
172
|
-
else
|
173
|
-
FileUtils.mv(source, target)
|
174
|
-
end
|
175
|
-
|
176
|
-
actions << [:backup, source, target]
|
177
|
-
target
|
178
|
-
end
|
179
|
-
|
180
|
-
# Creates a directory and all its parent directories. Directories created
|
181
|
-
# by mkdir_p removed on rollback.
|
182
|
-
def mkdir_p(dir)
|
183
|
-
dir = File.expand_path(dir)
|
184
|
-
|
185
|
-
dirs = []
|
186
|
-
while !File.exists?(dir)
|
187
|
-
dirs.unshift(dir)
|
188
|
-
dir = File.dirname(dir)
|
189
|
-
end
|
190
|
-
|
191
|
-
dirs.each {|d| mkdir(d) }
|
192
|
-
end
|
193
|
-
|
194
|
-
# Creates a directory. Directories created by mkdir removed on rollback.
|
195
|
-
def mkdir(dir)
|
196
|
-
dir = File.expand_path(dir)
|
197
|
-
|
198
|
-
unless File.exists?(dir)
|
199
|
-
log :mkdir, dir, Logger::DEBUG
|
200
|
-
FileUtils.mkdir(dir)
|
201
|
-
actions << [:make, dir]
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
# Prepares the path by backing up any existing file and ensuring that
|
206
|
-
# the parent directory for path exists. If a block is given, a file
|
207
|
-
# is opened and yielded to it (as in File.open). Prepared paths are
|
208
|
-
# removed and the backups restored on rollback.
|
209
|
-
#
|
210
|
-
# Returns the expanded path.
|
211
|
-
def prepare(path, backup_using_copy=false)
|
212
|
-
raise "not a file: #{path}" if File.directory?(path)
|
213
|
-
path = File.expand_path(path)
|
214
|
-
|
215
|
-
if File.exists?(path)
|
216
|
-
# backup or remove existing files
|
217
|
-
backup(path, backup_using_copy)
|
218
|
-
else
|
219
|
-
# ensure the parent directory exists
|
220
|
-
# for non-existant files
|
221
|
-
mkdir_p File.dirname(path)
|
222
|
-
end
|
223
|
-
log :prepare, path, Logger::DEBUG
|
224
|
-
actions << [:make, path]
|
225
|
-
|
226
|
-
if block_given?
|
227
|
-
File.open(path, "w") {|file| yield(file) }
|
228
|
-
end
|
229
|
-
|
230
|
-
path
|
231
|
-
end
|
232
|
-
|
233
|
-
# Removes a file. If a directory is provided, it's contents are removed
|
234
|
-
# recursively. Files and directories removed by rm_r are restored
|
235
|
-
# upon an execution error.
|
236
|
-
def rm_r(path)
|
237
|
-
path = File.expand_path(path)
|
238
|
-
|
239
|
-
backup(path, false)
|
240
|
-
log :rm_r, path, Logger::DEBUG
|
241
|
-
end
|
242
|
-
|
243
|
-
# Removes an empty directory. Directories removed by rmdir are restored
|
244
|
-
# upon an execution error.
|
245
|
-
def rmdir(dir)
|
246
|
-
dir = File.expand_path(dir)
|
247
|
-
|
248
|
-
unless Root.empty?(dir)
|
249
|
-
raise "not an empty directory: #{dir}"
|
250
|
-
end
|
251
|
-
|
252
|
-
backup(dir, false)
|
253
|
-
log :rmdir, dir, Logger::DEBUG
|
254
|
-
end
|
255
|
-
|
256
|
-
# Removes a file. Directories cannot be removed by this method.
|
257
|
-
# Files removed by rm are restored upon an execution error.
|
258
|
-
def rm(path)
|
259
|
-
path = File.expand_path(path)
|
260
|
-
|
261
|
-
unless File.file?(path)
|
262
|
-
raise "not a file: #{path}"
|
263
|
-
end
|
264
|
-
|
265
|
-
backup(path, false)
|
266
|
-
log :rm, path, Logger::DEBUG
|
267
|
-
end
|
268
|
-
|
269
|
-
# Copies source to target. Files and directories copied by cp are
|
270
|
-
# restored upon an execution error.
|
271
|
-
def cp(source, target)
|
272
|
-
target = File.join(target, File.basename(source)) if File.directory?(target)
|
273
|
-
prepare(target)
|
274
|
-
|
275
|
-
log :cp, "#{source} to #{target}", Logger::DEBUG
|
276
|
-
FileUtils.cp(source, target)
|
277
|
-
end
|
278
|
-
|
279
|
-
# Copies source to target. If source is a directory, the contents
|
280
|
-
# are copied recursively. If target is a directory, copies source
|
281
|
-
# to target/source. Files and directories copied by cp are restored
|
282
|
-
# upon an execution error.
|
283
|
-
def cp_r(source, target)
|
284
|
-
target = File.join(target, File.basename(source)) if File.directory?(target)
|
285
|
-
prepare(target)
|
286
|
-
|
287
|
-
log :cp_r, "#{source} to #{target}", Logger::DEBUG
|
288
|
-
FileUtils.cp_r(source, target)
|
289
|
-
end
|
290
|
-
|
291
|
-
# Moves source to target. Files and directories moved by mv are
|
292
|
-
# restored upon an execution error.
|
293
|
-
def mv(source, target, backup_source=true)
|
294
|
-
backup(source, true) if backup_source
|
295
|
-
prepare(target)
|
296
|
-
|
297
|
-
log :mv, "#{source} to #{target}", Logger::DEBUG
|
298
|
-
FileUtils.mv(source, target)
|
299
|
-
end
|
300
|
-
|
301
|
-
# Rolls back any actions capable of being rolled back.
|
302
|
-
#
|
303
|
-
# Rollback is forceful. For instance if you make a folder using
|
304
|
-
# mkdir, rollback will remove the folder and all files within it
|
305
|
-
# even if they were not added by self.
|
306
|
-
def rollback
|
307
|
-
while !actions.empty?
|
308
|
-
action, source, target = actions.pop
|
309
|
-
|
310
|
-
case action
|
311
|
-
when :make
|
312
|
-
log :rollback, "#{source}", Logger::DEBUG
|
313
|
-
FileUtils.rm_r(source)
|
314
|
-
when :backup
|
315
|
-
log :rollback, "#{target} to #{source}", Logger::DEBUG
|
316
|
-
dir = File.dirname(source)
|
317
|
-
FileUtils.mkdir_p(dir) unless File.exists?(dir)
|
318
|
-
FileUtils.mv(target, source, :force => true)
|
319
|
-
else
|
320
|
-
raise "unknown action: #{[action, source, target].inspect}"
|
321
|
-
end
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
# Removes backup files. Cleanup cannot be rolled back and prevents
|
326
|
-
# rollback of actions up to when cleanup is called. If cleanup_dirs
|
327
|
-
# is true, empty directories containing the backup files will be
|
328
|
-
# removed.
|
329
|
-
def cleanup(cleanup_dirs=true)
|
330
|
-
actions.each do |action, source, target|
|
331
|
-
if action == :backup
|
332
|
-
log :cleanup, target, Logger::DEBUG
|
333
|
-
FileUtils.rm_r(target) if File.exists?(target)
|
334
|
-
cleanup_dir(File.dirname(target)) if cleanup_dirs
|
335
|
-
end
|
336
|
-
end
|
337
|
-
actions.clear
|
338
|
-
end
|
339
|
-
|
340
|
-
# Removes the directory if empty, and all empty parent directories. This
|
341
|
-
# method cannot be rolled back.
|
342
|
-
def cleanup_dir(dir)
|
343
|
-
while Root.empty?(dir)
|
344
|
-
log :rmdir, dir, Logger::DEBUG
|
345
|
-
FileUtils.rmdir(dir)
|
346
|
-
dir = File.dirname(dir)
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
# Logs the given action, with the basenames of the input paths.
|
351
|
-
def log_basename(action, paths, level=Logger::INFO)
|
352
|
-
msg = [paths].flatten.collect {|path| File.basename(path) }.join(',')
|
353
|
-
log(action, msg, level)
|
354
|
-
end
|
355
|
-
|
356
|
-
protected
|
357
|
-
|
358
|
-
# An array tracking actions (backup, rm, mv, etc) performed by self,
|
359
|
-
# allowing rollback on an execution error. Not intended to be
|
360
|
-
# modified manually.
|
361
|
-
attr_reader :actions
|
362
|
-
|
363
|
-
# Clears actions so that a failure will not affect previous executions
|
364
|
-
def before_execute
|
365
|
-
actions.clear
|
366
|
-
end
|
367
|
-
|
368
|
-
# Removes made files/dirs and restores backed-up files upon
|
369
|
-
# an execute error.
|
370
|
-
def on_execute_error(original_error)
|
371
|
-
rollback if rollback_on_error
|
372
|
-
raise original_error
|
373
|
-
end
|
374
|
-
|
375
|
-
private
|
376
|
-
|
377
|
-
# utility method for backup_filepath; increments index until the
|
378
|
-
# path base.indexext does not exist.
|
379
|
-
def next_indexed_path(base, index, ext) # :nodoc:
|
380
|
-
path = sprintf('%s.%d%s', base, index, ext)
|
381
|
-
File.exists?(path) ? next_indexed_path(base, index + 1, ext) : path
|
382
|
-
end
|
383
|
-
end
|
384
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
module Tap
|
2
|
-
module Generator
|
3
|
-
|
4
|
-
# A special type of Lazydoc::Arguments that shifts off the standard 'm'
|
5
|
-
# argument on generator manifest methods, to properly reflect how may
|
6
|
-
# arguments the generator should receive.
|
7
|
-
class Arguments < Lazydoc::Arguments
|
8
|
-
def arguments(shift_manifest_arg=true)
|
9
|
-
shift_manifest_arg ? @arguments[1..-1] : @arguments
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
data/lib/tap/generator/base.rb
DELETED
@@ -1,176 +0,0 @@
|
|
1
|
-
require 'tap'
|
2
|
-
require 'tap/generator/manifest'
|
3
|
-
require 'tap/generator/arguments'
|
4
|
-
|
5
|
-
module Tap
|
6
|
-
module Generator
|
7
|
-
|
8
|
-
# :startdoc:::-
|
9
|
-
# Base provides the basic structure of a generator and custom generators
|
10
|
-
# inherit from it. Base is patterned after the {Ruby on Rails}[http://rubyonrails.org/]
|
11
|
-
# generators, but obviously takes on all the advantages of Tasks.
|
12
|
-
#
|
13
|
-
# === Usage
|
14
|
-
#
|
15
|
-
# Tap generators define a manifest method that defines what files and
|
16
|
-
# directories are created by the generator. Then, at execution time,
|
17
|
-
# a mixin with the appropriate funtion (ie Generate or Destory) is
|
18
|
-
# overlaid to figure out how to roll those actions forward or backwards.
|
19
|
-
#
|
20
|
-
# Unlike typical tasks, generators must be named like '<Name>Generator' and
|
21
|
-
# are identified using the ::generator flag rather than ::manifest. These
|
22
|
-
# requirements make generators available to the generate/destroy commands
|
23
|
-
# and not run.
|
24
|
-
#
|
25
|
-
# Typically, generators live in a directory structure like this:
|
26
|
-
#
|
27
|
-
# sample
|
28
|
-
# |- sample_generator.rb
|
29
|
-
# `- templates
|
30
|
-
# `- template_file.erb
|
31
|
-
#
|
32
|
-
# And take the form:
|
33
|
-
#
|
34
|
-
# [sample/sample_generator.rb]
|
35
|
-
# require 'tap/generator/base'
|
36
|
-
#
|
37
|
-
# # SampleGenerator::generator generates a directory, and two files
|
38
|
-
# #
|
39
|
-
# # An extended description of the
|
40
|
-
# # generator goes here...
|
41
|
-
# #
|
42
|
-
# class SampleGenerator < Tap::Generator::Base
|
43
|
-
#
|
44
|
-
# config :key, 'value' # a sample config
|
45
|
-
#
|
46
|
-
# def manifest(m, *args)
|
47
|
-
# # make a directory
|
48
|
-
# m.directory('path/to/dir')
|
49
|
-
#
|
50
|
-
# # make a file
|
51
|
-
# m.file('path/to/file.txt') do |file|
|
52
|
-
# file << "some content"
|
53
|
-
# end
|
54
|
-
#
|
55
|
-
# # template a file using config
|
56
|
-
# m.template('path/to/result.txt', 'template_file.erb', config.to_hash)
|
57
|
-
# end
|
58
|
-
# end
|
59
|
-
#
|
60
|
-
# As with any task, generators can have configurations and take arguments
|
61
|
-
# specified by manifest (minus the 'm' argument which is standard).
|
62
|
-
# Creating directories and files is straightforward, as above. Template
|
63
|
-
# generates a target file using the source file in the templates' directory;
|
64
|
-
# any attributes specified by the last argument will be available in the erb
|
65
|
-
# template.
|
66
|
-
# :startdoc:::+
|
67
|
-
class Base < Tap::Task
|
68
|
-
lazy_attr :manifest, 'generator'
|
69
|
-
lazy_attr :args, :manifest
|
70
|
-
lazy_register :manifest, Arguments
|
71
|
-
|
72
|
-
config :destination_root, Dir.pwd # The destination root directory
|
73
|
-
config :pretend, false, &c.flag # Run but rollback any changes.
|
74
|
-
config :force, false, &c.flag # Overwrite files that already exist.
|
75
|
-
config :skip, false, &c.flag # Skip files that already exist.
|
76
|
-
|
77
|
-
# The generator-specific templates directory. By default:
|
78
|
-
# 'path/to/name/templates' for 'path/to/name/name_generator.rb'
|
79
|
-
attr_accessor :template_dir
|
80
|
-
|
81
|
-
# The IO used to pull prompt inputs (default: $stdin)
|
82
|
-
attr_accessor :prompt_in
|
83
|
-
|
84
|
-
# The IO used to prompt users for input (default: $stdout)
|
85
|
-
attr_accessor :prompt_out
|
86
|
-
|
87
|
-
def initialize(*args)
|
88
|
-
super
|
89
|
-
@prompt_in = $stdin
|
90
|
-
@prompt_out = $stdout
|
91
|
-
@template_dir = File.dirname(self.class.source_file) + '/templates'
|
92
|
-
end
|
93
|
-
|
94
|
-
# Builds the manifest, then executes the actions of the manifest.
|
95
|
-
# Process returns the results of iterate, which normally will be
|
96
|
-
# an array of files and directories created (or destroyed) by self.
|
97
|
-
def process(*argv)
|
98
|
-
actions = []
|
99
|
-
manifest(Manifest.new(actions), *argv)
|
100
|
-
|
101
|
-
iterate(actions) do |action, args, block|
|
102
|
-
send(action, *args, &block)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# Overridden in subclasses to add actions to the input Manifest.
|
107
|
-
# Any arguments passed to process will be passed to manifest
|
108
|
-
# unchanged.
|
109
|
-
def manifest(m, *argv)
|
110
|
-
raise NotImplementedError
|
111
|
-
end
|
112
|
-
|
113
|
-
# Peforms each of the input actions in order, and collects the
|
114
|
-
# results. The process method returns these results.
|
115
|
-
def iterate(actions)
|
116
|
-
actions.collect {|action| yield(action) }
|
117
|
-
end
|
118
|
-
|
119
|
-
# Constructs a path relative to destination_root.
|
120
|
-
def path(*paths)
|
121
|
-
File.expand_path(File.join(*paths), destination_root)
|
122
|
-
end
|
123
|
-
|
124
|
-
# Peforms a directory action (ex generate or destroy). Must be
|
125
|
-
# overridden by one of the action mixins (ex Generate or Destroy).
|
126
|
-
def directory(target, options={})
|
127
|
-
raise NotImplementedError
|
128
|
-
end
|
129
|
-
|
130
|
-
# Peforms a file action (ex generate or destroy). Calls to file specify
|
131
|
-
# input for a target by providing a block; the block recieves an IO and
|
132
|
-
# pushes content to it. Must be overridden by one of the action mixins
|
133
|
-
# (ex Generate or Destroy).
|
134
|
-
def file(target, options={}) # :yields: io
|
135
|
-
raise NotImplementedError
|
136
|
-
end
|
137
|
-
|
138
|
-
# Makes (or destroys) the root and each of the targets, relative
|
139
|
-
# to root. Options are passed onto directory.
|
140
|
-
def directories(root, targets, options={})
|
141
|
-
directory(root, options)
|
142
|
-
targets.each do |target|
|
143
|
-
directory(File.join(root, target), options)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# Makes (or destroys) the target by templating the source using
|
148
|
-
# the specified attributes. Source is expanded relative to
|
149
|
-
# template_dir. Options are passed onto file.
|
150
|
-
def template(target, source, attributes={}, options={})
|
151
|
-
template_path = File.expand_path(source, template_dir)
|
152
|
-
templater = Support::Templater.new(File.read(template_path), attributes)
|
153
|
-
|
154
|
-
file(target, options) do |file|
|
155
|
-
file << templater.build
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
# Yields each source file under template_dir to the block, with
|
160
|
-
# a target path of the source relative to template_dir.
|
161
|
-
def template_files
|
162
|
-
Dir.glob(template_dir + "/**/*").sort.each do |source|
|
163
|
-
next unless File.file?(source)
|
164
|
-
|
165
|
-
target = Tap::Root.relative_filepath(template_dir, source)
|
166
|
-
yield(source, target)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# Logs the action with the relative filepath from Dir.pwd to path.
|
171
|
-
def log_relative(action, path)
|
172
|
-
log(action, Root.relative_filepath(Dir.pwd, path))
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|