diff --git a/lib/puppet/face/strings.rb b/lib/puppet/face/strings.rb index 01603aa..3ae536d 100644 --- a/lib/puppet/face/strings.rb +++ b/lib/puppet/face/strings.rb @@ -1,4 +1,5 @@ require 'puppet/face' +require 'puppet_x/puppetlabs/strings/yard/tags/directives' Puppet::Face.define(:strings, '0.0.1') do summary "Generate Puppet documentation with YARD." @@ -55,6 +56,9 @@ Puppet::Face.define(:strings, '0.0.1') do # all over the terminal. This should definitely not be in real code, but # it's very handy for debugging with pry #class YARD::Logger; def progress(*args); end; end + YARD::Tags::Library.define_directive("puppet.type.param", + :with_types_and_name, + PuppetX::PuppetLabs::Strings::YARD::Tags::PuppetTypeParameterDirective) yardoc_actions.generate_documentation(*yard_args) diff --git a/lib/puppet_x/puppetlabs/strings.rb b/lib/puppet_x/puppetlabs/strings.rb index 2bf4c16..727bd22 100644 --- a/lib/puppet_x/puppetlabs/strings.rb +++ b/lib/puppet_x/puppetlabs/strings.rb @@ -21,12 +21,18 @@ module PuppetX::PuppetLabs require 'puppet_x/puppetlabs/strings/yard/monkey_patches' require 'puppet_x/puppetlabs/strings/yard/parser' + module Tags + require 'puppet_x/puppetlabs/strings/yard/tags/directives' + end + # This submodule contains code objects which are used to represent relevant # aspects of puppet code in YARD's Registry module CodeObjects require 'puppet_x/puppetlabs/strings/yard/code_objects/puppet_namespace_object' require 'puppet_x/puppetlabs/strings/yard/code_objects/defined_type_object' require 'puppet_x/puppetlabs/strings/yard/code_objects/host_class_object' + require 'puppet_x/puppetlabs/strings/yard/code_objects/type_object' + require 'puppet_x/puppetlabs/strings/yard/code_objects/provider_object' end # This submodule contains handlers which are used to extract relevant data about @@ -38,6 +44,8 @@ module PuppetX::PuppetLabs require 'puppet_x/puppetlabs/strings/yard/handlers/host_class_handler' require 'puppet_x/puppetlabs/strings/yard/handlers/puppet_3x_function_handler' require 'puppet_x/puppetlabs/strings/yard/handlers/puppet_4x_function_handler' + require 'puppet_x/puppetlabs/strings/yard/handlers/type_handler' + require 'puppet_x/puppetlabs/strings/yard/handlers/provider_handler' end ::YARD::Parser::SourceParser.register_parser_type(:puppet, diff --git a/lib/puppet_x/puppetlabs/strings/yard/code_objects/provider_object.rb b/lib/puppet_x/puppetlabs/strings/yard/code_objects/provider_object.rb new file mode 100644 index 0000000..95f8d54 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/code_objects/provider_object.rb @@ -0,0 +1,5 @@ +class PuppetX::PuppetLabs::Strings::YARD::CodeObjects::ProviderObject < PuppetX::PuppetLabs::Strings::YARD::CodeObjects::PuppetNamespaceObject + # A list of parameters attached to this class. + # @return [Array] + attr_accessor :parameters +end diff --git a/lib/puppet_x/puppetlabs/strings/yard/code_objects/type_object.rb b/lib/puppet_x/puppetlabs/strings/yard/code_objects/type_object.rb new file mode 100644 index 0000000..0ca38b1 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/code_objects/type_object.rb @@ -0,0 +1,5 @@ +class PuppetX::PuppetLabs::Strings::YARD::CodeObjects::TypeObject < PuppetX::PuppetLabs::Strings::YARD::CodeObjects::PuppetNamespaceObject + # A list of parameters attached to this class. + # @return [Array] + attr_accessor :parameters +end diff --git a/lib/puppet_x/puppetlabs/strings/yard/handlers/heredoc_helper.rb b/lib/puppet_x/puppetlabs/strings/yard/handlers/heredoc_helper.rb new file mode 100644 index 0000000..73fe2aa --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/handlers/heredoc_helper.rb @@ -0,0 +1,80 @@ +class HereDocHelper + # NOTE: The following methods duplicate functionality from + # Puppet::Util::Reference and Puppet::Parser::Functions.functiondocs + # + # However, implementing this natively in YARD is a good test for the + # feasibility of extracting custom Ruby documentation. In the end, the + # existing approach taken by Puppet::Util::Reference may be the best due to + # the heavy use of metaprogramming in Types and Providers. + + # Extracts the Puppet function name and options hash from the parsed + # definition. + # + # @return [(String, Hash{String => String})] + def process_parameters(statement) + # Passing `false` to prameters excludes the block param from the returned + # list. + name, opts = statement.parameters(false).compact + + name = process_element(name) + + # Don't try to process options if we don't have any + if !opts.nil? + opts = opts.map do |tuple| + # Jump down into the S-Expression that represents a hashrocket, `=>`, + # and the values on either side of it. + tuple.jump(:assoc).map{|e| process_element(e)} + end + + options = Hash[opts] + else + options = {} + end + + [name, options] + end + + # Sometimes the YARD parser returns Heredoc strings that start with `<-` + # instead of `<<-`. + HEREDOC_START = /^ 2 + first = statement.children.first.first + return unless (first.source == 'Puppet::Type') || + (first.type == :var_ref && + first.source == 'Type') && + statement[2].source == 'provide' + i = statement.index { |s| YARD::Parser::Ruby::AstNode === s && s.type == :ident && s.source == 'provide' } + provider_name = statement[i+1].jump(:ident).source + type_name = statement.jump(:symbol).first.source + + obj = ProviderObject.new(:root, provider_name) + + docstring = nil + features = [] + commands = [] + confines = {} + defaults = {} + do_block = statement.jump(:do_block) + do_block.traverse do |node| + if is_a_func_call_named?('desc', node) + 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 + elsif is_a_func_call_named?('confine', node) + node.traverse do |s| + if s.type == :assoc + k = s.first.jump(:ident).source + v = s[1].first.source + confines[k] = v + end + end + elsif is_a_func_call_named?('has_feature', node) + list = node.jump :list + if list != nil && list != node + features += list.map { |s| s.source if YARD::Parser::Ruby::AstNode === s }.compact + end + elsif is_a_func_call_named?('commands', node) + assoc = node.jump(:assoc) + if assoc && assoc != node + ident = assoc.jump(:ident) + if ident && ident != assoc + commands << ident.source + end + end + elsif is_a_func_call_named?('defaultfor', node) + node.traverse do |s| + if s.type == :assoc + k = s.first.jump(:ident).source + v = s[1].first.source + defaults[k] = v + end + end + end + end + obj.features = features + obj.commands = commands + obj.confines = confines + obj.defaults = defaults + obj.type_name = type_name + + register_docstring(obj, docstring, nil) + register obj + end + + def is_a_func_call_named?(name, node) + (node.type == :fcall || node.type == :command || node.type == :vcall) && node.children.first.source == name + end +end diff --git a/lib/puppet_x/puppetlabs/strings/yard/handlers/puppet_3x_function_handler.rb b/lib/puppet_x/puppetlabs/strings/yard/handlers/puppet_3x_function_handler.rb index 7ae9bd3..5529b44 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/handlers/puppet_3x_function_handler.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/handlers/puppet_3x_function_handler.rb @@ -1,10 +1,13 @@ +require File.join(File.dirname(__FILE__),'./heredoc_helper') + class PuppetX::PuppetLabs::Strings::YARD::Handlers::Puppet3xFunctionHandler < YARD::Handlers::Ruby::Base include PuppetX::PuppetLabs::Strings::YARD::CodeObjects handles method_call(:newfunction) process do - name, options = process_parameters + @heredoc_helper = HereDocHelper.new + name, options = @heredoc_helper.process_parameters statement obj = MethodObject.new(function_namespace, name) @@ -47,78 +50,4 @@ class PuppetX::PuppetLabs::Strings::YARD::Handlers::Puppet3xFunctionHandler < YA obj end - # NOTE: The following methods duplicate functionality from - # Puppet::Util::Reference and Puppet::Parser::Functions.functiondocs - # - # However, implementing this natively in YARD is a good test for the - # feasibility of extracting custom Ruby documentation. In the end, the - # existing approach taken by Puppet::Util::Reference may be the best due to - # the heavy use of metaprogramming in Types and Providers. - - # Extracts the Puppet function name and options hash from the parsed - # definition. - # - # @return [(String, Hash{String => String})] - def process_parameters - # Passing `false` to prameters excludes the block param from the returned - # list. - name, opts = statement.parameters(false).compact - - name = process_element(name) - - # Don't try to process options if we don't have any - if !opts.nil? - opts = opts.map do |tuple| - # Jump down into the S-Expression that represents a hashrocket, `=>`, - # and the values on either side of it. - tuple.jump(:assoc).map{|e| process_element(e)} - end - - options = Hash[opts] - else - options = {} - end - - [name, options] - end - - # Sometimes the YARD parser returns Heredoc strings that start with `<-` - # instead of `<<-`. - HEREDOC_START = /^ 2 + first = statement.children.first + return unless (first.type == :const_path_ref and + first.source == 'Puppet::Type') or + (first.type == :var_ref and + first.source == 'Type') and + statement.children[1].source == "newtype" + + # Fetch the docstring for the types. 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 types 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 = [] + property_details = [] + features = [] + obj = TypeObject.new(:root, name) do |o| + # FIXME: This block gets yielded twice for whatever reason + parameter_details = [] + property_details = [] + o.parameters = [] + # Find the do block following the Type. + 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 is_param? node + # 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 + # types were deprecated with puppet 4 at the same time the type + # system was created. + + # Because of a ripper bug a symbol identifier is sometimes incorrectly parsed as a keyword. + # That is, the symbol `:true` will be represented as s(:symbol s(:kw, true... + param_name = node.children[1].jump(:ident) + if param_name == node.children[1] + param_name = node.children[1].jump(:kw) + end + param_name = param_name.source + o.parameters << [param_name, nil] + parameter_details << {:name => param_name, + :desc => fetch_description(node), :exists? => true, + :puppet_type => true, + :default => fetch_default(node), + :namevar => is_namevar?(node, param_name, name), + :parameter => true, + :allowed_values => get_parameter_allowed_values(node), + } + elsif is_prop? node + # Because of a ripper bug a symbol identifier is sometimes incorrectly parsed as a keyword. + # That is, the symbol `:true` will be represented as s(:symbol s(:kw, true... + prop_name = node.children[1].jump(:ident) + if prop_name == node.children[1] + prop_name = node.children[1].jump(:kw) + end + prop_name = prop_name.source + property_details << {:name => prop_name, + :desc => fetch_description(node), :exists? => true, + :default => fetch_default(node), + :puppet_type => true, + :property => true, + :allowed_values => get_property_allowed_values(node), + } + elsif is_feature? node + features << get_feature(node) + end + end + end + obj.parameter_details = parameter_details + obj.property_details = property_details + obj.features = features + + register_docstring(obj, docstring, nil) + + register obj + end + + + # See: + # https://docs.puppetlabs.com/guides/custom_types.html#namevar + # node should be a parameter + def is_namevar? node, param_name, type_name + # Option 1: + # Puppet::Type.newtype(:name) do + # ... + # newparam(:name) do + # ... + # end + if type_name == param_name + return true + end + # Option 2: + # newparam(:path, :namevar => true) do + # ... + # end + if node.children.length >= 2 + node.traverse do |s| + if s.type == :assoc and s.jump(:ident).source == 'namevar' and s.jump(:kw).source == 'true' + return true + end + end + end + # Option 3: + # newparam(:path) do + # isnamevar + # ... + # end + do_block = node.jump(:do_block).traverse do |s| + if is_a_func_call_named? 'isnamevar', s + return true + end + end + # Crazy implementations of types may just call #isnamevar directly on the object. + # We don't handle this today. + return false + end + + def is_param? node + is_a_func_call_named? 'newparam', node + end + def is_prop? node + is_a_func_call_named? 'newproperty', node + end + + def is_feature? node + is_a_func_call_named? 'feature', node + end + + def is_a_func_call_named? name, node + (node.type == :fcall or node.type == :command or node.type == :vcall) and node.children.first.source == name + end + + def get_feature node + name = node[1].jump(:ident).source + desc = node[1].jump(:tstring_content).source + methods = [] + if node[1].length == 4 and node.children[1][2].jump(:ident).source == 'methods' + arr = node[1][2].jump(:array) + if arr != node[1][2] + arr.traverse do |s| + if s.type == :ident + methods << s.source + end + end + end + end + { + :name => name, + :desc => desc, + :methods => methods != [] ? methods : nil, + } + end + + def get_parameter_allowed_values node + vals = [] + node.traverse do |s| + if is_a_func_call_named? 'newvalues', s + list = s.jump(:list) + if list != s + vals += list.map { |item| [item.source] if YARD::Parser::Ruby::AstNode === item } + end + end + end + vals.compact + end + + # Calls to newvalue only apply to properties, according to Dan & Nan's + # "Puppet Types and Providers", page 30. + def get_property_allowed_values node + vals = get_parameter_allowed_values node + node.traverse do |s| + if is_a_func_call_named? 'newvalue', s + required_features = nil + s.traverse do |ss| + if ss.type == :assoc and ss[0].source == ':required_features' + required_features = ss[1].source + end + end + list = s.jump(:list) + if list != s + vals << [list[0].source, required_features].compact + end + end + end + vals + end + + def fetch_default node + do_block = node.jump(:do_block) + do_block.traverse do |s| + if is_a_func_call_named? 'defaultto', s + return s[-1].source + end + end + nil + end + + def fetch_description(fcall) + fcall.traverse do |node| + if is_a_func_call_named? 'desc', node + 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 diff --git a/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb b/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb index abe1861..a1da734 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb @@ -6,7 +6,7 @@ require 'yard' class YARD::CLI::Yardoc def all_objects - YARD::Registry.all(:root, :module, :class, :puppetnamespace, :hostclass, :definedtype) + YARD::Registry.all(:root, :module, :class, :type, :provider, :puppetnamespace, :hostclass, :definedtype) end end @@ -16,7 +16,15 @@ class YARD::CLI::Stats end def stats_for_definedtypes - output 'Puppet Types', *type_statistics(:definedtype) + output 'Puppet Defined Types', *type_statistics(:definedtype) + end + + def stats_for_puppet_types + output 'Puppet Types', *type_statistics(:type) + end + + def stats_for_puppet_provider + output 'Puppet Providers', *type_statistics(:provider) end end diff --git a/lib/puppet_x/puppetlabs/strings/yard/tags/directives.rb b/lib/puppet_x/puppetlabs/strings/yard/tags/directives.rb new file mode 100644 index 0000000..dcb60e0 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/tags/directives.rb @@ -0,0 +1,9 @@ +require 'puppet_x/puppetlabs/strings/yard/core_ext/yard' +# Creates a new code object based on the directive +class PuppetX::PuppetLabs::Strings::YARD::Tags::PuppetTypeParameterDirective < YARD::Tags::Directive + def call + return if object.nil? + object.parameters << ([tag.text] + tag.types) + object.parameter_details << {:name => tag.name, :desc => tag.text, :exists? => true, :puppet_type => true} + end +end diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb new file mode 100644 index 0000000..582fe67 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb @@ -0,0 +1 @@ +<%= namespace_list(:namespace_types => [:provider]) %> diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_type.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_type.erb new file mode 100644 index 0000000..14748c8 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_type.erb @@ -0,0 +1 @@ +<%= namespace_list(:namespace_types => [:type]) %> diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/setup.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/setup.rb index 69fb957..2788460 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/setup.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/setup.rb @@ -27,6 +27,20 @@ def generate_puppet_plugin_list generate_list_contents end +def generate_puppet_type_list + @items = options.objects.select{|o| [:type].include? o.type} if options.objects + @list_title = "Puppet Type List" + @list_type = "puppet_type" + generate_list_contents +end + +def generate_puppet_provider_list + @items = options.objects.select{|o| [:provider].include? o.type} if options.objects + @list_title = "Puppet Provider List" + @list_type = "puppet_provider" + generate_list_contents +end + # A hacked version of class_list that can be instructed to only display certain # namespace types. This allows us to separate Puppet bits from Ruby bits. def namespace_list(opts = {}) diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/html_helper.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/html_helper.rb index 2b4c8d6..56ec2e0 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/templates/default/html_helper.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/html_helper.rb @@ -15,6 +15,30 @@ class HTMLHelper result.join end + def generate_features features, object + result = [] + + if features + features.each do |feat| + result << "
  • " + result << "#{feat[:name]} " + if feat[:desc] + result << "-

    #{feat[:desc]}

    " + end + if feat[:methods] + result << "

    Methods

    " + result << "
      " + feat[:methods].each do |method| + result << "
    • " << method << "
    • " + end + result << "
    " + end + result << "
  • " + end + end + result.join + end + # Generates the HTML to format the relevant data about parameters def generate_parameters(params, object) result = [] @@ -59,9 +83,21 @@ class HTMLHelper result << "(" << "" << possible_types.join(", ") << "" << ")" end # Give up. It can probably be anything. - elsif !param[:puppet_3_func] + elsif not (param[:puppet_3_func] or param[:puppet_type]) result << "(Unknown)" end + if param[:puppet_type] and param[:parameter] + result << "(Parameter) " + elsif param[:puppet_type] and param[:property] + result << "(Property) " + end + + if param[:namevar] + result << "(Namevar) " + end + if param[:default] + result << " Default value: " << param[:default] << " " + end result << "" @@ -81,6 +117,20 @@ class HTMLHelper result << "" end + if param[:allowed_values] and param[:allowed_values] != [] + result << " Allowed Values: " + result << "" + end + result << "" end diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/layout/html/setup.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/layout/html/setup.rb index 53a1ee0..05d8aeb 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/templates/default/layout/html/setup.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/layout/html/setup.rb @@ -2,7 +2,7 @@ # @objects_by_letter prevents that. Submit a pull request. def index @objects_by_letter = {} - objects = Registry.all(:class, :module, :puppetnamespace, :hostclass, :definedtype).sort_by {|o| o.name.to_s } + objects = Registry.all(:class, :module, :type, :puppetnamespace, :hostclass, :definedtype, :provider).sort_by {|o| o.name.to_s } objects = run_verifier(objects) objects.each {|o| (@objects_by_letter[o.name.to_s[0,1].upcase] ||= []) << o } erb(:index) @@ -11,6 +11,8 @@ end def menu_lists [ {:type => 'puppet_manifest', :title => 'Puppet Manifests', :search_title => "Puppet Manifest List"}, - {:type => 'puppet_plugin', :title => 'Puppet Plugins', :search_title => "Puppet Plugin List"} + {:type => 'puppet_plugin', :title => 'Puppet Plugins', :search_title => "Puppet Plugin List"}, + {:type => 'puppet_type', :title => 'Puppet Types', :search_title => "Puppet Type List"}, + {:type => 'puppet_provider', :title => 'Puppet Providers', :search_title => "Puppet Provider List"}, ] + super end diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.erb new file mode 100644 index 0000000..6e9bf56 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.erb @@ -0,0 +1,8 @@ +

    Commands Summary

    +
    +
      + <% @command_details.each do |command| %> +
    • <%= command %>
    • + <% end %> +
    +
    diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.rb new file mode 100644 index 0000000..3d57a79 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.rb @@ -0,0 +1,12 @@ +

    Parameter Summary

    +
    +
      + <%= @html_helper.generate_parameters(@param_details, object) %> +
    +
    +

    Features

    +
    +
      + <%= @html_helper.generate_features(@feature_details, object) %> +
    +
    diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/confine_details.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/confine_details.erb new file mode 100644 index 0000000..fa7b88d --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/confine_details.erb @@ -0,0 +1,10 @@ +

    Confines

    +<% if @confine_details != {} %> +
    +
      + <% @confine_details.each_pair do |key, value| %> +
    • <%= key %> - <%= value %>
    • + <% end %> +
    +
    +<% end %> diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/default_details.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/default_details.erb new file mode 100644 index 0000000..cc71ded --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/default_details.erb @@ -0,0 +1,10 @@ +

    Defaults

    +<% if @default_details != {} %> +
    +
      + <% @default_details.each_pair do |key, value| %> +
    • <%= key %> - <%= value %>
    • + <% end %> +
    +
    +<% end %> diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/docstring.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/docstring.erb new file mode 100644 index 0000000..da20e29 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/docstring.erb @@ -0,0 +1,34 @@ +
    +
    +

    <%= htmlify(Puppet::Util::Docs::scrub(@class_details[:desc])) %>

    +
    +
    +
    + <% if @class_details[:examples] != {}%> +
    +

    Examples:

    + <% @class_details[:examples].each do |title, text| %> +

    <%= title %>

    +
    <%= text %>
    + <% end %> +
    + <% end %> + <% if @class_details[:since] %> +

    Since:

    +
      +
    • +
      +

      <%= @class_details[:since] %>

      +
      +
    • +
    + <% end %> + <% if @class_details[:return] %> +

    Return:

    +
      +
    • + <%= @html_helper.generate_return_types(@class_details[:return][1], @class_details[:return][0]) %> +
    • +
    + <% end %> +
    diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/feature_details.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/feature_details.erb new file mode 100644 index 0000000..6ebfcff --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/feature_details.erb @@ -0,0 +1,10 @@ +

    Features

    +<% if @feature_details != [] %> +
    +
      + <% @feature_details.each do |feature| %> +
    • <%= feature %>
    • + <% end %> +
    +
    +<% end %> diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/header.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/header.erb new file mode 100644 index 0000000..74b3dba --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/header.erb @@ -0,0 +1,5 @@ +
    +

    + <%= @header_text %> +

    +
    diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/setup.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/setup.rb new file mode 100644 index 0000000..91e114a --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/setup.rb @@ -0,0 +1 @@ +include T('default/module/html') diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb new file mode 100644 index 0000000..f9cf10c --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb @@ -0,0 +1,44 @@ +include T('default/module') + +require File.join(File.dirname(__FILE__),'../html_helper') +require File.join(File.dirname(__FILE__),'../template_helper') + +def init + sections :header, :box_info, :pre_docstring, :docstring, :command_details, :confine_details, :default_details, :feature_details + + @template_helper = TemplateHelper.new + @html_helper = HTMLHelper.new +end + +def command_details + @command_details = object.commands + erb(:command_details) +end + +def confine_details + @confine_details = object.confines + erb(:confine_details) +end + +def default_details + @default_details = object.defaults + erb(:default_details) +end + +def feature_details + @feature_details = object.features + erb(:feature_details) +end + +def header + @header_text = "Puppet Provider: #{object.name}" + + erb(:header) +end + +def docstring + + @class_details = @template_helper.extract_tag_data(object) + + erb(:docstring) +end diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/docstring.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/docstring.erb new file mode 100644 index 0000000..da20e29 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/docstring.erb @@ -0,0 +1,34 @@ +
    +
    +

    <%= htmlify(Puppet::Util::Docs::scrub(@class_details[:desc])) %>

    +
    +
    +
    + <% if @class_details[:examples] != {}%> +
    +

    Examples:

    + <% @class_details[:examples].each do |title, text| %> +

    <%= title %>

    +
    <%= text %>
    + <% end %> +
    + <% end %> + <% if @class_details[:since] %> +

    Since:

    +
      +
    • +
      +

      <%= @class_details[:since] %>

      +
      +
    • +
    + <% end %> + <% if @class_details[:return] %> +

    Return:

    +
      +
    • + <%= @html_helper.generate_return_types(@class_details[:return][1], @class_details[:return][0]) %> +
    • +
    + <% end %> +
    diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/header.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/header.erb new file mode 100644 index 0000000..74b3dba --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/header.erb @@ -0,0 +1,5 @@ +
    +

    + <%= @header_text %> +

    +
    diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/parameter_details.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/parameter_details.erb new file mode 100644 index 0000000..3d57a79 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/parameter_details.erb @@ -0,0 +1,12 @@ +

    Parameter Summary

    +
    +
      + <%= @html_helper.generate_parameters(@param_details, object) %> +
    +
    +

    Features

    +
    +
      + <%= @html_helper.generate_features(@feature_details, object) %> +
    +
    diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/provider_details.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/provider_details.erb new file mode 100644 index 0000000..779af2d --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/provider_details.erb @@ -0,0 +1,10 @@ +

    Available Providers

    +<% if @providers != [] %> +
    + +
    +<% end %> diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/setup.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/setup.rb new file mode 100644 index 0000000..91e114a --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/setup.rb @@ -0,0 +1 @@ +include T('default/module/html') diff --git a/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/setup.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/setup.rb new file mode 100644 index 0000000..f9736b5 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/setup.rb @@ -0,0 +1,49 @@ +include T('default/module') + +require File.join(File.dirname(__FILE__),'../html_helper') +require File.join(File.dirname(__FILE__),'../template_helper') + +def init + sections :header, :box_info, :pre_docstring, :docstring, :parameter_details, :provider_details + + @template_helper = TemplateHelper.new + @html_helper = HTMLHelper.new +end + +def provider_details + type_name = object.name.to_s + @providers = YARD::Registry.all(:provider).select { |t| t.type_name == type_name } + + erb(:provider_details) +end + +def parameter_details + params = object.parameter_details.map { |h| h[:name] } + # Put properties and parameters in one big list where the descriptions are + # scrubbed and htmlified and the namevar is the first element, the ensure + # property the second, and the rest are alphabetized. + @param_details = (object.parameter_details + object.property_details).each { + |h| h[:desc] = htmlify(Puppet::Util::Docs::scrub(h[:desc])) if h[:desc] + }.sort { |a, b| a[:name] <=> b[:name] } + # Float ensurable and namevars to the top of the list + @param_details = @param_details.partition{|a| a[:name] == 'ensure'}.flatten + @param_details = @param_details.partition{|a| a[:namevar]}.flatten + + @feature_details = object.features + @template_helper.check_parameters_match_docs object + + erb(:parameter_details) +end + +def header + @header_text = "Puppet Type: #{object.name}" + + erb(:header) +end + +def docstring + + @class_details = @template_helper.extract_tag_data(object) + + erb(:docstring) +end diff --git a/spec/unit/puppet_x/puppetlabs/strings/yard/host_class_handler_spec.rb b/spec/unit/puppet_x/puppetlabs/strings/yard/host_class_handler_spec.rb index 20694ab..57cbb37 100644 --- a/spec/unit/puppet_x/puppetlabs/strings/yard/host_class_handler_spec.rb +++ b/spec/unit/puppet_x/puppetlabs/strings/yard/host_class_handler_spec.rb @@ -74,7 +74,9 @@ Classes: 0 ( 0 undocumented) Constants: 0 ( 0 undocumented) Methods: 0 ( 0 undocumented) Puppet Classes: 1 ( 0 undocumented) +Puppet Defined Types: 0 ( 0 undocumented) Puppet Types: 0 ( 0 undocumented) +Puppet Providers: 0 ( 0 undocumented) 100.00% documented output expected_stderr = "@param tag types do not match the code. The ident parameter is declared as types [\"Float\"] in the docstring, but the code specifies the types [Puppet::Pops::Types::PStringType] in file manifests/init.pp near line 2\n" diff --git a/spec/unit/puppet_x/puppetlabs/strings/yard/type_handler_spec.rb b/spec/unit/puppet_x/puppetlabs/strings/yard/type_handler_spec.rb new file mode 100644 index 0000000..338ce5c --- /dev/null +++ b/spec/unit/puppet_x/puppetlabs/strings/yard/type_handler_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' +require 'puppet_x/puppetlabs/strings/yard/handlers/type_handler' +require 'strings_spec/parsing' + + +describe PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetTypeHandler do + include StringsSpec::Parsing + + def the_type() + YARD::Registry.at("file") + end + + it "should have the proper docstring" do + parse <<-RUBY + Puppet::Type.newtype(:file) do + @doc = "Manages files, including their content, ownership, and perms." + newparam(:path) do + desc <<-'EOT' + The path to the file to manage. Must be fully qualified. + EOT + end + isnamevar + end + RUBY + + expect(the_type.docstring).to eq("Manages files, including their " + + "content, ownership, and perms.") + end + + it "should have the proper parameter details" do + parse <<-RUBY + Puppet::Type.newtype(:file) do + @doc = "Manages files, including their content, ownership, and perms." + newparam(:file) do + desc <<-'EOT' + The path to the file to manage. Must be fully qualified. + EOT + end + isnamevar + end + RUBY + + expect(the_type.parameter_details).to eq([{ :name => "file", + :desc => "The path to the file to manage. Must be fully qualified.", + :exists? => true, :puppet_type => true, :namevar => true, + :default => nil, + :parameter=>true, + :allowed_values=>[], + }]) + end + + it "should have the proper parameters" do + parse <<-RUBY + Puppet::Type.newtype(:file) do + @doc = "Manages files, including their content, ownership, and perms." + newparam(:path) do + desc <<-'EOT' + The path to the file to manage. Must be fully qualified. + EOT + end + isnamevar + end + RUBY + + expect(the_type.parameters).to eq([["path", nil]]) + end +end