lazydoc 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ module Lazydoc
2
+
3
+ # A special type of self-resolving Method that whose to_s returns arguments
4
+ # formatted for the command line.
5
+ #
6
+ # a = Arguments.new
7
+ # a.subject = "def method(a, b='default', *c, &d)"
8
+ # a.to_s # => "A B='default' C..."
9
+ #
10
+ class Arguments < Method
11
+
12
+ # Self-resolves and returns arguments formatted for the command line.
13
+ def to_s
14
+ resolve
15
+
16
+ arguments.collect do |arg|
17
+ case arg
18
+ when /^&/ then nil
19
+ when /^\*/ then "#{arg[1..-1].upcase }..."
20
+ when /^(.*?)=(.*)$/ then "#{$1.upcase}=#{$2}"
21
+ else arg.upcase
22
+ end
23
+ end.compact.join(' ')
24
+ end
25
+ end
26
+ end
@@ -1,53 +1,140 @@
1
- require 'lazydoc'
2
-
3
1
  module Lazydoc
4
- # Attributes adds methods to declare class-level accessors for Lazydoc
2
+
3
+ # Attributes adds methods to declare class-level accessors for constant
5
4
  # attributes.
6
5
  #
7
6
  # # ConstName::key value
8
7
  # class ConstName
9
8
  # extend Lazydoc::Attributes
10
- #
11
9
  # lazy_attr :key
12
10
  # end
13
11
  #
14
12
  # ConstName.source_file # => __FILE__
15
13
  # ConstName::key.subject # => 'value'
16
14
  #
15
+ # ==== Keys and Register
16
+ #
17
+ # Note that constant attributes parsed from a source file are stored in
18
+ # const_attrs, and will ALWAYS be keyed using a string (since the
19
+ # 'ConstName::key' syntax specifies a string key).
20
+ #
21
+ # ConstName.const_attrs['key'] # => ConstName::key
22
+ #
23
+ # 'Constant Attributes' specified by non-string keys are sometimes used to
24
+ # tie comments to a constant that will NOT be resolved from the constant
25
+ # attribute syntax. For instance you could register a method like this:
26
+ #
27
+ # class Sample
28
+ # extend Lazydoc::Attributes
29
+ #
30
+ # const_attrs[:method_one] = register___
31
+ # # this is the method one comment
32
+ # def method_one
33
+ # end
34
+ # end
35
+ #
36
+ # Sample.lazydoc.resolve
37
+ # Sample.const_attrs[:method_one].comment # => "this is the method one comment"
38
+ #
39
+ # For easier access, you could define a lazy_attr to access the registered
40
+ # comment. And in the simplest case, you pair a lazy_register with a
41
+ # lazy_attr.
42
+ #
43
+ # class Paired
44
+ # extend Lazydoc::Attributes
45
+ #
46
+ # lazy_attr(:one, :method_one)
47
+ # lazy_attr(:two, :method_two)
48
+ # lazy_register(:method_two)
49
+ #
50
+ # const_attrs[:method_one] = register___
51
+ # # this is the manually-registered method one comment
52
+ # def method_one
53
+ # end
54
+ #
55
+ # # this is the lazyily-registered method two comment
56
+ # def method_two
57
+ # end
58
+ # end
59
+ #
60
+ # Paired.lazydoc.resolve
61
+ # Paired.one.comment # => "this is the manually-registered method one comment"
62
+ # Paired.two.comment # => "this is the lazyily-registered method two comment"
63
+ #
17
64
  module Attributes
18
65
 
19
- # The source_file for self. By default set to the file where
20
- # Attributes extends a class (if you include Attributes, you
21
- # must set source_file manually).
66
+ # The source file for the extended class. By default source_file
67
+ # is set to the file where Attributes extends the class (if you
68
+ # include Attributes, you must set source_file manually).
22
69
  attr_accessor :source_file
23
70
 
24
71
  def self.extended(base) # :nodoc:
25
72
  caller[1] =~ CALLER_REGEXP
26
73
  base.source_file ||= $1
27
74
  end
28
-
29
- # Returns the lazydoc for source_file
30
- def lazydoc(resolve=true)
31
- lazydoc = Lazydoc[source_file]
32
- lazydoc.resolve if resolve
33
- lazydoc
75
+
76
+ # Inherits registered_methods from parent to child.
77
+ def inherited(child)
78
+ child.registered_methods.merge!(registered_methods)
79
+ super
80
+ end
81
+
82
+ # Lazily registers the added method if marked for lazy registration.
83
+ def method_added(sym)
84
+ if args = registered_methods[sym]
85
+ const_attrs[sym] ||= Lazydoc.register_caller(*args)
86
+ end
87
+
88
+ super
89
+ end
90
+
91
+ # Returns the constant attributes resolved for the extended class.
92
+ def const_attrs
93
+ Document[to_s]
34
94
  end
35
95
 
36
- # Creates a lazy attribute accessor for the specified attribute.
37
- def lazy_attr(key, attribute=key)
96
+ # Returns the Document for source_file
97
+ def lazydoc
98
+ Lazydoc[source_file]
99
+ end
100
+
101
+ # A hash of (method_name, [comment_class, caller_index]) pairs indicating
102
+ # methods to lazily register, and the inputs to Lazydoc.register_caller
103
+ # used to register the method.
104
+ def registered_methods
105
+ @registered_methods ||= {}
106
+ end
107
+
108
+ # Creates a method that reads and resolves the constant attribute specified
109
+ # by key, which should normally be a string (see above for more details).
110
+ # If writable is true, a corresponding writer is also created.
111
+ def lazy_attr(symbol, key=symbol.to_s, writable=true)
112
+ key = case key
113
+ when String, Symbol, Numeric, true, false, nil then key.inspect
114
+ else "YAML.load(\'#{YAML.dump(key)}\')"
115
+ end
116
+
38
117
  instance_eval %Q{
39
- def #{key}
40
- lazydoc[to_s]['#{attribute}'] ||= Lazydoc::Comment.new
41
- end
42
-
43
- def #{key}=(comment)
44
- Lazydoc[source_file][to_s]['#{attribute}'] = comment
118
+ def #{symbol}
119
+ comment = const_attrs[#{key}] ||= Subject.new(nil, lazydoc)
120
+ comment.kind_of?(Comment) ? comment.resolve : comment
45
121
  end}
122
+
123
+ instance_eval(%Q{
124
+ def #{symbol}=(comment)
125
+ const_attrs[#{key}] = comment
126
+ end}) if writable
127
+ end
128
+
129
+ # Marks the method for lazy registration. When the method is registered,
130
+ # it will be stored in const_attrs by method_name.
131
+ def lazy_register(method_name, comment_class=Method, caller_index=1)
132
+ registered_methods[method_name.to_sym] = [comment_class, caller_index]
46
133
  end
47
134
 
48
- # Registers the next method.
49
- def register_method___(comment_class=Method)
50
- lazydoc(false).register___(comment_class, 1)
135
+ # Registers the next comment (by default as a Method).
136
+ def register___(comment_class=Method)
137
+ lazydoc.register___(comment_class, 1)
51
138
  end
52
139
  end
53
140
  end
@@ -1,275 +1,31 @@
1
- require 'strscan'
1
+ require 'lazydoc/utils'
2
2
 
3
3
  module Lazydoc
4
- # Comment represents a code comment parsed by Lazydoc. Comments consist
5
- # of a subject and content.
6
- #
7
- # sample_comment = %Q{
8
- # # this is the content
9
- # #
10
- # # content may stretch across
11
- # # multiple lines
12
- # this is the subject
13
- # }
14
- #
15
- # Normally the subject is the first non-comment line following the content,
16
- # although in some cases the subject will be manually set to something else
17
- # (as in a Lazydoc constant attribute). The content is an array of comment
18
- # fragments organized by line:
19
- #
20
- # c = Comment.parse(sample_comment)
21
- # c.subject # => "this is the subject"
22
- # c.content
23
- # # => [
24
- # # ["this is the content"],
25
- # # [""],
26
- # # ["content may stretch across", "multiple lines"]]
27
- #
28
- # Comments may be initialized to the subject line and then resolved later:
29
- #
30
- # doc = %Q{
31
- # module Sample
32
- # # this is the content of the comment
33
- # # for method_one
34
- # def method_one
35
- # end
36
- #
37
- # # this is the content of the comment
38
- # # for method_two
39
- # def method_two
40
- # end
41
- # end}
42
- #
43
- # c1 = Comment.new(4).resolve(doc)
44
- # c1.subject # => " def method_one"
45
- # c1.content # => [["this is the content of the comment", "for method_one"]]
46
- #
47
- # c2 = Comment.new(9).resolve(doc)
48
- # c2.subject # => " def method_two"
49
- # c2.content # => [["this is the content of the comment", "for method_two"]]
50
- #
51
- # A Regexp (or Proc) may be used in place of a line number; during resolve,
52
- # the lines will be scanned and the first matching line will be used.
53
- #
54
- # c3 = Comment.new(/def method_two/).resolve(doc)
55
- # c3.subject # => " def method_two"
56
- # c3.content # => [["this is the content of the comment", "for method_two"]]
57
- #
58
- class Comment
59
-
60
- class << self
61
4
 
62
- # Parses the input string into a comment. Takes a string or a
63
- # StringScanner and returns the comment.
64
- #
65
- # comment_string = %Q{
66
- # # comments spanning multiple
67
- # # lines are collected
68
- # #
69
- # # while indented lines
70
- # # are preserved individually
71
- # #
72
- # this is the subject line
73
- #
74
- # # this line is not parsed
75
- # }
76
- #
77
- # c = Comment.parse(comment_string)
78
- # c.content
79
- # # => [
80
- # # ['comments spanning multiple', 'lines are collected'],
81
- # # [''],
82
- # # [' while indented lines'],
83
- # # [' are preserved individually'],
84
- # # [''],
85
- # # []]
86
- # c.subject # => "this is the subject line"
87
- #
88
- # Parsing may be manually ended by providing a block; parse yields
89
- # each line fragment to the block and stops parsing when the block
90
- # returns true. Note that no subject will be parsed under these
91
- # circumstances.
92
- #
93
- # c = Comment.parse(comment_string) {|frag| frag.strip.empty? }
94
- # c.content
95
- # # => [
96
- # # ['comments spanning multiple', 'lines are collected']]
97
- # c.subject # => nil
98
- #
99
- # Subject parsing may also be suppressed by setting parse_subject
100
- # to false.
101
- def parse(str, parse_subject=true) # :yields: fragment
102
- scanner = case str
103
- when StringScanner then str
104
- when String then StringScanner.new(str)
105
- else raise TypeError, "can't convert #{str.class} into StringScanner or String"
106
- end
107
-
108
- comment = self.new
109
- while scanner.scan(/\r?\n?[ \t]*#[ \t]?(([ \t]*).*?)\r?$/)
110
- fragment = scanner[1]
111
- indent = scanner[2]
112
-
113
- # collect continuous description line
114
- # fragments and join into a single line
115
- if block_given? && yield(fragment)
116
- # break on comment if the description end is reached
117
- parse_subject = false
118
- break
119
- else
120
- categorize(fragment, indent) {|f| comment.push(f) }
121
- end
122
- end
123
-
124
- if parse_subject
125
- scanner.skip(/\s+/)
126
- unless scanner.peek(1) == '#'
127
- if subject = scanner.scan(/.+?$/)
128
- subject.strip!
129
- end
130
- comment.subject = subject
131
- end
132
- end
133
-
134
- comment
135
- end
5
+ # Comment represents a code comment parsed by Lazydoc.
6
+ class Comment
7
+ include Utils
136
8
 
137
- # Scan determines if and how to add a line fragment to a comment and
138
- # yields the appropriate fragments to the block. Returns true if
139
- # fragments are yielded and false otherwise.
140
- #
141
- # Content 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.push(fragment)
158
- # end
159
- # end
160
- #
161
- # c.content
162
- # # => [
163
- # # ['comments spanning multiple', 'lines are collected'],
164
- # # [''],
165
- # # [' while indented lines'],
166
- # # [' are preserved individually'],
167
- # # [''],
168
- # # []]
169
- #
170
- def scan(line) # :yields: fragment
171
- return false unless line =~ /^[ \t]*#[ \t]?(([ \t]*).*?)\r?$/
172
- categorize($1, $2) do |fragment|
173
- yield(fragment)
174
- end
175
- true
176
- end
177
-
178
- # Scans a stripped trailing comment off of str, tolerant to a leader
179
- # that uses '#' within a string. Returns nil for strings without a
180
- # trailing comment.
181
- #
182
- # Comment.scan_trailer "str with # trailer" # => "trailer"
183
- # Comment.scan_trailer "'# in str' # trailer" # => "trailer"
184
- # Comment.scan_trailer "str with without trailer" # => nil
185
- #
186
- # Note the %-syntax for strings is not fully supported, ie %Q, %q,
187
- # etc. may not parse correctly. Accepts Strings or a StringScanner.
188
- def scan_trailer(str)
189
- scanner = case str
190
- when StringScanner then str
191
- when String then StringScanner.new(str)
192
- else raise TypeError, "can't convert #{str.class} into StringScanner or String"
193
- end
194
-
195
- args = []
196
- brakets = braces = parens = 0
197
- start = scanner.pos
198
- while scanner.skip(/.*?['"#]/)
199
- pos = scanner.pos - 1
200
-
201
- case str[pos]
202
- when ?# then return scanner.rest.strip # return the trailer
203
- when ?' then skip_quote(scanner, /'/) # parse over quoted strings
204
- when ?" then skip_quote(scanner, /"/) # parse over double-quoted string
205
- end
206
- end
207
-
208
- return nil
209
- end
210
-
211
- # Splits a line of text along whitespace breaks into fragments of cols
212
- # width. Tabs in the line will be expanded into tabsize spaces;
213
- # fragments are rstripped of whitespace.
214
- #
215
- # Comment.wrap("some line that will wrap", 10) # => ["some line", "that will", "wrap"]
216
- # Comment.wrap(" line that will wrap ", 10) # => [" line", "that will", "wrap"]
217
- # Comment.wrap(" ", 10) # => []
218
- #
219
- # The wrapping algorithm is slightly modified from:
220
- # http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
221
- def wrap(line, cols=80, tabsize=2)
222
- line = line.gsub(/\t/, " " * tabsize) unless tabsize == nil
223
- line.gsub(/(.{1,#{cols}})( +|$\r?\n?)|(.{1,#{cols}})/, "\\1\\3\n").split(/\s*?\n/)
224
- end
9
+ # Returns the line number for the subject line, if known.
10
+ # Although normally an integer, line_number may be
11
+ # set to a Regexp or Proc to dynamically determine
12
+ # the subject line during resolve
13
+ attr_accessor :line_number
225
14
 
226
- private
15
+ # A back-reference to the Document that registered self
16
+ attr_accessor :document
227
17
 
228
- # utility method used by scan to categorize and yield
229
- # the appropriate objects to add the fragment to a
230
- # comment
231
- def categorize(fragment, indent) # :nodoc:
232
- case
233
- when fragment == indent
234
- # empty comment line
235
- yield [""]
236
- yield []
237
- when indent.empty?
238
- # continuation line
239
- yield fragment.rstrip
240
- else
241
- # indented line
242
- yield [fragment.rstrip]
243
- yield []
244
- end
245
- end
246
-
247
- # helper method to skip to the next non-escaped instance
248
- # matching the quote regexp (/'/ or /"/).
249
- def skip_quote(scanner, regexp) # :nodoc:
250
- scanner.skip_until(regexp)
251
- scanner.skip_until(regexp) while scanner.string[scanner.pos-2] == ?\\
252
- end
253
- end
254
-
255
18
  # An array of comment fragments organized into lines
256
19
  attr_reader :content
257
20
 
258
- # The subject of the comment (normally set to the next
259
- # non-comment line after the content ends; ie the line
260
- # that would receive the comment in RDoc documentation)
21
+ # The subject of the comment
261
22
  attr_accessor :subject
262
-
263
- # Returns the line number for the subject line, if known.
264
- # Although normally an integer, line_number may be
265
- # set to a Regexp or Proc to dynamically determine
266
- # the subject line during resolve
267
- attr_accessor :line_number
268
-
269
- def initialize(line_number=nil)
23
+
24
+ def initialize(line_number=nil, document=nil)
25
+ @line_number = line_number
26
+ @document = document
270
27
  @content = []
271
28
  @subject = nil
272
- @line_number = line_number
273
29
  end
274
30
 
275
31
  # Alias for subject
@@ -281,10 +37,15 @@ module Lazydoc
281
37
  def value=(value)
282
38
  self.subject = value
283
39
  end
40
+
41
+ # Returns the comment trailing the subject.
42
+ def trailer
43
+ subject ? scan_trailer(subject) : nil
44
+ end
284
45
 
285
- # Pushes the fragment onto the last line array of content. If the
286
- # fragment is an array itself then it will be pushed onto content
287
- # as a new line.
46
+ # Pushes the fragment onto the last line of content. If the
47
+ # fragment is an array itself then it will be pushed onto
48
+ # content as a new line.
288
49
  #
289
50
  # c = Comment.new
290
51
  # c.push "some line"
@@ -342,12 +103,12 @@ module Lazydoc
342
103
  # # []]
343
104
  #
344
105
  def append(line)
345
- Comment.scan(line) {|f| push(f) }
106
+ scan(line) {|f| push(f) }
346
107
  end
347
108
 
348
- # Unshifts the fragment to the first line array of content. If the
349
- # fragment is an array itself then it will be unshifted onto content
350
- # as a new line.
109
+ # Unshifts the fragment to the first line of content. If the
110
+ # fragment is an array itself then it will be unshifted onto
111
+ # content as a new line.
351
112
  #
352
113
  # c = Comment.new
353
114
  # c.unshift "some line"
@@ -399,16 +160,16 @@ module Lazydoc
399
160
  # # ['']]
400
161
  #
401
162
  def prepend(line)
402
- Comment.scan(line) {|f| unshift(f) }
163
+ scan(line) {|f| unshift(f) }
403
164
  end
404
-
405
- # Builds the subject and content of self using lines; resolve sets
406
- # the subject to the line at line_number, and parses content up
407
- # from there. Any previously set subject and content is overridden.
408
- # Returns self.
165
+
166
+ # Builds the content of self by parsing comments up from line_number.
167
+ # Whitespace lines between line_number and the preceding comment are
168
+ # skipped. Previous content is overridden. Returns self.
409
169
  #
410
170
  # document = %Q{
411
171
  # module Sample
172
+ #
412
173
  # # this is the content of the comment
413
174
  # # for method_one
414
175
  # def method_one
@@ -416,97 +177,119 @@ module Lazydoc
416
177
  #
417
178
  # # this is the content of the comment
418
179
  # # for method_two
180
+ #
419
181
  # def method_two
420
182
  # end
421
183
  # end}
422
184
  #
423
185
  # c = Comment.new 4
424
- # c.resolve(document)
425
- # c.subject # => " def method_one"
426
- # c.content # => [["this is the content of the comment", "for method_one"]]
186
+ # c.parse_up(document)
187
+ # c.comment # => "this is the content of the comment for method_one"
188
+ #
189
+ # The input may be a String or StringScanner and, for optimal parsing of
190
+ # multiple comments from the same document, may also take an array of lines
191
+ # representing the input split along newline boundaries.
192
+ #
193
+ # ==== Stop Block
194
+ #
195
+ # A block may be provided to determine when to stop parsing comment
196
+ # content. When the block returns true, parsing stops.
197
+ #
198
+ # c = Comment.new 4
199
+ # c.parse_up(document) {|line| line =~ /# this is/ }
200
+ # c.comment # => "for method_one"
427
201
  #
428
- # Lines may be an array or a string; string inputs are split into an
429
- # array along newline boundaries.
202
+ # ==== Dynamic Line Numbers
430
203
  #
431
- # === dynamic line numbers
432
- # The line_number used by resolve may be determined dynamically from
433
- # lines by setting line_number to a Regexp and Proc. In the case
204
+ # The line_number used by parse_up may be determined dynamically from
205
+ # the input by setting line_number to a Regexp and Proc. In the case
434
206
  # of a Regexp, the first line matching the regexp is used:
435
207
  #
436
208
  # c = Comment.new(/def method/)
437
- # c.resolve(document)
438
- # c.line_number = 4
439
- # c.subject # => " def method_one"
440
- # c.content # => [["this is the content of the comment", "for method_one"]]
209
+ # c.parse_up(document)
210
+ # c.line_number # => 4
211
+ # c.comment # => "this is the content of the comment for method_one"
441
212
  #
442
213
  # Procs are called with lines and are expected to return the
443
214
  # actual line number.
444
215
  #
445
- # c = Comment.new lambda {|lines| 9 }
446
- # c.resolve(document)
447
- # c.line_number = 9
448
- # c.subject # => " def method_two"
449
- # c.content # => [["this is the content of the comment", "for method_two"]]
216
+ # c = Comment.new lambda {|scanner, lines| 9 }
217
+ # c.parse_up(document)
218
+ # c.line_number # => 9
219
+ # c.comment # => "this is the content of the comment for method_two"
450
220
  #
451
- # As shown in the examples, in both cases the dynamically determined
452
- # line_number overwrites the Regexp or Proc.
453
- def resolve(lines)
454
- lines = lines.split(/\r?\n/) if lines.kind_of?(String)
455
-
456
- # resolve late-evaluation line numbers
457
- n = case line_number
458
- when Regexp then match_index(line_number, lines)
459
- when Proc then line_number.call(lines)
460
- else line_number
461
- end
462
-
463
- # quietly exit if a line number was not found
464
- return self unless n.kind_of?(Integer)
465
-
466
- # update negative line numbers
467
- n += lines.length if n < 0
468
- unless n < lines.length
469
- raise RangeError, "line_number outside of lines: #{n} (#{lines.length})"
221
+ # As shown in the examples, the dynamically determined line_number
222
+ # overwrites the Regexp or Proc.
223
+ def parse_up(str, lines=nil, skip_subject=true)
224
+ parse(str, lines) do |n, lines|
225
+ # remove whitespace lines
226
+ n -= 1 if skip_subject
227
+ n -= 1 while n >=0 && lines[n].strip.empty?
228
+
229
+ # put together the comment
230
+ while n >= 0
231
+ line = lines[n]
232
+ break if block_given? && yield(line)
233
+ break unless prepend(line)
234
+ n -= 1
235
+ end
470
236
  end
471
-
472
- self.line_number = n
473
- self.subject = lines[n]
474
- self.content.clear
237
+ end
475
238
 
476
- # remove whitespace lines
477
- n -= 1
478
- n -= 1 while n >=0 && lines[n].strip.empty?
479
-
480
- # put together the comment
481
- while n >= 0
482
- break unless prepend(lines[n])
483
- n -= 1
239
+ # Like parse_up but builds the content of self by parsing comments down
240
+ # from line_number. Parsing begins immediately after line_number (no
241
+ # whitespace lines are skipped). Previous content is overridden.
242
+ # Returns self.
243
+ #
244
+ # document = %Q{
245
+ # # == Section One
246
+ # # documentation for section one
247
+ # # 'with' + 'indentation'
248
+ # #
249
+ # # == Section Two
250
+ # # documentation for section two
251
+ # }
252
+ #
253
+ # c = Comment.new 1
254
+ # c.parse_down(document) {|line| line =~ /Section Two/}
255
+ # c.comment # => "documentation for section one\n 'with' + 'indentation'"
256
+ #
257
+ # c = Comment.new /Section Two/
258
+ # c.parse_down(document)
259
+ # c.line_number # => 5
260
+ # c.comment # => "documentation for section two"
261
+ #
262
+ def parse_down(str, lines=nil, skip_subject=true)
263
+ parse(str, lines) do |n, lines|
264
+ # skip the subject line
265
+ n += 1 if skip_subject
266
+
267
+ # put together the comment
268
+ while line = lines[n]
269
+ break if block_given? && yield(line)
270
+ break unless append(line)
271
+ n += 1
272
+ end
484
273
  end
485
-
274
+ end
275
+
276
+ # Resolves the document for self, if set.
277
+ def resolve(str=nil, force=false)
278
+ document.resolve(str, force) if document
486
279
  self
487
280
  end
488
281
 
489
282
  # Removes leading and trailing lines from content that are
490
- # empty ([]) or whitespace (['']). Returns self.
283
+ # empty or whitespace. Returns self.
491
284
  def trim
492
285
  content.shift while !content.empty? && (content[0].empty? || content[0].join.strip.empty?)
493
286
  content.pop while !content.empty? && (content[-1].empty? || content[-1].join.strip.empty?)
494
287
  self
495
288
  end
496
-
497
- # True if all lines in content are empty.
498
- def empty?
499
- !content.find {|line| !line.empty?}
500
- end
501
-
502
- # Returns a comment trailing the subject.
503
- def trailer
504
- subject ? Comment.scan_trailer(subject) : nil
505
- end
506
289
 
507
290
  # Returns content as a string where line fragments are joined by
508
291
  # fragment_sep and lines are joined by line_sep.
509
- def to_s(fragment_sep=" ", line_sep="\n", strip=true)
292
+ def comment(fragment_sep=" ", line_sep="\n", strip=true)
510
293
  lines = content.collect {|line| line.join(fragment_sep)}
511
294
 
512
295
  # strip leading an trailing whitespace lines
@@ -517,32 +300,56 @@ module Lazydoc
517
300
 
518
301
  line_sep ? lines.join(line_sep) : lines
519
302
  end
520
-
521
- # Like to_s, but wraps the content to the specified number of cols
303
+
304
+ # Like comment, but wraps the content to the specified number of cols
522
305
  # and expands tabs to tabsize spaces.
523
306
  def wrap(cols=80, tabsize=2, line_sep="\n", fragment_sep=" ", strip=true)
524
- lines = Comment.wrap(to_s(fragment_sep, "\n", strip), cols, tabsize)
307
+ lines = super(comment(fragment_sep, "\n", strip), cols, tabsize)
525
308
  line_sep ? lines.join(line_sep) : lines
526
309
  end
527
-
528
- # Returns true if another is a Comment with the same
529
- # line_number, subject, and content as self
530
- def ==(another)
531
- another.kind_of?(Comment) &&
532
- self.line_number == another.line_number &&
533
- self.subject == another.subject &&
534
- self.content == another.content
310
+
311
+ # True if to_s is empty.
312
+ def empty?
313
+ to_s.empty?
535
314
  end
536
-
315
+
316
+ # Self-resolves and returns comment.
317
+ def to_s
318
+ resolve
319
+ comment
320
+ end
321
+
537
322
  private
538
-
539
- # utility method used to by resolve to find the index
540
- # of a line matching a regexp line_number.
541
- def match_index(regexp, lines) # :nodoc:
542
- lines.each_with_index do |line, index|
543
- return index if line =~ regexp
323
+
324
+ # helper standardizing the shared code of parse up/down
325
+ def parse(str, lines) # :nodoc:
326
+ scanner = convert_to_scanner(str)
327
+ lines ||= split_lines(scanner.string)
328
+
329
+ # resolve late-evaluation line numbers
330
+ n = case line_number
331
+ when nil then determine_line_number(scanner)
332
+ when Regexp then scan_index(scanner, line_number)
333
+ when Proc then line_number.call(scanner, lines)
334
+ else line_number
335
+ end
336
+
337
+ # do nothing unless a line number was found
338
+ unless n.kind_of?(Integer)
339
+ raise "invalid dynamic line number: #{line_number.inspect}"
340
+ end
341
+
342
+ # update negative line numbers
343
+ n += lines.length if n < 0
344
+ unless n < lines.length
345
+ raise RangeError, "line_number outside of lines: #{n} (#{lines.length})"
544
346
  end
545
- nil
347
+
348
+ self.line_number = n
349
+ self.content.clear
350
+ yield(n, lines)
351
+
352
+ self
546
353
  end
547
354
  end
548
355
  end