P
Phlip
Ruboids:
A recent post here, by Robert Dober, showed how to tweak RDoc to add a raw
HTML directive, %html.
Below my sig is a monkey-patch, for Ruby 1.8's RDoc, which includes this
tweak and adds another; the ability to insert a method's contents into
another method's documentation. This allows us to do "literate programming".
We can compete with the likes of Knuth and Sedgewick by inserting
automatically tested source code into our verbiage.
To use the monkey patch, simply save it to a file, say rdoc_patch.rb and
then add require 'rdoc_patch' inside your Rakefile. If your Rakefile is
normal, it will run RDoc in-process, so that will get the patch.
I use the %html and %transclude tags like this:
# %html <a name='assert_json' />
# Example:
#
# %transclude AssertJavaScriptTest#test_assert_json
#
def assert_json(...)
...
end
The contents of test_assert_json will explain to us how to use assert_json.
And the %html <a name=...> gives us a direct, external link to the method:
http://assertxpath.rubyforge.org/classes/AssertJavaScript.html#assert_json
I'm putting the code here because I don't think it's good enough as a direct
patch to RDoc yet. The main reasons:
- the directives escape with %, where :x: is the RDoc standard.
- both these tweaks gleefully ignore the other documentation flavors
- future directives are no easier.
The ideal system would help users easily plug new directives in, without
stitching them into each element of the Visitor Pattern.
--
Phlip
http://www.oreilly.com/catalog/9780596510657/
"Test Driven Ajax (on Rails)"
assert_xpath, assert_javascript, & assert_ajax
##################################################################
## eek eek ook ook patch to add directives to RDocage
require 'rdoc/rdoc'
require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/lines'
require 'rdoc/markup/simple_markup/fragments'
require 'rdoc/markup/simple_markup/to_html'
def find_method_contents(file_info, modool, method)
file_info.each do |top|
if mod = top.find_module_named(modool) and
symbol = mod.find_local_symbol(method)
return symbol.token_stream
end
end
return nil
end
module SM
class Line
PURE_HTML = URE_HTML
TRANSCLUDE = :TRANSCLUDE
end
class PureHTML < Fragment
type_name Line:URE_HTML
end
class Transclude < Fragment
type_name Line::TRANSCLUDE
end
class ToHtml
def accept_transclude(am, fragment)
if found = find_method_contents(@context.context.parent.in_files,
*fragment.txt.split('#'))
@res << '<pre>'
# to do: hilite syntax; make method name clickable
found.each_with_index do |tok, index|
next if 0 == index and tok.text =~ /^\#/
next if 1 == index and tok.text == "\n"
@res << tok.text
end
@res << '</pre>'
else
@res << fragment.txt
end
end
def accept_pure_html(am, fragment)
@res << fragment.txt
end
end
class LineCollection
def accept(am, visitor)
visitor.start_accepting
@fragments.each do |fragment|
case fragment
when Verbatim
visitor.accept_verbatim(am, fragment)
when PureHTML
visitor.accept_pure_html(am, fragment)
when Transclude
visitor.accept_transclude(am, fragment)
when Rule
visitor.accept_rule(am, fragment)
when ListStart
visitor.accept_list_start(am, fragment)
when ListEnd
visitor.accept_list_end(am, fragment)
when ListItem
visitor.accept_list_item(am, fragment)
when BlankLine
visitor.accept_blank_line(am, fragment)
when Heading
visitor.accept_heading(am, fragment)
when Paragraph
visitor.accept_paragraph(am, fragment)
end
end
visitor.end_accepting
end
end
class SimpleMarkup
private
def assign_types_to_lines(margin = 0, level = 0)
while line = @lines.next
if /^\s*%html/ === line.text then
line.text.sub!("%html","")
line.stamp( Line:URE_HTML, level )
next
end
if /^\s*%transclude/ === line.text then
line.text.sub!("%transclude","")
line.stamp( Line::TRANSCLUDE, level )
next
end
if line.isBlank?
line.stamp(Line::BLANK, level)
next
end
# if a line contains non-blanks before the margin, then it must
belong
# to an outer level
text = line.text
for i in 0...margin
if text != SPACE
@lines.unget
return
end
end
active_line = text[margin..-1]
# Rules (horizontal lines) look like
#
# --- (three or more hyphens)
#
# The more hyphens, the thicker the rule
#
if /^(---+)\s*$/ =~ active_line
line.stamp(Line::RULE, level, $1.length-2)
next
end
# Then look for list entries. First the ones that have to have
# text following them (* xxx, - xxx, and dd. xxx)
if SIMPLE_LIST_RE =~ active_line
offset = margin + $1.length
prefix = $2
prefix_length = prefix.length
flag = case prefix
when "*","-" then ListBase::BULLET
when /^\d/ then ListBase::NUMBER
when /^[A-Z]/ then ListBase::UPPERALPHA
when /^[a-z]/ then ListBase::LOWERALPHA
else raise "Invalid List Type: #{self.inspect}"
end
line.stamp(Line::LIST, level+1, prefix, flag)
text[margin, prefix_length] = " " * prefix_length
assign_types_to_lines(offset, level + 1)
next
end
if LABEL_LIST_RE =~ active_line
offset = margin + $1.length
prefix = $2
prefix_length = prefix.length
next if handled_labeled_list(line, level, margin, offset, prefix)
end
# Headings look like
# = Main heading
# == Second level
# === Third
#
# Headings reset the level to 0
if active_line[0] == ?= and active_line =~ /^(=+)\s*(.*)/
prefix_length = $1.length
prefix_length = 6 if prefix_length > 6
line.stamp(Line::HEADING, 0, prefix_length)
line.strip_leading(margin + prefix_length)
next
end
# If the character's a space, then we have verbatim text,
# otherwise
if active_line[0] == SPACE
line.strip_leading(margin) if margin > 0
line.stamp(Line::VERBATIM, level)
else
line.stamp(Line:ARAGRAPH, level)
end
end
end
end
end
## eek eek ook ook patch to add directives to RDocage
##################################################################
A recent post here, by Robert Dober, showed how to tweak RDoc to add a raw
HTML directive, %html.
Below my sig is a monkey-patch, for Ruby 1.8's RDoc, which includes this
tweak and adds another; the ability to insert a method's contents into
another method's documentation. This allows us to do "literate programming".
We can compete with the likes of Knuth and Sedgewick by inserting
automatically tested source code into our verbiage.
To use the monkey patch, simply save it to a file, say rdoc_patch.rb and
then add require 'rdoc_patch' inside your Rakefile. If your Rakefile is
normal, it will run RDoc in-process, so that will get the patch.
I use the %html and %transclude tags like this:
# %html <a name='assert_json' />
# Example:
#
# %transclude AssertJavaScriptTest#test_assert_json
#
def assert_json(...)
...
end
The contents of test_assert_json will explain to us how to use assert_json.
And the %html <a name=...> gives us a direct, external link to the method:
http://assertxpath.rubyforge.org/classes/AssertJavaScript.html#assert_json
I'm putting the code here because I don't think it's good enough as a direct
patch to RDoc yet. The main reasons:
- the directives escape with %, where :x: is the RDoc standard.
- both these tweaks gleefully ignore the other documentation flavors
- future directives are no easier.
The ideal system would help users easily plug new directives in, without
stitching them into each element of the Visitor Pattern.
--
Phlip
http://www.oreilly.com/catalog/9780596510657/
"Test Driven Ajax (on Rails)"
assert_xpath, assert_javascript, & assert_ajax
##################################################################
## eek eek ook ook patch to add directives to RDocage
require 'rdoc/rdoc'
require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/lines'
require 'rdoc/markup/simple_markup/fragments'
require 'rdoc/markup/simple_markup/to_html'
def find_method_contents(file_info, modool, method)
file_info.each do |top|
if mod = top.find_module_named(modool) and
symbol = mod.find_local_symbol(method)
return symbol.token_stream
end
end
return nil
end
module SM
class Line
PURE_HTML = URE_HTML
TRANSCLUDE = :TRANSCLUDE
end
class PureHTML < Fragment
type_name Line:URE_HTML
end
class Transclude < Fragment
type_name Line::TRANSCLUDE
end
class ToHtml
def accept_transclude(am, fragment)
if found = find_method_contents(@context.context.parent.in_files,
*fragment.txt.split('#'))
@res << '<pre>'
# to do: hilite syntax; make method name clickable
found.each_with_index do |tok, index|
next if 0 == index and tok.text =~ /^\#/
next if 1 == index and tok.text == "\n"
@res << tok.text
end
@res << '</pre>'
else
@res << fragment.txt
end
end
def accept_pure_html(am, fragment)
@res << fragment.txt
end
end
class LineCollection
def accept(am, visitor)
visitor.start_accepting
@fragments.each do |fragment|
case fragment
when Verbatim
visitor.accept_verbatim(am, fragment)
when PureHTML
visitor.accept_pure_html(am, fragment)
when Transclude
visitor.accept_transclude(am, fragment)
when Rule
visitor.accept_rule(am, fragment)
when ListStart
visitor.accept_list_start(am, fragment)
when ListEnd
visitor.accept_list_end(am, fragment)
when ListItem
visitor.accept_list_item(am, fragment)
when BlankLine
visitor.accept_blank_line(am, fragment)
when Heading
visitor.accept_heading(am, fragment)
when Paragraph
visitor.accept_paragraph(am, fragment)
end
end
visitor.end_accepting
end
end
class SimpleMarkup
private
def assign_types_to_lines(margin = 0, level = 0)
while line = @lines.next
if /^\s*%html/ === line.text then
line.text.sub!("%html","")
line.stamp( Line:URE_HTML, level )
next
end
if /^\s*%transclude/ === line.text then
line.text.sub!("%transclude","")
line.stamp( Line::TRANSCLUDE, level )
next
end
if line.isBlank?
line.stamp(Line::BLANK, level)
next
end
# if a line contains non-blanks before the margin, then it must
belong
# to an outer level
text = line.text
for i in 0...margin
if text != SPACE
@lines.unget
return
end
end
active_line = text[margin..-1]
# Rules (horizontal lines) look like
#
# --- (three or more hyphens)
#
# The more hyphens, the thicker the rule
#
if /^(---+)\s*$/ =~ active_line
line.stamp(Line::RULE, level, $1.length-2)
next
end
# Then look for list entries. First the ones that have to have
# text following them (* xxx, - xxx, and dd. xxx)
if SIMPLE_LIST_RE =~ active_line
offset = margin + $1.length
prefix = $2
prefix_length = prefix.length
flag = case prefix
when "*","-" then ListBase::BULLET
when /^\d/ then ListBase::NUMBER
when /^[A-Z]/ then ListBase::UPPERALPHA
when /^[a-z]/ then ListBase::LOWERALPHA
else raise "Invalid List Type: #{self.inspect}"
end
line.stamp(Line::LIST, level+1, prefix, flag)
text[margin, prefix_length] = " " * prefix_length
assign_types_to_lines(offset, level + 1)
next
end
if LABEL_LIST_RE =~ active_line
offset = margin + $1.length
prefix = $2
prefix_length = prefix.length
next if handled_labeled_list(line, level, margin, offset, prefix)
end
# Headings look like
# = Main heading
# == Second level
# === Third
#
# Headings reset the level to 0
if active_line[0] == ?= and active_line =~ /^(=+)\s*(.*)/
prefix_length = $1.length
prefix_length = 6 if prefix_length > 6
line.stamp(Line::HEADING, 0, prefix_length)
line.strip_leading(margin + prefix_length)
next
end
# If the character's a space, then we have verbatim text,
# otherwise
if active_line[0] == SPACE
line.strip_leading(margin) if margin > 0
line.stamp(Line::VERBATIM, level)
else
line.stamp(Line:ARAGRAPH, level)
end
end
end
end
end
## eek eek ook ook patch to add directives to RDocage
##################################################################