tap 0.10.1 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +12 -0
- data/MIT-LICENSE +0 -2
- data/README +23 -32
- data/bin/rap +116 -0
- data/bin/tap +6 -9
- data/cgi/run.rb +67 -0
- data/cmd/console.rb +1 -1
- data/cmd/destroy.rb +4 -4
- data/cmd/generate.rb +4 -4
- data/cmd/manifest.rb +61 -53
- data/cmd/run.rb +8 -75
- data/doc/Class Reference +130 -121
- data/doc/Command Reference +76 -124
- data/doc/Syntax Reference +290 -0
- data/doc/Tutorial +305 -237
- data/lib/tap/app.rb +140 -467
- data/lib/tap/constants.rb +2 -2
- data/lib/tap/declarations.rb +211 -0
- data/lib/tap/env.rb +171 -193
- data/lib/tap/exe.rb +100 -21
- data/lib/tap/file_task.rb +3 -3
- data/lib/tap/generator/base.rb +1 -1
- data/lib/tap/generator/destroy.rb +10 -10
- data/lib/tap/generator/generate.rb +29 -18
- data/lib/tap/generator/generators/command/command_generator.rb +2 -2
- data/lib/tap/generator/generators/command/templates/command.erb +2 -2
- data/lib/tap/generator/generators/config/config_generator.rb +3 -3
- data/lib/tap/generator/generators/config/templates/doc.erb +1 -1
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
- data/lib/tap/generator/generators/file_task/templates/task.erb +1 -1
- data/lib/tap/generator/generators/file_task/templates/test.erb +1 -1
- data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
- data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
- data/lib/tap/generator/generators/root/root_generator.rb +13 -13
- data/lib/tap/generator/generators/root/templates/README +0 -0
- data/lib/tap/generator/generators/root/templates/Rakefile +2 -2
- data/lib/tap/generator/generators/root/templates/gemspec +4 -5
- data/lib/tap/generator/generators/root/templates/tapfile +11 -8
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
- data/lib/tap/generator/generators/task/task_generator.rb +1 -3
- data/lib/tap/generator/generators/task/templates/test.erb +1 -3
- data/lib/tap/patches/optparse/summarize.rb +62 -0
- data/lib/tap/root.rb +41 -29
- data/lib/tap/support/aggregator.rb +16 -3
- data/lib/tap/support/assignments.rb +10 -9
- data/lib/tap/support/audit.rb +58 -64
- data/lib/tap/support/class_configuration.rb +33 -44
- data/lib/tap/support/combinator.rb +125 -0
- data/lib/tap/support/configurable.rb +13 -14
- data/lib/tap/support/configurable_class.rb +21 -43
- data/lib/tap/support/configuration.rb +55 -9
- data/lib/tap/support/constant.rb +87 -13
- data/lib/tap/support/constant_manifest.rb +116 -0
- data/lib/tap/support/dependencies.rb +54 -0
- data/lib/tap/support/dependency.rb +44 -0
- data/lib/tap/support/executable.rb +247 -32
- data/lib/tap/support/executable_queue.rb +1 -1
- data/lib/tap/support/gems/rake.rb +29 -8
- data/lib/tap/support/gems.rb +10 -30
- data/lib/tap/support/instance_configuration.rb +29 -3
- data/lib/tap/support/intern.rb +46 -0
- data/lib/tap/support/join.rb +143 -0
- data/lib/tap/support/joins/fork.rb +19 -0
- data/lib/tap/support/joins/merge.rb +22 -0
- data/lib/tap/support/joins/sequence.rb +21 -0
- data/lib/tap/support/joins/switch.rb +25 -0
- data/lib/tap/support/joins/sync_merge.rb +63 -0
- data/lib/tap/support/joins.rb +15 -0
- data/lib/tap/support/lazy_attributes.rb +17 -2
- data/lib/tap/support/lazydoc/comment.rb +503 -0
- data/lib/tap/support/lazydoc/config.rb +17 -0
- data/lib/tap/support/lazydoc/definition.rb +36 -0
- data/lib/tap/support/lazydoc/document.rb +152 -0
- data/lib/tap/support/lazydoc/method.rb +24 -0
- data/lib/tap/support/lazydoc.rb +269 -343
- data/lib/tap/support/manifest.rb +121 -103
- data/lib/tap/support/minimap.rb +90 -0
- data/lib/tap/support/node.rb +56 -0
- data/lib/tap/support/parser.rb +436 -0
- data/lib/tap/support/schema.rb +359 -0
- data/lib/tap/support/shell_utils.rb +3 -5
- data/lib/tap/support/string_ext.rb +60 -0
- data/lib/tap/support/tdoc.rb +7 -2
- data/lib/tap/support/templater.rb +30 -16
- data/lib/tap/support/validation.rb +77 -8
- data/lib/tap/task.rb +431 -143
- data/lib/tap/tasks/dump.rb +15 -10
- data/lib/tap/tasks/load.rb +112 -0
- data/lib/tap/tasks/rake.rb +4 -41
- data/lib/tap/test/assertions.rb +38 -0
- data/lib/tap/test/env_vars.rb +1 -1
- data/lib/tap/test/extensions.rb +79 -0
- data/lib/tap/test/file_test.rb +420 -0
- data/lib/tap/test/file_test_class.rb +12 -0
- data/lib/tap/test/regexp_escape.rb +87 -0
- data/lib/tap/test/script_test.rb +46 -0
- data/lib/tap/test/script_tester.rb +115 -0
- data/lib/tap/test/subset_test.rb +260 -0
- data/lib/tap/test/subset_test_class.rb +99 -0
- data/lib/tap/test/{tap_methods.rb → tap_test.rb} +45 -43
- data/lib/tap/test/utils.rb +231 -0
- data/lib/tap/test.rb +53 -26
- data/lib/tap.rb +3 -20
- metadata +50 -27
- data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
- data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
- data/lib/tap/patches/rake/testtask.rb +0 -57
- data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
- data/lib/tap/patches/ruby19/parsedate.rb +0 -16
- data/lib/tap/support/batchable.rb +0 -47
- data/lib/tap/support/batchable_class.rb +0 -107
- data/lib/tap/support/command_line.rb +0 -98
- data/lib/tap/support/comment.rb +0 -270
- data/lib/tap/support/constant_utils.rb +0 -127
- data/lib/tap/support/declarations.rb +0 -111
- data/lib/tap/support/framework.rb +0 -83
- data/lib/tap/support/framework_class.rb +0 -180
- data/lib/tap/support/run_error.rb +0 -39
- data/lib/tap/support/summary.rb +0 -30
- data/lib/tap/test/file_methods.rb +0 -377
- data/lib/tap/test/script_methods/script_test.rb +0 -98
- data/lib/tap/test/script_methods.rb +0 -107
- data/lib/tap/test/subset_methods.rb +0 -420
- data/lib/tap/workflow.rb +0 -200
@@ -0,0 +1,359 @@
|
|
1
|
+
require 'tap/support/node'
|
2
|
+
require 'tap/support/joins'
|
3
|
+
autoload(:Shellwords, 'shellwords')
|
4
|
+
|
5
|
+
module Tap
|
6
|
+
module Support
|
7
|
+
autoload(:Parser, 'tap/support/parser')
|
8
|
+
|
9
|
+
class Schema
|
10
|
+
module Utils
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# Shell quotes the input string by enclosing in quotes if
|
14
|
+
# str has no quotes, or double quotes if str has no double
|
15
|
+
# quotes. Returns the str if it has not whitespace, quotes
|
16
|
+
# or double quotes.
|
17
|
+
#
|
18
|
+
# Raises an ArgumentError if str has both quotes and double
|
19
|
+
# quotes.
|
20
|
+
def shell_quote(str)
|
21
|
+
return str unless str =~ /[\s'"]/
|
22
|
+
|
23
|
+
quote = str.include?("'")
|
24
|
+
double_quote = str.include?('"')
|
25
|
+
|
26
|
+
case
|
27
|
+
when !quote then "'#{str}'"
|
28
|
+
when !double_quote then "\"#{str}\""
|
29
|
+
else raise ArgumentError, "cannot shell quote: #{str}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Formats a round string.
|
34
|
+
#
|
35
|
+
# format_round(1, [1,2,3]) # => "+1[1,2,3]"
|
36
|
+
#
|
37
|
+
def format_round(round, indicies)
|
38
|
+
"+#{round}[#{indicies.join(',')}]"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Formats a sequence string.
|
42
|
+
#
|
43
|
+
# format_sequence(1, [2,3], {}) # => "1:2:3"
|
44
|
+
#
|
45
|
+
def format_sequence(source_index, target_indicies, options)
|
46
|
+
([source_index] + target_indicies).join(":") + format_options(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Formats a global instance string.
|
50
|
+
#
|
51
|
+
# format_instance(1) # => "*1"
|
52
|
+
#
|
53
|
+
def format_instance(index)
|
54
|
+
"*#{index}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Formats a fork string.
|
58
|
+
#
|
59
|
+
# format_fork(1, [2,3],{}) # => "1[2,3]"
|
60
|
+
#
|
61
|
+
def format_fork(source_index, target_indicies, options)
|
62
|
+
"#{source_index}[#{target_indicies.join(',')}]#{format_options(options)}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Formats a merge string (note the target index is
|
66
|
+
# provided first).
|
67
|
+
#
|
68
|
+
# format_merge(1, [2,3],{}) # => "1{2,3}"
|
69
|
+
#
|
70
|
+
def format_merge(target_index, source_indicies, options)
|
71
|
+
"#{target_index}{#{source_indicies.join(',')}}#{format_options(options)}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Formats a sync_merge string (note the target index
|
75
|
+
# is provided first).
|
76
|
+
#
|
77
|
+
# format_sync_merge(1, [2,3],{}) # => "1(2,3)"
|
78
|
+
#
|
79
|
+
def format_sync_merge(target_index, source_indicies, options)
|
80
|
+
"#{target_index}(#{source_indicies.join(',')})#{format_options(options)}"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Formats an options hash into a string. Raises an error
|
84
|
+
# for unknown options.
|
85
|
+
#
|
86
|
+
# format_options({:iterate => true}) # => "i"
|
87
|
+
#
|
88
|
+
def format_options(options)
|
89
|
+
options_str = []
|
90
|
+
options.each_pair do |key, value|
|
91
|
+
unless index = Join::FLAGS.index(key)
|
92
|
+
raise "unknown key in: #{options} (#{key})"
|
93
|
+
end
|
94
|
+
|
95
|
+
if value
|
96
|
+
options_str << Join::SHORT_FLAGS[index]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
options_str.sort.join
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
include Utils
|
104
|
+
|
105
|
+
class << self
|
106
|
+
def parse(argv=ARGV)
|
107
|
+
Support::Parser.new(argv).schema
|
108
|
+
end
|
109
|
+
|
110
|
+
def load(argv)
|
111
|
+
parser = Parser.new
|
112
|
+
parser.load(argv)
|
113
|
+
parser.schema
|
114
|
+
end
|
115
|
+
|
116
|
+
def load_file(path)
|
117
|
+
argv = YAML.load_file(path)
|
118
|
+
load(argv)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# An array of the nodes registered in self.
|
123
|
+
attr_reader :nodes
|
124
|
+
|
125
|
+
def initialize(nodes=[])
|
126
|
+
@nodes = nodes
|
127
|
+
@current_index = 1
|
128
|
+
end
|
129
|
+
|
130
|
+
# Retrieves the node at index, or instantiates
|
131
|
+
# a new Node if one does not already exists.
|
132
|
+
def [](index)
|
133
|
+
nodes[index] ||= Node.new
|
134
|
+
end
|
135
|
+
|
136
|
+
# Sets a join between the source and targets.
|
137
|
+
# Returns the new join.
|
138
|
+
def set(join_class, source_indicies, target_indicies, options={})
|
139
|
+
join = join_class.new(options)
|
140
|
+
|
141
|
+
[*source_indicies].each {|source_index| self[source_index].output = join }
|
142
|
+
[*target_indicies].each {|target_index| self[target_index].input = join }
|
143
|
+
|
144
|
+
join
|
145
|
+
end
|
146
|
+
|
147
|
+
# Removes all nil nodes, and nodes with empty argvs.
|
148
|
+
# Additionally reassigns rounds by shifting later
|
149
|
+
# rounds up to fill any nils in the rounds array.
|
150
|
+
#
|
151
|
+
# Returns self.
|
152
|
+
def compact
|
153
|
+
# remove nil and empty nodes
|
154
|
+
nodes.delete_if do |node|
|
155
|
+
node == nil || node.argv.empty?
|
156
|
+
end
|
157
|
+
|
158
|
+
# reassign rounds
|
159
|
+
index = 0
|
160
|
+
rounds.compact.each do |round|
|
161
|
+
round.each {|node| node.round = index }
|
162
|
+
index += 1
|
163
|
+
end
|
164
|
+
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns an array of the argvs for each nodes.
|
169
|
+
def argvs
|
170
|
+
nodes.collect do |node|
|
171
|
+
node == nil ? nil : node.argv
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns a collection of nodes sorted
|
176
|
+
# into arrays by node.round.
|
177
|
+
def rounds(as_indicies=false)
|
178
|
+
rounds = []
|
179
|
+
nodes.each do |node|
|
180
|
+
(rounds[node.round] ||= []) << node if node && node.round
|
181
|
+
end
|
182
|
+
|
183
|
+
rounds.each do |round|
|
184
|
+
next unless round
|
185
|
+
round.collect! {|node| nodes.index(node) }
|
186
|
+
end if as_indicies
|
187
|
+
|
188
|
+
rounds
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns a collection of global nodes
|
192
|
+
# (nodes with no input or output set).
|
193
|
+
def globals(as_indicies=false)
|
194
|
+
globals = []
|
195
|
+
nodes.each do |node|
|
196
|
+
globals << node if node && node.global?
|
197
|
+
end
|
198
|
+
|
199
|
+
globals.collect! do |node|
|
200
|
+
nodes.index(node)
|
201
|
+
end if as_indicies
|
202
|
+
|
203
|
+
globals
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns a hash of [join, [source_node, target_nodes]] pairs
|
207
|
+
# across all nodes.
|
208
|
+
def joins(as_indicies=false)
|
209
|
+
joins = {}
|
210
|
+
nodes.each do |node|
|
211
|
+
next unless node
|
212
|
+
|
213
|
+
case node.input
|
214
|
+
when Join, ReverseJoin
|
215
|
+
(joins[node.input] ||= [nil,[]])[1] << node
|
216
|
+
end
|
217
|
+
|
218
|
+
case node.output
|
219
|
+
when Join, ReverseJoin
|
220
|
+
(joins[node.output] ||= [nil,[]])[0] = node
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
if as_indicies
|
225
|
+
summary = []
|
226
|
+
joins.each_pair do |join, (source_node, target_nodes)|
|
227
|
+
target_indicies = target_nodes.collect {|node| nodes.index(node) }
|
228
|
+
summary << [join.name, nodes.index(source_node), target_indicies, join.options]
|
229
|
+
end
|
230
|
+
|
231
|
+
summary.sort_by {|entry| entry[1] || -1 }
|
232
|
+
else
|
233
|
+
joins
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def build(app)
|
238
|
+
tasks = {}
|
239
|
+
|
240
|
+
# instantiate the nodes
|
241
|
+
nodes.each do |node|
|
242
|
+
tasks[node] = yield(node.argv) if node
|
243
|
+
end
|
244
|
+
|
245
|
+
# instantiate and reconfigure globals
|
246
|
+
instances = []
|
247
|
+
globals.each do |node|
|
248
|
+
task, args = tasks.delete(node)
|
249
|
+
instance = task.class.instance
|
250
|
+
|
251
|
+
if instances.include?(instance)
|
252
|
+
raise "global specified multple times: #{instance}"
|
253
|
+
end
|
254
|
+
|
255
|
+
instance.reconfigure(task.config.to_hash)
|
256
|
+
instance.enq(*args)
|
257
|
+
instances << instance
|
258
|
+
end
|
259
|
+
|
260
|
+
# build the workflow
|
261
|
+
joins.each_pair do |join, (source_node, target_nodes)|
|
262
|
+
raise "unassigned join: #{join}" if source_node == nil
|
263
|
+
|
264
|
+
targets = target_nodes.collect do |target_node|
|
265
|
+
tasks[target_node][0]
|
266
|
+
end
|
267
|
+
source = tasks[source_node][0]
|
268
|
+
|
269
|
+
join.join(source, targets)
|
270
|
+
end
|
271
|
+
|
272
|
+
# build queues
|
273
|
+
queues = rounds.compact.collect do |round|
|
274
|
+
round.each do |node|
|
275
|
+
task, args = tasks.delete(node)
|
276
|
+
task.enq(*args)
|
277
|
+
end
|
278
|
+
|
279
|
+
app.queue.clear
|
280
|
+
end
|
281
|
+
|
282
|
+
# notify any args that will be overlooked
|
283
|
+
tasks.each_pair do |node, (task, args)|
|
284
|
+
next if args.empty?
|
285
|
+
warn "warning: ignoring args for node (#{nodes.index(node)}) #{task} [#{args.join(' ')}]"
|
286
|
+
end
|
287
|
+
|
288
|
+
queues
|
289
|
+
end
|
290
|
+
|
291
|
+
# Creates an array dump of the contents of self.
|
292
|
+
def dump
|
293
|
+
segments = argvs
|
294
|
+
each_schema_str {|str| segments << str }
|
295
|
+
segments
|
296
|
+
end
|
297
|
+
|
298
|
+
# Constructs a command-line string for the schema, ex:
|
299
|
+
# '-- a -- b --0:1'.
|
300
|
+
def to_s
|
301
|
+
segments = []
|
302
|
+
nodes.each do |node|
|
303
|
+
segments << "--"
|
304
|
+
|
305
|
+
node.argv.each do |arg|
|
306
|
+
segments << shell_quote(arg)
|
307
|
+
end unless node == nil
|
308
|
+
end
|
309
|
+
|
310
|
+
each_schema_str {|str| segments << "--#{str}" }
|
311
|
+
segments.join(' ')
|
312
|
+
end
|
313
|
+
|
314
|
+
protected
|
315
|
+
|
316
|
+
# Yields each formatted schema string (global, round, and join).
|
317
|
+
def each_schema_str # :nodoc:
|
318
|
+
each_globals_str {|str| yield str }
|
319
|
+
each_round_str {|str| yield str }
|
320
|
+
each_join_str {|str| yield str }
|
321
|
+
end
|
322
|
+
|
323
|
+
# Yields globals formatted as a string.
|
324
|
+
def each_globals_str # :nodoc:
|
325
|
+
globals.each do |node|
|
326
|
+
yield format_instance(nodes.index(node))
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Yields each round formatted as a string.
|
331
|
+
def each_round_str # :nodoc
|
332
|
+
index = 0
|
333
|
+
rounds.each do |indicies|
|
334
|
+
unless indicies == nil || index == 0
|
335
|
+
indicies = indicies.collect {|node| nodes.index(node) }
|
336
|
+
yield format_round(index, indicies)
|
337
|
+
end
|
338
|
+
index += 1
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Yields each join formatted as a string.
|
343
|
+
def each_join_str # :nodoc
|
344
|
+
joins.each_pair do |join, (source_node, target_nodes)|
|
345
|
+
source_index = nodes.index(source_node)
|
346
|
+
target_indicies = target_nodes.collect {|node| nodes.index(node) }
|
347
|
+
|
348
|
+
yield case join
|
349
|
+
when Joins::Sequence then format_sequence(source_index, target_indicies, join.options)
|
350
|
+
when Joins::Fork then format_fork(source_index, target_indicies, join.options)
|
351
|
+
when Joins::Merge then format_merge(source_index, target_indicies, join.options)
|
352
|
+
when Joins::SyncMerge then format_sync_merge(source_index, target_indicies, join.options)
|
353
|
+
else raise "unknown join type: #{join.class} (#{source_index}, [#{target_indicies.join(',')}])"
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
@@ -4,8 +4,7 @@ module Tap
|
|
4
4
|
module Support
|
5
5
|
# Provides several shell utility methods for calling programs.
|
6
6
|
#
|
7
|
-
# == Windows
|
8
|
-
# A couple warnings when running shell commands in the MSDOS prompt on Windows.
|
7
|
+
# == Windows
|
9
8
|
# MSDOS has command line length limits specific to the version of Windows being
|
10
9
|
# run (from http://www.ss64.com/nt/cmd.html):
|
11
10
|
#
|
@@ -13,9 +12,8 @@ module Tap
|
|
13
12
|
# Windows 2000:: 2046 characters
|
14
13
|
# Windows XP:: 8190 characters
|
15
14
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
15
|
+
# Commands longer than these limits fail, usually with something like: 'the input
|
16
|
+
# line is too long'
|
19
17
|
module ShellUtils
|
20
18
|
|
21
19
|
module_function
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
|
4
|
+
# StringExt provides two common string transformations, camelize and
|
5
|
+
# underscore. StringExt is automatically included in String.
|
6
|
+
#
|
7
|
+
# Both methods are directly taken from the ActiveSupport {Inflections}[http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/String/Inflections.html]
|
8
|
+
# module. StringExt should not cause conflicts if ActiveSupport is
|
9
|
+
# loaded alongside Tap.
|
10
|
+
#
|
11
|
+
# ActiveSupport is distributed with an MIT-LICENSE:
|
12
|
+
#
|
13
|
+
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
14
|
+
#
|
15
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
16
|
+
# associated documentation files (the "Software"), to deal in the Software without restriction,
|
17
|
+
# including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
18
|
+
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
19
|
+
# subject to the following conditions:
|
20
|
+
#
|
21
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial
|
22
|
+
# portions of the Software.
|
23
|
+
#
|
24
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
25
|
+
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
26
|
+
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
27
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
28
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
29
|
+
#
|
30
|
+
module StringExt
|
31
|
+
|
32
|
+
# camelize converts self to UpperCamelCase. If the argument to
|
33
|
+
# camelize is set to :lower then camelize produces lowerCamelCase.
|
34
|
+
# camelize will also convert '/' to '::' which is useful for
|
35
|
+
# converting paths to namespaces.
|
36
|
+
def camelize(first_letter = :upper)
|
37
|
+
case first_letter
|
38
|
+
when :upper then self.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
39
|
+
when :lower then self.first + camelize[1..-1]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# The reverse of camelize. Makes an underscored, lowercase form
|
44
|
+
# from self. underscore will also change '::' to '/' to convert
|
45
|
+
# namespaces to paths.
|
46
|
+
def underscore
|
47
|
+
self.gsub(/::/, '/').
|
48
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
49
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
50
|
+
tr("-", "_").
|
51
|
+
downcase
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class String # :nodoc:
|
59
|
+
include Tap::Support::StringExt
|
60
|
+
end
|
data/lib/tap/support/tdoc.rb
CHANGED
@@ -47,6 +47,11 @@ else
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
+
unless Object.const_defined?(:TokenStream)
|
51
|
+
TokenStream = RDoc::TokenStream
|
52
|
+
Options = RDoc::Options
|
53
|
+
end
|
54
|
+
|
50
55
|
module Tap
|
51
56
|
module Support
|
52
57
|
|
@@ -79,7 +84,7 @@ module Tap
|
|
79
84
|
# Now execute the rake task like:
|
80
85
|
#
|
81
86
|
# % rake rdoc
|
82
|
-
|
87
|
+
#--
|
83
88
|
# === Implementation
|
84
89
|
# RDoc is a beast to utilize in a non-standard way. One way to make RDoc parse unexpected
|
85
90
|
# flags like 'config' or 'config_attr' is to use the '--accessor' option (see 'rdoc --help' or
|
@@ -129,7 +134,7 @@ module Tap
|
|
129
134
|
|
130
135
|
# Encasulates information about the configuration. Designed to be utilized
|
131
136
|
# by the TDocHTMLGenerator as similarly as possible to standard attributes.
|
132
|
-
class ConfigAttr < RDoc::Attr
|
137
|
+
class ConfigAttr < RDoc::Attr # :nodoc:
|
133
138
|
# Contains the actual declaration for the config attribute. ex: "c [:key, 'value'] # comment"
|
134
139
|
attr_accessor :config_declaration, :default
|
135
140
|
|
@@ -5,10 +5,9 @@ module Tap
|
|
5
5
|
module Support
|
6
6
|
|
7
7
|
# Templater is a convenience class for creating ERB templates. As
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# Templater (and hence all the assigned attributes) are available
|
11
|
-
# in the template.
|
8
|
+
# a subclass of OpenStruct, attributes can be assigned/unassigned
|
9
|
+
# directly. When the template is built, all the method of
|
10
|
+
# Templater (and hence all the assigned attributes) are available.
|
12
11
|
#
|
13
12
|
# t = Templater.new( "key: <%= value %>")
|
14
13
|
# t.value = "default"
|
@@ -23,16 +22,14 @@ module Tap
|
|
23
22
|
#
|
24
23
|
# Templater hooks into the ERB templating mechanism by providing itself
|
25
24
|
# as the ERB output target (_erbout). ERB concatenates each line of an
|
26
|
-
# ERB template to _erbout, as can be seen
|
27
|
-
# evaluated by ERB:
|
25
|
+
# ERB template to _erbout, as can be seen here:
|
28
26
|
#
|
29
27
|
# e = ERB.new("<%= 1 + 2 %>")
|
30
28
|
# e.src # => "_erbout = ''; _erbout.concat(( 1 + 2 ).to_s); _erbout"
|
31
29
|
#
|
32
|
-
# By setting itself as _erbout, instances of Templater can redirect
|
33
|
-
# output to a temporary target
|
34
|
-
#
|
35
|
-
# nested content:
|
30
|
+
# By setting itself as _erbout, instances of Templater can redirect
|
31
|
+
# output to a temporary target and perform string transformations.
|
32
|
+
# For example, redirection allows indentation of nested content:
|
36
33
|
#
|
37
34
|
# template = %Q{
|
38
35
|
# # Un-nested content
|
@@ -62,10 +59,15 @@ module Tap
|
|
62
59
|
# yamlize converts the object to YAML (using to_yaml), omitting
|
63
60
|
# the header and final newline:
|
64
61
|
#
|
65
|
-
|
66
|
-
|
62
|
+
# {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
|
63
|
+
# yamlize {'key' => 'value'} # => "key: value"
|
67
64
|
def yamlize(object)
|
68
|
-
|
65
|
+
object == nil ? "~" : YAML.dump(object)[4...-1].strip
|
66
|
+
end
|
67
|
+
|
68
|
+
# Comments out the string.
|
69
|
+
def comment(str)
|
70
|
+
str.split("\n").collect {|line| "# #{line}" }.join("\n")
|
69
71
|
end
|
70
72
|
|
71
73
|
# Nest the return of the block in the nesting lines.
|
@@ -117,6 +119,12 @@ module Tap
|
|
117
119
|
end
|
118
120
|
end
|
119
121
|
|
122
|
+
class << self
|
123
|
+
def build(template, attributes={})
|
124
|
+
new(template, attributes).build
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
120
128
|
include Utils
|
121
129
|
|
122
130
|
# Initialized a new Templater. An ERB or String may be provided as the
|
@@ -124,8 +132,10 @@ module Tap
|
|
124
132
|
# ERB with a trim_mode of "<>".
|
125
133
|
def initialize(template, attributes={})
|
126
134
|
@template = case template
|
127
|
-
when ERB
|
128
|
-
|
135
|
+
when ERB
|
136
|
+
# matching w/wo the coding effectively checks @src
|
137
|
+
# across ruby versions (encoding appears in 1.9)
|
138
|
+
if template.instance_variable_get(:@src) !~ /^(#coding:US-ASCII\n)?_erbout =/
|
129
139
|
raise ArgumentError, "Templater does not work with ERB templates where eoutvar != '_erbout'"
|
130
140
|
end
|
131
141
|
template
|
@@ -170,7 +180,11 @@ module Tap
|
|
170
180
|
|
171
181
|
# Build the template. All methods of self will be
|
172
182
|
# accessible in the template.
|
173
|
-
def build
|
183
|
+
def build(attrs={})
|
184
|
+
attrs.each_pair do |key, value|
|
185
|
+
send("#{key}=", value)
|
186
|
+
end
|
187
|
+
|
174
188
|
@template.result(binding)
|
175
189
|
@_erbout
|
176
190
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
autoload(:PP, 'pp')
|
2
|
+
|
1
3
|
module Tap
|
2
4
|
module Support
|
3
5
|
|
4
6
|
# Validation generates blocks for common validations and transformations of
|
5
|
-
# configurations set through Configurable. In general these blocks
|
6
|
-
#
|
7
|
-
#
|
7
|
+
# configurations set through Configurable. In general these blocks load
|
8
|
+
# string inputs as YAML and valdiate the results; non-string inputs are
|
9
|
+
# simply validated.
|
8
10
|
#
|
9
11
|
# integer = Validation.integer
|
10
12
|
# integer.class # => Proc
|
@@ -21,7 +23,6 @@ module Tap
|
|
21
23
|
#
|
22
24
|
# This syntax plays well with RDoc, which otherwise gets jacked
|
23
25
|
# when you do it all in one step.
|
24
|
-
#++
|
25
26
|
module Validation
|
26
27
|
|
27
28
|
# Raised when Validation blocks fail.
|
@@ -202,9 +203,11 @@ module Tap
|
|
202
203
|
def boolean(); BOOLEAN; end
|
203
204
|
BOOLEAN = yamlize_and_check(true, false, nil)
|
204
205
|
|
206
|
+
# Same as boolean.
|
205
207
|
def switch(); SWITCH; end
|
206
208
|
SWITCH = yamlize_and_check(true, false, nil)
|
207
209
|
|
210
|
+
# Same as boolean.
|
208
211
|
def flag(); FLAG; end
|
209
212
|
FLAG = yamlize_and_check(true, false, nil)
|
210
213
|
|
@@ -227,13 +230,20 @@ module Tap
|
|
227
230
|
def array_or_nil(); ARRAY_OR_NIL; end
|
228
231
|
ARRAY_OR_NIL = yamlize_and_check(Array, nil)
|
229
232
|
|
233
|
+
# Returns a block that checks the input is an array.
|
234
|
+
# If the input is a string the string is split along
|
235
|
+
# commas and each value yamlized into an array.
|
236
|
+
#
|
237
|
+
# list.class # => Proc
|
238
|
+
# list.call([1,2,3]) # => [1,2,3]
|
239
|
+
# list.call('1,2,3') # => [1,2,3]
|
240
|
+
# list.call('str') # => ['str']
|
241
|
+
# list.call(nil) # => ValidationError
|
242
|
+
#
|
230
243
|
def list(); LIST; end
|
231
244
|
list_block = lambda do |input|
|
232
245
|
if input.kind_of?(String)
|
233
|
-
input =
|
234
|
-
when Array then processed_input
|
235
|
-
else input.split(/,/).collect {|arg| yamlize(arg) }
|
236
|
-
end
|
246
|
+
input = input.split(/,/).collect {|arg| yamlize(arg) }
|
237
247
|
end
|
238
248
|
|
239
249
|
validate(input, [Array])
|
@@ -405,6 +415,65 @@ module Tap
|
|
405
415
|
end
|
406
416
|
RANGE_OR_NIL = range_or_nil_block
|
407
417
|
|
418
|
+
# Returns a block that checks the input is a Time. String inputs are
|
419
|
+
# loaded using Time.parse and then converted into times. Parsed times
|
420
|
+
# are local unless specified otherwise.
|
421
|
+
#
|
422
|
+
# time.class # => Proc
|
423
|
+
#
|
424
|
+
# now = Time.now
|
425
|
+
# time.call(now) # => now
|
426
|
+
#
|
427
|
+
# time.call('2008-08-08 20:00:00.00 +08:00').getutc.strftime('%Y/%m/%d %H:%M:%S')
|
428
|
+
# # => '2008/08/08 12:00:00'
|
429
|
+
#
|
430
|
+
# time.call('2008-08-08').strftime('%Y/%m/%d %H:%M:%S')
|
431
|
+
# # => '2008/08/08 00:00:00'
|
432
|
+
#
|
433
|
+
# time.call(1) # => ValidationError
|
434
|
+
# time.call(nil) # => ValidationError
|
435
|
+
#
|
436
|
+
# Warning: Time.parse will parse a valid time (Time.now)
|
437
|
+
# even when no time is specified:
|
438
|
+
#
|
439
|
+
# time.call('str').strftime('%Y/%m/%d %H:%M:%S')
|
440
|
+
# # => Time.now.strftime('%Y/%m/%d %H:%M:%S')
|
441
|
+
#
|
442
|
+
def time()
|
443
|
+
# adding this here is a compromise to lazy-load the parse
|
444
|
+
# method (autoload doesn't work since Time already exists)
|
445
|
+
require 'time' unless Time.respond_to?(:parse)
|
446
|
+
TIME
|
447
|
+
end
|
448
|
+
|
449
|
+
time_block = lambda do |input|
|
450
|
+
input = Time.parse(input) if input.kind_of?(String)
|
451
|
+
validate(input, [Time])
|
452
|
+
end
|
453
|
+
TIME = time_block
|
454
|
+
|
455
|
+
# Same as time but allows nil:
|
456
|
+
#
|
457
|
+
# time_or_nil.call('~') # => nil
|
458
|
+
# time_or_nil.call(nil) # => nil
|
459
|
+
def time_or_nil()
|
460
|
+
# adding this check is a compromise to autoload the parse
|
461
|
+
# method (autoload doesn't work since Time already exists)
|
462
|
+
require 'time' unless Time.respond_to?(:parse)
|
463
|
+
TIME_OR_NIL
|
464
|
+
end
|
465
|
+
|
466
|
+
time_or_nil_block = lambda do |input|
|
467
|
+
input = case input
|
468
|
+
when nil, '~' then nil
|
469
|
+
when String then Time.parse(input)
|
470
|
+
else input
|
471
|
+
end
|
472
|
+
|
473
|
+
validate(input, [Time, nil])
|
474
|
+
end
|
475
|
+
TIME_OR_NIL = time_or_nil_block
|
476
|
+
|
408
477
|
end
|
409
478
|
end
|
410
479
|
end
|