tap 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/History +12 -0
  2. data/MIT-LICENSE +0 -2
  3. data/README +23 -32
  4. data/bin/rap +116 -0
  5. data/bin/tap +6 -9
  6. data/cgi/run.rb +67 -0
  7. data/cmd/console.rb +1 -1
  8. data/cmd/destroy.rb +4 -4
  9. data/cmd/generate.rb +4 -4
  10. data/cmd/manifest.rb +61 -53
  11. data/cmd/run.rb +8 -75
  12. data/doc/Class Reference +130 -121
  13. data/doc/Command Reference +76 -124
  14. data/doc/Syntax Reference +290 -0
  15. data/doc/Tutorial +305 -237
  16. data/lib/tap/app.rb +140 -467
  17. data/lib/tap/constants.rb +2 -2
  18. data/lib/tap/declarations.rb +211 -0
  19. data/lib/tap/env.rb +171 -193
  20. data/lib/tap/exe.rb +100 -21
  21. data/lib/tap/file_task.rb +3 -3
  22. data/lib/tap/generator/base.rb +1 -1
  23. data/lib/tap/generator/destroy.rb +10 -10
  24. data/lib/tap/generator/generate.rb +29 -18
  25. data/lib/tap/generator/generators/command/command_generator.rb +2 -2
  26. data/lib/tap/generator/generators/command/templates/command.erb +2 -2
  27. data/lib/tap/generator/generators/config/config_generator.rb +3 -3
  28. data/lib/tap/generator/generators/config/templates/doc.erb +1 -1
  29. data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
  30. data/lib/tap/generator/generators/file_task/templates/task.erb +1 -1
  31. data/lib/tap/generator/generators/file_task/templates/test.erb +1 -1
  32. data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
  33. data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
  34. data/lib/tap/generator/generators/root/root_generator.rb +13 -13
  35. data/lib/tap/generator/generators/root/templates/README +0 -0
  36. data/lib/tap/generator/generators/root/templates/Rakefile +2 -2
  37. data/lib/tap/generator/generators/root/templates/gemspec +4 -5
  38. data/lib/tap/generator/generators/root/templates/tapfile +11 -8
  39. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  40. data/lib/tap/generator/generators/task/task_generator.rb +1 -3
  41. data/lib/tap/generator/generators/task/templates/test.erb +1 -3
  42. data/lib/tap/patches/optparse/summarize.rb +62 -0
  43. data/lib/tap/root.rb +41 -29
  44. data/lib/tap/support/aggregator.rb +16 -3
  45. data/lib/tap/support/assignments.rb +10 -9
  46. data/lib/tap/support/audit.rb +58 -64
  47. data/lib/tap/support/class_configuration.rb +33 -44
  48. data/lib/tap/support/combinator.rb +125 -0
  49. data/lib/tap/support/configurable.rb +13 -14
  50. data/lib/tap/support/configurable_class.rb +21 -43
  51. data/lib/tap/support/configuration.rb +55 -9
  52. data/lib/tap/support/constant.rb +87 -13
  53. data/lib/tap/support/constant_manifest.rb +116 -0
  54. data/lib/tap/support/dependencies.rb +54 -0
  55. data/lib/tap/support/dependency.rb +44 -0
  56. data/lib/tap/support/executable.rb +247 -32
  57. data/lib/tap/support/executable_queue.rb +1 -1
  58. data/lib/tap/support/gems/rake.rb +29 -8
  59. data/lib/tap/support/gems.rb +10 -30
  60. data/lib/tap/support/instance_configuration.rb +29 -3
  61. data/lib/tap/support/intern.rb +46 -0
  62. data/lib/tap/support/join.rb +143 -0
  63. data/lib/tap/support/joins/fork.rb +19 -0
  64. data/lib/tap/support/joins/merge.rb +22 -0
  65. data/lib/tap/support/joins/sequence.rb +21 -0
  66. data/lib/tap/support/joins/switch.rb +25 -0
  67. data/lib/tap/support/joins/sync_merge.rb +63 -0
  68. data/lib/tap/support/joins.rb +15 -0
  69. data/lib/tap/support/lazy_attributes.rb +17 -2
  70. data/lib/tap/support/lazydoc/comment.rb +503 -0
  71. data/lib/tap/support/lazydoc/config.rb +17 -0
  72. data/lib/tap/support/lazydoc/definition.rb +36 -0
  73. data/lib/tap/support/lazydoc/document.rb +152 -0
  74. data/lib/tap/support/lazydoc/method.rb +24 -0
  75. data/lib/tap/support/lazydoc.rb +269 -343
  76. data/lib/tap/support/manifest.rb +121 -103
  77. data/lib/tap/support/minimap.rb +90 -0
  78. data/lib/tap/support/node.rb +56 -0
  79. data/lib/tap/support/parser.rb +436 -0
  80. data/lib/tap/support/schema.rb +359 -0
  81. data/lib/tap/support/shell_utils.rb +3 -5
  82. data/lib/tap/support/string_ext.rb +60 -0
  83. data/lib/tap/support/tdoc.rb +7 -2
  84. data/lib/tap/support/templater.rb +30 -16
  85. data/lib/tap/support/validation.rb +77 -8
  86. data/lib/tap/task.rb +431 -143
  87. data/lib/tap/tasks/dump.rb +15 -10
  88. data/lib/tap/tasks/load.rb +112 -0
  89. data/lib/tap/tasks/rake.rb +4 -41
  90. data/lib/tap/test/assertions.rb +38 -0
  91. data/lib/tap/test/env_vars.rb +1 -1
  92. data/lib/tap/test/extensions.rb +79 -0
  93. data/lib/tap/test/file_test.rb +420 -0
  94. data/lib/tap/test/file_test_class.rb +12 -0
  95. data/lib/tap/test/regexp_escape.rb +87 -0
  96. data/lib/tap/test/script_test.rb +46 -0
  97. data/lib/tap/test/script_tester.rb +115 -0
  98. data/lib/tap/test/subset_test.rb +260 -0
  99. data/lib/tap/test/subset_test_class.rb +99 -0
  100. data/lib/tap/test/{tap_methods.rb → tap_test.rb} +45 -43
  101. data/lib/tap/test/utils.rb +231 -0
  102. data/lib/tap/test.rb +53 -26
  103. data/lib/tap.rb +3 -20
  104. metadata +50 -27
  105. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
  106. data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
  107. data/lib/tap/patches/rake/testtask.rb +0 -57
  108. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
  109. data/lib/tap/patches/ruby19/parsedate.rb +0 -16
  110. data/lib/tap/support/batchable.rb +0 -47
  111. data/lib/tap/support/batchable_class.rb +0 -107
  112. data/lib/tap/support/command_line.rb +0 -98
  113. data/lib/tap/support/comment.rb +0 -270
  114. data/lib/tap/support/constant_utils.rb +0 -127
  115. data/lib/tap/support/declarations.rb +0 -111
  116. data/lib/tap/support/framework.rb +0 -83
  117. data/lib/tap/support/framework_class.rb +0 -180
  118. data/lib/tap/support/run_error.rb +0 -39
  119. data/lib/tap/support/summary.rb +0 -30
  120. data/lib/tap/test/file_methods.rb +0 -377
  121. data/lib/tap/test/script_methods/script_test.rb +0 -98
  122. data/lib/tap/test/script_methods.rb +0 -107
  123. data/lib/tap/test/subset_methods.rb +0 -420
  124. 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
- # No word on more recent versions of Windows. Commands longer than these limits
17
- # fail, usually with something like: 'the input line is too long'
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
@@ -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
- # an OpenStruct, attributes can be assigned/unassigned at will to
9
- # a Templater. When the template is built, all the method of
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 when you look at the src code
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 the
33
- # output to a temporary target which can then be used in string
34
- # transformations. For example, redirection allows indentation of
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
- # {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
66
- # yamlize {'key' => 'value'} # => "key: value"
62
+ # {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
63
+ # yamlize {'key' => 'value'} # => "key: value"
67
64
  def yamlize(object)
68
- object.to_yaml[5...-1]
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
- if template.instance_variable_get(:@src).index('_erbout =') != 0
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 allow
6
- # configurations to be set to objects of a particular class, or to a string
7
- # that can be loaded as YAML into such an object.
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 = case processed_input = yamlize(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