lib/yard/handlers/base.rb in yard-0.5.8 vs lib/yard/handlers/base.rb in yard-0.6.0
- old
+ new
@@ -9,12 +9,10 @@
attr_accessor :object
def initialize(object) @object = object end
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
@@ -186,22 +184,39 @@
#
def handles(*matches)
(@handlers ||= []).push(*matches)
end
+ # This class is implemented by {Ruby::Base} and {Ruby::Legacy::Base}.
+ # To implement a base handler class for another language, implement
+ # this method to return true if the handler should process the given
+ # statement object. Use {handlers} to enumerate the matchers declared
+ # for the handler class.
+ #
+ # @param statement a statement object or node (depends on language type)
+ # @return [Boolean] whether or not this handler object should process
+ # the given statement
def handles?(statement)
raise NotImplementedError, "override #handles? in a subclass"
end
+ # @return [Array] a list of matchers for the handler object.
+ # @see handles?
def handlers
@handlers ||= []
end
+ # Declares that the handler should only be called when inside a
+ # {CodeObjects::NamespaceObject}, not a method body.
+ #
+ # @return [void]
def namespace_only
@namespace_only = true
end
+ # @return [Boolean] whether the handler should only be processed inside
+ # a namespace.
def namespace_only?
(@namespace_only ||= false) ? true : false
end
# Generates a +process+ method, equivalent to +def process; ... end+.
@@ -209,10 +224,11 @@
# module so that the handler class can be extended with mixins that
# override the +process+ method without alias chaining.
#
# @see #process
# @return [void]
+ # @since 0.5.4
def process(&block)
mod = Module.new
mod.send(:define_method, :process, &block)
include mod
end
@@ -241,18 +257,40 @@
#
def process
raise NotImplementedError, "#{self} did not implement a #process method for handling."
end
+ # Parses the semantic "block" contained in the statement node.
+ #
+ # @abstract Subclasses should call {Processor#process parser.process}
def parse_block(*args)
raise NotImplementedError, "#{self} did not implement a #parse_block method for handling"
end
protected
- attr_reader :parser, :statement
- attr_accessor :owner, :namespace, :visibility, :scope
+ # @return [Processor] the processor object that manages all global state
+ # during handling.
+ attr_reader :parser
+
+ # @return [Object] the statement object currently being processed. Usually
+ # refers to one semantic language statement, though the strict definition
+ # depends on the parser used.
+ attr_reader :statement
+
+ # (see Processor#owner)
+ attr_accessor :owner
+
+ # (see Processor#namespace)
+ attr_accessor :namespace
+
+ # (see Processor#visibility)
+ attr_accessor :visibility
+
+ # (see Processor#scope)
+ attr_accessor :scope
+
undef owner, owner=, namespace, namespace=
undef visibility, visibility=, scope, scope=
def owner; parser.owner end
def owner=(v) parser.owner=(v) end
@@ -261,34 +299,41 @@
def visibility; parser.visibility end
def visibility=(v); parser.visibility=(v) end
def scope; parser.scope end
def scope=(v); parser.scope=(v) end
+ # Executes a given block with specific state values for {#owner},
+ # {#namespace} and {#scope}.
+ #
+ # @param [Proc] block the block to execute with specific state
+ # @option opts [CodeObjects::NamespaceObject] :namespace (value of #namespace)
+ # the namespace object that {#namespace} will be equal to for the
+ # duration of the block.
+ # @option opts [Symbol] :scope (:instance)
+ # the scope for the duration of the block.
+ # @option opts [CodeObjects::Base] :owner (value of #owner)
+ # the owner object (method) for the duration of the block
+ # @yield a block to execute with the given state values.
def push_state(opts = {}, &block)
opts = {
- :namespace => nil,
+ :namespace => namespace,
:scope => :instance,
- :owner => nil
+ :owner => owner || namespace
}.update(opts)
- if opts[:namespace]
- ns, vis, sc = namespace, visibility, scope
- self.namespace = opts[:namespace]
- self.visibility = :public
- self.scope = opts[:scope]
- end
+ ns, vis, sc, oo = namespace, visibility, scope, owner
+ self.namespace = opts[:namespace]
+ self.visibility = :public
+ self.scope = opts[:scope]
+ self.owner = opts[:owner]
- oldowner, self.owner = self.owner, opts[:owner] ? opts[:owner] : namespace
yield
- self.owner = oldowner
- if opts[:namespace]
- self.namespace = ns
- self.owner = namespace
- self.visibility = vis
- self.scope = sc
- end
+ self.namespace = ns
+ self.visibility = vis
+ self.scope = sc
+ self.owner = oo
end
# Do some post processing on a list of code objects.
# Adds basic attributes to the list of objects like
# the filename, line number, {CodeObjects::Base#dynamic},
@@ -322,10 +367,26 @@
# Add docstring if there is one.
object.docstring = statement.comments if statement.comments
object.docstring.line_range = statement.comments_range
+ # Add group information
+ if statement.group
+ unless object.namespace.is_a?(Proxy)
+ object.namespace.groups |= [statement.group]
+ end
+ object.group = statement.group
+ end
+
+ # Add transitive tags
+ Tags::Library.transitive_tags.each do |tag|
+ next if object.namespace.is_a?(Proxy)
+ next unless object.namespace.has_tag?(tag)
+ next if object.has_tag?(tag)
+ object.docstring.add_tag(*object.namespace.tags(tag))
+ end
+
# Add source only to non-class non-module objects
unless object.is_a?(NamespaceObject)
object.source ||= statement
end
@@ -334,9 +395,29 @@
object.dynamic = true if owner != namespace
end
objects.size == 1 ? objects.first : objects
end
+ # Ensures that a specific +object+ has been parsed and loaded into the
+ # registry. This is necessary when adding data to a namespace, for instance,
+ # since the namespace may not have been processed yet (it can be located
+ # in a file that has not been handled).
+ #
+ # Calling this method defers the handler until all other files have been
+ # processed. If the object gets resolved, the rest of the handler continues,
+ # otherwise an exception is raised.
+ #
+ # @example Adding a mixin to the String class programmatically
+ # ensure_loaded! P('String')
+ # # "String" is now guaranteed to be loaded
+ # P('String').mixins << P('MyMixin')
+ #
+ # @param [Proxy, CodeObjects::Base] object the object to resolve.
+ # @param [Integer] max_retries the number of times to defer the handler
+ # before raising a +NamespaceMissingError+.
+ # @raise [NamespaceMissingError] if the object is not resolved within
+ # +max_retries+ attempts, this exception is raised and the handler
+ # finishes processing.
def ensure_loaded!(object, max_retries = 1)
return if object.root?
unless parser.load_order_errors
if object.is_a?(Proxy)
raise NamespaceMissingError, object
\ No newline at end of file