An Update on Macros in YARD 0.7.x

By Loren Segal on April 04, 2011 at 428:721:506 PM

YARD 0.7.x has a feature on the table called macros. This allows developers documenting code to very quickly create documentation templates for repetitive DSL methods. The more trivial part of macros is that YARD will now detect any DSL method with a docstring attached to it and automatically turn it into an instance method, using the first parameter as the method name. Obviously this is not always what you want, so macros can be even more powerful and allow for arbitrary expansion of any of the method call parameters. This makes documenting frameworks/libs like DataMapper, Sinatra, Rails, Sequel, etc., extremely simple, and extremely customizable. I’m very happy to see this finally in YARD, and I think the wait is going to pay off.

All of this is available in the ‘macros‘ branch (probably only for a few more days) until it will be merged into master. It’s coming very, very, soon.

I copied in the full commit message that adds macros down below, since it explains the feature quite nicely. You can also look at commit aca2916 which shows how we removed ~40 lines of plugin code (more with the tests) to detect some DSL syntax in the YARD codebase with a 4 line macro!

Add MacroObject and MacroHandler (commit 615f39d)

Add MacroObject and MacroHandler which processes DSL methods in a namespace that have any @tags defined on them as dynamically generated methods, where the first argument is the method name. YARD will now automatically detect a dynamic method if it sees tags defined in its docstring, for example:

# The name property
# @return [String] a user's name
property :name, String

The above definition will automatically create an instance method named name with the docstring “The name property” (and return tag).

In addition to this default behaviour, the method name and object type/properties can be customized with the addition of the @method, @attribute, @scope, @visibility tags. Specifically, the example above can be defined as an attribute by specifying:

# @attribute
# @return [String] the name property
property :name, String

The attribute can be customized further by adding the ‘r’ or ‘w’ type to specify if it is writeonly:

# @attribute [w]
property :writeonlyproperty

The method/attribute name/signature can also be customized by adding a signature to the @method/@attibute tags:

# @method foobar(name, opts = {})
# @param [String] name
# @scope class
register :foobar

Note that because @scope is set, foobar will be made into a class method. Visibility can be set in the same way.

Macros

Finally, a new @macro tag is added, with a MacroObject to support it. Whenever a @macro is defined on a docstring, it creates a new re-usable named macro object that can be re-applied to any other object, either explicitly by calling on the name, or implicitly through the “attach” keyword (or by declaring the macro on a class method directly [not yet supported]). Macros are expanded when applied, interpolating $N or ${N-M} strings with the respective parameters in the original DSL method. $0 and $* are special tokens that refer to the DSL method name and the full source line, respectively.

The following is an example of how a DSL method for a CLI option parsing library (such as Rake, Thor, or others) can be easily documented with macro expansion:

# @macro command
#   @method $1(opts = {})
#   $3
#   @return [$2] the result of the $0
command :create_file, String, "Creates a new file and returns the filename"

The above will automatically expand the macro on the first usage, and then create a new macro named ‘command’ that can be reused by other commands. The above docstring would be expanded into the full docstring:

# @method create_file(opts = {})
# Creates a new file and returns the filename
# @return [String] the result of the command
command :create_file, String, "Creates a new file and returns the filename"

This macro can then be re-used on other commands, either explicitly via:

# @macro command
command :exit, Fixnum, "Exits the shell and returns the exit code"

Or implicitly by attaching the command macro to the command method call. This can be done by adding [attach] to the initial @macro definition:

# @macro [attach] command
# ...
command :create_file, ...

The next use of command will automatically use this macro, even without any explicit docstring or tags:

command :foobar, String, "this will be in my docs!"

Note that the command must come from a class that is in the same inheritance tree as the initially defined macro. That is, if command is used in another class, it will not be documented. A new macro can be defined for that separate method.

In the future, it will be possible to define a macro directly on the class method declaration itself, ie.:

class CommandParser
  # @macro command
  #   @method $1(opts = {})
  #   $3
  #   @return [$2]
  def self.command(name, return_type, docstring, &block)
    # implementation...
  end

  command :name, String, "retrieves a username" do
    DB.get(username)
  end
end

Summary of new tags

  • @attribute [rw] optional_name – defines the DSL method as an attribute
  • @macro name DOCSTRING – defines a new macro using name and expands DOCSTRING
  • @method signature – declares that the DSL method is a method with an optional signature. Use this when you otherwise have no other tags to specify for the docstring (since YARD requires at least one @tag to detect a dynamic DSL method)
  • @scope class|instance – sets the scope of the newly added method
  • @visibility public|private|protected – sets the visibility of the newly added method
Questions? Comments? Follow me on Twitter (@lsegal) or email me.