lazydoc 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|