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.
- data/README +52 -76
- data/lib/lazydoc.rb +48 -155
- data/lib/lazydoc/arguments.rb +26 -0
- data/lib/lazydoc/attributes.rb +111 -24
- data/lib/lazydoc/comment.rb +160 -353
- data/lib/lazydoc/document.rb +177 -101
- data/lib/lazydoc/method.rb +3 -77
- data/lib/lazydoc/subject.rb +19 -0
- data/lib/lazydoc/trailer.rb +19 -0
- data/lib/lazydoc/utils.rb +232 -0
- metadata +15 -15
@@ -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
|
data/lib/lazydoc/attributes.rb
CHANGED
@@ -1,53 +1,140 @@
|
|
1
|
-
require 'lazydoc'
|
2
|
-
|
3
1
|
module Lazydoc
|
4
|
-
|
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
|
20
|
-
# Attributes extends
|
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
|
-
#
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
#
|
37
|
-
def
|
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 #{
|
40
|
-
|
41
|
-
|
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
|
49
|
-
def
|
50
|
-
lazydoc
|
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
|
data/lib/lazydoc/comment.rb
CHANGED
@@ -1,275 +1,31 @@
|
|
1
|
-
require '
|
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
|
-
|
63
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
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
|
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
|
-
|
264
|
-
|
265
|
-
|
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
|
286
|
-
# fragment is an array itself then it will be pushed onto
|
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
|
-
|
106
|
+
scan(line) {|f| push(f) }
|
346
107
|
end
|
347
108
|
|
348
|
-
# Unshifts the fragment to the first line
|
349
|
-
# fragment is an array itself then it will be unshifted onto
|
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
|
-
|
163
|
+
scan(line) {|f| unshift(f) }
|
403
164
|
end
|
404
|
-
|
405
|
-
# Builds the
|
406
|
-
#
|
407
|
-
#
|
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.
|
425
|
-
# c.
|
426
|
-
#
|
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
|
-
#
|
429
|
-
# array along newline boundaries.
|
202
|
+
# ==== Dynamic Line Numbers
|
430
203
|
#
|
431
|
-
#
|
432
|
-
#
|
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.
|
438
|
-
# c.line_number
|
439
|
-
# c.
|
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.
|
447
|
-
# c.line_number
|
448
|
-
# c.
|
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,
|
452
|
-
#
|
453
|
-
def
|
454
|
-
lines
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
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
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
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
|
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
|
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
|
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 =
|
307
|
+
lines = super(comment(fragment_sep, "\n", strip), cols, tabsize)
|
525
308
|
line_sep ? lines.join(line_sep) : lines
|
526
309
|
end
|
527
|
-
|
528
|
-
#
|
529
|
-
|
530
|
-
|
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
|
-
#
|
540
|
-
|
541
|
-
|
542
|
-
lines
|
543
|
-
|
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
|
-
|
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
|