Apr 4

Writing a Yard Extension To Show Inherited Attributes

Category: Programming, Ruby

So RDoc is an absolutely terrible documentation generator both from the usage and the coding perspective. Although yard is not yet quite as stable as I’d like, it is everything RDoc is not, it’s pretty, well written, and provides numerous well thought out extension points. So as an exercise I decided to fix one of the more annoying issues with yard as an extension.

UPDATE: To see the change described below as a patch to YARD itself look here: https://github.com/mikesmit/yard/commit/3873dcf7fb40839c8c4e1412d6dec2f3ff0af113

The Target

Currently yard will conveniently list all the methods that a class or module inherits, but leave out any attributes. So although I have a nice list of methods to search through I still have to slog through every mixin and parent class to figure out what attributes are available.

Happily, Yard provides a very well thought out extension mechanism for modifying existing HTML and text output so I thought I’d leverage that to add inherited attributes myself.

Where to start

The Yard website includes this helpful document: http://gnuu.org/2009/11/18/customizing-yard-templates/ as a guide to modifying the existing templates.

Basically all the code for generating HTML/text/etc is a combination of a set of ERB templates describing the sections in the generated document and a controller object used to figure out what order to call those templates in to generate the full page.

In order to modify a template you

  1. create a directory structure mimicking the one or more in the templates section of the source code
  2. extend the controller for the folder you want to extend
  3. write an erb file for the new section(s) you inserted

What to Override

It took a little digging to figure out that the module template is where the code for generating the Instance Attributes list is located. The setup.rb from that directory is what defines which sections are generated in what order. In the file’s init section you will see this:

def init
  sections :header, :box_info, :pre_docstring, T('docstring'), :children,
    :constant_summary, [T('docstring')], :inherited_constants,
    :attribute_summary, [:item_summary],
    :method_summary, [:item_summary], :inherited_methods,
    :methodmissing, [T('method_details')],
    :attribute_details, [T('method_details')],
    :method_details_list, [T('method_details')]

As you can see the :method_summary section is followed by :inherited_methods, but :attribute_summary is not followed by :inherited_attributes.

So in my own folder I create directory “templates_custom/default/module” and create the following setup.rb which adds a new section called :inherited_attributes

def init

I then need to add an erb file called “templates_custom/default/module/html/inherited_attributes.erb”. To do this I started with the inherited methods ERB already provided in templates/default/module/html/inherited_methods.erb. The key to figuring this out was understanding that “object” in the context of the ERB file is the module/class object from the yard object model, both f which inherit from namespace. Using that reference documentation I got the following:

<% found_method = false %>
<% object.inheritance_tree(true)[1..-1].each do |superclass| %>
  <% next if superclass.is_a?(YARD::CodeObjects::Proxy) %>
  <% attribs = superclass.attributes[:instance] %>
  <% next if attribs.size == 0 %>
  <% if attr_listing.size == 0 && !found_method %><h2>Instance Attribute Summary</h2><% end %>
  <% found_method = true %>
  <h3 class="inherited">Attributes <%= superclass.type == :class ? 'inherited' : 'included' %> from <%= linkify superclass %></h3>
  <p class="inherited"><%=
    attribs.collect do |args|
      name, methods = args
      method = methods[:read] || methods[:write]
      linkify(method, method.name)
    end.join(", ")
<% end %>

Running yard with your extension

Now all I need to do is run yard which I can do either from the comand line

yardoc -p templates_custom/ "lib/**/*.rb"

or from Rake:

YARD::Rake::YardocTask.new do |t|
  t.files   = ['lib/**/*.rb'] 
  t.options = ['-p',  'templates_custom/']

Updating Text

The only thing left to do is update the text ERB file (assuming you want text output) using the same loop from the HTML output but different text around it.

1 comment

Comments are closed.