lazydoc 0.1.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/MIT-LICENSE +19 -0
- data/README +214 -0
- data/lib/lazydoc/attributes.rb +42 -0
- data/lib/lazydoc/comment.rb +499 -0
- data/lib/lazydoc/document.rb +148 -0
- data/lib/lazydoc.rb +180 -0
- metadata +68 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008, Regents of the University of Colorado.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
4
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
5
|
+
without restriction, including without limitation the rights to use, copy, modify, merge,
|
6
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
7
|
+
to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
10
|
+
substantial portions of the Software.
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
13
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
14
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
15
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
16
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
17
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
19
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
= Lazydoc[http://tap.rubyforge.org/lazydoc]
|
2
|
+
|
3
|
+
Lazydoc lazily pulls documentation out of source files and makes it
|
4
|
+
available in code through lazy attributes. Lazydoc is used by the
|
5
|
+
Tap[http://tap.rubyforge.org] framework.
|
6
|
+
|
7
|
+
== Description
|
8
|
+
|
9
|
+
Lazydoc can find two types of documentation: constant attributes and
|
10
|
+
code comments. To illustrate, consider the following:
|
11
|
+
|
12
|
+
# Sample::key <value>
|
13
|
+
# This is the comment content. A content
|
14
|
+
# string can span multiple lines...
|
15
|
+
#
|
16
|
+
# code.is_allowed
|
17
|
+
# much.as_in RDoc
|
18
|
+
#
|
19
|
+
# and stops at the next non-comment
|
20
|
+
# line, the next constant attribute,
|
21
|
+
# or an end key
|
22
|
+
class Sample
|
23
|
+
extend Lazydoc::Attributes
|
24
|
+
self.source_file = __FILE__
|
25
|
+
|
26
|
+
lazy_attr :key
|
27
|
+
|
28
|
+
# comment content for a code comment
|
29
|
+
# may similarly span multiple lines
|
30
|
+
def method_one
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
When a lazy attribute is called, Lazydoc scans <tt>source_file</tt> for
|
35
|
+
the corresponding constant attribute and makes it available as a
|
36
|
+
Lazydoc::Comment.
|
37
|
+
|
38
|
+
comment = Sample::key
|
39
|
+
comment.value
|
40
|
+
# => "<value>"
|
41
|
+
|
42
|
+
comment.content
|
43
|
+
# => [
|
44
|
+
# ["This is the comment content. A content", "string can span multiple lines..."],
|
45
|
+
# [""],
|
46
|
+
# [" code.is_allowed"],
|
47
|
+
# [" much.as_in RDoc"],
|
48
|
+
# [""],
|
49
|
+
# ["and stops at the next non-comment", "line, the next constant attribute,", "or an end key"]]
|
50
|
+
|
51
|
+
"\n#{'.' * 30}\n" + comment.wrap(30) + "\n#{'.' * 30}\n"
|
52
|
+
# => %q{
|
53
|
+
# ..............................
|
54
|
+
# This is the comment content.
|
55
|
+
# A content string can span
|
56
|
+
# multiple lines...
|
57
|
+
#
|
58
|
+
# code.is_allowed
|
59
|
+
# much.as_in RDoc
|
60
|
+
#
|
61
|
+
# and stops at the next
|
62
|
+
# non-comment line, the next
|
63
|
+
# constant attribute, or an end
|
64
|
+
# key
|
65
|
+
# ..............................
|
66
|
+
#}
|
67
|
+
|
68
|
+
In addition, individual lines of code may be registered and resolved by Lazydoc:
|
69
|
+
|
70
|
+
doc = Sample.lazydoc.reset
|
71
|
+
comment = doc.register(/method_one/)
|
72
|
+
|
73
|
+
doc.resolve
|
74
|
+
comment.subject # => " def method_one"
|
75
|
+
comment.content # => [["comment content for a code comment", "may similarly span multiple lines"]]
|
76
|
+
|
77
|
+
Check out these links for development, and bug tracking.
|
78
|
+
|
79
|
+
* Website[http://tap.rubyforge.org/lazydoc]
|
80
|
+
* Github[http://github.com/bahuvrihi/lazydoc/tree/master]
|
81
|
+
* {Google Group}[http://groups.google.com/group/ruby-on-tap]
|
82
|
+
|
83
|
+
== Usage
|
84
|
+
|
85
|
+
=== Constant Attributes
|
86
|
+
|
87
|
+
Constant attributes are like constants in Ruby, but with an extra 'key'
|
88
|
+
that must consist of only lowercase letters and/or underscores. For
|
89
|
+
example, these are constant attributes:
|
90
|
+
|
91
|
+
# Const::Name::key
|
92
|
+
# Const::Name::key_with_underscores
|
93
|
+
# ::key
|
94
|
+
|
95
|
+
While these are not:
|
96
|
+
|
97
|
+
# Const::Name::Key
|
98
|
+
# Const::Name::key2
|
99
|
+
# Const::Name::k@y
|
100
|
+
|
101
|
+
Lazydoc parses a Lazydoc::Comment for each constant attribute by using the
|
102
|
+
remainder of the line as a value (ie subject) and scanning down for content.
|
103
|
+
Scanning continues until a non-comment line, an end key, or a new attribute
|
104
|
+
is reached; the comment is then stored by constant name and key.
|
105
|
+
|
106
|
+
str = %Q{
|
107
|
+
# Const::Name::key value for key
|
108
|
+
# comment for key
|
109
|
+
# parsed until a
|
110
|
+
# non-comment line
|
111
|
+
|
112
|
+
# Const::Name::another value for another
|
113
|
+
# comment for another
|
114
|
+
# parsed to an end key
|
115
|
+
# Const::Name::another-
|
116
|
+
#
|
117
|
+
# ignored comment
|
118
|
+
}
|
119
|
+
|
120
|
+
doc = Lazydoc::Document.new
|
121
|
+
doc.resolve(str)
|
122
|
+
|
123
|
+
doc.to_hash {|comment| [comment.value, comment.to_s] }
|
124
|
+
# => {
|
125
|
+
# 'Const::Name' => {
|
126
|
+
# 'key' => ['value for key', 'comment for key parsed until a non-comment line'],
|
127
|
+
# 'another' => ['value for another', 'comment for another parsed to an end key']}
|
128
|
+
# }
|
129
|
+
|
130
|
+
Constant attributes are only parsed from commented lines. To turn off
|
131
|
+
attribute parsing for a section of documentation, use start/stop keys:
|
132
|
+
|
133
|
+
str = %Q{
|
134
|
+
Const::Name::not_parsed
|
135
|
+
|
136
|
+
# :::-
|
137
|
+
# Const::Name::not_parsed
|
138
|
+
# :::+
|
139
|
+
# Const::Name::parsed value
|
140
|
+
}
|
141
|
+
|
142
|
+
doc = Lazydoc::Document.new
|
143
|
+
doc.resolve(str)
|
144
|
+
doc.to_hash {|comment| comment.value } # => {'Const::Name' => {'parsed' => 'value'}}
|
145
|
+
|
146
|
+
To hide attributes from RDoc, make use of the RDoc <tt>:startdoc:</tt>
|
147
|
+
document modifier like this (note that spaces are added to prevent RDoc
|
148
|
+
from hiding the example):
|
149
|
+
|
150
|
+
# :start doc::Const::Name::one hidden in RDoc
|
151
|
+
# * This line is visible in RDoc.
|
152
|
+
# :start doc::Const::Name::one-
|
153
|
+
#
|
154
|
+
#--
|
155
|
+
# Const::Name::two
|
156
|
+
# You can hide attribute comments like this.
|
157
|
+
# Const::Name::two-
|
158
|
+
#++
|
159
|
+
#
|
160
|
+
# * This line is also visible in RDoc.
|
161
|
+
|
162
|
+
As a side note, <tt>Const::Name::key</tt> is not a reference to the 'key'
|
163
|
+
constant (as that would be invalid). In *very* idiomatic ruby
|
164
|
+
<tt>Const::Name::key</tt> is equivalent to the method call
|
165
|
+
<tt>Const::Name.key</tt>.
|
166
|
+
|
167
|
+
=== Code Comments
|
168
|
+
|
169
|
+
Code comments are lines registered for parsing if and when a Lazydoc gets
|
170
|
+
resolved. Unlike constant attributes, the registered line is the comment
|
171
|
+
subject (ie value) and contents are parsed up from it (basically mimicking
|
172
|
+
the behavior of RDoc).
|
173
|
+
|
174
|
+
str = %Q{
|
175
|
+
# comment lines for
|
176
|
+
# the method
|
177
|
+
def method
|
178
|
+
end
|
179
|
+
|
180
|
+
# as in RDoc, the comment can be
|
181
|
+
# separated from the method
|
182
|
+
|
183
|
+
def another_method
|
184
|
+
end
|
185
|
+
}
|
186
|
+
|
187
|
+
doc = Lazydoc::Document.new
|
188
|
+
doc.register(3)
|
189
|
+
doc.register(9)
|
190
|
+
doc.resolve(str)
|
191
|
+
|
192
|
+
doc.comments.collect {|comment| [comment.subject, comment.to_s] }
|
193
|
+
# => [
|
194
|
+
# ['def method', 'comment lines for the method'],
|
195
|
+
# ['def another_method', 'as in RDoc, the comment can be separated from the method']]
|
196
|
+
|
197
|
+
Comments may be registered to specific line numbers, or with a Proc or
|
198
|
+
Regexp that will determine the line number during resolution. In the case
|
199
|
+
of a Regexp, the first matching line is used; Procs receive an array of
|
200
|
+
lines and should return the line number that should be used. See
|
201
|
+
Lazydoc::Comment#resolve for more details.
|
202
|
+
|
203
|
+
== Installation
|
204
|
+
|
205
|
+
Lazydoc is available as a gem on RubyForge[http://rubyforge.org/projects/tap]. Use:
|
206
|
+
|
207
|
+
% gem install lazydoc
|
208
|
+
|
209
|
+
== Info
|
210
|
+
|
211
|
+
Copyright (c) 2008, Regents of the University of Colorado.
|
212
|
+
Developer:: {Simon Chiang}[http://bahuvrihi.wordpress.com], {Biomolecular Structure Program}[http://biomol.uchsc.edu/], {Hansen Lab}[http://hsc-proteomics.uchsc.edu/hansenlab/]
|
213
|
+
Support:: CU Denver School of Medicine Deans Academic Enrichment Fund
|
214
|
+
Licence:: {MIT-Style}[link:files/MIT-LICENSE.html]
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Lazydoc
|
2
|
+
# Attributes adds methods to declare class-level accessors
|
3
|
+
# for Lazydoc attributes. The source_file for the class must
|
4
|
+
# be set manually.
|
5
|
+
#
|
6
|
+
# # ConstName::key value
|
7
|
+
# class ConstName
|
8
|
+
# class << self
|
9
|
+
# include Lazydoc::Attributes
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# self.source_file = __FILE__
|
13
|
+
# lazy_attr :key
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# ConstName::key.subject # => 'value'
|
17
|
+
#
|
18
|
+
module Attributes
|
19
|
+
|
20
|
+
# The source_file for self. Must be set independently.
|
21
|
+
attr_accessor :source_file
|
22
|
+
|
23
|
+
# Returns the lazydoc for source_file
|
24
|
+
def lazydoc(resolve=true)
|
25
|
+
lazydoc = Lazydoc[source_file]
|
26
|
+
lazydoc.resolve if resolve
|
27
|
+
lazydoc
|
28
|
+
end
|
29
|
+
|
30
|
+
# Creates a lazy attribute accessor for the specified attribute.
|
31
|
+
def lazy_attr(key, attribute=key)
|
32
|
+
instance_eval %Q{
|
33
|
+
def #{key}
|
34
|
+
lazydoc[to_s]['#{attribute}'] ||= Lazydoc::Comment.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def #{key}=(comment)
|
38
|
+
Lazydoc[source_file][to_s]['#{attribute}'] = comment
|
39
|
+
end}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,499 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
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
|
+
|
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 = Comment.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
|
+
comment.subject = scanner.scan(/.+?$/)
|
128
|
+
comment.subject.strip! unless comment.subject == nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
comment
|
133
|
+
end
|
134
|
+
|
135
|
+
# Scan determines if and how to add a line fragment to a comment and
|
136
|
+
# yields the appropriate fragments to the block. Returns true if
|
137
|
+
# fragments are yielded and false otherwise.
|
138
|
+
#
|
139
|
+
# Content may be built from an array of lines using scan like so:
|
140
|
+
#
|
141
|
+
# lines = [
|
142
|
+
# "# comments spanning multiple",
|
143
|
+
# "# lines are collected",
|
144
|
+
# "#",
|
145
|
+
# "# while indented lines",
|
146
|
+
# "# are preserved individually",
|
147
|
+
# "# ",
|
148
|
+
# "not a comment line",
|
149
|
+
# "# skipped since the loop breaks",
|
150
|
+
# "# at the first non-comment line"]
|
151
|
+
#
|
152
|
+
# c = Comment.new
|
153
|
+
# lines.each do |line|
|
154
|
+
# break unless Comment.scan(line) do |fragment|
|
155
|
+
# c.push(fragment)
|
156
|
+
# end
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# c.content
|
160
|
+
# # => [
|
161
|
+
# # ['comments spanning multiple', 'lines are collected'],
|
162
|
+
# # [''],
|
163
|
+
# # [' while indented lines'],
|
164
|
+
# # [' are preserved individually'],
|
165
|
+
# # [''],
|
166
|
+
# # []]
|
167
|
+
#
|
168
|
+
def scan(line) # :yields: fragment
|
169
|
+
return false unless line =~ /^[ \t]*#[ \t]?(([ \t]*).*?)\r?$/
|
170
|
+
categorize($1, $2) do |fragment|
|
171
|
+
yield(fragment)
|
172
|
+
end
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
# Splits a line of text along whitespace breaks into fragments of cols
|
177
|
+
# width. Tabs in the line will be expanded into tabsize spaces;
|
178
|
+
# fragments are rstripped of whitespace.
|
179
|
+
#
|
180
|
+
# Comment.wrap("some line that will wrap", 10) # => ["some line", "that will", "wrap"]
|
181
|
+
# Comment.wrap(" line that will wrap ", 10) # => [" line", "that will", "wrap"]
|
182
|
+
# Comment.wrap(" ", 10) # => []
|
183
|
+
#
|
184
|
+
# The wrapping algorithm is slightly modified from:
|
185
|
+
# http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
|
186
|
+
def wrap(line, cols=80, tabsize=2)
|
187
|
+
line = line.gsub(/\t/, " " * tabsize) unless tabsize == nil
|
188
|
+
line.gsub(/(.{1,#{cols}})( +|$\r?\n?)|(.{1,#{cols}})/, "\\1\\3\n").split(/\s*?\n/)
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
# utility method used by scan to categorize and yield
|
194
|
+
# the appropriate objects to add the fragment to a
|
195
|
+
# comment
|
196
|
+
def categorize(fragment, indent) # :nodoc:
|
197
|
+
case
|
198
|
+
when fragment == indent
|
199
|
+
# empty comment line
|
200
|
+
yield [""]
|
201
|
+
yield []
|
202
|
+
when indent.empty?
|
203
|
+
# continuation line
|
204
|
+
yield fragment.rstrip
|
205
|
+
else
|
206
|
+
# indented line
|
207
|
+
yield [fragment.rstrip]
|
208
|
+
yield []
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# An array of comment fragments organized into lines
|
214
|
+
attr_reader :content
|
215
|
+
|
216
|
+
# The subject of the comment (normally set to the next
|
217
|
+
# non-comment line after the content ends; ie the line
|
218
|
+
# that would receive the comment in RDoc documentation)
|
219
|
+
attr_accessor :subject
|
220
|
+
|
221
|
+
# Returns the line number for the subject line, if known.
|
222
|
+
# Although normally an integer, line_number may be
|
223
|
+
# set to a Regexp or Proc to dynamically determine
|
224
|
+
# the subject line during resolve
|
225
|
+
attr_accessor :line_number
|
226
|
+
|
227
|
+
def initialize(line_number=nil)
|
228
|
+
@content = []
|
229
|
+
@subject = nil
|
230
|
+
@line_number = line_number
|
231
|
+
end
|
232
|
+
|
233
|
+
# Alias for subject
|
234
|
+
def value
|
235
|
+
subject
|
236
|
+
end
|
237
|
+
|
238
|
+
# Alias for subject=
|
239
|
+
def value=(value)
|
240
|
+
self.subject = value
|
241
|
+
end
|
242
|
+
|
243
|
+
# Pushes the fragment onto the last line array of content. If the
|
244
|
+
# fragment is an array itself then it will be pushed onto content
|
245
|
+
# as a new line.
|
246
|
+
#
|
247
|
+
# c = Comment.new
|
248
|
+
# c.push "some line"
|
249
|
+
# c.push "fragments"
|
250
|
+
# c.push ["a", "whole", "new line"]
|
251
|
+
#
|
252
|
+
# c.content
|
253
|
+
# # => [
|
254
|
+
# # ["some line", "fragments"],
|
255
|
+
# # ["a", "whole", "new line"]]
|
256
|
+
#
|
257
|
+
def push(fragment)
|
258
|
+
content << [] if content.empty?
|
259
|
+
|
260
|
+
case fragment
|
261
|
+
when Array
|
262
|
+
if content[-1].empty?
|
263
|
+
content[-1] = fragment
|
264
|
+
else
|
265
|
+
content.push fragment
|
266
|
+
end
|
267
|
+
else
|
268
|
+
content[-1].push fragment
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Alias for push.
|
273
|
+
def <<(fragment)
|
274
|
+
push(fragment)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Scans the comment line using Comment.scan and pushes the appropriate
|
278
|
+
# fragments onto self. Used to build a content by scanning down a set
|
279
|
+
# of lines.
|
280
|
+
#
|
281
|
+
# lines = [
|
282
|
+
# "# comment spanning multiple",
|
283
|
+
# "# lines",
|
284
|
+
# "#",
|
285
|
+
# "# indented line one",
|
286
|
+
# "# indented line two",
|
287
|
+
# "# ",
|
288
|
+
# "not a comment line"]
|
289
|
+
#
|
290
|
+
# c = Comment.new
|
291
|
+
# lines.each {|line| c.append(line) }
|
292
|
+
#
|
293
|
+
# c.content
|
294
|
+
# # => [
|
295
|
+
# # ['comment spanning multiple', 'lines'],
|
296
|
+
# # [''],
|
297
|
+
# # [' indented line one'],
|
298
|
+
# # [' indented line two'],
|
299
|
+
# # [''],
|
300
|
+
# # []]
|
301
|
+
#
|
302
|
+
def append(line)
|
303
|
+
Comment.scan(line) {|f| push(f) }
|
304
|
+
end
|
305
|
+
|
306
|
+
# Unshifts the fragment to the first line array of content. If the
|
307
|
+
# fragment is an array itself then it will be unshifted onto content
|
308
|
+
# as a new line.
|
309
|
+
#
|
310
|
+
# c = Comment.new
|
311
|
+
# c.unshift "some line"
|
312
|
+
# c.unshift "fragments"
|
313
|
+
# c.unshift ["a", "whole", "new line"]
|
314
|
+
#
|
315
|
+
# c.content
|
316
|
+
# # => [
|
317
|
+
# # ["a", "whole", "new line"],
|
318
|
+
# # ["fragments", "some line"]]
|
319
|
+
#
|
320
|
+
def unshift(fragment)
|
321
|
+
content << [] if content.empty?
|
322
|
+
|
323
|
+
case fragment
|
324
|
+
when Array
|
325
|
+
if content[0].empty?
|
326
|
+
content[0] = fragment
|
327
|
+
else
|
328
|
+
content.unshift fragment
|
329
|
+
end
|
330
|
+
else
|
331
|
+
content[0].unshift fragment
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Scans the comment line using Comment.scan and unshifts the appropriate
|
336
|
+
# fragments onto self. Used to build a content by scanning up a set of
|
337
|
+
# lines.
|
338
|
+
#
|
339
|
+
# lines = [
|
340
|
+
# "# comment spanning multiple",
|
341
|
+
# "# lines",
|
342
|
+
# "#",
|
343
|
+
# "# indented line one",
|
344
|
+
# "# indented line two",
|
345
|
+
# "# ",
|
346
|
+
# "not a comment line"]
|
347
|
+
#
|
348
|
+
# c = Comment.new
|
349
|
+
# lines.reverse_each {|line| c.prepend(line) }
|
350
|
+
#
|
351
|
+
# c.content
|
352
|
+
# # => [
|
353
|
+
# # ['comment spanning multiple', 'lines'],
|
354
|
+
# # [''],
|
355
|
+
# # [' indented line one'],
|
356
|
+
# # [' indented line two'],
|
357
|
+
# # ['']]
|
358
|
+
#
|
359
|
+
def prepend(line)
|
360
|
+
Comment.scan(line) {|f| unshift(f) }
|
361
|
+
end
|
362
|
+
|
363
|
+
# Builds the subject and content of self using lines; resolve sets
|
364
|
+
# the subject to the line at line_number, and parses content up
|
365
|
+
# from there. Any previously set subject and content is overridden.
|
366
|
+
# Returns self.
|
367
|
+
#
|
368
|
+
# document = %Q{
|
369
|
+
# module Sample
|
370
|
+
# # this is the content of the comment
|
371
|
+
# # for method_one
|
372
|
+
# def method_one
|
373
|
+
# end
|
374
|
+
#
|
375
|
+
# # this is the content of the comment
|
376
|
+
# # for method_two
|
377
|
+
# def method_two
|
378
|
+
# end
|
379
|
+
# end}
|
380
|
+
#
|
381
|
+
# c = Comment.new 4
|
382
|
+
# c.resolve(document)
|
383
|
+
# c.subject # => " def method_one"
|
384
|
+
# c.content # => [["this is the content of the comment", "for method_one"]]
|
385
|
+
#
|
386
|
+
# Lines may be an array or a string; string inputs are split into an
|
387
|
+
# array along newline boundaries.
|
388
|
+
#
|
389
|
+
# === dynamic line numbers
|
390
|
+
# The line_number used by resolve may be determined dynamically from
|
391
|
+
# lines by setting line_number to a Regexp and Proc. In the case
|
392
|
+
# of a Regexp, the first line matching the regexp is used:
|
393
|
+
#
|
394
|
+
# c = Comment.new(/def method/)
|
395
|
+
# c.resolve(document)
|
396
|
+
# c.line_number = 4
|
397
|
+
# c.subject # => " def method_one"
|
398
|
+
# c.content # => [["this is the content of the comment", "for method_one"]]
|
399
|
+
#
|
400
|
+
# Procs are called with lines and are expected to return the
|
401
|
+
# actual line number.
|
402
|
+
#
|
403
|
+
# c = Comment.new lambda {|lines| 9 }
|
404
|
+
# c.resolve(document)
|
405
|
+
# c.line_number = 9
|
406
|
+
# c.subject # => " def method_two"
|
407
|
+
# c.content # => [["this is the content of the comment", "for method_two"]]
|
408
|
+
#
|
409
|
+
# As shown in the examples, in both cases the dynamically determined
|
410
|
+
# line_number overwrites the Regexp or Proc.
|
411
|
+
def resolve(lines)
|
412
|
+
lines = lines.split(/\r?\n/) if lines.kind_of?(String)
|
413
|
+
|
414
|
+
# resolve late-evaluation line numbers
|
415
|
+
n = case line_number
|
416
|
+
when Regexp then match_index(line_number, lines)
|
417
|
+
when Proc then line_number.call(lines)
|
418
|
+
else line_number
|
419
|
+
end
|
420
|
+
|
421
|
+
# quietly exit if a line number was not found
|
422
|
+
return self unless n.kind_of?(Integer)
|
423
|
+
|
424
|
+
unless n < lines.length
|
425
|
+
raise RangeError, "line_number outside of lines: #{line_number} (#{lines.length})"
|
426
|
+
end
|
427
|
+
|
428
|
+
self.line_number = n
|
429
|
+
self.subject = lines[n]
|
430
|
+
self.content.clear
|
431
|
+
|
432
|
+
# remove whitespace lines
|
433
|
+
n -= 1
|
434
|
+
n -= 1 while n >=0 && lines[n].strip.empty?
|
435
|
+
|
436
|
+
# put together the comment
|
437
|
+
while n >= 0
|
438
|
+
break unless prepend(lines[n])
|
439
|
+
n -= 1
|
440
|
+
end
|
441
|
+
|
442
|
+
self
|
443
|
+
end
|
444
|
+
|
445
|
+
# Removes leading and trailing lines from content that are
|
446
|
+
# empty ([]) or whitespace (['']). Returns self.
|
447
|
+
def trim
|
448
|
+
content.shift while !content.empty? && (content[0].empty? || content[0].join.strip.empty?)
|
449
|
+
content.pop while !content.empty? && (content[-1].empty? || content[-1].join.strip.empty?)
|
450
|
+
self
|
451
|
+
end
|
452
|
+
|
453
|
+
# True if all lines in content are empty.
|
454
|
+
def empty?
|
455
|
+
!content.find {|line| !line.empty?}
|
456
|
+
end
|
457
|
+
|
458
|
+
# Returns content as a string where line fragments are joined by
|
459
|
+
# fragment_sep and lines are joined by line_sep.
|
460
|
+
def to_s(fragment_sep=" ", line_sep="\n", strip=true)
|
461
|
+
lines = content.collect {|line| line.join(fragment_sep)}
|
462
|
+
|
463
|
+
# strip leading an trailing whitespace lines
|
464
|
+
if strip
|
465
|
+
lines.shift while !lines.empty? && lines[0].empty?
|
466
|
+
lines.pop while !lines.empty? && lines[-1].empty?
|
467
|
+
end
|
468
|
+
|
469
|
+
line_sep ? lines.join(line_sep) : lines
|
470
|
+
end
|
471
|
+
|
472
|
+
# Like to_s, but wraps the content to the specified number of cols
|
473
|
+
# and expands tabs to tabsize spaces.
|
474
|
+
def wrap(cols=80, tabsize=2, line_sep="\n", fragment_sep=" ", strip=true)
|
475
|
+
lines = Comment.wrap(to_s(fragment_sep, "\n", strip), cols, tabsize)
|
476
|
+
line_sep ? lines.join(line_sep) : lines
|
477
|
+
end
|
478
|
+
|
479
|
+
# Returns true if another is a Comment with the same
|
480
|
+
# line_number, subject, and content as self
|
481
|
+
def ==(another)
|
482
|
+
another.kind_of?(Comment) &&
|
483
|
+
self.line_number == another.line_number &&
|
484
|
+
self.subject == another.subject &&
|
485
|
+
self.content == another.content
|
486
|
+
end
|
487
|
+
|
488
|
+
private
|
489
|
+
|
490
|
+
# utility method used to by resolve to find the index
|
491
|
+
# of a line matching a regexp line_number.
|
492
|
+
def match_index(regexp, lines) # :nodoc:
|
493
|
+
lines.each_with_index do |line, index|
|
494
|
+
return index if line =~ regexp
|
495
|
+
end
|
496
|
+
nil
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'lazydoc/comment'
|
2
|
+
|
3
|
+
module Lazydoc
|
4
|
+
|
5
|
+
# A Document tracks constant attributes and code comments for a particular
|
6
|
+
# source file. Documents may be assigned a default_const_name to be used
|
7
|
+
# when a constant attribute does not specify a constant.
|
8
|
+
#
|
9
|
+
# # KeyWithConst::key value a
|
10
|
+
# # ::key value b
|
11
|
+
#
|
12
|
+
# doc = Document.new(__FILE__, 'DefaultConst')
|
13
|
+
# doc.resolve
|
14
|
+
# doc['KeyWithConst']['key'].value # => 'value a'
|
15
|
+
# doc['DefaultConst']['key'].value # => 'value b'
|
16
|
+
#
|
17
|
+
class Document
|
18
|
+
|
19
|
+
# The source file for self, used during resolve
|
20
|
+
attr_reader :source_file
|
21
|
+
|
22
|
+
# An array of Comment objects identifying lines
|
23
|
+
# resolved or to-be-resolved
|
24
|
+
attr_reader :comments
|
25
|
+
|
26
|
+
# A hash of [const_name, attributes] pairs tracking the constant
|
27
|
+
# attributes resolved or to-be-resolved for self. Attributes
|
28
|
+
# are hashes of [key, comment] pairs.
|
29
|
+
attr_reader :const_attrs
|
30
|
+
|
31
|
+
# The default constant name used when no constant name
|
32
|
+
# is specified for a constant attribute
|
33
|
+
attr_reader :default_const_name
|
34
|
+
|
35
|
+
# Flag indicating whether or not self has been resolved
|
36
|
+
attr_accessor :resolved
|
37
|
+
|
38
|
+
def initialize(source_file=nil, default_const_name='')
|
39
|
+
self.source_file = source_file
|
40
|
+
@default_const_name = default_const_name
|
41
|
+
@comments = []
|
42
|
+
@const_attrs = {}
|
43
|
+
@resolved = false
|
44
|
+
self.reset
|
45
|
+
end
|
46
|
+
|
47
|
+
# Resets self by clearing const_attrs, comments, and setting
|
48
|
+
# resolved to false. Generally NOT recommended as this
|
49
|
+
# clears any work you've done registering lines; to simply
|
50
|
+
# allow resolve to re-scan a document, manually set
|
51
|
+
# resolved to false.
|
52
|
+
def reset
|
53
|
+
@const_attrs.clear
|
54
|
+
@comments.clear
|
55
|
+
@resolved = false
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Sets the source file for self. Expands the source file path if necessary.
|
60
|
+
def source_file=(source_file)
|
61
|
+
@source_file = source_file == nil ? nil : File.expand_path(source_file)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sets the default_const_name for self. Any const_attrs assigned to
|
65
|
+
# the previous default will be removed and merged with those already
|
66
|
+
# assigned to the new default.
|
67
|
+
def default_const_name=(const_name)
|
68
|
+
self[const_name].merge!(const_attrs.delete(@default_const_name) || {})
|
69
|
+
@default_const_name = const_name
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the attributes for the specified const_name.
|
73
|
+
def [](const_name)
|
74
|
+
const_attrs[const_name] ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns an array of the const_names in self with at
|
78
|
+
# least one attribute.
|
79
|
+
def const_names
|
80
|
+
names = []
|
81
|
+
const_attrs.each_pair do |const_name, attrs|
|
82
|
+
names << const_name unless attrs.empty?
|
83
|
+
end
|
84
|
+
names
|
85
|
+
end
|
86
|
+
|
87
|
+
# Register the specified line number to self. Register
|
88
|
+
# may take an integer or a regexp for late-evaluation.
|
89
|
+
# See Comment#resolve for more details.
|
90
|
+
#
|
91
|
+
# Returns a comment_class instance corresponding to the line.
|
92
|
+
def register(line_number, comment_class=Comment)
|
93
|
+
comment = comments.find {|c| c.class == comment_class && c.line_number == line_number }
|
94
|
+
|
95
|
+
if comment == nil
|
96
|
+
comment = comment_class.new(line_number)
|
97
|
+
comments << comment
|
98
|
+
end
|
99
|
+
|
100
|
+
comment
|
101
|
+
end
|
102
|
+
|
103
|
+
# Registers a regexp matching methods by the specified
|
104
|
+
# name.
|
105
|
+
def register_method(method, comment_class=Comment)
|
106
|
+
register(/^\s*def\s+#{method}(\W|$)/, comment_class)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Scans str for constant attributes and adds them to to self. Code
|
110
|
+
# comments are also resolved against str. If no str is specified,
|
111
|
+
# the contents of source_file are used instead.
|
112
|
+
#
|
113
|
+
# Resolve does nothing if resolved == true. Returns true if str
|
114
|
+
# was resolved, or false otherwise.
|
115
|
+
def resolve(str=nil)
|
116
|
+
return(false) if resolved
|
117
|
+
|
118
|
+
str = File.read(source_file) if str == nil
|
119
|
+
Lazydoc.parse(str) do |const_name, key, comment|
|
120
|
+
const_name = default_const_name if const_name.empty?
|
121
|
+
self[const_name][key] = comment
|
122
|
+
end
|
123
|
+
|
124
|
+
unless comments.empty?
|
125
|
+
lines = str.split(/\r?\n/)
|
126
|
+
comments.each do |comment|
|
127
|
+
comment.resolve(lines)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
@resolved = true
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_hash
|
135
|
+
const_hash = {}
|
136
|
+
const_attrs.each_pair do |const_name, attributes|
|
137
|
+
next if attributes.empty?
|
138
|
+
|
139
|
+
attr_hash = {}
|
140
|
+
attributes.each_pair do |key, comment|
|
141
|
+
attr_hash[key] = (block_given? ? yield(comment) : comment)
|
142
|
+
end
|
143
|
+
const_hash[const_name] = attr_hash
|
144
|
+
end
|
145
|
+
const_hash
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/lib/lazydoc.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'lazydoc/document'
|
2
|
+
|
3
|
+
module Lazydoc
|
4
|
+
autoload(:Attributes, 'lazydoc/attributes')
|
5
|
+
|
6
|
+
# A regexp matching an attribute start or end. After a match:
|
7
|
+
#
|
8
|
+
# $1:: const_name
|
9
|
+
# $3:: key
|
10
|
+
# $4:: end flag
|
11
|
+
#
|
12
|
+
ATTRIBUTE_REGEXP = /([A-Z][A-z]*(::[A-Z][A-z]*)*)?::([a-z_]+)(-?)/
|
13
|
+
|
14
|
+
# A regexp matching constants from the ATTRIBUTE_REGEXP leader
|
15
|
+
CONSTANT_REGEXP = /#.*?([A-Z][A-z]*(::[A-Z][A-z]*)*)?$/
|
16
|
+
|
17
|
+
# A regexp matching a caller line, to extract the calling file
|
18
|
+
# and line number. After a match:
|
19
|
+
#
|
20
|
+
# $1:: file
|
21
|
+
# $3:: line number (as a string, obviously)
|
22
|
+
#
|
23
|
+
# Note that line numbers in caller start at 1, not 0.
|
24
|
+
CALLER_REGEXP = /^(([A-z]:)?[^:]+):(\d+)/
|
25
|
+
|
26
|
+
module_function
|
27
|
+
|
28
|
+
# A hash of (source_file, lazydoc) pairs tracking the
|
29
|
+
# Lazydoc instance for the given source file.
|
30
|
+
def registry
|
31
|
+
@registry ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the lazydoc in registry for the specified source file.
|
35
|
+
# If no such lazydoc exists, one will be created for it.
|
36
|
+
def [](source_file)
|
37
|
+
source_file = File.expand_path(source_file.to_s)
|
38
|
+
lazydoc = registry.find {|doc| doc.source_file == source_file }
|
39
|
+
if lazydoc == nil
|
40
|
+
lazydoc = Document.new(source_file)
|
41
|
+
registry << lazydoc
|
42
|
+
end
|
43
|
+
lazydoc
|
44
|
+
end
|
45
|
+
|
46
|
+
# Register the specified line numbers to the lazydoc for source_file.
|
47
|
+
# Returns a comment_class instance corresponding to the line.
|
48
|
+
def register(source_file, line_number, comment_class=Comment)
|
49
|
+
Lazydoc[source_file].register(line_number, comment_class)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Resolves all lazydocs which include the specified code comments.
|
53
|
+
def resolve_comments(comments)
|
54
|
+
registry.each do |doc|
|
55
|
+
next if (comments & doc.comments).empty?
|
56
|
+
doc.resolve
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Scans the specified file for attributes keyed by key and stores
|
61
|
+
# the resulting comments in the source_file lazydoc. Returns the
|
62
|
+
# lazydoc.
|
63
|
+
def scan_doc(source_file, key)
|
64
|
+
lazydoc = nil
|
65
|
+
scan(File.read(source_file), key) do |const_name, attr_key, comment|
|
66
|
+
lazydoc = self[source_file] unless lazydoc
|
67
|
+
lazydoc[const_name][attr_key] = comment
|
68
|
+
end
|
69
|
+
lazydoc
|
70
|
+
end
|
71
|
+
|
72
|
+
# Scans the string or StringScanner for attributes matching the key
|
73
|
+
# (keys may be patterns, they are incorporated into a regexp). Yields
|
74
|
+
# each (const_name, key, value) triplet to the mandatory block and
|
75
|
+
# skips regions delimited by the stop and start keys <tt>:-</tt>
|
76
|
+
# and <tt>:+</tt>.
|
77
|
+
#
|
78
|
+
# str = %Q{
|
79
|
+
# # Const::Name::key value
|
80
|
+
# # ::alt alt_value
|
81
|
+
# #
|
82
|
+
# # Ignored::Attribute::not_matched value
|
83
|
+
# # :::-
|
84
|
+
# # Also::Ignored::key value
|
85
|
+
# # :::+
|
86
|
+
# # Another::key another value
|
87
|
+
#
|
88
|
+
# Ignored::key value
|
89
|
+
# }
|
90
|
+
#
|
91
|
+
# results = []
|
92
|
+
# Lazydoc.scan(str, 'key|alt') do |const_name, key, value|
|
93
|
+
# results << [const_name, key, value]
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# results
|
97
|
+
# # => [
|
98
|
+
# # ['Const::Name', 'key', 'value'],
|
99
|
+
# # ['', 'alt', 'alt_value'],
|
100
|
+
# # ['Another', 'key', 'another value']]
|
101
|
+
#
|
102
|
+
# Returns the StringScanner used during scanning.
|
103
|
+
def scan(str, key) # :yields: const_name, key, value
|
104
|
+
scanner = case str
|
105
|
+
when StringScanner then str
|
106
|
+
when String then StringScanner.new(str)
|
107
|
+
else raise TypeError, "can't convert #{str.class} into StringScanner or String"
|
108
|
+
end
|
109
|
+
|
110
|
+
regexp = /^(.*?)::(:-|#{key})/
|
111
|
+
while !scanner.eos?
|
112
|
+
break if scanner.skip_until(regexp) == nil
|
113
|
+
|
114
|
+
if scanner[2] == ":-"
|
115
|
+
scanner.skip_until(/:::\+/)
|
116
|
+
else
|
117
|
+
next unless scanner[1] =~ CONSTANT_REGEXP
|
118
|
+
key = scanner[2]
|
119
|
+
yield($1.to_s, key, scanner.matched.strip) if scanner.scan(/[ \r\t].*$|$/)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
scanner
|
124
|
+
end
|
125
|
+
|
126
|
+
# Parses constant attributes from the string or StringScanner. Yields
|
127
|
+
# each (const_name, key, comment) triplet to the mandatory block
|
128
|
+
# and skips regions delimited by the stop and start keys <tt>:-</tt>
|
129
|
+
# and <tt>:+</tt>.
|
130
|
+
#
|
131
|
+
# str = %Q{
|
132
|
+
# # Const::Name::key subject for key
|
133
|
+
# # comment for key
|
134
|
+
#
|
135
|
+
# # :::-
|
136
|
+
# # Ignored::key value
|
137
|
+
# # :::+
|
138
|
+
#
|
139
|
+
# # Ignored text before attribute ::another subject for another
|
140
|
+
# # comment for another
|
141
|
+
# }
|
142
|
+
#
|
143
|
+
# results = []
|
144
|
+
# Lazydoc.parse(str) do |const_name, key, comment|
|
145
|
+
# results << [const_name, key, comment.subject, comment.to_s]
|
146
|
+
# end
|
147
|
+
#
|
148
|
+
# results
|
149
|
+
# # => [
|
150
|
+
# # ['Const::Name', 'key', 'subject for key', 'comment for key'],
|
151
|
+
# # ['', 'another', 'subject for another', 'comment for another']]
|
152
|
+
#
|
153
|
+
# Returns the StringScanner used during scanning.
|
154
|
+
def parse(str) # :yields: const_name, key, comment
|
155
|
+
scanner = case str
|
156
|
+
when StringScanner then str
|
157
|
+
when String then StringScanner.new(str)
|
158
|
+
else raise TypeError, "can't convert #{str.class} into StringScanner or String"
|
159
|
+
end
|
160
|
+
|
161
|
+
scan(scanner, '[a-z_]+') do |const_name, key, value|
|
162
|
+
comment = Comment.parse(scanner, false) do |line|
|
163
|
+
if line =~ ATTRIBUTE_REGEXP
|
164
|
+
# rewind to capture the next attribute unless an end is specified.
|
165
|
+
scanner.unscan unless $4 == '-' && $3 == key && $1.to_s == const_name
|
166
|
+
true
|
167
|
+
else false
|
168
|
+
end
|
169
|
+
end
|
170
|
+
comment.subject = value
|
171
|
+
yield(const_name, key, comment)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def usage(path, cols=80)
|
176
|
+
scanner = StringScanner.new(File.read(path))
|
177
|
+
scanner.scan(/^#!.*?$/)
|
178
|
+
Comment.parse(scanner, false).wrap(cols, 2).strip
|
179
|
+
end
|
180
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lazydoc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simon Chiang
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-12 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: tap
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.11.1
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: simon.a.chiang@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README
|
33
|
+
- MIT-LICENSE
|
34
|
+
files:
|
35
|
+
- lib/lazydoc.rb
|
36
|
+
- lib/lazydoc/attributes.rb
|
37
|
+
- lib/lazydoc/comment.rb
|
38
|
+
- lib/lazydoc/document.rb
|
39
|
+
- README
|
40
|
+
- MIT-LICENSE
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://tap.rubyforge.org/lazydoc
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project: tap
|
63
|
+
rubygems_version: 1.2.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 2
|
66
|
+
summary: lazydoc
|
67
|
+
test_files: []
|
68
|
+
|