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 CHANGED
@@ -6,107 +6,80 @@ Tap[http://tap.rubyforge.org] framework.
6
6
 
7
7
  == Description
8
8
 
9
- Lazydoc can find two types of documentation: constant attributes and
10
- code comments. To illustrate, consider the following:
9
+ Lazydoc allows you to define lazy attributes that act as markers for
10
+ documentation in a source file. When you call the lazy attribute,
11
+ Lazydoc pulls out the documentation:
11
12
 
12
13
  # Sample::key <value>
13
14
  # This is the comment content. A content
14
15
  # 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
16
  class Sample
23
17
  extend Lazydoc::Attributes
24
- self.source_file = __FILE__
25
-
26
18
  lazy_attr :key
27
-
28
- # comment content for a code comment
29
- # may similarly span multiple lines
30
- def method_one
31
- end
32
19
  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
-
20
+
38
21
  comment = Sample::key
39
- comment.value
40
- # => "<value>"
22
+ comment.value # => "<value>"
23
+ comment.comment # => "This is the comment content. A content string can span multiple lines..."
41
24
 
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"
25
+ Comments support wrapping, allowing for easy presentation:
26
+
27
+ thirtydots = "\n#{'.' * 30}\n"
28
+
29
+ "#{thirtydots}#{comment.wrap(30)}#{thirtydots}"
52
30
  # => %q{
53
31
  # ..............................
54
32
  # This is the comment content.
55
33
  # A content string can span
56
34
  # 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
35
  # ..............................
66
36
  # }
67
37
 
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
- A variety of helper methods exist to register methods, in particular:
38
+ In addition, Lazydoc provides helpers to register individual lines of code,
39
+ particularly method definitions:
78
40
 
79
41
  class Helpers
80
42
  extend Lazydoc::Attributes
81
43
 
82
- register_method___
83
- # method_one was registered by the
84
- # helper
44
+ lazy_register(:method_one)
45
+
46
+ # method_one is registered whenever it
47
+ # gets defined
85
48
  def method_one(a, b='str', &c)
86
49
  end
87
50
 
88
- # method_two illustrates registration
89
- # of the line that *calls* method_two
51
+ # register_caller will register the line
52
+ # that *calls* method_two
90
53
  def method_two
91
54
  Lazydoc.register_caller
92
55
  end
93
56
  end
94
57
 
95
- # *THIS* is the line that gets registered
96
- # by method_two
97
- Helpers.new.method_two
58
+ # *THIS* is the line that gets
59
+ # registered by method_two
60
+ Helpers.const_attrs[:method_two] = Helpers.new.method_two
98
61
 
99
62
  doc = Helpers.lazydoc
100
63
  doc.resolve
101
64
 
102
- m1 = doc.comments[0]
103
- m1.method_name # => "method_one"
104
- m1.arguments # => ["a", "b='str'", "&c"]
105
- m1.to_s # => "method_one was registered by the helper"
65
+ one = Helpers.const_attrs[:method_one]
66
+ one.method_name # => "method_one"
67
+ one.arguments # => ["a", "b='str'", "&c"]
68
+ one.to_s # => "method_one is registered whenever it gets defined"
106
69
 
107
- comment = doc.comments[1]
108
- comment.subject # => "Helpers.new.method_two"
109
- comment.to_s # => "*THIS* is the line that gets registered by method_two"
70
+ two = Helpers.const_attrs[:method_two]
71
+ two.subject # => "Helpers.const_attrs[:method_two] = Helpers.new.method_two"
72
+ two.to_s # => "*THIS* is the line that gets registered by method_two"
73
+
74
+ Lazy accessors may be defined to map the registered lines as well:
75
+
76
+ class Helpers
77
+ lazy_attr(:one, :method_one)
78
+ lazy_attr(:two, :method_two)
79
+ end
80
+
81
+ Helpers.one.method_name # => "method_one"
82
+ Helpers.two.subject # => "Helpers.const_attrs[:method_two] = Helpers.new.method_two"
110
83
 
111
84
  Check out these links for development, and bug tracking.
112
85
 
@@ -117,6 +90,10 @@ Check out these links for development, and bug tracking.
117
90
 
118
91
  == Usage
119
92
 
93
+ Lazydoc can find two types of documentation, constant attributes and code
94
+ comments. The distinction is primarily how they are found and parsed; both
95
+ are represented by Comment objects.
96
+
120
97
  === Constant Attributes
121
98
 
122
99
  Constant attributes are like constants in Ruby, but with an extra 'key'
@@ -134,8 +111,8 @@ While these are not:
134
111
  # Const::Name::k@y
135
112
 
136
113
  Lazydoc parses a Lazydoc::Comment for each constant attribute by using the
137
- remainder of the line as a value (ie subject) and scanning down for content.
138
- Scanning continues until a non-comment line, an end key, or a new attribute
114
+ remainder of the line as a value (ie subject) and parsing down for content.
115
+ Parsing continues until a non-comment line, an end key, or a new attribute
139
116
  is reached; the comment is then stored by constant name and key.
140
117
 
141
118
  str = %Q{
@@ -155,7 +132,7 @@ is reached; the comment is then stored by constant name and key.
155
132
  doc = Lazydoc::Document.new
156
133
  doc.resolve(str)
157
134
 
158
- doc.to_hash {|comment| [comment.value, comment.to_s] }
135
+ doc.summarize {|c| [c.value, c.comment] }
159
136
  # => {
160
137
  # 'Const::Name' => {
161
138
  # 'key' => ['value for key', 'comment for key parsed until a non-comment line'],
@@ -176,7 +153,7 @@ attribute parsing for a section of documentation, use start/stop keys:
176
153
 
177
154
  doc = Lazydoc::Document.new
178
155
  doc.resolve(str)
179
- doc.to_hash {|comment| comment.value } # => {'Const::Name' => {'parsed' => 'value'}}
156
+ doc.summarize {|comment| comment.value } # => {'Const::Name' => {'parsed' => 'value'}}
180
157
 
181
158
  To hide attributes from RDoc, make use of the RDoc <tt>:startdoc:</tt>
182
159
  document modifier like this (note that spaces are added to prevent RDoc
@@ -194,10 +171,9 @@ from hiding the example):
194
171
  #
195
172
  # * This line is also visible in RDoc.
196
173
 
197
- As a side note, <tt>Const::Name::key</tt> is not a reference to the 'key'
198
- constant (as that would be invalid). In *very* idiomatic ruby
199
- <tt>Const::Name::key</tt> is equivalent to the method call
200
- <tt>Const::Name.key</tt>.
174
+ As a side note, 'Const::Name::key' is not a reference to the 'key'
175
+ constant (that would be invalid). In *very* idiomatic ruby
176
+ 'Const::Name::key' is equivalent to the method call 'Const::Name.key'.
201
177
 
202
178
  === Code Comments
203
179
 
@@ -224,7 +200,7 @@ the behavior of RDoc).
224
200
  doc.register(9)
225
201
  doc.resolve(str)
226
202
 
227
- doc.comments.collect {|comment| [comment.subject, comment.to_s] }
203
+ doc.comments.collect {|c| [c.subject, c.comment] }
228
204
  # => [
229
205
  # ['def method', 'comment lines for the method'],
230
206
  # ['def another_method', 'as in RDoc, the comment can be separated from the method']]
@@ -233,7 +209,7 @@ Comments may be registered to specific line numbers, or with a Proc or
233
209
  Regexp that will determine the line number during resolution. In the case
234
210
  of a Regexp, the first matching line is used; Procs receive an array of
235
211
  lines and should return the line number that should be used. See
236
- Lazydoc::Comment#resolve for more details.
212
+ {Comment#parse_up}[link://classes/Lazydoc/Comment.html] for more details.
237
213
 
238
214
  == Installation
239
215
 
@@ -1,58 +1,50 @@
1
1
  require 'lazydoc/document'
2
2
 
3
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
4
  module_function
27
5
 
28
- # A hash of (source_file, lazydoc) pairs tracking the
29
- # Lazydoc instance for the given source file.
6
+ # An array of documents registered with Lazydoc.
30
7
  def registry
31
8
  @registry ||= []
32
9
  end
33
10
 
34
- # Returns the lazydoc in registry for the specified source file.
35
- # If no such lazydoc exists, one will be created for it.
11
+ # Returns the Document in registry for the specified source file.
12
+ # If no such Document exists, one will be created for it.
36
13
  def [](source_file)
14
+ source_file = File.expand_path(source_file.to_s)
15
+ registry.find {|doc| doc.source_file == source_file } || register_file(source_file)
16
+ end
17
+
18
+ # Generates a Document the source_file and default_const_name and adds it to
19
+ # registry, or returns the document already registered to source_file. An
20
+ # error is raised if you try to re-register a source_file with an inconsistent
21
+ # default_const_name.
22
+ def register_file(source_file, default_const_name=nil)
37
23
  source_file = File.expand_path(source_file.to_s)
38
24
  lazydoc = registry.find {|doc| doc.source_file == source_file }
39
- if lazydoc == nil
40
- lazydoc = Document.new(source_file)
25
+
26
+ unless lazydoc
27
+ lazydoc = Document.new(source_file, default_const_name)
41
28
  registry << lazydoc
42
29
  end
30
+
31
+ if lazydoc.default_const_name != default_const_name
32
+ raise ArgumentError, "inconsistent default_const_name specified for #{source_file}: #{lazydoc.default_const_name.inspect} != #{default_const_name.inspect}"
33
+ end
34
+
43
35
  lazydoc
44
36
  end
45
37
 
46
- # Register the specified line numbers to the lazydoc for source_file.
47
- # Returns a comment_class instance corresponding to the line.
38
+ # Registers the line number to the document for source_file and
39
+ # returns the corresponding comment.
48
40
  def register(source_file, line_number, comment_class=Comment)
49
41
  Lazydoc[source_file].register(line_number, comment_class)
50
42
  end
51
43
 
52
- # Registers the method at the specified index in the call stack, to
44
+ # Registers the method at the specified index in the call stack to
53
45
  # the file where the method was called. Using the default index of
54
46
  # 1, register_caller registers the caller of the method where
55
- # register_caller is called. For instance:
47
+ # register_caller is called (whew!). For instance:
56
48
  #
57
49
  # module Sample
58
50
  # module_function
@@ -62,141 +54,42 @@ module Lazydoc
62
54
  # end
63
55
  #
64
56
  # # this is the line that gets registered
65
- # Sample.method
57
+ # c = Sample.method
58
+ #
59
+ # c.resolve
60
+ # c.subject # => "c = Sample.method"
61
+ # c.comment # => "this is the line that gets registered"
66
62
  #
67
63
  def register_caller(comment_class=Comment, caller_index=1)
68
64
  caller[caller_index] =~ CALLER_REGEXP
69
65
  Lazydoc[$1].register($3.to_i - 1, comment_class)
70
66
  end
71
67
 
72
- # Resolves all lazydocs which include the specified code comments.
73
- def resolve_comments(comments)
74
- registry.each do |doc|
75
- next if (comments & doc.comments).empty?
76
- doc.resolve
77
- end
78
- end
79
-
80
- # Scans the specified file for attributes keyed by key and stores
81
- # the resulting comments in the source_file lazydoc. Returns the
82
- # lazydoc.
83
- def scan_doc(source_file, key)
84
- lazydoc = nil
85
- scan(File.read(source_file), key) do |const_name, attr_key, comment|
86
- lazydoc = self[source_file] unless lazydoc
87
- lazydoc[const_name][attr_key] = comment
88
- end
89
- lazydoc
90
- end
91
-
92
- # Scans the string or StringScanner for attributes matching the key
93
- # (keys may be patterns, they are incorporated into a regexp). Yields
94
- # each (const_name, key, value) triplet to the mandatory block and
95
- # skips regions delimited by the stop and start keys <tt>:-</tt>
96
- # and <tt>:+</tt>.
97
- #
98
- # str = %Q{
99
- # # Const::Name::key value
100
- # # ::alt alt_value
68
+ # Parses the usage for a file (ie the first comment in the file
69
+ # following an optional bang line), wrapped to n cols. For
70
+ # example, with this:
71
+ #
72
+ # [hello_world.rb]
73
+ # #!/usr/bin/env ruby
74
+ # # This is your basic hello world
75
+ # # script:
101
76
  # #
102
- # # Ignored::Attribute::not_matched value
103
- # # :::-
104
- # # Also::Ignored::key value
105
- # # :::+
106
- # # Another::key another value
107
- #
108
- # Ignored::key value
109
- # }
110
- #
111
- # results = []
112
- # Lazydoc.scan(str, 'key|alt') do |const_name, key, value|
113
- # results << [const_name, key, value]
114
- # end
115
- #
116
- # results
117
- # # => [
118
- # # ['Const::Name', 'key', 'value'],
119
- # # ['', 'alt', 'alt_value'],
120
- # # ['Another', 'key', 'another value']]
121
- #
122
- # Returns the StringScanner used during scanning.
123
- def scan(str, key) # :yields: const_name, key, value
124
- scanner = case str
125
- when StringScanner then str
126
- when String then StringScanner.new(str)
127
- else raise TypeError, "can't convert #{str.class} into StringScanner or String"
128
- end
129
-
130
- regexp = /^(.*?)::(:-|#{key})/
131
- while !scanner.eos?
132
- break if scanner.skip_until(regexp) == nil
133
-
134
- if scanner[2] == ":-"
135
- scanner.skip_until(/:::\+/)
136
- else
137
- next unless scanner[1] =~ CONSTANT_REGEXP
138
- key = scanner[2]
139
- yield($1.to_s, key, scanner.matched.strip) if scanner.scan(/[ \r\t].*$|$/)
140
- end
141
- end
142
-
143
- scanner
144
- end
145
-
146
- # Parses constant attributes from the string or StringScanner. Yields
147
- # each (const_name, key, comment) triplet to the mandatory block
148
- # and skips regions delimited by the stop and start keys <tt>:-</tt>
149
- # and <tt>:+</tt>.
150
- #
151
- # str = %Q{
152
- # # Const::Name::key subject for key
153
- # # comment for key
77
+ # # % ruby hello_world.rb
154
78
  #
155
- # # :::-
156
- # # Ignored::key value
157
- # # :::+
79
+ # puts 'hello world'
158
80
  #
159
- # # Ignored text before attribute ::another subject for another
160
- # # comment for another
161
- # }
81
+ # You get this:
162
82
  #
163
- # results = []
164
- # Lazydoc.parse(str) do |const_name, key, comment|
165
- # results << [const_name, key, comment.subject, comment.to_s]
166
- # end
167
- #
168
- # results
169
- # # => [
170
- # # ['Const::Name', 'key', 'subject for key', 'comment for key'],
171
- # # ['', 'another', 'subject for another', 'comment for another']]
83
+ # "\n" + Lazydoc.usage('hello_world.rb')
84
+ # # => %Q{
85
+ # # This is your basic hello world script:
86
+ # #
87
+ # # % ruby hello_world.rb}
172
88
  #
173
- # Returns the StringScanner used during scanning.
174
- def parse(str) # :yields: const_name, key, comment
175
- scanner = case str
176
- when StringScanner then str
177
- when String then StringScanner.new(str)
178
- else raise TypeError, "can't convert #{str.class} into StringScanner or String"
179
- end
180
-
181
- scan(scanner, '[a-z_]+') do |const_name, key, value|
182
- comment = Comment.parse(scanner, false) do |line|
183
- if line =~ ATTRIBUTE_REGEXP
184
- # rewind to capture the next attribute unless an end is specified.
185
- scanner.unscan unless $4 == '-' && $3 == key && $1.to_s == const_name
186
- true
187
- else false
188
- end
189
- end
190
- comment.subject = value
191
- yield(const_name, key, comment)
192
- end
193
- end
194
-
195
- # Parses the usage for a file, ie the first comment in the file
196
- # following an optional bang line.
197
89
  def usage(path, cols=80)
198
90
  scanner = StringScanner.new(File.read(path))
199
- scanner.scan(/^#!.*?$/)
200
- Comment.parse(scanner, false).wrap(cols, 2).strip
91
+ scanner.scan(/#!.*?\r?\n/)
92
+ scanner.scan(/\s*#/m)
93
+ Comment.new.parse_down(scanner, nil, false).wrap(cols, 2).strip
201
94
  end
202
95
  end