bahuvrihi-tap 0.10.4 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/bin/rap +112 -0
  2. data/bin/tap +21 -10
  3. data/cmd/destroy.rb +1 -1
  4. data/cmd/generate.rb +1 -1
  5. data/cmd/run.rb +4 -48
  6. data/cmd/server.rb +3 -1
  7. data/lib/tap/constants.rb +1 -1
  8. data/lib/tap/env.rb +37 -39
  9. data/lib/tap/exe.rb +59 -29
  10. data/lib/tap/generator/base.rb +1 -1
  11. data/lib/tap/generator/generators/config/templates/doc.erb +1 -1
  12. data/lib/tap/generator/generators/file_task/templates/test.erb +1 -1
  13. data/lib/tap/generator/generators/root/templates/README +0 -0
  14. data/lib/tap/generator/generators/root/templates/gemspec +3 -4
  15. data/lib/tap/generator/generators/root/templates/tapfile +3 -3
  16. data/lib/tap/parser.rb +35 -0
  17. data/lib/tap/patches/optparse/summarize.rb +62 -0
  18. data/lib/tap/root.rb +24 -18
  19. data/lib/tap/support/class_configuration.rb +1 -1
  20. data/lib/tap/support/configurable_class.rb +3 -1
  21. data/lib/tap/support/configuration.rb +19 -0
  22. data/lib/tap/support/constant.rb +14 -2
  23. data/lib/tap/support/declarations.rb +33 -39
  24. data/lib/tap/support/dependable.rb +21 -2
  25. data/lib/tap/support/gems.rb +4 -30
  26. data/lib/tap/support/gems/rack.rb +14 -11
  27. data/lib/tap/support/lazy_attributes.rb +1 -1
  28. data/lib/tap/support/lazydoc.rb +257 -340
  29. data/lib/tap/support/lazydoc/comment.rb +499 -0
  30. data/lib/tap/support/lazydoc/config.rb +17 -0
  31. data/lib/tap/support/lazydoc/declaration.rb +20 -0
  32. data/lib/tap/support/lazydoc/document.rb +118 -0
  33. data/lib/tap/support/lazydoc/method.rb +24 -0
  34. data/lib/tap/support/manifest.rb +33 -4
  35. data/lib/tap/support/validation.rb +56 -0
  36. data/lib/tap/task.rb +46 -44
  37. data/lib/tap/tasks/dump.rb +15 -10
  38. data/lib/tap/tasks/load.rb +25 -0
  39. data/lib/tap/tasks/rake.rb +2 -2
  40. data/lib/tap/test.rb +55 -36
  41. data/lib/tap/test/file_methods.rb +204 -178
  42. data/lib/tap/test/file_methods_class.rb +4 -18
  43. data/lib/tap/test/script_methods.rb +76 -90
  44. data/lib/tap/test/script_methods/regexp_escape.rb +92 -0
  45. data/lib/tap/test/script_methods/script_test.rb +4 -2
  46. data/lib/tap/test/subset_methods.rb +46 -49
  47. data/lib/tap/test/subset_methods_class.rb +17 -54
  48. data/lib/tap/test/tap_methods.rb +1 -5
  49. data/lib/tap/test/utils.rb +142 -32
  50. metadata +12 -3
  51. data/lib/tap/support/command_line.rb +0 -55
  52. data/lib/tap/support/comment.rb +0 -270
@@ -0,0 +1,499 @@
1
+ require 'strscan'
2
+
3
+ module Tap
4
+ module Support
5
+ module Lazydoc
6
+ # Comment represents a code comment parsed by Lazydoc. Comments consist
7
+ # of a subject and the content of the comment which normally break down
8
+ # like this:
9
+ #
10
+ # sample_comment = %Q{
11
+ # # this is the content of the comment
12
+ # #
13
+ # # which may stretch across
14
+ # # multiple lines
15
+ # this is the subject
16
+ # }
17
+ #
18
+ # The subject of a comment is the first non-comment line following the
19
+ # content, and the content is an array of comment fragments organized
20
+ # by line as they would be printed in an output:
21
+ #
22
+ # c = Comment.parse(sample_comment)
23
+ # c.subject # => "this is the subject"
24
+ # c.content
25
+ # # => [
26
+ # # ["this is the content of the comment"],
27
+ # # [""],
28
+ # # ["which may stretch across", "multiple lines"]]
29
+ #
30
+ # When pulling comments out of a document, comments may be initialized
31
+ # to the line of the subject and then resolved using the document:
32
+ #
33
+ # document = %Q{
34
+ # module Sample
35
+ # # this is the content of the comment
36
+ # # for method_one
37
+ # def method_one
38
+ # end
39
+ #
40
+ # # this is the content of the comment
41
+ # # for method_two
42
+ # def method_two
43
+ # end
44
+ # end}
45
+ #
46
+ # # resolve will split the document, but
47
+ # # splitting once beforehand is more efficient
48
+ # lines = document.split(/\r?\n/)
49
+ #
50
+ # c1 = Comment.new(4).resolve(lines)
51
+ # c1.subject # => " def method_one"
52
+ # c1.content # => [["this is the content of the comment", "for method_one"]]
53
+ #
54
+ # c2 = Comment.new(9).resolve(lines)
55
+ # c2.subject # => " def method_two"
56
+ # c2.content # => [["this is the content of the comment", "for method_two"]]
57
+ #
58
+ class Comment
59
+
60
+ class << self
61
+
62
+ # Parses the input string into a comment, stopping at end_regexp
63
+ # or the first non-comment line. Also parses the next non-comment
64
+ # line as the comment subject. Takes a string or a StringScanner
65
+ # and returns the new comment.
66
+ #
67
+ # comment_string = %Q{
68
+ # # comments spanning multiple
69
+ # # lines are collected
70
+ # #
71
+ # # while indented lines
72
+ # # are preserved individually
73
+ # #
74
+ # this is the subject line
75
+ #
76
+ # # this line is not parsed as it
77
+ # # is after a non-comment line
78
+ # }
79
+ #
80
+ # c = Comment.parse(comment_string)
81
+ # c.content
82
+ # # => [
83
+ # # ['comments spanning multiple', 'lines are collected'],
84
+ # # [''],
85
+ # # [' while indented lines'],
86
+ # # [' are preserved individually'],
87
+ # # [''],
88
+ # # []]
89
+ # c.subject # => "this is the subject line"
90
+ #
91
+ # Parsing may be manually ended by providing a block; parse yields
92
+ # each line fragment to the block and stops parsing when the block
93
+ # returns true. Note that no subject will be parsed under these
94
+ # circumstances.
95
+ #
96
+ # c = Comment.parse(comment_string) {|frag| frag.strip.empty? }
97
+ # c.content
98
+ # # => [
99
+ # # ['comments spanning multiple', 'lines are collected']]
100
+ # c.subject # => nil
101
+ #
102
+ # Subject parsing may also be suppressed by setting parse_subject
103
+ # to false.
104
+ def parse(str, parse_subject=true) # :yields: fragment
105
+ scanner = case str
106
+ when StringScanner then str
107
+ when String then StringScanner.new(str)
108
+ else raise TypeError, "can't convert #{str.class} into StringScanner or String"
109
+ end
110
+
111
+ comment = Comment.new
112
+ while scanner.scan(/\r?\n?[ \t]*#[ \t]?(([ \t]*).*?)\r?$/)
113
+ fragment = scanner[1]
114
+ indent = scanner[2]
115
+
116
+ # collect continuous description line
117
+ # fragments and join into a single line
118
+ if block_given? && yield(fragment)
119
+ # break on comment if the description end is reached
120
+ parse_subject = false
121
+ break
122
+ else
123
+ categorize(fragment, indent) {|f| comment.push(f) }
124
+ end
125
+ end
126
+
127
+ if parse_subject
128
+ scanner.skip(/\s+/)
129
+ unless scanner.peek(1) == '#'
130
+ comment.subject = scanner.scan(/.+?$/)
131
+ comment.subject.strip! unless comment.subject == nil
132
+ end
133
+ end
134
+
135
+ comment
136
+ end
137
+
138
+ # Scan determines if and how to add a line fragment to a comment and
139
+ # yields the appropriate fragments to the block. Returns true if
140
+ # fragments are yielded and false otherwise. A comment's content
141
+ # may be built from an array of lines using scan like so:
142
+ #
143
+ # lines = [
144
+ # "# comments spanning multiple",
145
+ # "# lines are collected",
146
+ # "#",
147
+ # "# while indented lines",
148
+ # "# are preserved individually",
149
+ # "# ",
150
+ # "not a comment line",
151
+ # "# skipped since the loop breaks",
152
+ # "# at the first non-comment line"]
153
+ #
154
+ # c = Comment.new
155
+ # lines.each do |line|
156
+ # break unless Comment.scan(line) do |fragment|
157
+ # # c.unshift will also work if building in reverse
158
+ # c.push(fragment)
159
+ # end
160
+ # end
161
+ #
162
+ # c.content
163
+ # # => [
164
+ # # ['comments spanning multiple', 'lines are collected'],
165
+ # # [''],
166
+ # # [' while indented lines'],
167
+ # # [' are preserved individually'],
168
+ # # [''],
169
+ # # []]
170
+ #
171
+ def scan(line) # :yields: fragment
172
+ return false unless line =~ /^[ \t]*#[ \t]?(([ \t]*).*?)\r?$/
173
+ categorize($1, $2) do |fragment|
174
+ yield(fragment)
175
+ end
176
+ true
177
+ end
178
+
179
+ # Splits a line of text along whitespace breaks into fragments of cols
180
+ # width. Tabs in the line will be expanded into tabsize spaces;
181
+ # fragments are rstripped of whitespace.
182
+ #
183
+ # Comment.wrap("some line that will wrap", 10) # => ["some line", "that will", "wrap"]
184
+ # Comment.wrap(" line that will wrap ", 10) # => [" line", "that will", "wrap"]
185
+ # Comment.wrap(" ", 10) # => []
186
+ #
187
+ # The wrapping algorithm is slightly modified from:
188
+ # http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
189
+ def wrap(line, cols=80, tabsize=2)
190
+ line = line.gsub(/\t/, " " * tabsize) unless tabsize == nil
191
+ line.gsub(/(.{1,#{cols}})( +|$\r?\n?)|(.{1,#{cols}})/, "\\1\\3\n").split(/\s*?\n/)
192
+ end
193
+
194
+ private
195
+
196
+ # utility method used by scan to categorize and yield
197
+ # the appropriate objects to add the fragment to a
198
+ # comment
199
+ def categorize(fragment, indent) # :nodoc:
200
+ case
201
+ when fragment == indent
202
+ # empty comment line
203
+ yield [""]
204
+ yield []
205
+ when indent.empty?
206
+ # continuation line
207
+ yield fragment.rstrip
208
+ else
209
+ # indented line
210
+ yield [fragment.rstrip]
211
+ yield []
212
+ end
213
+ end
214
+ end
215
+
216
+ # An array of comment fragments organized into
217
+ # lines as they would be printed in an output.
218
+ # Ex: [["fragments", "of line", "one"],
219
+ # ["fragments", "of line", "two"]]
220
+ attr_reader :content
221
+
222
+ # The subject of the comment (normally set to the next
223
+ # non-comment line after the content ends; ie the line
224
+ # that would receive the comment in RDoc documentation).
225
+ attr_accessor :subject
226
+
227
+ # Returns the line number for the subject line, if known.
228
+ # Although normally an integer, line_number may be
229
+ # set to a Regexp or Proc to dynamically determine
230
+ # itself during resolve.
231
+ attr_accessor :line_number
232
+
233
+ def initialize(line_number=nil)
234
+ @content = []
235
+ @subject = nil
236
+ @line_number = line_number
237
+ end
238
+
239
+ # Pushes the fragment onto the last line array of content. If the
240
+ # fragment is an array itself then it will be pushed onto content
241
+ # as a new line.
242
+ #
243
+ # c = Comment.new
244
+ # c.push "some line"
245
+ # c.push "fragments"
246
+ # c.push ["a", "whole", "new line"]
247
+ #
248
+ # c.content
249
+ # # => [
250
+ # # ["some line", "fragments"],
251
+ # # ["a", "whole", "new line"]]
252
+ #
253
+ def push(fragment)
254
+ content << [] if content.empty?
255
+
256
+ case fragment
257
+ when Array
258
+ if content[-1].empty?
259
+ content[-1] = fragment
260
+ else
261
+ content.push fragment
262
+ end
263
+ else
264
+ content[-1].push fragment
265
+ end
266
+ end
267
+
268
+ # Alias for push.
269
+ def <<(fragment)
270
+ push(fragment)
271
+ end
272
+
273
+ # Scans the comment line using Comment.scan and pushes the appropriate
274
+ # fragments onto self. Used to build a content by scanning down a set
275
+ # of lines.
276
+ #
277
+ # lines = [
278
+ # "# comment spanning multiple",
279
+ # "# lines",
280
+ # "#",
281
+ # "# indented line one",
282
+ # "# indented line two",
283
+ # "# ",
284
+ # "not a comment line"]
285
+ #
286
+ # c = Comment.new
287
+ # lines.each {|line| c.append(line) }
288
+ #
289
+ # c.content
290
+ # # => [
291
+ # # ['comment spanning multiple', 'lines'],
292
+ # # [''],
293
+ # # [' indented line one'],
294
+ # # [' indented line two'],
295
+ # # [''],
296
+ # # []]
297
+ #
298
+ def append(line)
299
+ Comment.scan(line) {|f| push(f) }
300
+ end
301
+
302
+ # Unshifts the fragment to the first line array of content. If the
303
+ # fragment is an array itself then it will be unshifted onto content
304
+ # as a new line.
305
+ #
306
+ # c = Comment.new
307
+ # c.unshift "some line"
308
+ # c.unshift "fragments"
309
+ # c.unshift ["a", "whole", "new line"]
310
+ #
311
+ # c.content
312
+ # # => [
313
+ # # ["a", "whole", "new line"],
314
+ # # ["fragments", "some line"]]
315
+ #
316
+ def unshift(fragment)
317
+ content << [] if content.empty?
318
+
319
+ case fragment
320
+ when Array
321
+ if content[0].empty?
322
+ content[0] = fragment
323
+ else
324
+ content.unshift fragment
325
+ end
326
+ else
327
+ content[0].unshift fragment
328
+ end
329
+ end
330
+
331
+ # Scans the comment line using Comment.scan and unshifts the appropriate
332
+ # fragments onto self. Used to build a content by scanning up a set of
333
+ # lines.
334
+ #
335
+ # lines = [
336
+ # "# comment spanning multiple",
337
+ # "# lines",
338
+ # "#",
339
+ # "# indented line one",
340
+ # "# indented line two",
341
+ # "# ",
342
+ # "not a comment line"]
343
+ #
344
+ # c = Comment.new
345
+ # lines.reverse_each {|line| c.prepend(line) }
346
+ #
347
+ # c.content
348
+ # # => [
349
+ # # ['comment spanning multiple', 'lines'],
350
+ # # [''],
351
+ # # [' indented line one'],
352
+ # # [' indented line two'],
353
+ # # ['']]
354
+ #
355
+ def prepend(line)
356
+ Comment.scan(line) {|f| unshift(f) }
357
+ end
358
+
359
+ # Builds the subject and content of self using lines; resolve sets
360
+ # the subject to the line at line_number, and parses content up
361
+ # from there. Any previously set subject and content is overridden.
362
+ # Returns self.
363
+ #
364
+ # document = %Q{
365
+ # module Sample
366
+ # # this is the content of the comment
367
+ # # for method_one
368
+ # def method_one
369
+ # end
370
+ #
371
+ # # this is the content of the comment
372
+ # # for method_two
373
+ # def method_two
374
+ # end
375
+ # end}
376
+ #
377
+ # c = Comment.new 4
378
+ # c.resolve(document)
379
+ # c.subject # => " def method_one"
380
+ # c.content # => [["this is the content of the comment", "for method_one"]]
381
+ #
382
+ # Notes:
383
+ # - resolve is a good hook for post-processing in subclasses
384
+ # - lines may be an array or a string; string inputs are split
385
+ # into lines along newline boundaries.
386
+ #
387
+ # === late-evaluation line numbers
388
+ # The line_number used by resolve may be determined directly from
389
+ # lines by setting line_number to a Regexp and Proc. In the case
390
+ # of a Regexp, the first line matching the regexp is used:
391
+ #
392
+ # c = Comment.new(/def method/)
393
+ # c.resolve(document)
394
+ # c.line_number = 4
395
+ # c.subject # => " def method_one"
396
+ # c.content # => [["this is the content of the comment", "for method_one"]]
397
+ #
398
+ # Procs are called with lines and are expected to return the
399
+ # actual line number.
400
+ #
401
+ # c = Comment.new lambda {|lines| 9 }
402
+ # c.resolve(document)
403
+ # c.line_number = 9
404
+ # c.subject # => " def method_two"
405
+ # c.content # => [["this is the content of the comment", "for method_two"]]
406
+ #
407
+ # As shown in the examples, in both cases the late-evaluation
408
+ # line_number overwrites the Regexp or Proc.
409
+ def resolve(lines)
410
+ lines = lines.split(/\r?\n/) if lines.kind_of?(String)
411
+
412
+ # resolve late-evaluation line numbers
413
+ n = case line_number
414
+ when Regexp then match_index(line_number, lines)
415
+ when Proc then line_number.call(lines)
416
+ else line_number
417
+ end
418
+
419
+ # quietly exit if a line number was not found
420
+ return self unless n.kind_of?(Integer)
421
+
422
+ unless n < lines.length
423
+ raise RangeError, "line_number outside of lines: #{line_number} (#{lines.length})"
424
+ end
425
+
426
+ self.line_number = n
427
+ self.subject = lines[n]
428
+ self.content.clear
429
+
430
+ # remove whitespace lines
431
+ n -= 1
432
+ n -= 1 while n >=0 && lines[n].strip.empty?
433
+
434
+ # put together the comment
435
+ while n >= 0
436
+ break unless prepend(lines[n])
437
+ n -= 1
438
+ end
439
+
440
+ self
441
+ end
442
+
443
+ # Removes leading and trailing lines from content that are
444
+ # empty ([]) or whitespace (['']). Returns self.
445
+ def trim
446
+ content.shift while !content.empty? && (content[0].empty? || content[0].join.strip.empty?)
447
+ content.pop while !content.empty? && (content[-1].empty? || content[-1].join.strip.empty?)
448
+ self
449
+ end
450
+
451
+ # True if all lines in content are empty.
452
+ def empty?
453
+ !content.find {|line| !line.empty?}
454
+ end
455
+
456
+ # Returns content as a string where line fragments are joined by
457
+ # fragment_sep and lines are joined by line_sep.
458
+ def to_s(fragment_sep=" ", line_sep="\n", strip=true)
459
+ lines = content.collect {|line| line.join(fragment_sep)}
460
+
461
+ # strip leading an trailing whitespace lines
462
+ if strip
463
+ lines.shift while !lines.empty? && lines[0].empty?
464
+ lines.pop while !lines.empty? && lines[-1].empty?
465
+ end
466
+
467
+ line_sep ? lines.join(line_sep) : lines
468
+ end
469
+
470
+ # Like to_s, but wraps the content to the specified number of cols
471
+ # and expands tabs to tabsize spaces.
472
+ def wrap(cols=80, tabsize=2, line_sep="\n", fragment_sep=" ", strip=true)
473
+ lines = Comment.wrap(to_s(fragment_sep, "\n", strip), cols, tabsize)
474
+ line_sep ? lines.join(line_sep) : lines
475
+ end
476
+
477
+ # Returns true if another is a Comment with the same
478
+ # line_number, subject, and content as self
479
+ def ==(another)
480
+ another.kind_of?(Comment) &&
481
+ self.line_number == another.line_number &&
482
+ self.subject == another.subject &&
483
+ self.content == another.content
484
+ end
485
+
486
+ private
487
+
488
+ # utility method used to by resolve to find the index
489
+ # of a line matching a regexp line_number.
490
+ def match_index(regexp, lines) # :nodoc:
491
+ lines.each_with_index do |line, index|
492
+ return index if line =~ regexp
493
+ end
494
+ nil
495
+ end
496
+ end
497
+ end
498
+ end
499
+ end