129 lines
4.9 KiB
Ruby
129 lines
4.9 KiB
Ruby
# Handles `dispatch` calls within a future parser function declaration. For
|
|
# now, it just treats any docstring as an `@overlaod` tag and attaches the
|
|
# overload to the parent function.
|
|
class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler < YARD::Handlers::Ruby::Base
|
|
include PuppetX::PuppetLabs::Strings::YARD::CodeObjects
|
|
|
|
handles :call
|
|
|
|
process do
|
|
@heredoc_helper = HereDocHelper.new
|
|
# Puppet providers always begin with:
|
|
# Puppet::Types.newtype...
|
|
# Therefore, we match the corresponding trees which look like this:
|
|
# s(:call,
|
|
# s(:const_path_ref,
|
|
# s(:var_ref, s(:const, "Puppet", ...), ...),
|
|
# s(:const, "Type", ...),
|
|
# You think this is ugly? It's better than the alternative.
|
|
return unless statement.children.length > 2
|
|
first = statement.children.first
|
|
return unless first.type == :const_path_ref and
|
|
first.children.length == 2 and
|
|
first.children.map { |o| o.source } == ["Puppet", "Type"] and
|
|
statement.children[1].source == "newtype"
|
|
|
|
# Fetch the docstring for the provider. The docstring is the string literal
|
|
# assigned to the @doc parameter or absent, like this:
|
|
# @doc "docstring goes here"
|
|
# We assume that docstrings nodes have the following shape in the source
|
|
# code:
|
|
# ...
|
|
# s(s(:assign,
|
|
# s(:..., s(:ivar, "@doc", ...), ...),
|
|
# s(:...,
|
|
# s(:...,
|
|
# s(:tstring_content,
|
|
# "Manages files, including their content, etc.", ...
|
|
# Initialize the docstring to nil, the default value if we don't find
|
|
# anything
|
|
docstring = nil
|
|
# Walk the tree searching for assignments
|
|
statement.traverse do |node|
|
|
if node.type == :assign
|
|
# Once we have found and assignment, jump to the first ivar
|
|
# (the l-value)
|
|
# If we can't find an ivar return the node.
|
|
ivar = node.jump(:ivar)
|
|
# If we found and ivar and its source reads '@doc' then...
|
|
if ivar != node and ivar.source == '@doc'
|
|
# find the next string content
|
|
content = node.jump(:tstring_content)
|
|
# if we found the string content extract its source
|
|
if content != node
|
|
# The docstring is either the source stripped of heredoc
|
|
# annotations or the raw source.
|
|
if @heredoc_helper.is_heredoc? content.source
|
|
docstring = @heredoc_helper.process_heredoc content.source
|
|
else
|
|
docstring = content.source
|
|
end
|
|
end
|
|
# Since we found the @doc parameter (regardless of whether we
|
|
# successfully extracted its source), we're done.
|
|
break
|
|
# But if we didn't find the ivar loop around again.
|
|
else
|
|
next
|
|
end
|
|
end
|
|
end
|
|
|
|
# The providers begin with:
|
|
# Puppet::Types.newtype(:symbol)
|
|
# Jump to the first identifier (':symbol') after the third argument
|
|
# ('(:symbol)') to the current statement
|
|
name = statement.children[2].jump(:ident).source
|
|
parameter_details = []
|
|
obj = ProviderObject.new(:root, name) do |o|
|
|
# FIXME: This block gets yielded twice for whatever reason
|
|
parameter_details = []
|
|
o.parameters = []
|
|
# Find the de block following the Provider.
|
|
do_block = statement.jump(:do_block)
|
|
# traverse the do block's children searching for function calls whose
|
|
# identifier is newparam (we're calling the newparam function)
|
|
do_block.traverse do |node|
|
|
if node.type == :fcall and node.children.first.source == 'newparam'
|
|
# The first member of the parameter tuple is the parameter name.
|
|
# Find the second identifier node under the fcall tree. The first one
|
|
# is 'newparam', the second one is the function name.
|
|
# Get its source.
|
|
# The second parameter is nil because we cannot infer types for these
|
|
# functions. In fact, that's a silly thing to ask because ruby
|
|
# providers were deprecated with puppet 4 at the same time the type
|
|
# system was created.
|
|
param_name = node.children[1].jump(:ident).source
|
|
o.parameters << [param_name, nil]
|
|
parameter_details << {:name => param_name,
|
|
:desc => fetch_description(node), :exists? => true,
|
|
:provider => true}
|
|
end
|
|
end
|
|
end
|
|
obj.parameter_details = parameter_details
|
|
|
|
register_docstring(obj, docstring, nil)
|
|
|
|
register obj
|
|
end
|
|
|
|
def fetch_description(fcall)
|
|
fcall.traverse do |node|
|
|
if node.type == :command and node.children.first.source == 'desc'
|
|
content = node.jump(:string_content)
|
|
if content != node
|
|
@heredoc_helper = HereDocHelper.new
|
|
if @heredoc_helper.is_heredoc? content.source
|
|
docstring = @heredoc_helper.process_heredoc content.source
|
|
else
|
|
docstring = content.source
|
|
end
|
|
return docstring
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
end
|