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
@@ -19,6 +19,7 @@ module Tap
19
19
  def clear_dependencies
20
20
  @registry = []
21
21
  @results = []
22
+ @resolve_stack = []
22
23
  end
23
24
 
24
25
  # Returns the index of the [instance, argv] pair in self,
@@ -44,12 +45,24 @@ module Tap
44
45
 
45
46
  # Resolves the instance-argv pairs at the specified indicies by calling
46
47
  # instance._execute(*argv). Results are collected in results; a pair is
47
- # only resolved if an existing result does not exist. Returns self.
48
+ # only resolved if an existing result does not exist and an error is
49
+ # raised if circular dependencies are detected. Returns self.
48
50
  def resolve(indicies)
49
51
  indicies.each do |index|
50
- next if results[index]
52
+ next if resolved?(index)
53
+
54
+ if @resolve_stack.include?(index)
55
+ raise CircularDependencyError.new(@resolve_stack)
56
+ end
57
+
58
+ # mark the results at the index to prevent
59
+ # infinite loops with circular dependencies
60
+ @resolve_stack.push index
61
+
51
62
  instance, inputs = registry[index]
52
63
  results[index] = instance._execute(*inputs)
64
+
65
+ @resolve_stack.pop
53
66
  end
54
67
  self
55
68
  end
@@ -68,6 +81,12 @@ module Tap
68
81
  self
69
82
  end
70
83
 
84
+ # Raised when resolve detects circular dependencies.
85
+ class CircularDependencyError < StandardError
86
+ def initialize(resolve_stack)
87
+ super "circular dependency: [#{resolve_stack.join(', ')}]"
88
+ end
89
+ end
71
90
  end
72
91
  end
73
92
  end
@@ -1,36 +1,10 @@
1
- autoload(:Gem, 'rubygems')
1
+ require 'rubygems'
2
2
 
3
3
  module Tap
4
4
  module Support
5
- module Gems
6
- module_function
7
-
8
- # Finds the home directory for the user (method taken from Rubygems).
9
- def find_home
10
- ['HOME', 'USERPROFILE'].each do |homekey|
11
- return ENV[homekey] if ENV[homekey]
12
- end
13
-
14
- if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
15
- return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
16
- end
17
-
18
- begin
19
- File.expand_path("~")
20
- rescue
21
- if File::ALT_SEPARATOR then
22
- "C:/"
23
- else
24
- "/"
25
- end
26
- end
27
- end
28
-
29
- # The home directory for the user.
30
- def user_home
31
- @user_home ||= find_home
32
- end
33
-
5
+ module Gems
6
+ module_function
7
+
34
8
  # Returns the gemspec for the specified gem. A gem version
35
9
  # can be specified in the name, like 'gem >= 1.2'. The gem
36
10
  # will be activated using +gem+ if necessary.
@@ -12,6 +12,9 @@ module Tap
12
12
  end
13
13
 
14
14
  module Rack
15
+
16
+ attr_accessor :handler
17
+
15
18
  def call(env)
16
19
  path = env['PATH_INFO']
17
20
 
@@ -43,17 +46,17 @@ module Tap
43
46
  end
44
47
  end
45
48
 
46
- def known_path(dir, path)
47
- each do |env|
48
- directory = env.root.filepath(dir)
49
- file = env.root.filepath(dir, path)
50
-
51
- if file != directory && file.index(directory) == 0 && File.exists?(file)
52
- return file
53
- end
54
- end
55
-
56
- nil
49
+ def known_path(dir, path)
50
+ each do |env|
51
+ directory = env.root.filepath(dir)
52
+ file = env.root.filepath(dir, path)
53
+
54
+ if file != directory && file.index(directory) == 0 && File.exists?(file)
55
+ return file
56
+ end
57
+ end
58
+
59
+ nil
57
60
  end
58
61
 
59
62
  #--
@@ -22,7 +22,7 @@ module Tap
22
22
  private
23
23
 
24
24
  def get_lazy_attr(attribute)
25
- lazydoc[self.to_s][attribute] || (lazydoc.attributes(self.to_s)[attribute] = Tap::Support::Comment.new)
25
+ lazydoc[self.to_s][attribute] ||= Lazydoc::Comment.new
26
26
  end
27
27
 
28
28
  end
@@ -1,25 +1,105 @@
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 <this is the subject line>
12
+ # # a constant attribute content string that
13
+ # # 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.subject
39
+ # # => "<this is the subject line>"
40
+ #
41
+ # comment.content
42
+ # # => [
43
+ # # ["a constant attribute content string that", "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
+ # # a constant attribute content
54
+ # # string that can span multiple
55
+ # # 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 subject and scanning down for content. Scanning
95
+ # continues until a non-comment line, an end key, or a new attribute is
96
+ # reached; the comment is then stored by constant name and key.
18
97
  #
19
98
  # str = %Q{
20
99
  # # Const::Name::key subject 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
104
  # # Const::Name::another subject for another
25
105
  # # comment for 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.subject, comment.to_s] }
116
+ # # => {
117
+ # # 'Const::Name' => {
118
+ # # 'key' => ['subject for key', 'comment for key parsed until a non-comment line'],
119
+ # # 'another' => ['subject 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
131
  # # Const::Name::parsed subject
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.subject } # => {'Const::Name' => {'parsed' => 'subject'}}
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 and contents are parsed up from it (basically mimicking the
177
+ # behavior of RDoc).
99
178
  #
100
179
  # str = %Q{
101
180
  # # comment lines for
@@ -110,17 +189,23 @@ 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
210
  # A regexp matching an attribute start or end. After a match:
126
211
  #
@@ -142,327 +227,159 @@ module Tap
142
227
  # Note that line numbers in caller start at 1, not 0.
143
228
  CALLER_REGEXP = /^(([A-z]:)?[^:]+):(\d+)/
144
229
 
145
- class << self
146
-
147
- # A hash of (source_file, lazydoc) pairs tracking the
148
- # Lazydoc instance for the given source file.
149
- def registry
150
- @registry ||= []
151
- end
152
-
153
- # Returns the lazydoc in registry for the specified source file.
154
- # If no such lazydoc exists, one will be created for it.
155
- def [](source_file)
156
- source_file = File.expand_path(source_file.to_s)
157
- lazydoc = registry.find {|doc| doc.source_file == source_file }
158
- if lazydoc == nil
159
- lazydoc = new(source_file)
160
- registry << lazydoc
161
- end
162
- lazydoc
163
- end
164
-
165
- # Register the specified line numbers to the lazydoc for source_file.
166
- # Returns a CodeComment corresponding to the line.
167
- def register(source_file, line_number)
168
- Lazydoc[source_file].register(line_number)
169
- end
170
-
171
- # Resolves all lazydocs which include the specified code comments.
172
- def resolve_comments(code_comments)
173
- registry.each do |doc|
174
- next if (code_comments & doc.code_comments).empty?
175
- doc.resolve
176
- end
177
- end
178
-
179
- # Scans the specified file for attributes keyed by key and stores
180
- # the resulting comments in the corresponding lazydoc.
181
- # Returns the lazydoc.
182
- def scan_doc(source_file, key)
183
- lazydoc = nil
184
- scan(File.read(source_file), key) do |const_name, attr_key, comment|
185
- lazydoc = self[source_file] unless lazydoc
186
- lazydoc.attributes(const_name)[attr_key] = comment
187
- end
188
- lazydoc
189
- end
190
-
191
- # Scans the string or StringScanner for attributes matching the key;
192
- # keys may be patterns, they are incorporated into a regexp. Yields
193
- # each (const_name, key, value) triplet to the mandatory block and
194
- # skips regions delimited by the stop and start keys <tt>:-</tt>
195
- # and <tt>:+</tt>.
196
- #
197
- # str = %Q{
198
- # # Const::Name::key value
199
- # # ::alt alt_value
200
- # #
201
- # # Ignored::Attribute::not_matched value
202
- # # :::-
203
- # # Also::Ignored::key value
204
- # # :::+
205
- # # Another::key another value
206
- #
207
- # Ignored::key value
208
- # }
209
- #
210
- # results = []
211
- # Lazydoc.scan(str, 'key|alt') do |const_name, key, value|
212
- # results << [const_name, key, value]
213
- # end
214
- #
215
- # results
216
- # # => [
217
- # # ['Const::Name', 'key', 'value'],
218
- # # ['', 'alt', 'alt_value'],
219
- # # ['Another', 'key', 'another value']]
220
- #
221
- # Returns the StringScanner used during scanning.
222
- def scan(str, key) # :yields: const_name, key, value
223
- scanner = case str
224
- when StringScanner then str
225
- when String then StringScanner.new(str)
226
- else raise TypeError, "can't convert #{str.class} into StringScanner or String"
227
- end
228
-
229
- regexp = /^(.*?)::(:-|#{key})/
230
- while !scanner.eos?
231
- break if scanner.skip_until(regexp) == nil
232
-
233
- if scanner[2] == ":-"
234
- scanner.skip_until(/:::\+/)
235
- else
236
- next unless scanner[1] =~ CONSTANT_REGEXP
237
- key = scanner[2]
238
- yield($1.to_s, key, scanner.matched.strip) if scanner.scan(/[ \r\t].*$|$/)
239
- end
240
- end
241
-
242
- scanner
243
- end
230
+ module_function
244
231
 
245
- # Parses constant attributes from the string or StringScanner. Yields
246
- # each (const_name, key, comment) triplet to the mandatory block
247
- # and skips regions delimited by the stop and start keys <tt>:-</tt>
248
- # and <tt>:+</tt>.
249
- #
250
- # str = %Q{
251
- # # Const::Name::key subject for key
252
- # # comment for key
253
- #
254
- # # :::-
255
- # # Ignored::key value
256
- # # :::+
257
- #
258
- # # Ignored text before attribute ::another subject for another
259
- # # comment for another
260
- # }
261
- #
262
- # results = []
263
- # Lazydoc.parse(str) do |const_name, key, comment|
264
- # results << [const_name, key, comment.subject, comment.to_s]
265
- # end
266
- #
267
- # results
268
- # # => [
269
- # # ['Const::Name', 'key', 'subject for key', 'comment for key'],
270
- # # ['', 'another', 'subject for another', 'comment for another']]
271
- #
272
- # Returns the StringScanner used during scanning.
273
- def parse(str) # :yields: const_name, key, comment
274
- scanner = case str
275
- when StringScanner then str
276
- when String then StringScanner.new(str)
277
- else raise TypeError, "can't convert #{str.class} into StringScanner or String"
278
- end
279
-
280
- scan(scanner, '[a-z_]+') do |const_name, key, value|
281
- comment = Comment.parse(scanner, false) do |line|
282
- if line =~ ATTRIBUTE_REGEXP
283
- # rewind to capture the next attribute unless an end is specified.
284
- scanner.unscan unless $4 == '-' && $3 == key && $1.to_s == const_name
285
- true
286
- else false
287
- end
288
- end
289
- comment.subject = value
290
- yield(const_name, key, comment)
291
- end
292
- end
232
+ # A hash of (source_file, lazydoc) pairs tracking the
233
+ # Lazydoc instance for the given source file.
234
+ def registry
235
+ @registry ||= []
293
236
  end
294
237
 
295
- include Enumerable
296
-
297
- # The source file for self, used in resolving comments and
298
- # attributes.
299
- attr_reader :source_file
300
-
301
- # An array of Comment objects identifying lines resolved or
302
- # to-be-resolved for self.
303
- attr_reader :code_comments
304
-
305
- # A hash of (const_name, attributes) pairs tracking the constant
306
- # attributes resolved or to-be-resolved for self. Attributes
307
- # are hashes of (key, comment) pairs.
308
- attr_reader :const_attrs
309
-
310
- attr_reader :patterns
311
-
312
- def initialize(source_file=nil)
313
- self.source_file = source_file
314
- @code_comments = []
315
- @patterns = {}
316
- @const_attrs = {}
317
- @resolved = false
318
- end
319
-
320
- # Sets the source file for self. Expands the source file path if necessary.
321
- def source_file=(source_file)
322
- @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
323
248
  end
324
249
 
325
- # Returns the attributes for the specified const_name.
326
- def attributes(const_name)
327
- const_attrs[const_name] ||= {}
328
- end
329
-
330
- # Returns default document attributes (ie attributes(''))
331
- def default_attributes
332
- attributes('')
333
- end
334
-
335
- # Returns the attributes for const_name merged to default_attributes.
336
- # Set merge_defaults to false to get just the attributes for const_name.
337
- def [](const_name, merge_defaults=true)
338
- 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)
339
254
  end
340
255
 
341
- # Yields each (const_name, attributes) pair to the block; const_names where
342
- # the attributes are empty are skipped.
343
- def each
344
- const_attrs.each_pair do |const_name, attrs|
345
- 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
346
261
  end
347
262
  end
348
263
 
349
- # Returns true if the attributes for const_name are not empty.
350
- def has_const?(const_name)
351
- const_attrs.each_pair do |constname, attrs|
352
- next unless constname == const_name
353
- 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
354
272
  end
355
-
356
- false
273
+ lazydoc
357
274
  end
358
275
 
359
- # Returns an array of the constant names in self, for which
360
- # the constant attributes are not empty.
361
- def const_names
362
- names = []
363
- const_attrs.each_pair do |const_name, attrs|
364
- 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"
365
312
  end
366
- names
367
- end
368
-
369
- # Register the specified line number to self. Returns a
370
- # Comment object corresponding to the line.
371
- def register(line_number)
372
- comment = code_comments.find {|c| c.line_number == line_number }
373
313
 
374
- if comment == nil
375
- comment = Comment.new(line_number)
376
- code_comments << comment
377
- end
314
+ regexp = /^(.*?)::(:-|#{key})/
315
+ while !scanner.eos?
316
+ break if scanner.skip_until(regexp) == nil
378
317
 
379
- comment
380
- end
381
-
382
- def register_pattern(key, regexp, &block) # :yields: comment, match
383
- patterns[key] = [regexp, block]
384
- end
385
-
386
- def register_method_pattern(key, method, range=0..-1)
387
- register_pattern(key, /^\s*def\s+#{method}(\((.*?)\))?/) do |comment, match|
388
- args = match[2].to_s.split(',').collect do |arg|
389
- arg = arg.strip.upcase
390
- case arg
391
- when /^&/ then nil
392
- when /^\*/ then arg[1..-1] + "..."
393
- else arg
394
- 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].*$|$/)
395
324
  end
396
-
397
- comment.subject = args[range].join(', ')
398
325
  end
399
- end
400
326
 
401
- # Returns true if the code_comments for source_file are frozen.
402
- def resolved?
403
- @resolved
327
+ scanner
404
328
  end
405
-
406
- attr_writer :resolved
407
-
408
- def resolve(str=nil)
409
- return(false) if resolved?
410
-
411
- if str == nil
412
- raise ArgumentError, "no source file specified" unless source_file && File.exists?(source_file)
413
- str = File.read(source_file)
414
- end
415
-
416
- Lazydoc.parse(str) do |const_name, key, comment|
417
- 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"
418
363
  end
419
364
 
420
- lines = str.split(/\r?\n/)
421
-
422
- patterns.each_pair do |key, (regexp, block)|
423
- next if default_attributes.has_key?(key)
424
-
425
- lines.each_with_index do |line, line_number|
426
- next unless line =~ regexp
427
-
428
- comment = register(line_number)
429
- default_attributes[key] = comment
430
- break if block.call(comment, $~)
431
- end
432
- end unless patterns.empty?
433
-
434
- code_comments.collect! do |comment|
435
- line_number = comment.line_number
436
- comment.subject = lines[line_number] if comment.subject == nil
437
-
438
- # remove whitespace lines
439
- line_number -= 1
440
- while lines[line_number].strip.empty?
441
- line_number -= 1
442
- end
443
-
444
- # put together the comment
445
- while line_number >= 0
446
- break unless comment.prepend(lines[line_number])
447
- 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
448
373
  end
449
-
450
- comment
374
+ comment.subject = value
375
+ yield(const_name, key, comment)
451
376
  end
452
-
453
- @resolved = true
454
377
  end
455
378
 
456
- def to_hash
457
- const_hash = {}
458
- const_names.sort.each do |const_name|
459
- attr_hash = {}
460
- self[const_name, false].each_pair do |key, comment|
461
- attr_hash[key] = (block_given? ? yield(comment) : comment)
462
- end
463
- const_hash[const_name] = attr_hash
464
- end
465
- 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
466
383
  end
467
384
  end
468
385
  end