(PDOC-63) Implement a Puppet provider YARD handler.
This commit implements a YARD handler for Puppet providers and the associated code object and templates.
This commit is contained in:
		
							parent
							
								
									22396a13d0
								
							
						
					
					
						commit
						b1a15bd43a
					
				| 
						 | 
				
			
			@ -41,6 +41,7 @@ class YARD::CLI::Yardoc
 | 
			
		|||
      :puppet_class,
 | 
			
		||||
      :puppet_defined_type,
 | 
			
		||||
      :puppet_type,
 | 
			
		||||
      :puppet_provider,
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +62,10 @@ class YARD::CLI::Stats
 | 
			
		|||
    output 'Puppet Types', *type_statistics_all(:puppet_type)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def stats_for_puppet_providers
 | 
			
		||||
    output 'Puppet Providers', *type_statistics_all(:puppet_provider)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def output(name, data, undoc = nil)
 | 
			
		||||
    # Monkey patch output to accommodate our larger header widths
 | 
			
		||||
    @total += data if data.is_a?(Integer) && undoc
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,4 +3,5 @@ module PuppetStrings::Yard::CodeObjects
 | 
			
		|||
  require 'puppet-strings/yard/code_objects/class'
 | 
			
		||||
  require 'puppet-strings/yard/code_objects/defined_type'
 | 
			
		||||
  require 'puppet-strings/yard/code_objects/type'
 | 
			
		||||
  require 'puppet-strings/yard/code_objects/provider'
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
require 'puppet-strings/yard/code_objects/group'
 | 
			
		||||
 | 
			
		||||
# Implements the group for Puppet providers.
 | 
			
		||||
class PuppetStrings::Yard::CodeObjects::Providers < PuppetStrings::Yard::CodeObjects::Group
 | 
			
		||||
  # Gets the singleton instance of the group.
 | 
			
		||||
  # @param [String] type The resource type name for the provider.
 | 
			
		||||
  # @return Returns the singleton instance of the group.
 | 
			
		||||
  def self.instance(type)
 | 
			
		||||
    super("puppet_providers_#{type}".intern)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Gets the display name of the group.
 | 
			
		||||
  # @param [Boolean] prefix whether to show a prefix. Ignored for Puppet group namespaces.
 | 
			
		||||
  # @return [String] Returns the display name of the group.
 | 
			
		||||
  def name(prefix = false)
 | 
			
		||||
    'Providers'
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Implements the Puppet provider code object.
 | 
			
		||||
class PuppetStrings::Yard::CodeObjects::Provider < PuppetStrings::Yard::CodeObjects::Base
 | 
			
		||||
  attr_reader :type_name, :confines, :features, :defaults, :commands
 | 
			
		||||
 | 
			
		||||
  # Initializes a Puppet provider code object.
 | 
			
		||||
  # @param [String] type_name The resource type name for the provider.
 | 
			
		||||
  # @param [String] name The name of the provider.s
 | 
			
		||||
  # @return [void]
 | 
			
		||||
  def initialize(type_name, name)
 | 
			
		||||
    @type_name = type_name
 | 
			
		||||
    super(PuppetStrings::Yard::CodeObjects::Providers.instance(type_name), name)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Gets the type of the code object.
 | 
			
		||||
  # @return Returns the type of the code object.
 | 
			
		||||
  def type
 | 
			
		||||
    :puppet_provider
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Adds a confine to the provider.
 | 
			
		||||
  # @param [String] key The confine's key.
 | 
			
		||||
  # @param [String] value The confine's value.
 | 
			
		||||
  # @return [void]
 | 
			
		||||
  def add_confine(key, value)
 | 
			
		||||
    return unless key && value
 | 
			
		||||
    @confines ||= {}
 | 
			
		||||
    @confines[key] = value
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Adds a feature to the provider.
 | 
			
		||||
  # @param [String] feature The feature to add to the provider.
 | 
			
		||||
  # @return [void]
 | 
			
		||||
  def add_feature(feature)
 | 
			
		||||
    return unless feature
 | 
			
		||||
    @features ||= []
 | 
			
		||||
    @features << feature
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Adds a default to the provider.
 | 
			
		||||
  # @param [String] key The default's key.
 | 
			
		||||
  # @param [String] value The default's value.
 | 
			
		||||
  # @return [void]
 | 
			
		||||
  def add_default(key, value)
 | 
			
		||||
    return unless key && value
 | 
			
		||||
    @defaults ||= {}
 | 
			
		||||
    @defaults[key] = value
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Adds a command to the provider.
 | 
			
		||||
  # @param [String] key The command's key.
 | 
			
		||||
  # @param [String] value The command's value.
 | 
			
		||||
  # @return [void]
 | 
			
		||||
  def add_command(key, value)
 | 
			
		||||
    return unless key && value
 | 
			
		||||
    @commands ||= {}
 | 
			
		||||
    @commands[key] = value
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ module PuppetStrings::Yard::Handlers
 | 
			
		|||
  # The module for custom Ruby YARD handlers.
 | 
			
		||||
  module Ruby
 | 
			
		||||
    require 'puppet-strings/yard/handlers/ruby/type_handler'
 | 
			
		||||
    require 'puppet-strings/yard/handlers/ruby/provider_handler'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # The module for custom Puppet YARD handlers.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,112 @@
 | 
			
		|||
require 'puppet-strings/yard/handlers/ruby/base'
 | 
			
		||||
require 'puppet-strings/yard/code_objects'
 | 
			
		||||
require 'puppet/util/docs'
 | 
			
		||||
 | 
			
		||||
# Implements the handler for Puppet providers written in Ruby.
 | 
			
		||||
class PuppetStrings::Yard::Handlers::Ruby::ProviderHandler < PuppetStrings::Yard::Handlers::Ruby::Base
 | 
			
		||||
  namespace_only
 | 
			
		||||
  handles method_call(:provide)
 | 
			
		||||
 | 
			
		||||
  process do
 | 
			
		||||
    return unless statement.count >= 2
 | 
			
		||||
 | 
			
		||||
    # Check that provide is being called on Puppet::Type.type(<name>)
 | 
			
		||||
    type_call = statement[0]
 | 
			
		||||
    return unless type_call.is_a?(YARD::Parser::Ruby::MethodCallNode) && type_call.count >= 3
 | 
			
		||||
    return unless type_call[0].source == 'Puppet::Type'
 | 
			
		||||
    return unless type_call[2].source == 'type'
 | 
			
		||||
 | 
			
		||||
    # Extract the type name
 | 
			
		||||
    type_call_parameters = type_call.parameters(false)
 | 
			
		||||
    return unless type_call_parameters.count >= 1
 | 
			
		||||
    type_name = node_as_string(type_call_parameters.first)
 | 
			
		||||
    raise YARD::Parser::UndocumentableError, "Could not determine the resource type name for the provider defined at #{statement.file}:#{statement.line}." unless type_name
 | 
			
		||||
 | 
			
		||||
    # Register the object
 | 
			
		||||
    object = PuppetStrings::Yard::CodeObjects::Provider.new(type_name, get_name)
 | 
			
		||||
    register object
 | 
			
		||||
 | 
			
		||||
    # Extract the docstring
 | 
			
		||||
    register_provider_docstring object
 | 
			
		||||
 | 
			
		||||
    # Populate the provider data
 | 
			
		||||
    populate_provider_data object
 | 
			
		||||
 | 
			
		||||
    # Mark the provider as public if it doesn't already have an api tag
 | 
			
		||||
    object.add_tag YARD::Tags::Tag.new(:api, 'public') unless object.has_tag? :api
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
  def get_name
 | 
			
		||||
    parameters = statement.parameters(false)
 | 
			
		||||
    raise YARD::Parser::UndocumentableError, "Expected at least one parameter to 'provide' at #{statement.file}:#{statement.line}." if parameters.empty?
 | 
			
		||||
    name = node_as_string(parameters.first)
 | 
			
		||||
    raise YARD::Parser::UndocumentableError, "Expected a symbol or string literal for first parameter but found '#{parameters.first.type}' at #{statement.file}:#{statement.line}." unless name
 | 
			
		||||
    name
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def register_provider_docstring(object)
 | 
			
		||||
    # Walk the tree searching for assignments or calls to desc/doc=
 | 
			
		||||
    statement.traverse do |child|
 | 
			
		||||
      if child.type == :assign
 | 
			
		||||
        ivar = child.jump(:ivar)
 | 
			
		||||
        next unless ivar != child && ivar.source == '@doc'
 | 
			
		||||
        docstring = node_as_string(child[1])
 | 
			
		||||
        log.error "Failed to parse docstring for Puppet provider '#{object.name}' (resource type '#{object.type_name}') near #{child.file}:#{child.line}." and return nil unless docstring
 | 
			
		||||
        register_docstring(object, Puppet::Util::Docs.scrub(docstring), nil)
 | 
			
		||||
        return nil
 | 
			
		||||
      elsif child.is_a?(YARD::Parser::Ruby::MethodCallNode)
 | 
			
		||||
        # Look for a call to a dispatch method with a block
 | 
			
		||||
        next unless child.method_name &&
 | 
			
		||||
            (child.method_name.source == 'desc' || child.method_name.source == 'doc=') &&
 | 
			
		||||
            child.parameters(false).count == 1
 | 
			
		||||
 | 
			
		||||
        docstring = node_as_string(child.parameters[0])
 | 
			
		||||
        log.error "Failed to parse docstring for Puppet provider '#{object.name}' (resource type '#{object.type_name}') near #{child.file}:#{child.line}." and return nil unless docstring
 | 
			
		||||
        register_docstring(object, Puppet::Util::Docs.scrub(docstring), nil)
 | 
			
		||||
        return nil
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    log.warn "Missing a description for Puppet provider '#{object.name}' (resource type '#{object.type_name}') at #{statement.file}:#{statement.line}."
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def populate_provider_data(object)
 | 
			
		||||
    # Traverse the block looking for confines/defaults/commands
 | 
			
		||||
    block = statement.block
 | 
			
		||||
    return unless block && block.count >= 2
 | 
			
		||||
    block[1].children.each do |node|
 | 
			
		||||
      next unless node.is_a?(YARD::Parser::Ruby::MethodCallNode) && node.method_name
 | 
			
		||||
 | 
			
		||||
      method_name = node.method_name.source
 | 
			
		||||
      parameters = node.parameters(false)
 | 
			
		||||
 | 
			
		||||
      if method_name == 'confine'
 | 
			
		||||
        # Add a confine to the object
 | 
			
		||||
        next unless parameters.count >= 1
 | 
			
		||||
        parameters[0].each do |kvp|
 | 
			
		||||
          next unless kvp.count == 2
 | 
			
		||||
          object.add_confine(node_as_string(kvp[0]) || kvp[0].source, node_as_string(kvp[1]) || kvp[1].source)
 | 
			
		||||
        end
 | 
			
		||||
      elsif method_name == 'has_feature' || method_name == 'has_features'
 | 
			
		||||
        # Add the features to the object
 | 
			
		||||
        parameters.each do |parameter|
 | 
			
		||||
          object.add_feature(node_as_string(parameter) || parameter.source)
 | 
			
		||||
        end
 | 
			
		||||
      elsif method_name == 'defaultfor'
 | 
			
		||||
        # Add a default to the object
 | 
			
		||||
        next unless parameters.count >= 1
 | 
			
		||||
        parameters[0].each do |kvp|
 | 
			
		||||
          next unless kvp.count == 2
 | 
			
		||||
          object.add_default(node_as_string(kvp[0]) || kvp[0].source, node_as_string(kvp[1]) || kvp[1].source)
 | 
			
		||||
        end
 | 
			
		||||
      elsif method_name == 'commands'
 | 
			
		||||
        # Add the commands to the object
 | 
			
		||||
        next unless parameters.count >= 1
 | 
			
		||||
        parameters[0].each do |kvp|
 | 
			
		||||
          next unless kvp.count == 2
 | 
			
		||||
          object.add_command(node_as_string(kvp[0]) || kvp[0].source, node_as_string(kvp[1]) || kvp[1].source)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<% even = false  %>
 | 
			
		||||
<% @items.each do |item| %>
 | 
			
		||||
    <li id="object_<%=item.path%>" class="<%= even ? 'even' : 'odd' %>">
 | 
			
		||||
      <div class="item">
 | 
			
		||||
        <%= linkify item, h(item.name(true)) %>
 | 
			
		||||
        <small>Resource type: <em><%=item.type_name%></em></small>
 | 
			
		||||
      </div>
 | 
			
		||||
    </li>
 | 
			
		||||
    <% even = !even %>
 | 
			
		||||
<% end %>
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +25,15 @@ def generate_puppet_type_list
 | 
			
		|||
  generate_list_contents
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Generates the searchable Puppet provider list.
 | 
			
		||||
# @return [void]
 | 
			
		||||
def generate_puppet_provider_list
 | 
			
		||||
  @items = Registry.all(:puppet_provider).sort_by {|p| p.name.to_s }
 | 
			
		||||
  @list_title = 'Provider List'
 | 
			
		||||
  @list_type = 'puppet_provider'
 | 
			
		||||
  generate_list_contents
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Generates the searchable Ruby method list.
 | 
			
		||||
# @return [void]
 | 
			
		||||
def generate_method_list
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,8 @@
 | 
			
		|||
      <%= linkify obj, obj.name %>
 | 
			
		||||
      <% if (obj.type == :module || obj.type == :class) && !obj.namespace.root? %>
 | 
			
		||||
              <small>(<%= obj.namespace.path %>)</small>
 | 
			
		||||
      <% elsif obj.type == :puppet_provider %>
 | 
			
		||||
              <small>(Resource type: <%= obj.type_name %>)</small>
 | 
			
		||||
      <% end %>
 | 
			
		||||
            </li>
 | 
			
		||||
    <% end %>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ def init
 | 
			
		|||
  case object
 | 
			
		||||
  when '_index.html'
 | 
			
		||||
    @page_title = options.title
 | 
			
		||||
    sections :layout, [:index, [:listing, [:classes, :defined_types, :types, :files, :objects]]]
 | 
			
		||||
    sections :layout, [:index, [:listing, [:classes, :defined_types, :types, :providers, :files, :objects]]]
 | 
			
		||||
  else
 | 
			
		||||
    super
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +38,10 @@ def layout
 | 
			
		|||
    @nav_url = url_for_list('puppet_type')
 | 
			
		||||
    @page_title = "Resource Type: #{object.name}"
 | 
			
		||||
    @path = object.path
 | 
			
		||||
  when PuppetStrings::Yard::CodeObjects::Provider
 | 
			
		||||
    @nav_url = url_for_list('puppet_provider')
 | 
			
		||||
    @page_title = "Provider: #{object.name}"
 | 
			
		||||
    @path = object.path
 | 
			
		||||
  else
 | 
			
		||||
    @path = object.path
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +68,11 @@ def create_menu_lists
 | 
			
		|||
      title: 'Resource Types',
 | 
			
		||||
      search_title: 'Resource Types'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      type: 'puppet_provider',
 | 
			
		||||
      title: 'Providers',
 | 
			
		||||
      search_title: 'Providers'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      type: 'class',
 | 
			
		||||
      title: 'Ruby Classes',
 | 
			
		||||
| 
						 | 
				
			
			@ -129,6 +138,14 @@ def types
 | 
			
		|||
  erb(:objects)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Renders the providers section.
 | 
			
		||||
# @return [String] Returns the rendered section.
 | 
			
		||||
def providers
 | 
			
		||||
  @title = 'Puppet Provider Listing A-Z'
 | 
			
		||||
  @objects_by_letter = objects_by_letter(:puppet_provider)
 | 
			
		||||
  erb(:objects)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Renders the objects section.
 | 
			
		||||
# @return [String] Returns the rendered section.
 | 
			
		||||
def objects
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
<div class="box_info">
 | 
			
		||||
  <dl>
 | 
			
		||||
    <dt>Defined in:</dt>
 | 
			
		||||
    <dd>
 | 
			
		||||
      <%= object.file %><% if object.files.size > 1 %><span class="defines">,<br />
 | 
			
		||||
      <%= object.files[1..-1].map {|f| f.first }.join(",<br /> ") %></div>
 | 
			
		||||
      <% end %>
 | 
			
		||||
    </dd>
 | 
			
		||||
  </dl>
 | 
			
		||||
  <dl>
 | 
			
		||||
    <dt>Resource type:</dt>
 | 
			
		||||
    <dd><%= linkify(Registry["puppet_types::#{object.type_name}"], object.type_name) %></dd>
 | 
			
		||||
  </dl>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<% if @collection && !@collection.empty? %>
 | 
			
		||||
<div class="tags">
 | 
			
		||||
  <p class="tag_title"><%= @title %></p>
 | 
			
		||||
  <ul>
 | 
			
		||||
  <% @collection.each do |key, value| %>
 | 
			
		||||
    <li><tt><%= key %> — <%= value %></tt></li>
 | 
			
		||||
  <% end %>
 | 
			
		||||
  </ul>
 | 
			
		||||
</div>
 | 
			
		||||
<% end %>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<% if object.features && !object.features.empty? %>
 | 
			
		||||
<div class="tags">
 | 
			
		||||
  <p class="tag_title">Features</p>
 | 
			
		||||
  <ul>
 | 
			
		||||
    <% object.features.each do |feature| %>
 | 
			
		||||
    <li>
 | 
			
		||||
      <span class="name"><%= feature %></span>
 | 
			
		||||
    </li>
 | 
			
		||||
    <% end %>
 | 
			
		||||
  </ul>
 | 
			
		||||
</div>
 | 
			
		||||
<% end %>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<h1>Provider: <%= object.name %></h1>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
<h2>Overview</h2>
 | 
			
		||||
<div class="docstring">
 | 
			
		||||
  <div class="discussion">
 | 
			
		||||
    <%= htmlify(object.docstring) %>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
# Initializes the template.
 | 
			
		||||
# @return [void]
 | 
			
		||||
def init
 | 
			
		||||
  sections :header, :box_info, :overview, T('tags'), :features, :confines, :defaults, :commands
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Renders the confines section.
 | 
			
		||||
# @return [String] Returns the rendered section.
 | 
			
		||||
def confines
 | 
			
		||||
  @title = 'Confines'
 | 
			
		||||
  @collection = object.confines
 | 
			
		||||
  erb(:collection)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Renders the defaults section.
 | 
			
		||||
# @return [String] Returns the rendered section.
 | 
			
		||||
def defaults
 | 
			
		||||
  @title = 'Default Provider For'
 | 
			
		||||
  @collection = object.defaults
 | 
			
		||||
  erb(:collection)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Renders the commands section.
 | 
			
		||||
# @return [String] Returns the rendered section.
 | 
			
		||||
def commands
 | 
			
		||||
  @title = 'Commands'
 | 
			
		||||
  @collection = object.commands
 | 
			
		||||
  erb(:collection)
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ end
 | 
			
		|||
# Renders the box_info section.
 | 
			
		||||
# @return [String] Returns the rendered section.
 | 
			
		||||
def box_info
 | 
			
		||||
  @providers = []
 | 
			
		||||
  @providers = PuppetStrings::Yard::CodeObjects::Providers.instance(object.name).children
 | 
			
		||||
  erb(:box_info)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue