tap 0.12.4 → 0.17.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.
- 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
|