Module: Tap::Support::Lazydoc
- Defined in:
- lib/tap/support/lazydoc.rb,
lib/tap/support/lazydoc/config.rb,
lib/tap/support/lazydoc/method.rb,
lib/tap/support/lazydoc/comment.rb,
lib/tap/support/lazydoc/document.rb,
lib/tap/support/lazydoc/definition.rb
Overview
Lazydoc lazily pulls documentation out of source files and makes it available through LazyAttributes. Lazydoc can find two types of documentation, constant attributes and code comments. To illustrate, consider the following:
# Sample::key <value>
# This is the comment content. A content
# string can span multiple lines...
#
# code.is_allowed
# much.as_in RDoc
#
# and stops at the next non-comment
# line, the next constant attribute,
# or an end key
class Sample
extend Tap::Support::LazyAttributes
self.source_file = __FILE__
lazy_attr :key
# comment content for a code comment
# may similarly span multiple lines
def method_one
end
end
When a lazy attribute is called, Lazydoc scans source_file
for the corresponding constant attribute and makes it available as a Lazydoc::Comment.
comment = Sample::key
comment.value
# => "<value>"
comment.content
# => [
# ["This is the comment content. A content", "string can span multiple lines..."],
# [""],
# [" code.is_allowed"],
# [" much.as_in RDoc"],
# [""],
# ["and stops at the next non-comment", "line, the next constant attribute,", "or an end key"]]
"\n#{'.' * 30}\n" + comment.wrap(30) + "\n#{'.' * 30}\n"
# => %q{
# ..............................
# This is the comment content.
# A content string can span
# multiple lines...
#
# code.is_allowed
# much.as_in RDoc
#
# and stops at the next
# non-comment line, the next
# constant attribute, or an end
# key
# ..............................
#}
In addition, individual lines of code may be registered and resolved by Lazydoc:
doc = Sample.lazydoc.reset
comment = doc.register(/method_one/)
doc.resolve
comment.subject # => " def method_one"
comment.content # => [["comment content for a code comment", "may similarly span multiple lines"]]
With these basics in mind, here are some details…
Constant Attributes
Constant attributes are like constants in Ruby, but with an extra ‘key’ that must consist of only lowercase letters and/or underscores. For example, these are constant attributes:
# Const::Name::key
# Const::Name::key_with_underscores
# ::key
While these are not:
# Const::Name::Key
# Const::Name::key2
# Const::Name::k@y
Lazydoc parses a Lazydoc::Comment for each constant attribute by using the remainder of the line as a value (ie subject) and scanning down for content. Scanning continues until a non-comment line, an end key, or a new attribute is reached; the comment is then stored by constant name and key.
str = %Q{
# Const::Name::key value for key
# comment for key
# parsed until a
# non-comment line
# Const::Name::another value for another
# comment for another
# parsed to an end key
# Const::Name::another-
#
# ignored comment
}
doc = Lazydoc::Document.new
doc.resolve(str)
doc.to_hash {|comment| [comment.value, comment.to_s] }
# => {
# 'Const::Name' => {
# 'key' => ['value for key', 'comment for key parsed until a non-comment line'],
# 'another' => ['value for another', 'comment for another parsed to an end key']}
# }
Constant attributes are only parsed from commented lines. To turn off attribute parsing for a section of documentation, use start/stop keys:
str = %Q{
Const::Name::not_parsed
# :::-
# Const::Name::not_parsed
# :::+
# Const::Name::parsed value
}
doc = Lazydoc::Document.new
doc.resolve(str)
doc.to_hash {|comment| comment.value } # => {'Const::Name' => {'parsed' => 'value'}}
To hide attributes from RDoc, make use of the RDoc :startdoc:
document modifier like this (note that spaces are added to prevent RDoc from hiding the example):
# :start doc::Const::Name::one hidden in RDoc
# * This line is visible in RDoc.
# :start doc::Const::Name::one-
#
#--
# Const::Name::two
# You can hide attribute comments like this.
# Const::Name::two-
#++
#
# * This line is also visible in RDoc.
Here is the same text, for comparison if you are reading this as RDoc:
:startdoc::Const::Name::one hidden in RDoc
-
This line is visible in RDoc.
:startdoc::Const::Name::one-
– Const::Name::two You can hide attribute comments like this. Const::Name::two- ++
-
This line is also visible in RDoc.
As a side note, Const::Name::key
is not a reference to the ‘key’ constant (as that would be invalid). In very idiomatic ruby Const::Name::key
is equivalent to the method call Const::Name.key
.
Code Comments
Code comments are lines registered for parsing if and when a Lazydoc gets resolved. Unlike constant attributes, the registered line is the comment subject (ie value) and contents are parsed up from it (basically mimicking the behavior of RDoc).
str = %Q{
# comment lines for
# the method
def method
end
# as in RDoc, the comment can be
# separated from the method
def another_method
end
}
doc = Lazydoc::Document.new
doc.register(3)
doc.register(9)
doc.resolve(str)
doc.comments.collect {|comment| [comment.subject, comment.to_s] }
# => [
# ['def method', 'comment lines for the method'],
# ['def another_method', 'as in RDoc, the comment can be separated from the method']]
Comments may be registered to specific line numbers, or with a Proc or Regexp that will determine the line number during resolution. In the case of a Regexp, the first matching line is used; Procs receive an array of lines and should return the line number that should be used. See Lazydoc::Comment#resolve for more details.
Defined Under Namespace
Classes: Comment, Config, Definition, Document, Method
Constant Summary collapse
- ATTRIBUTE_REGEXP =
A regexp matching an attribute start or end. After a match:
- $1
-
const_name
- $3
-
key
- $4
-
end flag
/([A-Z][A-z]*(::[A-Z][A-z]*)*)?::([a-z_]+)(-?)/
- CONSTANT_REGEXP =
A regexp matching constants from the ATTRIBUTE_REGEXP leader
/#.*?([A-Z][A-z]*(::[A-Z][A-z]*)*)?$/
- CALLER_REGEXP =
A regexp matching a caller line, to extract the calling file and line number. After a match:
- $1
-
file
- $3
-
line number (as a string, obviously)
Note that line numbers in caller start at 1, not 0.
/^(([A-z]:)?[^:]+):(\d+)/
Class Method Summary collapse
-
.[](source_file) ⇒ Object
Returns the lazydoc in registry for the specified source file.
-
.parse(str) ⇒ Object
Parses constant attributes from the string or StringScanner.
-
.register(source_file, line_number, comment_class = Comment) ⇒ Object
Register the specified line numbers to the lazydoc for source_file.
-
.registry ⇒ Object
A hash of (source_file, lazydoc) pairs tracking the Lazydoc instance for the given source file.
-
.resolve_comments(comments) ⇒ Object
Resolves all lazydocs which include the specified code comments.
-
.scan(str, key) ⇒ Object
Scans the string or StringScanner for attributes matching the key (keys may be patterns, they are incorporated into a regexp).
-
.scan_doc(source_file, key) ⇒ Object
Scans the specified file for attributes keyed by key and stores the resulting comments in the source_file lazydoc.
- .usage(path, cols = 80) ⇒ Object
Class Method Details
.[](source_file) ⇒ Object
Returns the lazydoc in registry for the specified source file. If no such lazydoc exists, one will be created for it.
240 241 242 243 244 245 246 247 248 |
# File 'lib/tap/support/lazydoc.rb', line 240 def [](source_file) source_file = File.(source_file.to_s) lazydoc = registry.find {|doc| doc.source_file == source_file } if lazydoc == nil lazydoc = Document.new(source_file) registry << lazydoc end lazydoc end |
.parse(str) ⇒ Object
Parses constant attributes from the string or StringScanner. Yields each (const_name, key, comment) triplet to the mandatory block and skips regions delimited by the stop and start keys :-
and :+
.
str = %Q{
# Const::Name::key subject for key
# comment for key
# :::-
# Ignored::key value
# :::+
# Ignored text before attribute ::another subject for another
# comment for another
}
results = []
Lazydoc.parse(str) do |const_name, key, comment|
results << [const_name, key, comment.subject, comment.to_s]
end
results
# => [
# ['Const::Name', 'key', 'subject for key', 'comment for key'],
# ['', 'another', 'subject for another', 'comment for another']]
Returns the StringScanner used during scanning.
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/tap/support/lazydoc.rb', line 358 def parse(str) # :yields: const_name, key, comment scanner = case str when StringScanner then str when String then StringScanner.new(str) else raise TypeError, "can't convert #{str.class} into StringScanner or String" end scan(scanner, '[a-z_]+') do |const_name, key, value| comment = Comment.parse(scanner, false) do |line| if line =~ ATTRIBUTE_REGEXP # rewind to capture the next attribute unless an end is specified. scanner.unscan unless $4 == '-' && $3 == key && $1.to_s == const_name true else false end end comment.subject = value yield(const_name, key, comment) end end |
.register(source_file, line_number, comment_class = Comment) ⇒ Object
Register the specified line numbers to the lazydoc for source_file. Returns a comment_class instance corresponding to the line.
252 253 254 |
# File 'lib/tap/support/lazydoc.rb', line 252 def register(source_file, line_number, comment_class=Comment) Lazydoc[source_file].register(line_number, comment_class) end |
.registry ⇒ Object
A hash of (source_file, lazydoc) pairs tracking the Lazydoc instance for the given source file.
234 235 236 |
# File 'lib/tap/support/lazydoc.rb', line 234 def registry @registry ||= [] end |
.resolve_comments(comments) ⇒ Object
Resolves all lazydocs which include the specified code comments.
257 258 259 260 261 262 |
# File 'lib/tap/support/lazydoc.rb', line 257 def resolve_comments(comments) registry.each do |doc| next if (comments & doc.comments).empty? doc.resolve end end |
.scan(str, key) ⇒ Object
Scans the string or StringScanner for attributes matching the key (keys may be patterns, they are incorporated into a regexp). Yields each (const_name, key, value) triplet to the mandatory block and skips regions delimited by the stop and start keys :-
and :+
.
str = %Q{
# Const::Name::key value
# ::alt alt_value
#
# Ignored::Attribute::not_matched value
# :::-
# Also::Ignored::key value
# :::+
# Another::key another value
Ignored::key value
}
results = []
Lazydoc.scan(str, 'key|alt') do |const_name, key, value|
results << [const_name, key, value]
end
results
# => [
# ['Const::Name', 'key', 'value'],
# ['', 'alt', 'alt_value'],
# ['Another', 'key', 'another value']]
Returns the StringScanner used during scanning.
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/tap/support/lazydoc.rb', line 307 def scan(str, key) # :yields: const_name, key, value scanner = case str when StringScanner then str when String then StringScanner.new(str) else raise TypeError, "can't convert #{str.class} into StringScanner or String" end regexp = /^(.*?)::(:-|#{key})/ while !scanner.eos? break if scanner.skip_until(regexp) == nil if scanner[2] == ":-" scanner.skip_until(/:::\+/) else next unless scanner[1] =~ CONSTANT_REGEXP key = scanner[2] yield($1.to_s, key, scanner.matched.strip) if scanner.scan(/[ \r\t].*$|$/) end end scanner end |
.scan_doc(source_file, key) ⇒ Object
Scans the specified file for attributes keyed by key and stores the resulting comments in the source_file lazydoc. Returns the lazydoc.
267 268 269 270 271 272 273 274 |
# File 'lib/tap/support/lazydoc.rb', line 267 def scan_doc(source_file, key) lazydoc = nil scan(File.read(source_file), key) do |const_name, attr_key, comment| lazydoc = self[source_file] unless lazydoc lazydoc[const_name][attr_key] = comment end lazydoc end |
.usage(path, cols = 80) ⇒ Object
379 380 381 382 383 |
# File 'lib/tap/support/lazydoc.rb', line 379 def usage(path, cols=80) scanner = StringScanner.new(File.read(path)) scanner.scan(/^#!.*?$/) Comment.parse(scanner, false).wrap(cols, 2).strip end |