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
data/lib/lazydoc/document.rb
CHANGED
@@ -2,128 +2,179 @@ require 'lazydoc/comment'
|
|
2
2
|
require 'lazydoc/method'
|
3
3
|
|
4
4
|
module Lazydoc
|
5
|
+
autoload(:Attributes, 'lazydoc/attributes')
|
6
|
+
autoload(:Arguments, 'lazydoc/arguments')
|
7
|
+
autoload(:Subject, 'lazydoc/subject')
|
8
|
+
autoload(:Trailer, 'lazydoc/trailer')
|
5
9
|
|
6
|
-
# A
|
7
|
-
#
|
10
|
+
# A regexp matching an attribute start or end. After a match:
|
11
|
+
#
|
12
|
+
# $1:: const_name
|
13
|
+
# $3:: key
|
14
|
+
# $4:: end flag
|
15
|
+
#
|
16
|
+
ATTRIBUTE_REGEXP = /([A-Z][A-z]*(::[A-Z][A-z]*)*)?::([a-z_]+)(-?)/
|
17
|
+
|
18
|
+
# A regexp matching constants from the ATTRIBUTE_REGEXP leader
|
19
|
+
CONSTANT_REGEXP = /#.*?([A-Z][A-z]*(::[A-Z][A-z]*)*)?$/
|
20
|
+
|
21
|
+
# A regexp matching a caller line, to extract the calling file
|
22
|
+
# and line number. After a match:
|
23
|
+
#
|
24
|
+
# $1:: file
|
25
|
+
# $3:: line number (as a string, obviously)
|
26
|
+
#
|
27
|
+
# Note that line numbers in caller start at 1, not 0.
|
28
|
+
CALLER_REGEXP = /^(([A-z]:)?[^:]+):(\d+)/
|
29
|
+
|
30
|
+
# A Document resolves constant attributes and code comments for a particular
|
31
|
+
# source file. Documents may be assigned a default_const_name to be used
|
8
32
|
# when a constant attribute does not specify a constant.
|
9
33
|
#
|
10
|
-
# #
|
34
|
+
# # Const::Name::key value a
|
11
35
|
# # ::key value b
|
12
36
|
#
|
13
|
-
# doc = Document.new(__FILE__, '
|
37
|
+
# doc = Document.new(__FILE__, 'Default')
|
14
38
|
# doc.resolve
|
15
|
-
# doc['KeyWithConst']['key'].value # => 'value a'
|
16
|
-
# doc['DefaultConst']['key'].value # => 'value b'
|
17
39
|
#
|
40
|
+
# Document['Const::Name']['key'].value # => 'value a'
|
41
|
+
# Document['Default']['key'].value # => 'value b'
|
42
|
+
#
|
43
|
+
# As shown in the example, constant attibutes for all documents are cached in
|
44
|
+
# the class-level const_attrs hash and are normally consumed through Document
|
45
|
+
# itself.
|
18
46
|
class Document
|
47
|
+
class << self
|
48
|
+
|
49
|
+
# A nested hash of (const_name, (key, comment)) pairs tracking
|
50
|
+
# the constant attributes assigned to a constant name.
|
51
|
+
def const_attrs
|
52
|
+
@const_attrs ||= {}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the hash of (key, comment) pairs for const_name stored
|
56
|
+
# in const_attrs. If no such hash exists, one will be created.
|
57
|
+
def [](const_name)
|
58
|
+
const_attrs[const_name] ||= {}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Scans the string or StringScanner for attributes matching the key
|
62
|
+
# (keys may be patterns; they are incorporated into a regexp).
|
63
|
+
# Regions delimited by the stop and start keys <tt>:::-</tt> and
|
64
|
+
# <tt>:::+</tt> are skipped. Yields each (const_name, key, value)
|
65
|
+
# triplet to the block.
|
66
|
+
#
|
67
|
+
# str = %Q{
|
68
|
+
# # Name::Space::key value
|
69
|
+
# # ::alt alt_value
|
70
|
+
# #
|
71
|
+
# # Ignored::Attribute::not_matched value
|
72
|
+
# # :::-
|
73
|
+
# # Also::Ignored::key value
|
74
|
+
# # :::+
|
75
|
+
# # Another::key another value
|
76
|
+
#
|
77
|
+
# Ignored::key value
|
78
|
+
# }
|
79
|
+
#
|
80
|
+
# results = []
|
81
|
+
# Document.scan(str, 'key|alt') do |const_name, key, value|
|
82
|
+
# results << [const_name, key, value]
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# results
|
86
|
+
# # => [
|
87
|
+
# # ['Name::Space', 'key', 'value'],
|
88
|
+
# # ['', 'alt', 'alt_value'],
|
89
|
+
# # ['Another', 'key', 'another value']]
|
90
|
+
#
|
91
|
+
# Returns the StringScanner used during scanning.
|
92
|
+
def scan(str, key) # :yields: const_name, key, value
|
93
|
+
scanner = case str
|
94
|
+
when StringScanner then str
|
95
|
+
when String then StringScanner.new(str)
|
96
|
+
else raise TypeError, "can't convert #{str.class} into StringScanner or String"
|
97
|
+
end
|
98
|
+
|
99
|
+
regexp = /^(.*?)::(:-|#{key})/
|
100
|
+
while !scanner.eos?
|
101
|
+
break if scanner.skip_until(regexp) == nil
|
102
|
+
|
103
|
+
if scanner[2] == ":-"
|
104
|
+
scanner.skip_until(/:::\+/)
|
105
|
+
else
|
106
|
+
next unless scanner[1] =~ CONSTANT_REGEXP
|
107
|
+
key = scanner[2]
|
108
|
+
yield($1.to_s, key, scanner.matched.strip) if scanner.scan(/[ \r\t].*$|$/)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
scanner
|
113
|
+
end
|
114
|
+
end
|
19
115
|
|
20
116
|
# The source file for self, used during resolve
|
21
117
|
attr_reader :source_file
|
22
|
-
|
23
|
-
# An array of Comment objects identifying lines
|
24
|
-
# resolved or to-be-resolved
|
25
|
-
attr_reader :comments
|
26
|
-
|
27
|
-
# A hash of [const_name, attributes] pairs tracking the constant
|
28
|
-
# attributes resolved or to-be-resolved for self. Attributes
|
29
|
-
# are hashes of [key, comment] pairs.
|
30
|
-
attr_reader :const_attrs
|
31
|
-
|
118
|
+
|
32
119
|
# The default constant name used when no constant name
|
33
120
|
# is specified for a constant attribute
|
34
121
|
attr_reader :default_const_name
|
35
122
|
|
123
|
+
# An array of Comment objects registered to self
|
124
|
+
attr_reader :comments
|
125
|
+
|
36
126
|
# Flag indicating whether or not self has been resolved
|
37
127
|
attr_accessor :resolved
|
38
|
-
|
39
|
-
def initialize(source_file=nil, default_const_name=
|
128
|
+
|
129
|
+
def initialize(source_file=nil, default_const_name=nil)
|
40
130
|
self.source_file = source_file
|
41
131
|
@default_const_name = default_const_name
|
42
132
|
@comments = []
|
43
|
-
@const_attrs = {}
|
44
133
|
@resolved = false
|
45
|
-
self.reset
|
46
134
|
end
|
47
|
-
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
@const_attrs.clear
|
55
|
-
@comments.clear
|
56
|
-
@resolved = false
|
57
|
-
self
|
135
|
+
|
136
|
+
# Returns the attributes for the specified const_name. If an empty
|
137
|
+
# const_name ('') is specified, and a default_const_name is set,
|
138
|
+
# the default_const_name will be used instead.
|
139
|
+
def [](const_name)
|
140
|
+
const_name = default_const_name if default_const_name && const_name == ''
|
141
|
+
Document[const_name]
|
58
142
|
end
|
59
143
|
|
60
|
-
#
|
144
|
+
# Expands and sets the source file for self.
|
61
145
|
def source_file=(source_file)
|
62
146
|
@source_file = source_file == nil ? nil : File.expand_path(source_file)
|
63
147
|
end
|
64
|
-
|
65
|
-
# Sets the default_const_name for self. Any const_attrs assigned to
|
66
|
-
# the previous default will be removed and merged with those already
|
67
|
-
# assigned to the new default.
|
68
|
-
def default_const_name=(const_name)
|
69
|
-
self[const_name].merge!(const_attrs.delete(@default_const_name) || {})
|
70
|
-
@default_const_name = const_name
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns the attributes for the specified const_name.
|
74
|
-
def [](const_name)
|
75
|
-
const_attrs[const_name] ||= {}
|
76
|
-
end
|
77
148
|
|
78
|
-
#
|
79
|
-
#
|
80
|
-
|
81
|
-
names = []
|
82
|
-
const_attrs.each_pair do |const_name, attrs|
|
83
|
-
names << const_name unless attrs.empty?
|
84
|
-
end
|
85
|
-
names
|
86
|
-
end
|
87
|
-
|
88
|
-
# Register the specified line number to self. Register
|
89
|
-
# may take an integer or a regexp for late-evaluation.
|
90
|
-
# See Comment#resolve for more details.
|
149
|
+
# Registers the specified line number to self. Register may take an
|
150
|
+
# integer or a regexp for dynamic evaluation. See Comment#resolve for
|
151
|
+
# more details.
|
91
152
|
#
|
92
|
-
# Returns
|
153
|
+
# Returns the newly registered comment.
|
93
154
|
def register(line_number, comment_class=Comment)
|
94
|
-
comment =
|
95
|
-
|
96
|
-
if comment == nil
|
97
|
-
comment = comment_class.new(line_number)
|
98
|
-
comments << comment
|
99
|
-
end
|
100
|
-
|
155
|
+
comment = comment_class.new(line_number, self)
|
156
|
+
comments << comment
|
101
157
|
comment
|
102
158
|
end
|
103
159
|
|
104
|
-
# Registers a regexp matching the first method by the specified name.
|
105
|
-
def register_method(method_name, comment_class=Method)
|
106
|
-
register(Method.method_regexp(method_name), comment_class)
|
107
|
-
end
|
108
|
-
|
109
160
|
# Registers the next comment.
|
110
161
|
#
|
111
162
|
# lazydoc = Document.new(__FILE__)
|
112
163
|
#
|
113
|
-
# lazydoc.register___
|
164
|
+
# c = lazydoc.register___
|
114
165
|
# # this is the comment
|
115
166
|
# # that is registered
|
116
167
|
# def method(a,b,c)
|
117
168
|
# end
|
118
169
|
#
|
119
170
|
# lazydoc.resolve
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
171
|
+
#
|
172
|
+
# c.subject # => "def method(a,b,c)"
|
173
|
+
# c.comment # => "this is the comment that is registered"
|
123
174
|
#
|
124
175
|
def register___(comment_class=Comment, caller_index=0)
|
125
176
|
caller[caller_index] =~ CALLER_REGEXP
|
126
|
-
block = lambda do |lines|
|
177
|
+
block = lambda do |scanner, lines|
|
127
178
|
n = $3.to_i
|
128
179
|
n += 1 while lines[n] =~ /^\s*(#.*)?$/
|
129
180
|
n
|
@@ -131,41 +182,66 @@ module Lazydoc
|
|
131
182
|
register(block, comment_class)
|
132
183
|
end
|
133
184
|
|
134
|
-
# Scans str for constant attributes and adds them to
|
135
|
-
#
|
136
|
-
# the contents of source_file are used instead.
|
185
|
+
# Scans str for constant attributes and adds them to Document.const_attrs.
|
186
|
+
# Comments registered with self are also resolved against str. If no str
|
187
|
+
# is specified, the contents of source_file are used instead.
|
137
188
|
#
|
138
|
-
# Resolve does nothing if resolved == true
|
139
|
-
# was resolved, or false otherwise.
|
140
|
-
def resolve(str=nil)
|
141
|
-
return
|
142
|
-
|
189
|
+
# Resolve does nothing if resolved == true, unless force is also specified.
|
190
|
+
# Returns true if str was resolved, or false otherwise.
|
191
|
+
def resolve(str=nil, force=false)
|
192
|
+
return false if resolved && !force
|
193
|
+
@resolved = true
|
194
|
+
|
143
195
|
str = File.read(source_file) if str == nil
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
196
|
+
lines = Utils.split_lines(str)
|
197
|
+
scanner = Utils.convert_to_scanner(str)
|
198
|
+
|
199
|
+
Document.scan(scanner, '[a-z_]+') do |const_name, key, value|
|
200
|
+
# get or initialize the comment that will be parsed
|
201
|
+
comment = (self[const_name][key] ||= Subject.new(nil, self))
|
202
|
+
|
203
|
+
# skip non-comment constant attributes
|
204
|
+
next unless comment.kind_of?(Comment)
|
205
|
+
|
206
|
+
# parse the comment
|
207
|
+
comment.parse_down(scanner, lines) do |line|
|
208
|
+
if line =~ ATTRIBUTE_REGEXP
|
209
|
+
# rewind to capture the next attribute unless an end is specified.
|
210
|
+
scanner.unscan unless $4 == '-' && $3 == key && $1.to_s == const_name
|
211
|
+
true
|
212
|
+
else false
|
213
|
+
end
|
153
214
|
end
|
215
|
+
|
216
|
+
# set the subject
|
217
|
+
comment.subject = value
|
154
218
|
end
|
155
|
-
|
156
|
-
|
219
|
+
|
220
|
+
# resolve registered comments
|
221
|
+
comments.each do |comment|
|
222
|
+
comment.parse_up(scanner, lines)
|
223
|
+
|
224
|
+
n = comment.line_number
|
225
|
+
comment.subject = n.kind_of?(Integer) ? lines[n] : nil
|
226
|
+
end
|
227
|
+
|
228
|
+
true
|
157
229
|
end
|
158
|
-
|
159
|
-
|
230
|
+
|
231
|
+
# Summarizes constant attributes registered to self by collecting them
|
232
|
+
# into a nested hash of (const_name, (key, comment)) pairs. A block
|
233
|
+
# may be provided to collect values from the comments; each comment is
|
234
|
+
# yielded to the block and the return stored in it's place.
|
235
|
+
def summarize
|
160
236
|
const_hash = {}
|
161
|
-
const_attrs.each_pair do |const_name, attributes|
|
237
|
+
Document.const_attrs.each_pair do |const_name, attributes|
|
162
238
|
next if attributes.empty?
|
163
|
-
|
164
|
-
attr_hash = {}
|
239
|
+
|
240
|
+
const_hash[const_name] = attr_hash = {}
|
165
241
|
attributes.each_pair do |key, comment|
|
242
|
+
next unless comment.document == self
|
166
243
|
attr_hash[key] = (block_given? ? yield(comment) : comment)
|
167
244
|
end
|
168
|
-
const_hash[const_name] = attr_hash
|
169
245
|
end
|
170
246
|
const_hash
|
171
247
|
end
|
data/lib/lazydoc/method.rb
CHANGED
@@ -10,88 +10,14 @@ module Lazydoc
|
|
10
10
|
# end
|
11
11
|
# }
|
12
12
|
#
|
13
|
-
# m =
|
13
|
+
# m = Document.new.register(2, Method)
|
14
|
+
# m.resolve(sample_method)
|
14
15
|
# m.method_name # => "method_name"
|
15
16
|
# m.arguments # => ["a", "b='default'", "&c"]
|
16
17
|
# m.trailer # => "trailing comment"
|
17
18
|
# m.to_s # => "This is the comment body"
|
18
19
|
#
|
19
20
|
class Method < Comment
|
20
|
-
class << self
|
21
|
-
|
22
|
-
# Generates a regexp matching a standard definition of the
|
23
|
-
# specified method.
|
24
|
-
#
|
25
|
-
# m = Method.method_regexp("method")
|
26
|
-
# m =~ "def method" # => true
|
27
|
-
# m =~ "def method(with, args, &block)" # => true
|
28
|
-
# m !~ "def some_other_method" # => true
|
29
|
-
#
|
30
|
-
def method_regexp(method_name)
|
31
|
-
/^\s*def\s+#{method_name}(\W|$)/
|
32
|
-
end
|
33
|
-
|
34
|
-
# Parses an argument string (anything following the method name in a
|
35
|
-
# standard method definition, including parenthesis/comments/default
|
36
|
-
# values etc) into an array of strings.
|
37
|
-
#
|
38
|
-
# Method.parse_args("(a, b='default', &block)")
|
39
|
-
# # => ["a", "b='default'", "&block"]
|
40
|
-
#
|
41
|
-
# Note the %-syntax for strings and arrays is not fully supported,
|
42
|
-
# ie %w, %Q, %q, etc. may not parse correctly. The same is true
|
43
|
-
# for multiline argument strings.
|
44
|
-
def parse_args(str)
|
45
|
-
scanner = case str
|
46
|
-
when StringScanner then str
|
47
|
-
when String then StringScanner.new(str)
|
48
|
-
else raise TypeError, "can't convert #{str.class} into StringScanner or String"
|
49
|
-
end
|
50
|
-
str = scanner.string
|
51
|
-
|
52
|
-
# skip whitespace and leading LPAREN
|
53
|
-
scanner.skip(/\s*\(?\s*/)
|
54
|
-
|
55
|
-
args = []
|
56
|
-
brakets = braces = parens = 0
|
57
|
-
start = scanner.pos
|
58
|
-
broke = while scanner.skip(/.*?['"#,\(\)\{\}\[\]]/)
|
59
|
-
pos = scanner.pos - 1
|
60
|
-
|
61
|
-
case str[pos]
|
62
|
-
when ?,,nil
|
63
|
-
# skip if in brakets, braces, or parenthesis
|
64
|
-
next if parens > 0 || brakets > 0 || braces > 0
|
65
|
-
|
66
|
-
# ok, found an arg
|
67
|
-
args << str[start, pos-start].strip
|
68
|
-
start = pos + 1
|
69
|
-
|
70
|
-
when ?# then break(true) # break on a comment
|
71
|
-
when ?' then skip_quote(scanner, /'/) # parse over quoted strings
|
72
|
-
when ?" then skip_quote(scanner, /"/) # parse over double-quoted string
|
73
|
-
|
74
|
-
when ?( then parens += 1 # for brakets, braces, and parenthesis
|
75
|
-
when ?) # simply track the nesting EXCEPT for
|
76
|
-
break(true) if parens == 0 # RPAREN. If the closing parenthesis
|
77
|
-
parens -= 1 # is found, break.
|
78
|
-
when ?[ then braces += 1
|
79
|
-
when ?] then braces -= 1
|
80
|
-
when ?{ then brakets += 1
|
81
|
-
when ?} then brakets -= 1
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# parse out the final arg. if the loop broke (ie
|
86
|
-
# a comment or the closing parenthesis was found)
|
87
|
-
# then the end position is determined by the
|
88
|
-
# scanner, otherwise take all that remains
|
89
|
-
pos = broke ? scanner.pos-1 : str.length
|
90
|
-
args << str[start, pos-start].strip
|
91
|
-
|
92
|
-
args
|
93
|
-
end
|
94
|
-
end
|
95
21
|
|
96
22
|
# Matches a standard method definition. After the match:
|
97
23
|
#
|
@@ -120,7 +46,7 @@ module Lazydoc
|
|
120
46
|
end
|
121
47
|
|
122
48
|
@method_name = $1
|
123
|
-
@arguments =
|
49
|
+
@arguments = scan_args($2)
|
124
50
|
|
125
51
|
super
|
126
52
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Lazydoc
|
2
|
+
|
3
|
+
# A special type of self-resolving Comment whose to_s returns the
|
4
|
+
# subject, or an empty string if subject is nil.
|
5
|
+
#
|
6
|
+
# s = Subject.new
|
7
|
+
# s.subject = "subject string"
|
8
|
+
# s.to_s # => "subject string"
|
9
|
+
#
|
10
|
+
class Subject < Comment
|
11
|
+
|
12
|
+
# Self-resolves and returns subject, or an empty
|
13
|
+
# string if subject is nil.
|
14
|
+
def to_s
|
15
|
+
resolve
|
16
|
+
subject.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|