(PDOC-63) Implement a Puppet function YARD handler.
This commit implements a YARD handler for Puppet functions and the associated code object and templates.
This commit is contained in:
		
							parent
							
								
									b1a15bd43a
								
							
						
					
					
						commit
						f025efe557
					
				| 
						 | 
				
			
			@ -26,6 +26,7 @@ module PuppetStrings::Yard
 | 
			
		|||
 | 
			
		||||
    # Ignore documentation on Puppet DSL calls
 | 
			
		||||
    # This prevents the YARD DSL parser from emitting warnings for Puppet's Ruby DSL
 | 
			
		||||
    YARD::Handlers::Ruby::DSLHandlerMethods::IGNORE_METHODS['create_function'] = true
 | 
			
		||||
    YARD::Handlers::Ruby::DSLHandlerMethods::IGNORE_METHODS['newtype'] = true
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +43,7 @@ class YARD::CLI::Yardoc
 | 
			
		|||
      :puppet_defined_type,
 | 
			
		||||
      :puppet_type,
 | 
			
		||||
      :puppet_provider,
 | 
			
		||||
      :puppet_function,
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +68,10 @@ class YARD::CLI::Stats
 | 
			
		|||
    output 'Puppet Providers', *type_statistics_all(:puppet_provider)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def stats_for_puppet_functions
 | 
			
		||||
    output 'Puppet Functions', *type_statistics_all(:puppet_function)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def output(name, data, undoc = nil)
 | 
			
		||||
    # Monkey patch output to accommodate our larger header widths
 | 
			
		||||
    @total += data if data.is_a?(Integer) && undoc
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,4 +4,5 @@ module PuppetStrings::Yard::CodeObjects
 | 
			
		|||
  require 'puppet-strings/yard/code_objects/defined_type'
 | 
			
		||||
  require 'puppet-strings/yard/code_objects/type'
 | 
			
		||||
  require 'puppet-strings/yard/code_objects/provider'
 | 
			
		||||
  require 'puppet-strings/yard/code_objects/function'
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
require 'puppet-strings/yard/code_objects/group'
 | 
			
		||||
 | 
			
		||||
# Implements the group for Puppet functions.
 | 
			
		||||
class PuppetStrings::Yard::CodeObjects::Functions < PuppetStrings::Yard::CodeObjects::Group
 | 
			
		||||
  # Gets the singleton instance of the group.
 | 
			
		||||
  # @param [Symbol] type The function type to get the group for.
 | 
			
		||||
  # @return Returns the singleton instance of the group.
 | 
			
		||||
  def self.instance(type)
 | 
			
		||||
    super("puppet_functions_#{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)
 | 
			
		||||
    'Puppet Functions'
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Implements the Puppet function code object.
 | 
			
		||||
class PuppetStrings::Yard::CodeObjects::Function < PuppetStrings::Yard::CodeObjects::Base
 | 
			
		||||
  # Identifier for 3.x Ruby API functions
 | 
			
		||||
  RUBY_3X = :ruby3x
 | 
			
		||||
  # Identifier for 4.x Ruby API functions
 | 
			
		||||
  RUBY_4X = :ruby4x
 | 
			
		||||
  # Identifier for Puppet language functions
 | 
			
		||||
  PUPPET = :puppet
 | 
			
		||||
 | 
			
		||||
  attr_accessor :parameters
 | 
			
		||||
 | 
			
		||||
  # Initializes a Puppet function code object.
 | 
			
		||||
  # @param [String] name The name of the function.
 | 
			
		||||
  # @param [Symbol] function_type The type of function (e.g. :ruby3x, :ruby4x, :puppet)
 | 
			
		||||
  # @return [void]
 | 
			
		||||
  def initialize(name, function_type)
 | 
			
		||||
    super(PuppetStrings::Yard::CodeObjects::Functions.instance(function_type), name)
 | 
			
		||||
    @parameters = []
 | 
			
		||||
    @function_type = function_type
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Gets the type of the code object.
 | 
			
		||||
  # @return Returns the type of the code object.
 | 
			
		||||
  def type
 | 
			
		||||
    :puppet_function
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Gets the function type display string.
 | 
			
		||||
  # @return Returns the function type display string.
 | 
			
		||||
  def function_type
 | 
			
		||||
    case @function_type
 | 
			
		||||
    when RUBY_3X
 | 
			
		||||
      'Ruby 3.x API'
 | 
			
		||||
    when RUBY_4X
 | 
			
		||||
      'Ruby 4.x API'
 | 
			
		||||
    else
 | 
			
		||||
      'Puppet Language'
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Gets the Puppet signature of the function.
 | 
			
		||||
  # @return [String] Returns the Puppet signature of the function.
 | 
			
		||||
  def signature
 | 
			
		||||
    tags = self.tags(:param)
 | 
			
		||||
    args = @parameters.map do |parameter|
 | 
			
		||||
      name, default = parameter
 | 
			
		||||
      tag = tags.find { |tag| tag.name == name } if tags
 | 
			
		||||
      type = tag && tag.types ? "#{tag.type} " : 'Any '
 | 
			
		||||
      prefix = "#{name[0]}" if name.start_with?('*', '&')
 | 
			
		||||
      name = name[1..-1] if prefix
 | 
			
		||||
      default = " = #{default}" if default
 | 
			
		||||
      "#{type}#{prefix}$#{name}#{default}"
 | 
			
		||||
    end.join(', ')
 | 
			
		||||
    @name.to_s + '(' + args + ')'
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -4,11 +4,13 @@ module PuppetStrings::Yard::Handlers
 | 
			
		|||
  module Ruby
 | 
			
		||||
    require 'puppet-strings/yard/handlers/ruby/type_handler'
 | 
			
		||||
    require 'puppet-strings/yard/handlers/ruby/provider_handler'
 | 
			
		||||
    require 'puppet-strings/yard/handlers/ruby/function_handler'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # The module for custom Puppet YARD handlers.
 | 
			
		||||
  module Puppet
 | 
			
		||||
    require 'puppet-strings/yard/handlers/puppet/class_handler'
 | 
			
		||||
    require 'puppet-strings/yard/handlers/puppet/defined_type_handler'
 | 
			
		||||
    require 'puppet-strings/yard/handlers/puppet/function_handler'
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
require 'puppet-strings/yard/handlers/puppet/base'
 | 
			
		||||
require 'puppet-strings/yard/parsers'
 | 
			
		||||
require 'puppet-strings/yard/code_objects'
 | 
			
		||||
 | 
			
		||||
# Implements the handler for Puppet classes.
 | 
			
		||||
class PuppetStrings::Yard::Handlers::Puppet::FunctionHandler < PuppetStrings::Yard::Handlers::Puppet::Base
 | 
			
		||||
  handles PuppetStrings::Yard::Parsers::Puppet::FunctionStatement
 | 
			
		||||
 | 
			
		||||
  process do
 | 
			
		||||
    # Register the object
 | 
			
		||||
    object = PuppetStrings::Yard::CodeObjects::Function.new(statement.name, PuppetStrings::Yard::CodeObjects::Function::PUPPET)
 | 
			
		||||
    object.source = statement.source
 | 
			
		||||
    object.source_type = parser.parser_type
 | 
			
		||||
    register object
 | 
			
		||||
 | 
			
		||||
    # Log a warning if missing documentation
 | 
			
		||||
    log.warn "Missing documentation for Puppet function '#{object.name}' at #{statement.file}:#{statement.line}." if object.docstring.empty?
 | 
			
		||||
 | 
			
		||||
    # Set the parameter tag types
 | 
			
		||||
    set_parameter_types(object)
 | 
			
		||||
 | 
			
		||||
    # Add a return tag
 | 
			
		||||
    add_return_tag(object)
 | 
			
		||||
 | 
			
		||||
    # Set the parameters on the object
 | 
			
		||||
    object.parameters = statement.parameters.map { |p| [p.name, p.value] }
 | 
			
		||||
 | 
			
		||||
    # Mark the class 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 add_return_tag(object)
 | 
			
		||||
    tag = object.tag(:return)
 | 
			
		||||
    if tag
 | 
			
		||||
      tag.types = ['Any'] unless tag.types
 | 
			
		||||
      return
 | 
			
		||||
    end
 | 
			
		||||
    log.warn "Missing @return tag near #{statement.file}:#{statement.line}."
 | 
			
		||||
    object.add_tag YARD::Tags::Tag.new(:return, '', 'Any')
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,357 @@
 | 
			
		|||
require 'puppet-strings/yard/handlers/ruby/base'
 | 
			
		||||
require 'puppet-strings/yard/code_objects'
 | 
			
		||||
require 'puppet/util/docs'
 | 
			
		||||
 | 
			
		||||
# Implements the handler for Puppet functions written in Ruby.
 | 
			
		||||
class PuppetStrings::Yard::Handlers::Ruby::FunctionHandler < PuppetStrings::Yard::Handlers::Ruby::Base
 | 
			
		||||
  # Represents the list of Puppet 4.x function API methods to support.
 | 
			
		||||
  DISPATCH_METHOD_NAMES = %w(
 | 
			
		||||
    param
 | 
			
		||||
    required_param
 | 
			
		||||
    optional_param
 | 
			
		||||
    repeated_param
 | 
			
		||||
    optional_repeated_param
 | 
			
		||||
    required_repeated_param
 | 
			
		||||
    block_param
 | 
			
		||||
    required_block_param
 | 
			
		||||
    optional_block_param
 | 
			
		||||
  ).freeze
 | 
			
		||||
 | 
			
		||||
  namespace_only
 | 
			
		||||
  handles method_call(:create_function)
 | 
			
		||||
  handles method_call(:newfunction)
 | 
			
		||||
 | 
			
		||||
  process do
 | 
			
		||||
    # Only accept calls to Puppet::Functions (4.x) or Puppet::Parser::Functions (3.x)
 | 
			
		||||
    return unless statement.count > 1
 | 
			
		||||
    module_name = statement[0].source
 | 
			
		||||
    return unless module_name == 'Puppet::Functions' || module_name == 'Puppet::Parser::Functions'
 | 
			
		||||
 | 
			
		||||
    # Create and register the function object
 | 
			
		||||
    is_3x = module_name == 'Puppet::Parser::Functions'
 | 
			
		||||
    object = PuppetStrings::Yard::CodeObjects::Function.new(
 | 
			
		||||
      get_name,
 | 
			
		||||
      is_3x ? PuppetStrings::Yard::CodeObjects::Function::RUBY_3X : PuppetStrings::Yard::CodeObjects::Function::RUBY_4X
 | 
			
		||||
    )
 | 
			
		||||
    object.source = statement
 | 
			
		||||
    register object
 | 
			
		||||
 | 
			
		||||
    # For 3x, parse the doc parameter for the docstring
 | 
			
		||||
    # This must be done after the `register` call above because `register` always uses the statement's docstring
 | 
			
		||||
    if is_3x
 | 
			
		||||
      docstring = get_3x_docstring(object.name)
 | 
			
		||||
      register_docstring(object, docstring, nil) if docstring
 | 
			
		||||
 | 
			
		||||
      # Default any typeless param tag to 'Any'
 | 
			
		||||
      object.tags(:param).each do |tag|
 | 
			
		||||
        tag.types = ['Any'] unless tag.types && !tag.types.empty?
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Populate the parameters and the return tag
 | 
			
		||||
      object.parameters = object.tags(:param).map{ |p| [p.name, nil] }
 | 
			
		||||
      add_return_tag(object, statement.file, statement.line)
 | 
			
		||||
    else
 | 
			
		||||
      # For 4x, auto generate tags based on dispatch docstrings
 | 
			
		||||
      add_tags(object)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Mark the function 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 Puppet::Functions.create_function 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 add_tags(object)
 | 
			
		||||
    log.warn "Missing documentation for Puppet function '#{object.name}' at #{statement.file}:#{statement.line}." if object.docstring.empty?
 | 
			
		||||
    log.warn "The docstring for Puppet 4.x function '#{object.name}' contains @param tags near #{object.file}:#{object.line}: parameter documentation should be made on the dispatch call." unless object.tags(:param).empty?
 | 
			
		||||
    log.warn "The docstring for Puppet 4.x function '#{object.name}' contains @return tags near #{object.file}:#{object.line}: return value documentation should be made on the dispatch call." unless object.tags(:return).empty?
 | 
			
		||||
    log.warn "The docstring for Puppet 4.x function '#{object.name}' contains @overload tags near #{object.file}:#{object.line}: overload tags are automatically generated from the dispatch calls." unless object.tags(:overload).empty?
 | 
			
		||||
 | 
			
		||||
    # Delete any existing param/return/overload tags
 | 
			
		||||
    object.docstring.delete_tags(:param)
 | 
			
		||||
    object.docstring.delete_tags(:return)
 | 
			
		||||
    object.docstring.delete_tags(:overload)
 | 
			
		||||
 | 
			
		||||
    block = statement.block
 | 
			
		||||
    return unless block && block.count >= 2
 | 
			
		||||
 | 
			
		||||
    # Get the unqualified name of the Puppet function
 | 
			
		||||
    unqualified_name = object.name.to_s.split('::').last
 | 
			
		||||
 | 
			
		||||
    # Walk the block statements looking for dispatch calls and methods with the same name as the Puppet function
 | 
			
		||||
    default = nil
 | 
			
		||||
    block[1].children.each do |node|
 | 
			
		||||
      if node.is_a?(YARD::Parser::Ruby::MethodCallNode)
 | 
			
		||||
        add_overload_tag(object, node)
 | 
			
		||||
      elsif node.is_a?(YARD::Parser::Ruby::MethodDefinitionNode)
 | 
			
		||||
        default = node if node.method_name && node.method_name.source == unqualified_name
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Create an overload for the default method if there is one
 | 
			
		||||
    overloads = object.tags(:overload)
 | 
			
		||||
    if overloads.empty? && default
 | 
			
		||||
      add_method_overload(object, default)
 | 
			
		||||
      overloads = object.tags(:overload)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # If there's only one overload, move the tags to the object itself
 | 
			
		||||
    if overloads.count == 1
 | 
			
		||||
      overload = overloads.first
 | 
			
		||||
      object.parameters = overload.parameters
 | 
			
		||||
      object.add_tag(*overload.tags)
 | 
			
		||||
      object.docstring.delete_tags(:overload)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def add_overload_tag(object, node)
 | 
			
		||||
    # Look for a call to a dispatch method with a block
 | 
			
		||||
    return unless node.is_a?(YARD::Parser::Ruby::MethodCallNode) &&
 | 
			
		||||
                  node.method_name &&
 | 
			
		||||
                  node.method_name.source == 'dispatch' &&
 | 
			
		||||
                  node.parameters(false).count == 1 &&
 | 
			
		||||
                  node.block &&
 | 
			
		||||
                  node.block.count >= 2
 | 
			
		||||
 | 
			
		||||
    overload_tag = PuppetStrings::Yard::Tags::OverloadTag.new(object.name, node.docstring || '')
 | 
			
		||||
    param_tags = overload_tag.tags(:param)
 | 
			
		||||
 | 
			
		||||
    block = nil
 | 
			
		||||
    node.block[1].children.each do |child|
 | 
			
		||||
      next unless child.is_a?(YARD::Parser::Ruby::MethodCallNode) && child.method_name
 | 
			
		||||
 | 
			
		||||
      method_name = child.method_name.source
 | 
			
		||||
      next unless DISPATCH_METHOD_NAMES.include?(method_name)
 | 
			
		||||
 | 
			
		||||
      # Check for block
 | 
			
		||||
      if method_name.include?('block')
 | 
			
		||||
        if block
 | 
			
		||||
          log.warn "A duplicate block parameter was found for Puppet function '#{object.name}' at #{child.file}:#{child.line}."
 | 
			
		||||
          next
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # Store the block; needs to be appended last
 | 
			
		||||
        block = child
 | 
			
		||||
        next
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Ensure two parameters to parameter definition
 | 
			
		||||
      parameters = child.parameters(false)
 | 
			
		||||
      unless parameters.count == 2
 | 
			
		||||
        log.warn "Expected 2 arguments to '#{method_name}' call at #{child.file}:#{child.line}: parameter information may not be correct."
 | 
			
		||||
        next
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      add_param_tag(
 | 
			
		||||
        overload_tag,
 | 
			
		||||
        param_tags,
 | 
			
		||||
        node_as_string(parameters[1]),
 | 
			
		||||
        child.file,
 | 
			
		||||
        child.line,
 | 
			
		||||
        node_as_string(parameters[0]),
 | 
			
		||||
        nil, # TODO: determine default from corresponding Ruby method signature?
 | 
			
		||||
        method_name.include?('optional'),
 | 
			
		||||
        method_name.include?('repeated')
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Handle the block parameter after others so it appears last in the list
 | 
			
		||||
    if block
 | 
			
		||||
      parameters = block.parameters(false)
 | 
			
		||||
      if parameters.empty?
 | 
			
		||||
        name = 'block'
 | 
			
		||||
        type = 'Callable'
 | 
			
		||||
      elsif parameters.count == 1
 | 
			
		||||
        name = node_as_string(parameters[0])
 | 
			
		||||
        type = 'Callable'
 | 
			
		||||
      elsif parameters.count == 2
 | 
			
		||||
        type = node_as_string(parameters[0])
 | 
			
		||||
        name = node_as_string(parameters[1])
 | 
			
		||||
      else
 | 
			
		||||
        log.warn "Unexpected number of arguments to block definition at #{block.file}:#{block.line}."
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      if name && type
 | 
			
		||||
        add_param_tag(
 | 
			
		||||
          overload_tag,
 | 
			
		||||
          param_tags,
 | 
			
		||||
          name,
 | 
			
		||||
          block.file,
 | 
			
		||||
          block.line,
 | 
			
		||||
          type,
 | 
			
		||||
          nil, # TODO: determine default from corresponding Ruby method signature?
 | 
			
		||||
          block.method_name.source.include?('optional'),
 | 
			
		||||
          false, # Not repeated
 | 
			
		||||
          true   # Is block
 | 
			
		||||
        )
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Add a return tag if missing
 | 
			
		||||
    add_return_tag(overload_tag, node.file, node.line)
 | 
			
		||||
 | 
			
		||||
    # Validate that tags have parameters
 | 
			
		||||
    validate_overload(overload_tag, node.file, node.line)
 | 
			
		||||
 | 
			
		||||
    object.add_tag overload_tag
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def add_method_overload(object, node)
 | 
			
		||||
    overload_tag = PuppetStrings::Yard::Tags::OverloadTag.new(object.name, node.docstring || '')
 | 
			
		||||
    param_tags = overload_tag.tags(:param)
 | 
			
		||||
 | 
			
		||||
    parameters = node.parameters
 | 
			
		||||
 | 
			
		||||
    # Populate the required parameters
 | 
			
		||||
    params = parameters.unnamed_required_params
 | 
			
		||||
    if params
 | 
			
		||||
      params.each do |parameter|
 | 
			
		||||
        add_param_tag(
 | 
			
		||||
          overload_tag,
 | 
			
		||||
          param_tags,
 | 
			
		||||
          parameter.source,
 | 
			
		||||
          parameter.file,
 | 
			
		||||
          parameter.line
 | 
			
		||||
        )
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Populate the optional parameters
 | 
			
		||||
    params = parameters.unnamed_optional_params
 | 
			
		||||
    if params
 | 
			
		||||
      params.each do |parameter|
 | 
			
		||||
        add_param_tag(
 | 
			
		||||
          overload_tag,
 | 
			
		||||
          param_tags,
 | 
			
		||||
          parameter[0].source,
 | 
			
		||||
          parameter.file,
 | 
			
		||||
          parameter.line,
 | 
			
		||||
          nil,
 | 
			
		||||
          parameter[1].source,
 | 
			
		||||
          true
 | 
			
		||||
        )
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Populate the splat parameter
 | 
			
		||||
    param = parameters.splat_param
 | 
			
		||||
    if param
 | 
			
		||||
      add_param_tag(
 | 
			
		||||
        overload_tag,
 | 
			
		||||
        param_tags,
 | 
			
		||||
        param.source,
 | 
			
		||||
        param.file,
 | 
			
		||||
        param.line,
 | 
			
		||||
        nil,
 | 
			
		||||
        nil,
 | 
			
		||||
        false,
 | 
			
		||||
        true
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Populate the block parameter
 | 
			
		||||
    param = parameters.block_param
 | 
			
		||||
    if param
 | 
			
		||||
      add_param_tag(
 | 
			
		||||
        overload_tag,
 | 
			
		||||
        param_tags,
 | 
			
		||||
        param.source,
 | 
			
		||||
        param.file,
 | 
			
		||||
        param.line,
 | 
			
		||||
        nil,
 | 
			
		||||
        nil,
 | 
			
		||||
        false,
 | 
			
		||||
        false,
 | 
			
		||||
        true
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Add a return tag if missing
 | 
			
		||||
    add_return_tag(overload_tag, node.file, node.line)
 | 
			
		||||
 | 
			
		||||
    # Validate that tags have parameters
 | 
			
		||||
    validate_overload(overload_tag, node.file, node.line)
 | 
			
		||||
 | 
			
		||||
    object.add_tag overload_tag
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def add_param_tag(object, tags, name, file, line, type = nil, default = nil, optional = false, repeated = false, block = false)
 | 
			
		||||
    tag = tags.find { |tag| tag.name == name } if tags
 | 
			
		||||
    log.warn "Missing @param tag for parameter '#{name}' near #{file}:#{line}." unless tag || object.docstring.all.empty?
 | 
			
		||||
    log.warn "The @param tag for parameter '#{name}' should not contain a type specification near #{file}:#{line}: ignoring in favor of dispatch type information." if type && tag && tag.types && !tag.types.empty?
 | 
			
		||||
 | 
			
		||||
    if repeated
 | 
			
		||||
      name = '*' + name
 | 
			
		||||
    elsif block
 | 
			
		||||
      name = '&' + name
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    unless type
 | 
			
		||||
      type = tag && tag.types ? tag.type : 'Any'
 | 
			
		||||
    end
 | 
			
		||||
    type = optional ? "Optional[#{type}]" : type
 | 
			
		||||
 | 
			
		||||
    object.parameters << [name, to_puppet_literal(default)]
 | 
			
		||||
 | 
			
		||||
    if tag
 | 
			
		||||
      tag.name = name
 | 
			
		||||
      tag.types = [type]
 | 
			
		||||
    else
 | 
			
		||||
      object.add_tag YARD::Tags::Tag.new(:param, '', type, name)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def add_return_tag(object, file, line)
 | 
			
		||||
    tag = object.tag(:return)
 | 
			
		||||
    if tag
 | 
			
		||||
      tag.types = ['Any'] unless tag.types
 | 
			
		||||
      return
 | 
			
		||||
    end
 | 
			
		||||
    log.warn "Missing @return tag near #{file}:#{line}."
 | 
			
		||||
    object.add_tag YARD::Tags::Tag.new(:return, '', 'Any')
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def validate_overload(overload, file, line)
 | 
			
		||||
    # Validate that tags have matching parameters
 | 
			
		||||
    overload.tags(:param).each do |tag|
 | 
			
		||||
      next if overload.parameters.find { |p| tag.name == p[0] }
 | 
			
		||||
      log.warn "The @param tag for parameter '#{tag.name}' has no matching parameter at #{file}:#{line}."
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def get_3x_docstring(name)
 | 
			
		||||
    parameters = statement.parameters(false)
 | 
			
		||||
    if parameters.count >= 2
 | 
			
		||||
      parameters[1].each do |kvp|
 | 
			
		||||
        next unless kvp.count == 2
 | 
			
		||||
        next unless node_as_string(kvp[0]) == 'doc'
 | 
			
		||||
        docstring = node_as_string(kvp[1])
 | 
			
		||||
 | 
			
		||||
        log.error "Failed to parse docstring for 3.x Puppet function '#{name}' near #{statement.file}:#{statement.line}." and return nil unless docstring
 | 
			
		||||
        return Puppet::Util::Docs.scrub(docstring)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Log a warning for missing docstring
 | 
			
		||||
    log.warn "Missing documentation for Puppet function '#{name}' at #{statement.file}:#{statement.line}."
 | 
			
		||||
    nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def to_puppet_literal(literal)
 | 
			
		||||
    case literal
 | 
			
		||||
    when 'nil'
 | 
			
		||||
      'undef'
 | 
			
		||||
    when ':default'
 | 
			
		||||
      'default'
 | 
			
		||||
    else
 | 
			
		||||
      literal
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -2,4 +2,5 @@
 | 
			
		|||
module PuppetStrings::Yard::Tags
 | 
			
		||||
  require 'puppet-strings/yard/tags/parameter_directive'
 | 
			
		||||
  require 'puppet-strings/yard/tags/property_directive'
 | 
			
		||||
  require 'puppet-strings/yard/tags/overload_tag'
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,94 @@
 | 
			
		|||
# Implements an overload tag for Puppet functions
 | 
			
		||||
#
 | 
			
		||||
# This differs from Yard's overload tag in that the signatures are formatted according to Puppet language rules.
 | 
			
		||||
class PuppetStrings::Yard::Tags::OverloadTag < YARD::Tags::Tag
 | 
			
		||||
  attr_reader :parameters, :docstring
 | 
			
		||||
 | 
			
		||||
  # Initializes the overload tag.
 | 
			
		||||
  # @param [String, Symbol] name The name of the function being overloaded.
 | 
			
		||||
  # @param [String] docstring The docstring for the overload.
 | 
			
		||||
  # @return [void]
 | 
			
		||||
  def initialize(name, docstring)
 | 
			
		||||
    super(:overload, nil)
 | 
			
		||||
    @name = name.to_s
 | 
			
		||||
    @parameters = []
 | 
			
		||||
    @docstring = YARD::Docstring.new(docstring)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Gets the signature of the overload.
 | 
			
		||||
  # @return [String] Returns the signature of the overload.
 | 
			
		||||
  def signature
 | 
			
		||||
    tags = self.tags(:param)
 | 
			
		||||
    args = @parameters.map do |parameter|
 | 
			
		||||
      name, default = parameter
 | 
			
		||||
      tag = tags.find { |tag| tag.name == name } if tags
 | 
			
		||||
      type = tag && tag.types ? "#{tag.type} " : 'Any '
 | 
			
		||||
      prefix = "#{name[0]}" if name.start_with?('*', '&')
 | 
			
		||||
      name = name[1..-1] if prefix
 | 
			
		||||
      default = " = #{default}" if default
 | 
			
		||||
      "#{type}#{prefix}$#{name}#{default}"
 | 
			
		||||
    end.join(', ')
 | 
			
		||||
    @name + '(' + args + ')'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Adds a tag to the overload's docstring.
 | 
			
		||||
  # @param [YARD::Tag] tag The tag to add to the overload's docstring.
 | 
			
		||||
  # @return [void]
 | 
			
		||||
  def add_tag(tag)
 | 
			
		||||
    @docstring.add_tag(tag)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Gets the first tag of the given name.
 | 
			
		||||
  # @param [String, Symbol] name The name of the tag.
 | 
			
		||||
  # @return [YARD::Tag] Returns the first tag if found or nil if not found.
 | 
			
		||||
  def tag(name)
 | 
			
		||||
    @docstring.tag(name)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Gets all tags or tags of a given name.
 | 
			
		||||
  # @param [String, Symbol] name The name of the tag to get or nil for all tags.
 | 
			
		||||
  # @return [Array<Yard::Tag>] Returns an array of tags.
 | 
			
		||||
  def tags(name = nil)
 | 
			
		||||
    @docstring.tags(name)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Determines if a tag with the given name is present.
 | 
			
		||||
  # @param [String, Symbol] name The tag name.
 | 
			
		||||
  # @return [Boolean] Returns true if there is at least one tag with the given name or false if not.
 | 
			
		||||
  def has_tag?(name)
 | 
			
		||||
    @docstring.has_tag?(name)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Sets the object associated with this tag.
 | 
			
		||||
  # @param [Object] value The object to associate with this tag.
 | 
			
		||||
  # @return [void]
 | 
			
		||||
  def object=(value)
 | 
			
		||||
    super(value)
 | 
			
		||||
    @docstring.object = value
 | 
			
		||||
    @docstring.tags.each {|tag| tag.object = value }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Responsible for forwarding method calls to the associated object.
 | 
			
		||||
  # @param [Symbol] method_name The method being invoked.
 | 
			
		||||
  # @param [Array] args The args passed to the method.
 | 
			
		||||
  # @param block The block passed to the method.
 | 
			
		||||
  # @return Returns what the method call on the object would return.
 | 
			
		||||
  def method_missing(method_name, *args, &block)
 | 
			
		||||
    return object.send(method_name, *args, &block) if object.respond_to? method_name
 | 
			
		||||
    super
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Determines if the associated object responds to the give missing method name.
 | 
			
		||||
  # @param [Symbol, String] method_name The name of the method to check.
 | 
			
		||||
  # @param [Boolean] include_all True to include all methods in the check or false for only public methods.
 | 
			
		||||
  # @return [Boolean] Returns true if the object responds to the method or false if not.
 | 
			
		||||
  def respond_to_missing?(method_name, include_all = false)
 | 
			
		||||
    object.respond_to?(method_name, include_all) || super
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Gets the type of the object associated with this tag.
 | 
			
		||||
  # @return [Symbol] Returns the type of the object associated with this tag.
 | 
			
		||||
  def type
 | 
			
		||||
    object.type
 | 
			
		||||
  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(false)) %>
 | 
			
		||||
        <small><%= item.function_type %></small>
 | 
			
		||||
      </div>
 | 
			
		||||
    </li>
 | 
			
		||||
    <% even = !even %>
 | 
			
		||||
<% end %>
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +34,15 @@ def generate_puppet_provider_list
 | 
			
		|||
  generate_list_contents
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Generates the searchable Puppet function list.
 | 
			
		||||
# @return [void]
 | 
			
		||||
def generate_puppet_function_list
 | 
			
		||||
  @items = Registry.all(:puppet_function).sort_by {|f| f.name.to_s }
 | 
			
		||||
  @list_title = 'Puppet Function List'
 | 
			
		||||
  @list_type = 'puppet_function'
 | 
			
		||||
  generate_list_contents
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Generates the searchable Ruby method list.
 | 
			
		||||
# @return [void]
 | 
			
		||||
def generate_method_list
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,8 @@
 | 
			
		|||
              <small>(<%= obj.namespace.path %>)</small>
 | 
			
		||||
      <% elsif obj.type == :puppet_provider %>
 | 
			
		||||
              <small>(Resource type: <%= obj.type_name %>)</small>
 | 
			
		||||
      <% elsif obj.type == :puppet_function %>
 | 
			
		||||
              <small>(<%= obj.function_type %>)</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, :providers, :files, :objects]]]
 | 
			
		||||
    sections :layout, [:index, [:listing, [:classes, :defined_types, :types, :providers, :functions, :files, :objects]]]
 | 
			
		||||
  else
 | 
			
		||||
    super
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +42,10 @@ def layout
 | 
			
		|||
    @nav_url = url_for_list('puppet_provider')
 | 
			
		||||
    @page_title = "Provider: #{object.name}"
 | 
			
		||||
    @path = object.path
 | 
			
		||||
  when PuppetStrings::Yard::CodeObjects::Function
 | 
			
		||||
    @nav_url = url_for_list('puppet_function')
 | 
			
		||||
    @page_title = "Puppet Function: #{object.name} (#{object.function_type})"
 | 
			
		||||
    @path = object.path
 | 
			
		||||
  else
 | 
			
		||||
    @path = object.path
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +77,11 @@ def create_menu_lists
 | 
			
		|||
      title: 'Providers',
 | 
			
		||||
      search_title: 'Providers'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      type: 'puppet_function',
 | 
			
		||||
      title: 'Puppet Functions',
 | 
			
		||||
      search_title: 'Puppet Functions'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      type: 'class',
 | 
			
		||||
      title: 'Ruby Classes',
 | 
			
		||||
| 
						 | 
				
			
			@ -146,6 +155,14 @@ def providers
 | 
			
		|||
  erb(:objects)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Renders the functions section.
 | 
			
		||||
# @return [String] Returns the rendered section.
 | 
			
		||||
def functions
 | 
			
		||||
  @title = 'Puppet Function Listing A-Z'
 | 
			
		||||
  @objects_by_letter = objects_by_letter(:puppet_function)
 | 
			
		||||
  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>Function type:</dt>
 | 
			
		||||
    <dd><%= object.function_type %></dd>
 | 
			
		||||
  </dl>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<h1>Puppet Function: <%= object.name %></h1>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
<h2>Overview</h2>
 | 
			
		||||
<div class="method_details first">
 | 
			
		||||
  <% unless object.has_tag? :overload %>
 | 
			
		||||
  <div class="tags overload overload_item">
 | 
			
		||||
    <span class="overload">
 | 
			
		||||
      <span class="overload_item">
 | 
			
		||||
        <span class="signature first" style="margin-left: 0px;"><%= "<strong>#{h(object.signature)}</strong> ⇒ #{signature_types(object, false)}" %></span>
 | 
			
		||||
      </span>
 | 
			
		||||
    </span>
 | 
			
		||||
  </div>
 | 
			
		||||
  <% end %>
 | 
			
		||||
  <div class="docstring">
 | 
			
		||||
    <div class="discussion">
 | 
			
		||||
      <%= htmlify(object.docstring) %>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <%= yieldall %>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
# Initializes the template.
 | 
			
		||||
# @return [void]
 | 
			
		||||
def init
 | 
			
		||||
  sections :header, :box_info, :overview, [T('tags'), :source]
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<div class="method_details_list">
 | 
			
		||||
  <table class="source_code">
 | 
			
		||||
    <tr>
 | 
			
		||||
      <td>
 | 
			
		||||
        <pre class="lines"><%= "\n\n\n" %><%= h format_lines(object) %></pre>
 | 
			
		||||
      </td>
 | 
			
		||||
      <td>
 | 
			
		||||
        <pre class="code"><span class="info file"># File '<%= h object.file %>'<% if object.line %>, line <%= object.line %><% end %></span><%= "\n\n" %><%= html_syntax_highlight object.source %></pre>
 | 
			
		||||
      </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
  </table>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<% if object.has_tag?(:overload) && object.tags(:overload).any? {|o| !o.docstring.blank? } %>
 | 
			
		||||
  <p class="tag_title">Overloads:</p>
 | 
			
		||||
  <ul class="overload">
 | 
			
		||||
    <% object.tags(:overload).each_with_index do |overload, index| %>
 | 
			
		||||
      <% next if overload.docstring.blank? %>
 | 
			
		||||
      <li class="overload_item">
 | 
			
		||||
        <span class="signature"><%= "<strong>#{h(overload.signature)}</strong> ⇒ #{signature_types(overload, false)}" %></span>
 | 
			
		||||
        <%= yieldall :object => overload %>
 | 
			
		||||
      </li>
 | 
			
		||||
    <% end %>
 | 
			
		||||
  </ul>
 | 
			
		||||
<% end %>
 | 
			
		||||
| 
						 | 
				
			
			@ -4,5 +4,12 @@ def param
 | 
			
		|||
  tag(:param) if
 | 
			
		||||
    object.type == :method ||
 | 
			
		||||
    object.type == :puppet_class ||
 | 
			
		||||
    object.type == :puppet_defined_type
 | 
			
		||||
    object.type == :puppet_defined_type ||
 | 
			
		||||
    object.type == :puppet_function
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# Renders the overload section.
 | 
			
		||||
# @return [String] Returns the rendered section.
 | 
			
		||||
def overload
 | 
			
		||||
  erb(if object.type == :puppet_function then :puppet_overload else :overload end)
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue