rdoc snippets

How to create a custom RDoc generator

Tagged rdoc, ruby, generator  Languages ruby

This is a work in progress... Heavily influenced by RAnnotate. Copy this to rdoc/generators:

module Generators

  class CUSTOMGenerator                 

    TYPE        = {:file => 1, :class => 2, :module => 3 }
    VISIBILITY  = {:public => 1, :private => 2, :protected => 3 }
        
    def CUSTOMGenerator.for(options)
      new(options)
    end
        
    def initialize(options) #:not-new:
      @options = options
      
      # set up a hash to keep track of all the classes/modules we have processed
      @already_processed = {}
      
      # set up a hash to keep track of all of the objects to be output
      @output = {
        :files => [], 
        :classes => [], 
        :modules => [], 
        :attributes => [], 
        :methods => [], 
        :aliases => [], 
        :constants => [], 
        :requires => [], 
        :includes => []
      }
    end

    # Rdoc passes in TopLevel objects from the code_objects.rb tree (all files)
    def generate(files)                             
      # Each object passed in is a file, process it
      files.each { |file| process_file(file) }
    end

    private

    # process a file from the code_object.rb tree
    def process_file(file)
      @output[:files].push(file)

      puts "#{file.comment}"
          
      # Process all of the objects that this file contains
      file.method_list.each { |child| process_method(child) }
      file.aliases.each { |child| process_alias(child) }
      file.constants.each { |child| process_constant(child) }
      file.requires.each { |child| process_require(child) }
      file.includes.each { |child| process_include(child) }
      file.attributes.each { |child| process_attribute(child) }   
    
      # Recursively process contained subclasses and modules 
      file.each_classmodule do |child| 
          process_class_or_module(child)      
      end   
    end
    
    # Process classes and modiles   
    def process_class_or_module(obj)
      obj.is_module? ? type = :modules : type = :classes
 
      # One important note about the code_objects.rb structure. A class or module
      # definition can be spread a cross many files in Ruby so code_objects.rb handles
      # this by keeping only *one* reference to each class or module that has a definition
      # at the root level of a file (ie. not contained in another class or module).
      # This means that when we are processing files we may run into the same class/module
      # twice. So we need to keep track of what classes/modules we have
      # already seen and make sure we don't create two INSERT statements for the same
      # object.
      if(!@already_processed.has_key?(obj.full_name)) then      
        @output[type].push(obj)
        @already_processed[obj.full_name] = true
          
        # Process all of the objects that this class or module contains
        obj.method_list.each { |child| process_method(child) }
        obj.aliases.each { |child| process_alias(child) }
        obj.constants.each { |child| process_constant(child) }
        obj.requires.each { |child| process_require(child) }
        obj.includes.each { |child| process_include(child) }
        obj.attributes.each { |child| process_attribute(child) }   
      end
      
      id = @already_processed[obj.full_name]
      # Recursively process contained subclasses and modules 
      obj.each_classmodule do |child| 
        process_class_or_module(child) 
      end
    end       
    
    def process_method(obj)  
      obj.source_code = get_source_code(obj)
       puts "#{obj.name}#{obj.param_seq}"
       puts "#{obj.source_code}" # Source code, unformatted

       puts "====================================="

      @output[:methods].push(obj)                                
    end
    
    def process_alias(obj)
      @output[:aliases].push(obj)  
    end
    
    def process_constant(obj)
      @output[:constants].push(obj)    
    end
    
    def process_attribute(obj)
      @output[:attributes].push(obj)     
    end
    
    def process_require(obj)
      @output[:requires].push(obj) 
    end
    
    def process_include(obj) 
      @output[:includes].push(obj)     
    end   
    
               
    # get the source code
    def get_source_code(method)
      src = ""
      if(ts = method.token_stream)    
        ts.each do |t|
        next unless t               
          src << t.text
        end
      end
      return src
    end
         
  end


   # dynamically add a source code attribute to the base oject of code_objects.rb
   class RDoc::AnyMethod
     attr_accessor :source_code   
   end

end

How to parse Ruby source code documentation with RDoc and a custom RDoc generator

Tagged ruby, rdoc, generator, documentation  Languages ruby

This is a skeleton for an RDoc generator that extends the existing HtmlGenerator. This means we get the same documentation as seen at, for example, http://api.rubyonrails.org/; with links and HTML formatted documentation.

It can be used for doing whatever you would like and can imagine doing with RDoc documentation. Currently it prints out the files, modules, classes and methods found in the processesed files.

To use it, create a new file named custom_generator.rb in the Ruby installation and the subfolder /rdoc/generators. Then put the following code in the file:

require 'rdoc/generators/html_generator'

module Generators

  class HTMLGenerator
  
    # We don't need a template
    def load_html_template
    end

    def generate(toplevels)             
      @toplevels  = toplevels
      @files      = []
      @classes    = []

      build_indices
      
      puts "===================="
      puts "Files"
      puts "===================="
      
      @files.each do |item|
        puts item.name
        #values = file.value_hash
        #puts item.description
      end
      
      puts "===================="
      puts "Modules and classes"
      puts "===================="           
      
      @classes.each do |item|
        puts item.name
        #values = file.value_hash
        #puts item.description
      end
      
      puts "===================="
      puts "Methods"
      puts "===================="      
      
      HtmlMethod.all_methods.each do |item|
        puts item.name
      end
    end
  end
  
  class HtmlFile
    # Add a description method, after all HtmlMethod has it...
    def description
      value_hash if @values.size == 0      
      @values["description"]
    end
  end
  class HtmlClass
    # Add a description method, after all HtmlMethod has it...
    def description
      value_hash if @values.size == 0      
      @values["description"]
    end
  end
  
  class CUSTOMGenerator < HTMLGenerator
  end

end

Then run the custom generator by using the fmt parameter:

rdoc --fmt custom lib/base64.rb lib/pp.rb

You can also control RDoc programatically, with the following code:

#!/usr/bin/env ruby
require 'rdoc/rdoc'

rm -rf doc

begin
  r = RDoc::RDoc.new
  r.document(['--inline-source', '--fmt', 'custom'] + ARGV)
rescue RDoc::RDocError => e
  $stderr.puts e.message
  exit(1)
end

How to parse RI generated documentation using RDoc and Ruby

Tagged rdoc, ri, documentation, ruby  Languages ruby

RI stores the generated documentation as YAML files. This code uses RDoc to parse the YAML files:

require 'yaml'
require 'find'
require "rdoc/ri/ri_driver"

dirs = RI::Paths::PATH
dirs.each do |dir|
  Find.find(dir) do |fn|
    next unless File.file?(fn)
    doc = YAML.load(File.read(fn))
    next unless doc.respond_to?(:comment)
    next unless doc.comment
    
    # Print name of object
    puts doc.full_name
    
    # Print the body: RDoc comments, but only partial...
    puts doc.comment.map{|f| f.body if f.respond_to?(:body)}.join("\n")
  end
end

Originally from the article Fun with Ferret.