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 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