module YARD module Handlers class UndocumentableError < Exception; end # = Handlers # # Handlers are pluggable semantic parsers for YARD's code generation # phase. They allow developers to control what information gets # generated by YARD, giving them the ability to, for instance, document # any Ruby DSLs that a customized framework may use. A good example # of this would be the ability to document and generate meta data for # the 'describe' declaration of the RSpec testing framework by simply # adding a handler for such a keyword. Similarly, any Ruby API that # takes advantage of class level declarations could add these to the # documentation in a very explicit format by treating them as first- # class objects in any outputted documentation. # # == Overview of a Typical Handler Scenario # # Generally, a handler class will declare a set of statements which # it will handle using the {handles} class declaration. It will then # implement the {#process} method to do the work. The processing would # usually involve the manipulation of the {#namespace}, {#owner} # {CodeObjects::Base code objects} or the creation of new ones, in # which case they would ideally be returned by the method so that # they may be registered by {#register}, a method that sets some basic # attributes for the new objects. # # Handlers are usually simple and take up to a page of code to process # and register a new object or add new attributes to the current +namespace+. # # == Setting up a Handler for Use # # A Handler is automatically registered when it is subclassed from the # base class. The only other thing that needs to be done is to specify # which statement the handler will process. This is done with the +handles+ # declaration, taking either a {Parser::RubyToken}, {String} or {Regexp}. # Here is a simple example which processes module statements. # # class MyModuleHandler < YARD::Handlers::Base # handles TkMODULE # # def process # # do something # end # end # # == Processing Handler Data # # The goal of a specific handler is really up to the developer, and as # such there is no real guideline on how to process the data. However, # it is important to know where the data is coming from to be able to use # it. # # === +statement+ Attribute # # The +statement+ attribute pertains to the {Parser::Statement} object # containing a set of tokens parsed in by the parser. This is the main set # of data to be analyzed and processed. The comments attached to the statement # can be accessed by the {Parser::Statement#comments} method, but generally # the data to be processed will live in the +tokens+ attribute. This list # can be converted to a +String+ using +#to_s+ to parse the data with # regular expressions (or other text processing mechanisms), if needed. # # === +namespace+ Attribute # # The +namespace+ attribute is a {CodeObjects::NamespaceObject namespace object} # which represents the current namespace that the parser is in. For instance: # # module SomeModule # class MyClass # def mymethod; end # end # end # # If a handler was to parse the 'class MyClass' statement, it would # be necessary to know that it belonged inside the SomeModule module. # This is the value that +namespace+ would return when processing such # a statement. If the class was then entered and another handler was # called on the method, the +namespace+ would be set to the 'MyClass' # code object. # # === +owner+ Attribute # # The +owner+ attribute is similar to the +namespace+ attribute in that # it also follows the scope of the code during parsing. However, a namespace # object is loosely defined as a module or class and YARD has the ability # to parse beyond module and class blocks (inside methods, for instance), # so the +owner+ attribute would not be limited to modules and classes. # # To put this into context, the example from above will be used. If a method # handler was added to the mix and decided to parse inside the method body, # the +owner+ would be set to the method object but the namespace would remain # set to the class. This would allow the developer to process any method # definitions set inside a method (def x; def y; 2 end end) by adding them # to the correct namespace (the class, not the method). # # In summary, the distinction between +namespace+ and +owner+ can be thought # of as the difference between first-class Ruby objects (namespaces) and # second-class Ruby objects (methods). # # === +visibility+ and +scope+ Attributes # # Mainly needed for parsing methods, the +visibility+ and +scope+ attributes # refer to the public/protected/private and class/instance values (respectively) # of the current parsing position. # # == Parsing Blocks in Statements # # In addition to parsing a statement and creating new objects, some # handlers may wish to continue parsing the code inside the statement's # block (if there is one). In this context, a block means the inside # of any statement, be it class definition, module definition, if # statement or classic 'Ruby block'. # # For example, a class statement would be "class MyClass" and the block # would be a list of statements including the method definitions inside # the class. For a class handler, the programmer would execute the # {#parse_block} method to continue parsing code inside the block, with # the +namespace+ now pointing to the class object the handler created. # # YARD has the ability to continue into any block: class, module, method, # even if statements. For this reason, the block parsing method must be # invoked explicitly out of efficiency sake. # # @see CodeObjects::Base # @see CodeObjects::NamespaceObject # @see handles # @see #namespace # @see #owner # @see #register # @see #parse_block # class Base # For accessing convenience, eg. "MethodObject" # instead of the full qualified namespace include YARD::CodeObjects # For tokens like TkDEF, TkCLASS, etc. include YARD::Parser::RubyToken class << self def clear_subclasses @@subclasses = [] end def subclasses @@subclasses || [] end def inherited(subclass) @@subclasses ||= [] @@subclasses << subclass end # Declares the statement type which will be processed # by this handler. # # A match need not be unique to a handler. Multiple # handlers can process the same statement. However, # in this case, care should be taken to make sure that # {#parse_block} would only be executed by one of # the handlers, otherwise the same code will be parsed # multiple times and slow YARD down. # # @param [Parser::RubyToken, String, Regexp] match # statements that match the declaration will be # processed by this handler. A {String} match is # equivalent to a +/\Astring/+ regular expression # (match from the beginning of the line), and all # token matches match only the first token of the # statement. # def handles(match) @handler = match end def handles?(tokens) case @handler when String tokens.first.text == @handler when Regexp tokens.to_s =~ @handler ? true : false else @handler == tokens.first.class end end end # The main handler method called by the parser on a statement # that matches the {handles} declaration. # # Subclasses should override this method to provide the handling # functionality for the class. # # @return [CodeObjects::Base, Array, Object] # If this method returns a code object (or a list of them), # they are passed to the +#register+ method which adds basic # attributes. It is not necessary to return any objects and in # some cases you may want to explicitly avoid the returning of # any objects for post-processing by the register method. # # @see handles # @see #register # def process raise NotImplementedError, "#{self} did not implement a #process method for handling." end # Do some post processing on a list of code objects # either returned from {#process} or explicitly # called from a handler. Adds basic attributes # to the list of objects like the filename, # line number, {CodeObjects::Base#dynamic}, # source code and {CodeObjects::Base#docstring}, # but only if they don't exist. # # As mentioned above, this method is automatically # called on the result of {#process}. Sometimes # it may be easier to explicitly call # # @param [Array] objects # the list of objects to post-process. # # @return [NilClass] # def register(*objects) objects.each do |object| next unless object.is_a?(CodeObjects::Base) # Add file and line number object.file ||= parser.file object.line ||= statement.tokens.first.line_no # Add docstring if it's not set object.docstring = statement.comments if object.docstring.empty? # Add source only to non-class non-module objects unless object.is_a?(ClassObject) || object.is_a?(ModuleObject) object.source ||= statement end # Method Object gets signature if object.is_a?(MethodObject) object.signature ||= statement.tokens.to_s end # Make it dynamic if it's owner is not it's namespace. # This generally means it was defined in a method (or block of some sort) object.dynamic ||= true if owner != namespace end nil # Don't return anything just in case a register call is at the # end of a process method-- we don't want to accidentally do this twice. end attr_reader :parser, :statement def initialize(source_parser, stmt) @parser = source_parser @statement = stmt end def parse_block(opts = nil) opts = { :namespace => nil, :scope => :instance, :owner => nil }.update(opts || {}) if opts[:namespace] ns, vis, sc = namespace, visibility, scope self.namespace = opts[:namespace] self.visibility = :public self.scope = opts[:scope] end self.owner = opts[:owner] ? opts[:owner] : namespace parser.parse(statement.block) if statement.block if opts[:namespace] self.namespace = ns self.owner = namespace self.visibility = vis self.scope = sc end end def owner; @parser.owner end def owner=(v) @parser.owner=(v) end def namespace; @parser.namespace end def namespace=(v); @parser.namespace=(v) end def visibility; @parser.visibility end def visibility=(v); @parser.visibility=(v) end def scope; @parser.scope end def scope=(v); @parser.scope=(v) end end end end