# Tweak RDoc to transclude a function into another function's

**URL:** <https://www.ruby-forum.com/t/tweak-rdoc-to-transclude-a-function-into-another-functions/110927>\
**Category:** Ruby\
**Created:** [August 1, 2007, 4:29am UTC](https://www.ruby-forum.com/t/tweak-rdoc-to-transclude-a-function-into-another-functions/110927 "2007-08-01T04:29:11Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Phlip](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/p/ed8c4c/32.png) [@Phlip](https://www.ruby-forum.com/u/Phlip)\
**Post date:** [August 1, 2007, 4:29am UTC](https://www.ruby-forum.com/t/tweak-rdoc-to-transclude-a-function-into-another-functions/110927/1 "2007-08-01T04:29:11Z")

</div>

Ruboids:

A recent post here, by Robert D., 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
 
# 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 gives us a direct, external link to the  
method:

 

[http://assertxpath.rubyforge.org/classes/AssertJavaScript.html#assert\_json](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 ❌ 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/](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 = :PURE\_HTML  
TRANSCLUDE = :TRANSCLUDE  
end

class PureHTML \< Fragment  
type\_name Line::PURE\_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 \<\< ‘

```
’
# 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 << ‘
```
’  
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::PURE_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[i] != 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::PARAGRAPH, level)
    end
  end
end

```

end  
end

## eek eek ook ook patch to add directives to RDocage

##################################################################
