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
@@ -1,27 +1,107 @@
1
- require 'tap/support/comment'
1
+ require 'tap/support/lazydoc/document'
2
2
 
3
3
  module Tap
4
4
  module Support
5
5
 
6
- # Lazydoc scans source files to pull out documentation. Lazydoc can find two
7
- # types of documentation, constant attributes and code comments.
6
+ # Lazydoc lazily pulls documentation out of source files and makes it
7
+ # available through LazyAttributes. Lazydoc can find two types of
8
+ # documentation, constant attributes and code comments. To illustrate,
9
+ # consider the following:
10
+ #
11
+ # # Sample::key <value>
12
+ # # This is the comment content. A content
13
+ # # string can span multiple lines...
14
+ # #
15
+ # # code.is_allowed
16
+ # # much.as_in RDoc
17
+ # #
18
+ # # and stops at the next non-comment
19
+ # # line, the next constant attribute,
20
+ # # or an end key
21
+ # class Sample
22
+ # extend Tap::Support::LazyAttributes
23
+ # self.source_file = __FILE__
24
+ #
25
+ # lazy_attr :key
26
+ #
27
+ # # comment content for a code comment
28
+ # # may similarly span multiple lines
29
+ # def method_one
30
+ # end
31
+ # end
32
+ #
33
+ # When a lazy attribute is called, Lazydoc scans <tt>source_file</tt> for
34
+ # the corresponding constant attribute and makes it available as a
35
+ # Lazydoc::Comment.
36
+ #
37
+ # comment = Sample::key
38
+ # comment.value
39
+ # # => "<value>"
40
+ #
41
+ # comment.content
42
+ # # => [
43
+ # # ["This is the comment content. A content", "string can span multiple lines..."],
44
+ # # [""],
45
+ # # [" code.is_allowed"],
46
+ # # [" much.as_in RDoc"],
47
+ # # [""],
48
+ # # ["and stops at the next non-comment", "line, the next constant attribute,", "or an end key"]]
49
+ #
50
+ # "\n#{'.' * 30}\n" + comment.wrap(30) + "\n#{'.' * 30}\n"
51
+ # # => %q{
52
+ # # ..............................
53
+ # # This is the comment content.
54
+ # # A content string can span
55
+ # # multiple lines...
56
+ # #
57
+ # # code.is_allowed
58
+ # # much.as_in RDoc
59
+ # #
60
+ # # and stops at the next
61
+ # # non-comment line, the next
62
+ # # constant attribute, or an end
63
+ # # key
64
+ # # ..............................
65
+ # #}
66
+ #
67
+ # In addition, individual lines of code may be registered and resolved by Lazydoc:
68
+ #
69
+ # doc = Sample.lazydoc.reset
70
+ # comment = doc.register(/method_one/)
71
+ #
72
+ # doc.resolve
73
+ # comment.subject # => " def method_one"
74
+ # comment.content # => [["comment content for a code comment", "may similarly span multiple lines"]]
75
+ #
76
+ # With these basics in mind, here are some details...
8
77
  #
9
78
  # === Constant Attributes
79
+ # Constant attributes are like constants in Ruby, but with an extra 'key'
80
+ # that must consist of only lowercase letters and/or underscores. For
81
+ # example, these are constant attributes:
82
+ #
83
+ # # Const::Name::key
84
+ # # Const::Name::key_with_underscores
85
+ # # ::key
10
86
  #
11
- # Constant attributes are designated the same as constants in Ruby, but with
12
- # an extra 'key' constant that must consist of only lowercase letters and/or
13
- # underscores. Attributes are only parsed from comment lines.
87
+ # While these are not:
14
88
  #
15
- # When Lazydoc finds an attribute it parses a Comment value where the subject
16
- # is the remainder of the line, and comment lines are parsed down until a
17
- # non-comment line, an end key, or a new attribute is reached.
89
+ # # Const::Name::Key
90
+ # # Const::Name::key2
91
+ # # Const::Name::k@y
92
+ #
93
+ # Lazydoc parses a Lazydoc::Comment for each constant attribute by using the
94
+ # remainder of the line as a value (ie subject) and scanning down for content.
95
+ # Scanning continues until a non-comment line, an end key, or a new attribute
96
+ # is reached; the comment is then stored by constant name and key.
18
97
  #
19
98
  # str = %Q{
20
- # # Const::Name::key subject for key
99
+ # # Const::Name::key value for key
21
100
  # # comment for key
22
- # # parsed until a non-comment line
101
+ # # parsed until a
102
+ # # non-comment line
23
103
  #
24
- # # Const::Name::another subject for another
104
+ # # Const::Name::another value for another
25
105
  # # comment for another
26
106
  # # parsed to an end key
27
107
  # # Const::Name::another-
@@ -29,40 +109,35 @@ module Tap
29
109
  # # ignored comment
30
110
  # }
31
111
  #
32
- # lazydoc = Lazydoc.new
33
- # lazydoc.resolve(str)
112
+ # doc = Lazydoc::Document.new
113
+ # doc.resolve(str)
34
114
  #
35
- # lazydoc.to_hash {|comment| [comment.subject, comment.to_s] }
36
- # # => {'Const::Name' => {
37
- # # 'key' => ['subject for key', 'comment for key parsed until a non-comment line'],
38
- # # 'another' => ['subject for another', 'comment for another parsed to an end key']
39
- # # }}
115
+ # doc.to_hash {|comment| [comment.value, comment.to_s] }
116
+ # # => {
117
+ # # 'Const::Name' => {
118
+ # # 'key' => ['value for key', 'comment for key parsed until a non-comment line'],
119
+ # # 'another' => ['value for another', 'comment for another parsed to an end key']}
120
+ # # }
40
121
  #
41
- # A constant name does not need to be specified; when no constant name is
42
- # specified, Lazydoc will store the key as a default for the document. To
43
- # turn off attribute parsing for a section of documentation, use start/stop
44
- # keys:
122
+ # Constant attributes are only parsed from commented lines. To turn off
123
+ # attribute parsing for a section of documentation, use start/stop keys:
45
124
  #
46
125
  # str = %Q{
126
+ # Const::Name::not_parsed
127
+ #
47
128
  # # :::-
48
129
  # # Const::Name::not_parsed
49
130
  # # :::+
50
- #
51
- # Const::Name::not_parsed
52
- #
53
- # # Const::Name::parsed subject
131
+ # # Const::Name::parsed value
54
132
  # }
55
133
  #
56
- # lazydoc = Lazydoc.new
57
- # lazydoc.resolve(str)
58
- # lazydoc.to_hash {|comment| comment.subject } # => {'Const::Name' => {'parsed' => 'subject'}}
134
+ # doc = Lazydoc::Document.new
135
+ # doc.resolve(str)
136
+ # doc.to_hash {|comment| comment.value } # => {'Const::Name' => {'parsed' => 'value'}}
59
137
  #
60
- # ==== startdoc
61
- #
62
- # Lazydoc is completely separate from RDoc, but the syntax of Lazydoc was developed
63
- # with RDoc in mind. To hide attributes in one line, make use of the RDoc
64
- # <tt>:startdoc:</tt> document modifier like this (spaces added to keep them in the
65
- # example):
138
+ # To hide attributes from RDoc, make use of the RDoc <tt>:startdoc:</tt>
139
+ # document modifier like this (note that spaces are added to prevent RDoc
140
+ # from hiding the example):
66
141
  #
67
142
  # # :start doc::Const::Name::one hidden in RDoc
68
143
  # # * This line is visible in RDoc.
@@ -76,7 +151,7 @@ module Tap
76
151
  # #
77
152
  # # * This line is also visible in RDoc.
78
153
  #
79
- # Here is the same text, actually in RDoc:
154
+ # Here is the same text, for comparison if you are reading this as RDoc:
80
155
  #
81
156
  # :startdoc::Const::Name::one hidden in RDoc
82
157
  # * This line is visible in RDoc.
@@ -90,12 +165,16 @@ module Tap
90
165
  #
91
166
  # * This line is also visible in RDoc.
92
167
  #
93
- # === Code Comments
168
+ # As a side note, <tt>Const::Name::key</tt> is not a reference to the 'key'
169
+ # constant (as that would be invalid). In *very* idiomatic ruby
170
+ # <tt>Const::Name::key</tt> is equivalent to the method call
171
+ # <tt>Const::Name.key</tt>.
94
172
  #
95
- # Code comments are lines marked for parsing if and when a Lazydoc gets resolved.
96
- # Unlike constant attributes, the line is the subject of a code comment and
97
- # comment lines are parsed up from it (effectively mimicking the behavior of
98
- # RDoc).
173
+ # === Code Comments
174
+ # Code comments are lines registered for parsing if and when a Lazydoc gets
175
+ # resolved. Unlike constant attributes, the registered line is the comment
176
+ # subject (ie value) and contents are parsed up from it (basically mimicking
177
+ # the behavior of RDoc).
99
178
  #
100
179
  # str = %Q{
101
180
  # # comment lines for
@@ -110,19 +189,25 @@ module Tap
110
189
  # end
111
190
  # }
112
191
  #
113
- # lazydoc = Lazydoc.new
114
- # lazydoc.register(3)
115
- # lazydoc.register(9)
116
- # lazydoc.resolve(str)
192
+ # doc = Lazydoc::Document.new
193
+ # doc.register(3)
194
+ # doc.register(9)
195
+ # doc.resolve(str)
117
196
  #
118
- # lazydoc.code_comments.collect {|comment| [comment.subject, comment.to_s] }
197
+ # doc.comments.collect {|comment| [comment.subject, comment.to_s] }
119
198
  # # => [
120
199
  # # ['def method', 'comment lines for the method'],
121
200
  # # ['def another_method', 'as in RDoc, the comment can be separated from the method']]
122
201
  #
123
- class Lazydoc
202
+ # Comments may be registered to specific line numbers, or with a Proc or
203
+ # Regexp that will determine the line number during resolution. In the case
204
+ # of a Regexp, the first matching line is used; Procs receive an array of
205
+ # lines and should return the line number that should be used. See
206
+ # Lazydoc::Comment#resolve for more details.
207
+ #
208
+ module Lazydoc
124
209
 
125
- # A regexp matching an attribute start or end. For the match:
210
+ # A regexp matching an attribute start or end. After a match:
126
211
  #
127
212
  # $1:: const_name
128
213
  # $3:: key
@@ -133,327 +218,168 @@ module Tap
133
218
  # A regexp matching constants from the ATTRIBUTE_REGEXP leader
134
219
  CONSTANT_REGEXP = /#.*?([A-Z][A-z]*(::[A-Z][A-z]*)*)?$/
135
220
 
136
- class << self
137
-
138
- # A hash of (source_file, lazydoc) pairs tracking the
139
- # Lazydoc instance for the given source file.
140
- def registry
141
- @registry ||= []
142
- end
143
-
144
- # Returns the lazydoc in registry for the specified source file.
145
- # If no such lazydoc exists, one will be created for it.
146
- def [](source_file)
147
- source_file = File.expand_path(source_file.to_s)
148
- lazydoc = registry.find {|doc| doc.source_file == source_file }
149
- if lazydoc == nil
150
- lazydoc = new(source_file)
151
- registry << lazydoc
152
- end
153
- lazydoc
154
- end
155
-
156
- # Register the specified line numbers to the lazydoc for source_file.
157
- # Returns a CodeComment corresponding to the line.
158
- def register(source_file, line_number)
159
- Lazydoc[source_file].register(line_number)
160
- end
161
-
162
- # Resolves all lazydocs which include the specified code comments.
163
- def resolve_comments(code_comments)
164
- registry.each do |doc|
165
- next if (code_comments & doc.code_comments).empty?
166
- doc.resolve
167
- end
168
- end
169
-
170
- # Scans the specified file for attributes keyed by key and stores
171
- # the resulting comments in the corresponding lazydoc.
172
- # Returns the lazydoc.
173
- def scan_doc(source_file, key)
174
- lazydoc = nil
175
- scan(File.read(source_file), key) do |const_name, attr_key, comment|
176
- lazydoc = self[source_file] unless lazydoc
177
- lazydoc.attributes(const_name)[attr_key] = comment
178
- end
179
- lazydoc
180
- end
181
-
182
- # Scans the string or StringScanner for attributes matching the key;
183
- # keys may be patterns, they are incorporated into a regexp. Yields
184
- # each (const_name, key, value) triplet to the mandatory block and
185
- # skips regions delimited by the stop and start keys <tt>:-</tt>
186
- # and <tt>:+</tt>.
187
- #
188
- # str = %Q{
189
- # # Const::Name::key value
190
- # # ::alt alt_value
191
- # #
192
- # # Ignored::Attribute::not_matched value
193
- # # :::-
194
- # # Also::Ignored::key value
195
- # # :::+
196
- # # Another::key another value
197
- #
198
- # Ignored::key value
199
- # }
200
- #
201
- # results = []
202
- # Lazydoc.scan(str, 'key|alt') do |const_name, key, value|
203
- # results << [const_name, key, value]
204
- # end
205
- #
206
- # results
207
- # # => [
208
- # # ['Const::Name', 'key', 'value'],
209
- # # ['', 'alt', 'alt_value'],
210
- # # ['Another', 'key', 'another value']]
211
- #
212
- # Returns the StringScanner used during scanning.
213
- def scan(str, key) # :yields: const_name, key, value
214
- scanner = case str
215
- when StringScanner then str
216
- when String then StringScanner.new(str)
217
- else raise TypeError, "can't convert #{str.class} into StringScanner or String"
218
- end
219
-
220
- regexp = /^(.*?)::(:-|#{key})/
221
- while !scanner.eos?
222
- break if scanner.skip_until(regexp) == nil
223
-
224
- if scanner[2] == ":-"
225
- scanner.skip_until(/:::\+/)
226
- else
227
- next unless scanner[1] =~ CONSTANT_REGEXP
228
- key = scanner[2]
229
- yield($1.to_s, key, scanner.matched.strip) if scanner.scan(/[ \r\t].*$|$/)
230
- end
231
- end
232
-
233
- scanner
234
- end
235
-
236
- # Parses constant attributes from the string or StringScanner. Yields
237
- # each (const_name, key, comment) triplet to the mandatory block
238
- # and skips regions delimited by the stop and start keys <tt>:-</tt>
239
- # and <tt>:+</tt>.
240
- #
241
- # str = %Q{
242
- # # Const::Name::key subject for key
243
- # # comment for key
244
- #
245
- # # :::-
246
- # # Ignored::key value
247
- # # :::+
248
- #
249
- # # Ignored text before attribute ::another subject for another
250
- # # comment for another
251
- # }
252
- #
253
- # results = []
254
- # Lazydoc.parse(str) do |const_name, key, comment|
255
- # results << [const_name, key, comment.subject, comment.to_s]
256
- # end
257
- #
258
- # results
259
- # # => [
260
- # # ['Const::Name', 'key', 'subject for key', 'comment for key'],
261
- # # ['', 'another', 'subject for another', 'comment for another']]
262
- #
263
- # Returns the StringScanner used during scanning.
264
- def parse(str) # :yields: const_name, key, comment
265
- scanner = case str
266
- when StringScanner then str
267
- when String then StringScanner.new(str)
268
- else raise TypeError, "can't convert #{str.class} into StringScanner or String"
269
- end
270
-
271
- scan(scanner, '[a-z_]+') do |const_name, key, value|
272
- comment = Comment.parse(scanner, false) do |line|
273
- if line =~ ATTRIBUTE_REGEXP
274
- # rewind to capture the next attribute unless an end is specified.
275
- scanner.unscan unless $4 == '-' && $3 == key && $1.to_s == const_name
276
- true
277
- else false
278
- end
279
- end
280
- comment.subject = value
281
- yield(const_name, key, comment)
282
- end
283
- end
284
- end
285
-
286
- include Enumerable
287
-
288
- # The source file for self, used in resolving comments and
289
- # attributes.
290
- attr_reader :source_file
291
-
292
- # An array of Comment objects identifying lines resolved or
293
- # to-be-resolved for self.
294
- attr_reader :code_comments
295
-
296
- # A hash of (const_name, attributes) pairs tracking the constant
297
- # attributes resolved or to-be-resolved for self. Attributes
298
- # are hashes of (key, comment) pairs.
299
- attr_reader :const_attrs
221
+ # A regexp matching a caller line, to extract the calling file
222
+ # and line number. After a match:
223
+ #
224
+ # $1:: file
225
+ # $3:: line number (as a string, obviously)
226
+ #
227
+ # Note that line numbers in caller start at 1, not 0.
228
+ CALLER_REGEXP = /^(([A-z]:)?[^:]+):(\d+)/
300
229
 
301
- attr_reader :patterns
230
+ module_function
302
231
 
303
- def initialize(source_file=nil)
304
- self.source_file = source_file
305
- @code_comments = []
306
- @patterns = {}
307
- @const_attrs = {}
308
- @resolved = false
232
+ # A hash of (source_file, lazydoc) pairs tracking the
233
+ # Lazydoc instance for the given source file.
234
+ def registry
235
+ @registry ||= []
309
236
  end
310
237
 
311
- # Sets the source file for self. Expands the source file path if necessary.
312
- def source_file=(source_file)
313
- @source_file = source_file == nil ? nil : File.expand_path(source_file)
238
+ # Returns the lazydoc in registry for the specified source file.
239
+ # If no such lazydoc exists, one will be created for it.
240
+ def [](source_file)
241
+ source_file = File.expand_path(source_file.to_s)
242
+ lazydoc = registry.find {|doc| doc.source_file == source_file }
243
+ if lazydoc == nil
244
+ lazydoc = Document.new(source_file)
245
+ registry << lazydoc
246
+ end
247
+ lazydoc
314
248
  end
315
249
 
316
- # Returns the attributes for the specified const_name.
317
- def attributes(const_name)
318
- const_attrs[const_name] ||= {}
319
- end
320
-
321
- # Returns default document attributes (ie attributes(''))
322
- def default_attributes
323
- attributes('')
324
- end
325
-
326
- # Returns the attributes for const_name merged to default_attributes.
327
- # Set merge_defaults to false to get just the attributes for const_name.
328
- def [](const_name, merge_defaults=true)
329
- merge_defaults ? default_attributes.merge(attributes(const_name)) : attributes(const_name)
250
+ # Register the specified line numbers to the lazydoc for source_file.
251
+ # Returns a comment_class instance corresponding to the line.
252
+ def register(source_file, line_number, comment_class=Comment)
253
+ Lazydoc[source_file].register(line_number, comment_class)
330
254
  end
331
255
 
332
- # Yields each (const_name, attributes) pair to the block; const_names where
333
- # the attributes are empty are skipped.
334
- def each
335
- const_attrs.each_pair do |const_name, attrs|
336
- yield(const_name, attrs) unless attrs.empty?
256
+ # Resolves all lazydocs which include the specified code comments.
257
+ def resolve_comments(comments)
258
+ registry.each do |doc|
259
+ next if (comments & doc.comments).empty?
260
+ doc.resolve
337
261
  end
338
262
  end
339
263
 
340
- # Returns true if the attributes for const_name are not empty.
341
- def has_const?(const_name)
342
- const_attrs.each_pair do |constname, attrs|
343
- next unless constname == const_name
344
- return !attrs.empty?
264
+ # Scans the specified file for attributes keyed by key and stores
265
+ # the resulting comments in the source_file lazydoc. Returns the
266
+ # lazydoc.
267
+ def scan_doc(source_file, key)
268
+ lazydoc = nil
269
+ scan(File.read(source_file), key) do |const_name, attr_key, comment|
270
+ lazydoc = self[source_file] unless lazydoc
271
+ lazydoc[const_name][attr_key] = comment
345
272
  end
346
-
347
- false
273
+ lazydoc
348
274
  end
349
275
 
350
- # Returns an array of the constant names in self, for which
351
- # the constant attributes are not empty.
352
- def const_names
353
- names = []
354
- const_attrs.each_pair do |const_name, attrs|
355
- names << const_name unless attrs.empty?
276
+ # Scans the string or StringScanner for attributes matching the key
277
+ # (keys may be patterns, they are incorporated into a regexp). Yields
278
+ # each (const_name, key, value) triplet to the mandatory block and
279
+ # skips regions delimited by the stop and start keys <tt>:-</tt>
280
+ # and <tt>:+</tt>.
281
+ #
282
+ # str = %Q{
283
+ # # Const::Name::key value
284
+ # # ::alt alt_value
285
+ # #
286
+ # # Ignored::Attribute::not_matched value
287
+ # # :::-
288
+ # # Also::Ignored::key value
289
+ # # :::+
290
+ # # Another::key another value
291
+ #
292
+ # Ignored::key value
293
+ # }
294
+ #
295
+ # results = []
296
+ # Lazydoc.scan(str, 'key|alt') do |const_name, key, value|
297
+ # results << [const_name, key, value]
298
+ # end
299
+ #
300
+ # results
301
+ # # => [
302
+ # # ['Const::Name', 'key', 'value'],
303
+ # # ['', 'alt', 'alt_value'],
304
+ # # ['Another', 'key', 'another value']]
305
+ #
306
+ # Returns the StringScanner used during scanning.
307
+ def scan(str, key) # :yields: const_name, key, value
308
+ scanner = case str
309
+ when StringScanner then str
310
+ when String then StringScanner.new(str)
311
+ else raise TypeError, "can't convert #{str.class} into StringScanner or String"
356
312
  end
357
- names
358
- end
359
-
360
- # Register the specified line number to self. Returns a
361
- # Comment object corresponding to the line.
362
- def register(line_number)
363
- comment = code_comments.find {|c| c.line_number == line_number }
364
313
 
365
- if comment == nil
366
- comment = Comment.new(line_number)
367
- code_comments << comment
368
- end
314
+ regexp = /^(.*?)::(:-|#{key})/
315
+ while !scanner.eos?
316
+ break if scanner.skip_until(regexp) == nil
369
317
 
370
- comment
371
- end
372
-
373
- def register_pattern(key, regexp, &block) # :yields: comment, match
374
- patterns[key] = [regexp, block]
375
- end
376
-
377
- def register_method_pattern(key, method, range=0..-1)
378
- register_pattern(key, /^\s*def\s+#{method}(\((.*?)\))?/) do |comment, match|
379
- args = match[2].to_s.split(',').collect do |arg|
380
- arg = arg.strip.upcase
381
- case arg
382
- when /^&/ then nil
383
- when /^\*/ then arg[1..-1] + "..."
384
- else arg
385
- end
318
+ if scanner[2] == ":-"
319
+ scanner.skip_until(/:::\+/)
320
+ else
321
+ next unless scanner[1] =~ CONSTANT_REGEXP
322
+ key = scanner[2]
323
+ yield($1.to_s, key, scanner.matched.strip) if scanner.scan(/[ \r\t].*$|$/)
386
324
  end
387
-
388
- comment.subject = args[range].join(', ')
389
325
  end
390
- end
391
326
 
392
- # Returns true if the code_comments for source_file are frozen.
393
- def resolved?
394
- @resolved
327
+ scanner
395
328
  end
396
-
397
- attr_writer :resolved
398
-
399
- def resolve(str=nil)
400
- return(false) if resolved?
401
-
402
- if str == nil
403
- raise ArgumentError, "no source file specified" unless source_file && File.exists?(source_file)
404
- str = File.read(source_file)
405
- end
406
-
407
- Lazydoc.parse(str) do |const_name, key, comment|
408
- attributes(const_name)[key] = comment
329
+
330
+ # Parses constant attributes from the string or StringScanner. Yields
331
+ # each (const_name, key, comment) triplet to the mandatory block
332
+ # and skips regions delimited by the stop and start keys <tt>:-</tt>
333
+ # and <tt>:+</tt>.
334
+ #
335
+ # str = %Q{
336
+ # # Const::Name::key subject for key
337
+ # # comment for key
338
+ #
339
+ # # :::-
340
+ # # Ignored::key value
341
+ # # :::+
342
+ #
343
+ # # Ignored text before attribute ::another subject for another
344
+ # # comment for another
345
+ # }
346
+ #
347
+ # results = []
348
+ # Lazydoc.parse(str) do |const_name, key, comment|
349
+ # results << [const_name, key, comment.subject, comment.to_s]
350
+ # end
351
+ #
352
+ # results
353
+ # # => [
354
+ # # ['Const::Name', 'key', 'subject for key', 'comment for key'],
355
+ # # ['', 'another', 'subject for another', 'comment for another']]
356
+ #
357
+ # Returns the StringScanner used during scanning.
358
+ def parse(str) # :yields: const_name, key, comment
359
+ scanner = case str
360
+ when StringScanner then str
361
+ when String then StringScanner.new(str)
362
+ else raise TypeError, "can't convert #{str.class} into StringScanner or String"
409
363
  end
410
364
 
411
- lines = str.split(/\r?\n/)
412
-
413
- patterns.each_pair do |key, (regexp, block)|
414
- next if default_attributes.has_key?(key)
415
-
416
- lines.each_with_index do |line, line_number|
417
- next unless line =~ regexp
418
-
419
- comment = register(line_number)
420
- default_attributes[key] = comment
421
- break if block.call(comment, $~)
422
- end
423
- end unless patterns.empty?
424
-
425
- code_comments.collect! do |comment|
426
- line_number = comment.line_number
427
- comment.subject = lines[line_number] if comment.subject == nil
428
-
429
- # remove whitespace lines
430
- line_number -= 1
431
- while lines[line_number].strip.empty?
432
- line_number -= 1
433
- end
434
-
435
- # put together the comment
436
- while line_number >= 0
437
- break unless comment.prepend(lines[line_number])
438
- line_number -= 1
365
+ scan(scanner, '[a-z_]+') do |const_name, key, value|
366
+ comment = Comment.parse(scanner, false) do |line|
367
+ if line =~ ATTRIBUTE_REGEXP
368
+ # rewind to capture the next attribute unless an end is specified.
369
+ scanner.unscan unless $4 == '-' && $3 == key && $1.to_s == const_name
370
+ true
371
+ else false
372
+ end
439
373
  end
440
-
441
- comment
374
+ comment.subject = value
375
+ yield(const_name, key, comment)
442
376
  end
443
-
444
- @resolved = true
445
377
  end
446
378
 
447
- def to_hash
448
- const_hash = {}
449
- const_names.sort.each do |const_name|
450
- attr_hash = {}
451
- self[const_name, false].each_pair do |key, comment|
452
- attr_hash[key] = (block_given? ? yield(comment) : comment)
453
- end
454
- const_hash[const_name] = attr_hash
455
- end
456
- const_hash
379
+ def usage(path, cols=80)
380
+ scanner = StringScanner.new(File.read(path))
381
+ scanner.scan(/^#!.*?$/)
382
+ Comment.parse(scanner, false).wrap(cols, 2).strip
457
383
  end
458
384
  end
459
385
  end