diff --git a/lib/puppet-strings/yard.rb b/lib/puppet-strings/yard.rb index 9fa71c0..8c92acd 100644 --- a/lib/puppet-strings/yard.rb +++ b/lib/puppet-strings/yard.rb @@ -18,6 +18,15 @@ module PuppetStrings::Yard # Register our handlers YARD::Handlers::Processor.register_handler_namespace(:puppet, PuppetStrings::Yard::Handlers::Puppet) + YARD::Handlers::Processor.register_handler_namespace(:puppet_ruby, PuppetStrings::Yard::Handlers::Ruby) + + # Register the tag directives + PuppetStrings::Yard::Tags::ParameterDirective.register! + PuppetStrings::Yard::Tags::PropertyDirective.register! + + # 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['newtype'] = true end end @@ -31,6 +40,7 @@ class YARD::CLI::Yardoc :class, :puppet_class, :puppet_defined_type, + :puppet_type, ) end end @@ -47,6 +57,10 @@ class YARD::CLI::Stats output 'Puppet Defined Types', *type_statistics_all(:puppet_defined_type) end + def stats_for_puppet_types + output 'Puppet Types', *type_statistics_all(:puppet_type) + end + def output(name, data, undoc = nil) # Monkey patch output to accommodate our larger header widths @total += data if data.is_a?(Integer) && undoc diff --git a/lib/puppet-strings/yard/code_objects.rb b/lib/puppet-strings/yard/code_objects.rb index da6af39..dc6b9e3 100644 --- a/lib/puppet-strings/yard/code_objects.rb +++ b/lib/puppet-strings/yard/code_objects.rb @@ -2,4 +2,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' end diff --git a/lib/puppet-strings/yard/code_objects/type.rb b/lib/puppet-strings/yard/code_objects/type.rb new file mode 100644 index 0000000..bf80e14 --- /dev/null +++ b/lib/puppet-strings/yard/code_objects/type.rb @@ -0,0 +1,110 @@ +require 'puppet-strings/yard/code_objects/group' + +# Implements the group for Puppet resource types. +class PuppetStrings::Yard::CodeObjects::Types < PuppetStrings::Yard::CodeObjects::Group + # Gets the singleton instance of the group. + # @return Returns the singleton instance of the group. + def self.instance + super(:puppet_types) + 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) + 'Resource Types' + end +end + +# Implements the Puppet resource type code object. +class PuppetStrings::Yard::CodeObjects::Type < PuppetStrings::Yard::CodeObjects::Base + # Represents a resource type parameter. + class Parameter + attr_reader :name, :values, :aliases + attr_accessor :docstring, :isnamevar, :default + + # Initializes a resource type parameter or property. + # @param [String] name The name of the parameter or property. + # @param [String] docstring The docstring for the parameter or property.s + def initialize(name, docstring = nil) + @name = name + @docstring = docstring || '' + @values = [] + @aliases = {} + @isnamevar = false + @default = nil + end + + # Adds a value to the parameter or property. + # @param [String] value The value to add. + # @return [void] + def add(value) + @values << value + end + + # Aliases a value to another value. + # @param [String] new The new (alias) value. + # @param [String] old The old (existing) value. + # @return [void] + def alias(new, old) + @values << new unless @values.include? new + @aliases[new] = old + end + end + + # Represents a resource type property (same attributes as a parameter). + class Property < Parameter + end + + # Represents a resource type feature. + class Feature + attr_reader :name, :docstring + + # Initializes a new feature. + # @param [String] name The name of the feature. + # @param [String] docstring The docstring of the feature. + def initialize(name, docstring) + @name = name + @docstring = docstring + end + end + + attr_reader :properties, :parameters, :features + + # Initializes a new resource type. + # @param [String] name The resource type name. + # @return [void] + def initialize(name) + super(PuppetStrings::Yard::CodeObjects::Types.instance, name) + end + + # Gets the type of the code object. + # @return Returns the type of the code object. + def type + :puppet_type + end + + # Adds a parameter to the resource type + # @param [PuppetStrings::Yard::CodeObjects::Type::Parameter] parameter The parameter to add. + # @return [void] + def add_parameter(parameter) + @parameters ||= [] + @parameters << parameter + end + + # Adds a property to the resource type + # @param [PuppetStrings::Yard::CodeObjects::Type::Property] property The property to add. + # @return [void] + def add_property(property) + @properties ||= [] + @properties << property + end + + # Adds a feature to the resource type. + # @param [PuppetStrings::Yard::CodeObjects::Type::Feature] feature The feature to add. + # @return [void] + def add_feature(feature) + @features ||= [] + @features << feature + end +end diff --git a/lib/puppet-strings/yard/handlers.rb b/lib/puppet-strings/yard/handlers.rb index 9eadc55..f3c683c 100644 --- a/lib/puppet-strings/yard/handlers.rb +++ b/lib/puppet-strings/yard/handlers.rb @@ -1,5 +1,10 @@ # The module for custom YARD handlers. module PuppetStrings::Yard::Handlers + # The module for custom Ruby YARD handlers. + module Ruby + require 'puppet-strings/yard/handlers/ruby/type_handler' + end + # The module for custom Puppet YARD handlers. module Puppet require 'puppet-strings/yard/handlers/puppet/class_handler' diff --git a/lib/puppet-strings/yard/handlers/ruby/base.rb b/lib/puppet-strings/yard/handlers/ruby/base.rb new file mode 100644 index 0000000..d2fb041 --- /dev/null +++ b/lib/puppet-strings/yard/handlers/ruby/base.rb @@ -0,0 +1,38 @@ +require 'ripper' + +# Implements the base handler for Ruby language handlers. +class PuppetStrings::Yard::Handlers::Ruby::Base < YARD::Handlers::Ruby::Base + # A regular expression for detecting the start of a Ruby heredoc. + # Note: the first character of the heredoc start may have been cut off by YARD. + HEREDOC_START = /^ 1 + end + + source + end + end +end diff --git a/lib/puppet-strings/yard/handlers/ruby/type_handler.rb b/lib/puppet-strings/yard/handlers/ruby/type_handler.rb new file mode 100644 index 0000000..86ee94e --- /dev/null +++ b/lib/puppet-strings/yard/handlers/ruby/type_handler.rb @@ -0,0 +1,194 @@ +require 'puppet-strings/yard/handlers/ruby/base' +require 'puppet-strings/yard/code_objects' +require 'puppet/util' + +# Implements the handler for Puppet resource types written in Ruby. +class PuppetStrings::Yard::Handlers::Ruby::TypeHandler < PuppetStrings::Yard::Handlers::Ruby::Base + # The default docstring when ensurable is used without given a docstring. + DEFAULT_ENSURABLE_DOCSTRING = 'The basic property that the resource should be in.'.freeze + + namespace_only + handles method_call(:newtype) + + process do + # Only accept calls to Puppet::Type + return unless statement.count > 1 + module_name = statement[0].source + return unless module_name == 'Puppet::Type' || module_name == 'Type' + + object = PuppetStrings::Yard::CodeObjects::Type.new(get_name) + register object + + docstring = find_docstring(statement, "Puppet resource type '#{object.name}'") + register_docstring(object, docstring, nil) if docstring + + # Populate the parameters/properties/features to the type + populate_type_data(object) + + # Set the default namevar + set_default_namevar(object) + + # Mark the type 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::Type.newtype 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 find_docstring(node, kind) + # Walk the tree searching for assignments or calls to desc/doc= + node.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 #{kind} near #{child.file}:#{child.line}." and return nil unless docstring + return Puppet::Util::Docs.scrub(docstring) + 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 #{kind} near #{child.file}:#{child.line}." and return nil unless docstring + return Puppet::Util::Docs.scrub(docstring) + end + end + log.warn "Missing a description for #{kind} at #{node.file}:#{node.line}." + nil + end + + def populate_type_data(object) + # Traverse the block looking for properties/parameters/features + 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 == 'newproperty' + # Add a property to the object + next unless parameters.count >= 1 + name = node_as_string(parameters[0]) + next unless name + object.add_property(create_property(name, node)) + elsif method_name == 'newparam' + # Add a parameter to the object + next unless parameters.count >= 1 + name = node_as_string(parameters[0]) + next unless name + object.add_parameter(create_parameter(name, node)) + elsif method_name == 'feature' + # Add a feature to the object + next unless parameters.count >= 2 + name = node_as_string(parameters[0]) + next unless name + + docstring = node_as_string(parameters[1]) + next unless docstring + + object.add_feature(PuppetStrings::Yard::CodeObjects::Type::Feature.new(name, docstring)) + elsif method_name == 'ensurable' + if node.block + property = create_property('ensure', node) + property.docstring = DEFAULT_ENSURABLE_DOCSTRING if property.docstring.empty? + else + property = PuppetStrings::Yard::CodeObjects::Type::Property.new('ensure', DEFAULT_ENSURABLE_DOCSTRING) + property.add('present') + property.add('absent') + property.default = 'present' + end + object.add_property property + end + end + end + + def create_parameter(name, node) + parameter = PuppetStrings::Yard::CodeObjects::Type::Parameter.new(name, find_docstring(node, "Puppet resource parameter '#{name}'")) + set_values(node, parameter) + parameter + end + + def create_property(name, node) + property = PuppetStrings::Yard::CodeObjects::Type::Property.new(name, find_docstring(node, "Puppet resource property '#{name}'")) + set_values(node, property) + property + end + + def set_values(node, object) + return unless node.block && node.block.count >= 2 + + 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 + parameters = child.parameters(false) + + if method_name == 'newvalue' + next unless parameters.count >= 1 + object.add(node_as_string(parameters[0]) || parameters[0].source) + elsif method_name == 'newvalues' + parameters.each do |p| + object.add(node_as_string(p) || p.source) + end + elsif method_name == 'aliasvalue' + next unless parameters.count >= 2 + object.alias(node_as_string(parameters[0]) || parameters[0].source, node_as_string(parameters[1]) || parameters[1].source) + elsif method_name == 'defaultto' + next unless parameters.count >= 1 + object.default = node_as_string(parameters[0]) || parameters[0].source + elsif method_name == 'isnamevar' + object.isnamevar = true + elsif method_name == 'defaultvalues' && object.name == 'ensure' + object.add('present') + object.add('absent') + object.default = 'present' + end + end + if object.is_a? PuppetStrings::Yard::CodeObjects::Type::Parameter + # Process the options for parameter base types + parameters = node.parameters(false) + if parameters.count >= 2 + parameters[1].each do |kvp| + next unless kvp.count == 2 + next unless node_as_string(kvp[0]) == 'parent' + if kvp[1].source == 'Puppet::Parameter::Boolean' + object.add('true') unless object.values.include? 'true' + object.add('false') unless object.values.include? 'false' + object.add('yes') unless object.values.include? 'yes' + object.add('no') unless object.values.include? 'no' + end + break + end + end + end + end + + def set_default_namevar(object) + return unless object.properties || object.parameters + default = nil + if object.properties + object.properties.each do |property| + return nil if property.isnamevar + default = property if property.name == 'name' + end + end + if object.parameters + object.parameters.each do |parameter| + return nil if parameter.isnamevar + default ||= parameter if parameter.name == 'name' + end + end + default.isnamevar = true if default + end +end diff --git a/lib/puppet-strings/yard/tags.rb b/lib/puppet-strings/yard/tags.rb index 8bd1d4b..ed0ffa9 100644 --- a/lib/puppet-strings/yard/tags.rb +++ b/lib/puppet-strings/yard/tags.rb @@ -1,3 +1,5 @@ # The module for custom YARD tags. module PuppetStrings::Yard::Tags + require 'puppet-strings/yard/tags/parameter_directive' + require 'puppet-strings/yard/tags/property_directive' end diff --git a/lib/puppet-strings/yard/tags/parameter_directive.rb b/lib/puppet-strings/yard/tags/parameter_directive.rb new file mode 100644 index 0000000..9cf68f9 --- /dev/null +++ b/lib/puppet-strings/yard/tags/parameter_directive.rb @@ -0,0 +1,24 @@ +require 'puppet-strings/yard/code_objects' + +# Implements a parameter directive (e.g. #@!puppet.type.param) for documenting Puppet resource types. +class PuppetStrings::Yard::Tags::ParameterDirective < YARD::Tags::Directive + # Called to invoke the directive. + # @return [void] + def call + return unless object && object.respond_to?(:add_parameter) + # Add a parameter to the resource + parameter = PuppetStrings::Yard::CodeObjects::Type::Parameter.new(tag.name, tag.text) + if tag.types + tag.types.each do |value| + parameter.add(value) + end + end + object.add_parameter parameter + end + + # Registers the directive with YARD. + # @return [void] + def self.register! + YARD::Tags::Library.define_directive('puppet.type.param', :with_types_and_name, self) + end +end diff --git a/lib/puppet-strings/yard/tags/property_directive.rb b/lib/puppet-strings/yard/tags/property_directive.rb new file mode 100644 index 0000000..a1c1e00 --- /dev/null +++ b/lib/puppet-strings/yard/tags/property_directive.rb @@ -0,0 +1,24 @@ +require 'puppet-strings/yard/code_objects' + +# Implements a parameter directive (e.g. #@!puppet.type.property) for documenting Puppet resource types. +class PuppetStrings::Yard::Tags::PropertyDirective < YARD::Tags::Directive + # Called to invoke the directive. + # @return [void] + def call + return unless object && object.respond_to?(:add_property) + # Add a property to the resource + property = PuppetStrings::Yard::CodeObjects::Type::Property.new(tag.name, tag.text) + if tag.types + tag.types.each do |value| + property.add(value) + end + end + object.add_property property + end + + # Registers the directive with YARD. + # @return [void] + def self.register! + YARD::Tags::Library.define_directive('puppet.type.property', :with_types_and_name, self) + end +end diff --git a/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_type.erb b/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_type.erb new file mode 100644 index 0000000..095188f --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_type.erb @@ -0,0 +1,9 @@ +<% even = false %> +<% @items.each do |item| %> +
  • +
    + <%= linkify item, h(item.name(true)) %> +
    +
  • + <% even = !even %> +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb b/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb index 70f4b3f..86b3e10 100644 --- a/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb @@ -16,6 +16,15 @@ def generate_puppet_defined_type_list generate_list_contents end +# Generates the searchable Puppet resource type list. +# @return [void] +def generate_puppet_type_list + @items = Registry.all(:puppet_type).sort_by {|t| t.name.to_s } + @list_title = 'Resource Type List' + @list_type = 'puppet_type' + generate_list_contents +end + # Generates the searchable Ruby method list. # @return [void] def generate_method_list diff --git a/lib/puppet-strings/yard/templates/default/layout/html/setup.rb b/lib/puppet-strings/yard/templates/default/layout/html/setup.rb index 656e655..11ea423 100644 --- a/lib/puppet-strings/yard/templates/default/layout/html/setup.rb +++ b/lib/puppet-strings/yard/templates/default/layout/html/setup.rb @@ -4,7 +4,7 @@ def init case object when '_index.html' @page_title = options.title - sections :layout, [:index, [:listing, [:classes, :defined_types, :files, :objects]]] + sections :layout, [:index, [:listing, [:classes, :defined_types, :types, :files, :objects]]] else super end @@ -34,6 +34,10 @@ def layout @nav_url = url_for_list('puppet_defined_type') @page_title = "Defined Type: #{object.name}" @path = object.path + when PuppetStrings::Yard::CodeObjects::Type + @nav_url = url_for_list('puppet_type') + @page_title = "Resource Type: #{object.name}" + @path = object.path else @path = object.path end @@ -55,6 +59,11 @@ def create_menu_lists title: 'Defined Types', search_title: 'Defined Types', }, + { + type: 'puppet_type', + title: 'Resource Types', + search_title: 'Resource Types' + }, { type: 'class', title: 'Ruby Classes', @@ -112,6 +121,14 @@ def defined_types erb(:objects) end +# Renders the types section. +# @return [String] Returns the rendered section. +def types + @title = 'Resource Type Listing A-Z' + @objects_by_letter = objects_by_letter(:puppet_type) + erb(:objects) +end + # Renders the objects section. # @return [String] Returns the rendered section. def objects diff --git a/lib/puppet-strings/yard/templates/default/puppet_type/html/box_info.erb b/lib/puppet-strings/yard/templates/default/puppet_type/html/box_info.erb new file mode 100644 index 0000000..0276e3f --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_type/html/box_info.erb @@ -0,0 +1,20 @@ +
    +
    +
    Defined in:
    +
    + <%= object.file %><% if object.files.size > 1 %>,
    + <%= object.files[1..-1].map {|f| f.first }.join(",
    ") %>
    + <% end %> + + + <% if @providers && !@providers.empty? %> +
    +
    Providers:
    +
    + <% @providers.each do |provider| %> + <%= linkify(provider, provider.name.to_s) %>
    + <% end %> +
    +
    + <% end %> + diff --git a/lib/puppet-strings/yard/templates/default/puppet_type/html/features.erb b/lib/puppet-strings/yard/templates/default/puppet_type/html/features.erb new file mode 100644 index 0000000..1cba5c5 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_type/html/features.erb @@ -0,0 +1,13 @@ +<% if object.features && !object.features.empty? %> +
    +

    Features

    + +
    +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_type/html/header.erb b/lib/puppet-strings/yard/templates/default/puppet_type/html/header.erb new file mode 100644 index 0000000..3f79aeb --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_type/html/header.erb @@ -0,0 +1 @@ +

    Resource Type: <%= object.name %>

    diff --git a/lib/puppet-strings/yard/templates/default/puppet_type/html/overview.erb b/lib/puppet-strings/yard/templates/default/puppet_type/html/overview.erb new file mode 100644 index 0000000..a5b527a --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_type/html/overview.erb @@ -0,0 +1,6 @@ +

    Overview

    +
    +
    + <%= htmlify(object.docstring) %> +
    +
    diff --git a/lib/puppet-strings/yard/templates/default/puppet_type/html/parameters.erb b/lib/puppet-strings/yard/templates/default/puppet_type/html/parameters.erb new file mode 100644 index 0000000..2e75754 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_type/html/parameters.erb @@ -0,0 +1,35 @@ +<% if @parameters && !@parameters.empty? %> +
    +

    <%= @tag_title %>

    + +
    +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_type/html/setup.rb b/lib/puppet-strings/yard/templates/default/puppet_type/html/setup.rb new file mode 100644 index 0000000..35c0b24 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_type/html/setup.rb @@ -0,0 +1,32 @@ +# Initializes the template. +# @return [void] +def init + sections :header, :box_info, :overview, T('tags'), :properties, :parameters, :features +end + +# Renders the box_info section. +# @return [String] Returns the rendered section. +def box_info + @providers = [] + erb(:box_info) +end + +# Renders the properties section. +# @return [String] Returns the rendered section. +def properties + # Properties are the same thing as parameters (from the documentation standpoint), + # so reuse the same template but with a different title and data source. + @parameters = object.properties || [] + @parameters.sort_by! { |p| p.name } + @tag_title = 'Properties' + erb(:parameters) +end + +# Renders the parameters section. +# @return [String] Returns the rendered section. +def parameters + @parameters = object.parameters || [] + @parameters.sort_by! { |p| p.name } + @tag_title = 'Parameters' + erb(:parameters) +end