From 676364bd172cc5cbbd67ced6b30e66a38283c474 Mon Sep 17 00:00:00 2001 From: Ian Kronquist Date: Fri, 31 Jul 2015 14:59:15 -0700 Subject: [PATCH 1/4] (PDOC-35) First pass at documenting providers Adding a provider page and menu * Add categories to html search bar * Require provider handler and object classes * Fetch provider code objects from registry * Add function to generate the provider list * Fetch providers from registry in monkey patches * Add provider templates * Add provider code object * Add provider handler * Add erb file to populate the provider list * Don't emit type information for providers in html * Add tests for provider handler Refactor heredoc: * Remove heredoc annotations * Move heredoc functions into a heredoc helper * Add heredoc helper class --- lib/puppet_x/puppetlabs/strings.rb | 2 + .../yard/code_objects/provider_object.rb | 5 + .../strings/yard/handlers/heredoc_helper.rb | 80 +++++++++++ .../strings/yard/handlers/provider_handler.rb | 128 ++++++++++++++++++ .../handlers/puppet_3x_function_handler.rb | 79 +---------- .../puppetlabs/strings/yard/monkey_patches.rb | 2 +- .../html/full_list_puppet_provider.erb | 1 + .../templates/default/fulldoc/html/setup.rb | 8 ++ .../yard/templates/default/html_helper.rb | 2 +- .../templates/default/layout/html/setup.rb | 5 +- .../default/provider/html/docstring.erb | 34 +++++ .../default/provider/html/header.erb | 5 + .../provider/html/parameter_details.erb | 6 + .../templates/default/provider/html/setup.rb | 1 + .../yard/templates/default/provider/setup.rb | 35 +++++ .../strings/yard/provider_handler_spec.rb | 63 +++++++++ 16 files changed, 377 insertions(+), 79 deletions(-) create mode 100644 lib/puppet_x/puppetlabs/strings/yard/code_objects/provider_object.rb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/handlers/heredoc_helper.rb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/docstring.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/header.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/parameter_details.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/setup.rb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb create mode 100644 spec/unit/puppet_x/puppetlabs/strings/yard/provider_handler_spec.rb diff --git a/lib/puppet_x/puppetlabs/strings.rb b/lib/puppet_x/puppetlabs/strings.rb index 2bf4c16..619bf0d 100644 --- a/lib/puppet_x/puppetlabs/strings.rb +++ b/lib/puppet_x/puppetlabs/strings.rb @@ -27,6 +27,7 @@ module PuppetX::PuppetLabs 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/provider_object' end # This submodule contains handlers which are used to extract relevant data about @@ -38,6 +39,7 @@ 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/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/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 + return unless first.type == :const_path_ref and + first.children.length == 2 and + first.children.map { |o| o.source } == ["Puppet", "Type"] and + statement.children[1].source == "newtype" + + # Fetch the docstring for the provider. The docstring is the string literal + # assigned to the @doc parameter or absent, like this: + # @doc "docstring goes here" + # We assume that docstrings nodes have the following shape in the source + # code: + # ... + # s(s(:assign, + # s(:..., s(:ivar, "@doc", ...), ...), + # s(:..., + # s(:..., + # s(:tstring_content, + # "Manages files, including their content, etc.", ... + # Initialize the docstring to nil, the default value if we don't find + # anything + docstring = nil + # Walk the tree searching for assignments + statement.traverse do |node| + if node.type == :assign + # Once we have found and assignment, jump to the first ivar + # (the l-value) + # If we can't find an ivar return the node. + ivar = node.jump(:ivar) + # If we found and ivar and its source reads '@doc' then... + if ivar != node and ivar.source == '@doc' + # find the next string content + content = node.jump(:tstring_content) + # if we found the string content extract its source + if content != node + # The docstring is either the source stripped of heredoc + # annotations or the raw source. + if @heredoc_helper.is_heredoc? content.source + docstring = @heredoc_helper.process_heredoc content.source + else + docstring = content.source + end + end + # Since we found the @doc parameter (regardless of whether we + # successfully extracted its source), we're done. + break + # But if we didn't find the ivar loop around again. + else + next + end + end + end + + # The providers begin with: + # Puppet::Types.newtype(:symbol) + # Jump to the first identifier (':symbol') after the third argument + # ('(:symbol)') to the current statement + name = statement.children[2].jump(:ident).source + parameter_details = [] + obj = ProviderObject.new(:root, name) do |o| + # FIXME: This block gets yielded twice for whatever reason + parameter_details = [] + o.parameters = [] + # Find the de block following the Provider. + do_block = statement.jump(:do_block) + # traverse the do block's children searching for function calls whose + # identifier is newparam (we're calling the newparam function) + do_block.traverse do |node| + if node.type == :fcall and node.children.first.source == 'newparam' + # The first member of the parameter tuple is the parameter name. + # Find the second identifier node under the fcall tree. The first one + # is 'newparam', the second one is the function name. + # Get its source. + # The second parameter is nil because we cannot infer types for these + # functions. In fact, that's a silly thing to ask because ruby + # providers were deprecated with puppet 4 at the same time the type + # system was created. + param_name = node.children[1].jump(:ident).source + o.parameters << [param_name, nil] + parameter_details << {:name => param_name, + :desc => fetch_description(node), :exists? => true, + :provider => true} + end + end + end + obj.parameter_details = parameter_details + + register_docstring(obj, docstring, nil) + + register obj + end + + def fetch_description(fcall) + fcall.traverse do |node| + if node.type == :command and node.children.first.source == 'desc' + content = node.jump(:string_content) + if content != node + @heredoc_helper = HereDocHelper.new + if @heredoc_helper.is_heredoc? content.source + docstring = @heredoc_helper.process_heredoc content.source + else + docstring = content.source + end + return docstring + end + end + end + return nil + end +end 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 = /^ [:provider]) %> 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..c6dd7c8 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,14 @@ def generate_puppet_plugin_list 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..d633fa9 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 @@ -59,7 +59,7 @@ 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[:provider]) result << "(Unknown)" 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..62a6369 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, :provider, :puppetnamespace, :hostclass, :definedtype).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,7 @@ 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_provider', :title => 'Puppet Providers', :search_title => "Puppet Providers List"} ] + super 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..900985f --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/docstring.erb @@ -0,0 +1,34 @@ +
+
+

<%= htmlify(@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/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/parameter_details.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/parameter_details.erb new file mode 100644 index 0000000..d03666e --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/parameter_details.erb @@ -0,0 +1,6 @@ +

Parameter Summary

+
+
    + <%= @html_helper.generate_parameters(@param_details, object) %> +
+
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..0b249f3 --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb @@ -0,0 +1,35 @@ +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 + + @template_helper = TemplateHelper.new + @html_helper = HTMLHelper.new +end + +def parameter_details + + params = object.parameter_details.map { |h| h[:name] } + + @param_details = [] + @param_details = object.parameter_details + @template_helper.check_parameters_match_docs object + + erb(:parameter_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/spec/unit/puppet_x/puppetlabs/strings/yard/provider_handler_spec.rb b/spec/unit/puppet_x/puppetlabs/strings/yard/provider_handler_spec.rb new file mode 100644 index 0000000..16f0487 --- /dev/null +++ b/spec/unit/puppet_x/puppetlabs/strings/yard/provider_handler_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' +require 'puppet_x/puppetlabs/strings/yard/handlers/provider_handler' +require 'strings_spec/parsing' + + +describe PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler do + include StringsSpec::Parsing + + def the_provider() + 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_provider.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(:path) do + desc <<-'EOT' + The path to the file to manage. Must be fully qualified. + EOT + end + isnamevar + end + RUBY + + expect(the_provider.parameter_details).to eq([{ :name => "path", + :desc => "The path to the file to manage. Must be fully qualified.", + :exists? => true, :provider => true, }]) + 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_provider.parameters).to eq([["path", nil]]) + end +end From 798cd5b8162ff1ec2c837c1b5fa3cfd86491753e Mon Sep 17 00:00:00 2001 From: Ian Kronquist Date: Mon, 10 Aug 2015 10:16:26 -0700 Subject: [PATCH 2/4] (PDOC-35) Add a directive for puppet parameters * Print yard documentation statistics for providers * Define a directive which takes a type and a name --- lib/puppet/face/strings.rb | 4 ++++ lib/puppet_x/puppetlabs/strings.rb | 4 ++++ lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb | 4 ++++ lib/puppet_x/puppetlabs/strings/yard/tags/directives.rb | 9 +++++++++ .../strings/yard/templates/default/provider/setup.rb | 5 +---- 5 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 lib/puppet_x/puppetlabs/strings/yard/tags/directives.rb diff --git a/lib/puppet/face/strings.rb b/lib/puppet/face/strings.rb index 01603aa..d9f9661 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.provider.param", + :with_types_and_name, + PuppetX::PuppetLabs::Strings::YARD::Tags::PuppetProviderParameterDirective) yardoc_actions.generate_documentation(*yard_args) diff --git a/lib/puppet_x/puppetlabs/strings.rb b/lib/puppet_x/puppetlabs/strings.rb index 619bf0d..e857dc5 100644 --- a/lib/puppet_x/puppetlabs/strings.rb +++ b/lib/puppet_x/puppetlabs/strings.rb @@ -21,6 +21,10 @@ 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 diff --git a/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb b/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb index 6dd5903..781dca6 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb @@ -18,6 +18,10 @@ class YARD::CLI::Stats def stats_for_definedtypes output 'Puppet Types', *type_statistics(:definedtype) end + + def stats_for_providers + output 'Puppet Providers', *type_statistics(:provider) + end end class YARD::Logger 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..531af50 --- /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::PuppetProviderParameterDirective < 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, :provider => true} + end +end 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 index 0b249f3..3878183 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb @@ -11,11 +11,8 @@ def init end def parameter_details - params = object.parameter_details.map { |h| h[:name] } - - @param_details = [] - @param_details = object.parameter_details + @param_details = object.parameter_details.each { |h| h[:desc] = htmlify(h[:desc]) } @template_helper.check_parameters_match_docs object erb(:parameter_details) From 0206c78ee0de88d124219db08e011a18ad2514cf Mon Sep 17 00:00:00 2001 From: Ian Kronquist Date: Wed, 26 Aug 2015 16:26:04 -0700 Subject: [PATCH 3/4] (PDOC-35) Document types & providers separately * Dunno, I just plowed through a bunch of features * Expect puppet provider in stats output * Fetch default values for Type params and props * Detect allowed values * Add allowed values to test * htmlify scrubbed text * Add features to Type html output * Add infrastructure for types * Add methods for generating lists, etc. * Add provider code object * Add provider handler * Generate list for puppet provider dropdown * Add puppet provider template * Add provider details to puppet type template * Get description properly for types --- lib/puppet/face/strings.rb | 4 +- lib/puppet_x/puppetlabs/strings.rb | 2 + .../strings/yard/code_objects/type_object.rb | 5 + .../strings/yard/handlers/provider_handler.rb | 162 ++++------ .../strings/yard/handlers/type_handler.rb | 283 ++++++++++++++++++ .../puppetlabs/strings/yard/monkey_patches.rb | 10 +- .../strings/yard/tags/directives.rb | 4 +- .../fulldoc/html/full_list_puppet_type.erb | 1 + .../templates/default/fulldoc/html/setup.rb | 8 + .../yard/templates/default/html_helper.rb | 52 +++- .../templates/default/layout/html/setup.rb | 5 +- .../default/provider/html/command_details.erb | 8 + .../default/provider/html/command_details.rb | 12 + .../default/provider/html/confine_details.erb | 10 + .../default/provider/html/default_details.erb | 10 + .../default/provider/html/docstring.erb | 2 +- .../default/provider/html/feature_details.erb | 10 + .../yard/templates/default/provider/setup.rb | 24 +- .../templates/default/type/html/docstring.erb | 34 +++ .../templates/default/type/html/header.erb | 5 + .../html/parameter_details.erb | 6 + .../default/type/html/provider_details.erb | 10 + .../yard/templates/default/type/html/setup.rb | 1 + .../yard/templates/default/type/setup.rb | 51 ++++ .../strings/yard/host_class_handler_spec.rb | 2 + ...r_handler_spec.rb => type_handler_spec.rb} | 20 +- 26 files changed, 618 insertions(+), 123 deletions(-) create mode 100644 lib/puppet_x/puppetlabs/strings/yard/code_objects/type_object.rb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/handlers/type_handler.rb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_type.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.rb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/confine_details.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/default_details.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/feature_details.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/docstring.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/header.erb rename lib/puppet_x/puppetlabs/strings/yard/templates/default/{provider => type}/html/parameter_details.erb (51%) create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/provider_details.erb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/setup.rb create mode 100644 lib/puppet_x/puppetlabs/strings/yard/templates/default/type/setup.rb rename spec/unit/puppet_x/puppetlabs/strings/yard/{provider_handler_spec.rb => type_handler_spec.rb} (71%) diff --git a/lib/puppet/face/strings.rb b/lib/puppet/face/strings.rb index d9f9661..3ae536d 100644 --- a/lib/puppet/face/strings.rb +++ b/lib/puppet/face/strings.rb @@ -56,9 +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.provider.param", + YARD::Tags::Library.define_directive("puppet.type.param", :with_types_and_name, - PuppetX::PuppetLabs::Strings::YARD::Tags::PuppetProviderParameterDirective) + 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 e857dc5..727bd22 100644 --- a/lib/puppet_x/puppetlabs/strings.rb +++ b/lib/puppet_x/puppetlabs/strings.rb @@ -31,6 +31,7 @@ module PuppetX::PuppetLabs 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 @@ -43,6 +44,7 @@ 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 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/provider_handler.rb b/lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb index e48453b..ec118be 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb @@ -4,11 +4,11 @@ class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler < YARD::Handlers::Ruby::Base include PuppetX::PuppetLabs::Strings::YARD::CodeObjects - handles :call + handles :command_call, :call process do @heredoc_helper = HereDocHelper.new - # Puppet providers always begin with: + # Puppet types always begin with: # Puppet::Types.newtype... # Therefore, we match the corresponding trees which look like this: # s(:call, @@ -17,112 +17,78 @@ class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler < YARD # s(:const, "Type", ...), # You think this is ugly? It's better than the alternative. return unless statement.children.length > 2 - first = statement.children.first - return unless first.type == :const_path_ref and - first.children.length == 2 and - first.children.map { |o| o.source } == ["Puppet", "Type"] and - statement.children[1].source == "newtype" + first = statement.children.first.first + return unless (first.source == 'Puppet::Type') or + (first.type == :var_ref and + first.source == 'Type') and + statement[2].source == 'provide' + i = statement.index { |s| YARD::Parser::Ruby::AstNode === s and s.type == :ident and s.source == 'provide' } + provider_name = statement[i+1].jump(:ident).source + type_name = statement.jump(:symbol).first.source + + obj = ProviderObject.new(:root, provider_name) - # Fetch the docstring for the provider. The docstring is the string literal - # assigned to the @doc parameter or absent, like this: - # @doc "docstring goes here" - # We assume that docstrings nodes have the following shape in the source - # code: - # ... - # s(s(:assign, - # s(:..., s(:ivar, "@doc", ...), ...), - # s(:..., - # s(:..., - # s(:tstring_content, - # "Manages files, including their content, etc.", ... - # Initialize the docstring to nil, the default value if we don't find - # anything docstring = nil - # Walk the tree searching for assignments - statement.traverse do |node| - if node.type == :assign - # Once we have found and assignment, jump to the first ivar - # (the l-value) - # If we can't find an ivar return the node. - ivar = node.jump(:ivar) - # If we found and ivar and its source reads '@doc' then... - if ivar != node and ivar.source == '@doc' - # find the next string content - content = node.jump(:tstring_content) - # if we found the string content extract its source - if content != node - # The docstring is either the source stripped of heredoc - # annotations or the raw source. - if @heredoc_helper.is_heredoc? content.source - docstring = @heredoc_helper.process_heredoc content.source - else - docstring = content.source - end - end - # Since we found the @doc parameter (regardless of whether we - # successfully extracted its source), we're done. - break - # But if we didn't find the ivar loop around again. - else - next - end - end - end - - # The providers begin with: - # Puppet::Types.newtype(:symbol) - # Jump to the first identifier (':symbol') after the third argument - # ('(:symbol)') to the current statement - name = statement.children[2].jump(:ident).source - parameter_details = [] - obj = ProviderObject.new(:root, name) do |o| - # FIXME: This block gets yielded twice for whatever reason - parameter_details = [] - o.parameters = [] - # Find the de block following the Provider. - do_block = statement.jump(:do_block) - # traverse the do block's children searching for function calls whose - # identifier is newparam (we're calling the newparam function) - do_block.traverse do |node| - if node.type == :fcall and node.children.first.source == 'newparam' - # The first member of the parameter tuple is the parameter name. - # Find the second identifier node under the fcall tree. The first one - # is 'newparam', the second one is the function name. - # Get its source. - # The second parameter is nil because we cannot infer types for these - # functions. In fact, that's a silly thing to ask because ruby - # providers were deprecated with puppet 4 at the same time the type - # system was created. - param_name = node.children[1].jump(:ident).source - o.parameters << [param_name, nil] - parameter_details << {:name => param_name, - :desc => fetch_description(node), :exists? => true, - :provider => true} - end - end - end - obj.parameter_details = parameter_details - - register_docstring(obj, docstring, nil) - - register obj - end - - def fetch_description(fcall) - fcall.traverse do |node| - if node.type == :command and node.children.first.source == 'desc' - content = node.jump(:string_content) + 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 - @heredoc_helper = HereDocHelper.new + # 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 - return docstring + 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 and 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 and assoc != node + ident = assoc.jump(:ident) + if ident and 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 - return nil + 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 or node.type == :command or node.type == :vcall) and node.children.first.source == name end end diff --git a/lib/puppet_x/puppetlabs/strings/yard/handlers/type_handler.rb b/lib/puppet_x/puppetlabs/strings/yard/handlers/type_handler.rb new file mode 100644 index 0000000..d146e6f --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/handlers/type_handler.rb @@ -0,0 +1,283 @@ +# Handles `dispatch` calls within a future parser function declaration. For +# now, it just treats any docstring as an `@overlaod` tag and attaches the +# overload to the parent function. +class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetTypeHandler < YARD::Handlers::Ruby::Base + include PuppetX::PuppetLabs::Strings::YARD::CodeObjects + + handles :call + + process do + @heredoc_helper = HereDocHelper.new + # Puppet types always begin with: + # Puppet::Types.newtype... + # Therefore, we match the corresponding trees which look like this: + # s(:call, + # s(:const_path_ref, + # s(:var_ref, s(:const, "Puppet", ...), ...), + # s(:const, "Type", ...), + # You think this is ugly? It's better than the alternative. + return unless statement.children.length > 2 + first = statement.children.first + return unless (first.type == :const_path_ref and + first.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 781dca6..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, :provider, :puppetnamespace, :hostclass, :definedtype) + YARD::Registry.all(:root, :module, :class, :type, :provider, :puppetnamespace, :hostclass, :definedtype) end end @@ -16,10 +16,14 @@ 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_providers + 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 index 531af50..dcb60e0 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/tags/directives.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/tags/directives.rb @@ -1,9 +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::PuppetProviderParameterDirective < YARD::Tags::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, :provider => true} + 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_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 c6dd7c8..285ef9a 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,13 @@ 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" @@ -35,6 +42,7 @@ def generate_puppet_provider_list 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 d633fa9..7469083 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 not (param[:puppet_3_func] or param[:provider]) + 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 << "" @@ -76,6 +112,20 @@ class HTMLHelper if param[:desc] result << " -

    #{param[:desc]}

    " end + if param[:allowed_values] and param[:allowed_values] != [] + result << "

    Allowed Values:

    " + result << "
      " + param[:allowed_values].each do |value_thing| + result << "
    • " + result << "" << value_thing.first << "" + if value_thing[1] + result << " only available if " << "" << value_thing[1] << "" + end + result << "
    • " + end + result << "
    " + end + if !param[:exists?] result << "" 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 62a6369..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, :provider, :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) @@ -12,6 +12,7 @@ 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_provider', :title => 'Puppet Providers', :search_title => "Puppet Providers 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 index 900985f..da20e29 100644 --- 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 @@ -1,6 +1,6 @@
    -

    <%= htmlify(@class_details[:desc]) %>

    +

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

    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/setup.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb index 3878183..f9cf10c 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb @@ -4,18 +4,30 @@ 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 + 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 parameter_details - params = object.parameter_details.map { |h| h[:name] } - @param_details = object.parameter_details.each { |h| h[:desc] = htmlify(h[:desc]) } - @template_helper.check_parameters_match_docs object +def command_details + @command_details = object.commands + erb(:command_details) +end - erb(:parameter_details) +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 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/provider/html/parameter_details.erb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/parameter_details.erb similarity index 51% rename from lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/parameter_details.erb rename to lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/parameter_details.erb index d03666e..3d57a79 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/parameter_details.erb +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/parameter_details.erb @@ -4,3 +4,9 @@ <%= @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..ca8bd9d --- /dev/null +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/setup.rb @@ -0,0 +1,51 @@ +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] } + if ensurable = @param_details.index { |h| h[:name] == 'ensure' } + @param_details = @param_details.unshift(@param_details.delete_at(ensurable)) + end + if namevar = @param_details.index { |h| h[:namevar] } + @param_details = @param_details.unshift(@param_details.delete_at(namevar)) + end + @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/provider_handler_spec.rb b/spec/unit/puppet_x/puppetlabs/strings/yard/type_handler_spec.rb similarity index 71% rename from spec/unit/puppet_x/puppetlabs/strings/yard/provider_handler_spec.rb rename to spec/unit/puppet_x/puppetlabs/strings/yard/type_handler_spec.rb index 16f0487..338ce5c 100644 --- a/spec/unit/puppet_x/puppetlabs/strings/yard/provider_handler_spec.rb +++ b/spec/unit/puppet_x/puppetlabs/strings/yard/type_handler_spec.rb @@ -1,12 +1,12 @@ require 'spec_helper' -require 'puppet_x/puppetlabs/strings/yard/handlers/provider_handler' +require 'puppet_x/puppetlabs/strings/yard/handlers/type_handler' require 'strings_spec/parsing' -describe PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler do +describe PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetTypeHandler do include StringsSpec::Parsing - def the_provider() + def the_type() YARD::Registry.at("file") end @@ -23,7 +23,7 @@ describe PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler do end RUBY - expect(the_provider.docstring).to eq("Manages files, including their " + + expect(the_type.docstring).to eq("Manages files, including their " + "content, ownership, and perms.") end @@ -31,7 +31,7 @@ describe PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler do parse <<-RUBY Puppet::Type.newtype(:file) do @doc = "Manages files, including their content, ownership, and perms." - newparam(:path) do + newparam(:file) do desc <<-'EOT' The path to the file to manage. Must be fully qualified. EOT @@ -40,9 +40,13 @@ describe PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler do end RUBY - expect(the_provider.parameter_details).to eq([{ :name => "path", + expect(the_type.parameter_details).to eq([{ :name => "file", :desc => "The path to the file to manage. Must be fully qualified.", - :exists? => true, :provider => true, }]) + :exists? => true, :puppet_type => true, :namevar => true, + :default => nil, + :parameter=>true, + :allowed_values=>[], + }]) end it "should have the proper parameters" do @@ -58,6 +62,6 @@ describe PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler do end RUBY - expect(the_provider.parameters).to eq([["path", nil]]) + expect(the_type.parameters).to eq([["path", nil]]) end end From 1991475b4cb07b28c793c4dc7e00bfe8fdaf0ce6 Mon Sep 17 00:00:00 2001 From: Ian Kronquist Date: Wed, 2 Sep 2015 11:56:07 -0700 Subject: [PATCH 4/4] (PDOC-35) Fix formatting from Nick's suggestions * Float several namevars/ensurables to top of list * Fix minor code style issues --- .../strings/yard/handlers/provider_handler.rb | 30 +++++++++---------- .../templates/default/fulldoc/html/setup.rb | 2 -- .../yard/templates/default/html_helper.rb | 18 +++++------ .../yard/templates/default/type/setup.rb | 10 +++---- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb b/lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb index ec118be..f4654a6 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb @@ -18,11 +18,11 @@ class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler < YARD # You think this is ugly? It's better than the alternative. return unless statement.children.length > 2 first = statement.children.first.first - return unless (first.source == 'Puppet::Type') or - (first.type == :var_ref and - first.source == 'Type') and + 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 and s.type == :ident and s.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 @@ -35,19 +35,19 @@ class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler < YARD defaults = {} do_block = statement.jump(:do_block) do_block.traverse do |node| - if is_a_func_call_named? 'desc', 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 + 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 + elsif is_a_func_call_named?('confine', node) node.traverse do |s| if s.type == :assoc k = s.first.jump(:ident).source @@ -55,20 +55,20 @@ class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler < YARD confines[k] = v end end - elsif is_a_func_call_named? 'has_feature', node + elsif is_a_func_call_named?('has_feature', node) list = node.jump :list - if list != nil and list != node + 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 + elsif is_a_func_call_named?('commands', node) assoc = node.jump(:assoc) - if assoc and assoc != node + if assoc && assoc != node ident = assoc.jump(:ident) - if ident and ident != assoc + if ident && ident != assoc commands << ident.source end end - elsif is_a_func_call_named? 'defaultfor', node + elsif is_a_func_call_named?('defaultfor', node) node.traverse do |s| if s.type == :assoc k = s.first.jump(:ident).source @@ -88,7 +88,7 @@ class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler < YARD register obj 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 + 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/templates/default/fulldoc/html/setup.rb b/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/setup.rb index 285ef9a..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 @@ -41,8 +41,6 @@ def generate_puppet_provider_list 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 7469083..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 @@ -87,13 +87,13 @@ class HTMLHelper result << "(Unknown)" end if param[:puppet_type] and param[:parameter] - result << " Parameter " + result << "(Parameter) " elsif param[:puppet_type] and param[:property] - result << " Property " + result << "(Property) " end if param[:namevar] - result << " Namevar " + result << "(Namevar) " end if param[:default] result << " Default value: " << param[:default] << " " @@ -112,8 +112,13 @@ class HTMLHelper if param[:desc] result << " -

    #{param[:desc]}

    " end + + if !param[:exists?] + result << "" + end + if param[:allowed_values] and param[:allowed_values] != [] - result << "

    Allowed Values:

    " + result << " Allowed Values: " result << "
      " param[:allowed_values].each do |value_thing| result << "
    • " @@ -126,11 +131,6 @@ class HTMLHelper result << "
    " end - - if !param[:exists?] - result << "" - end - result << "" end 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 index ca8bd9d..f9736b5 100644 --- a/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/setup.rb +++ b/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/setup.rb @@ -25,12 +25,10 @@ def parameter_details @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] } - if ensurable = @param_details.index { |h| h[:name] == 'ensure' } - @param_details = @param_details.unshift(@param_details.delete_at(ensurable)) - end - if namevar = @param_details.index { |h| h[:namevar] } - @param_details = @param_details.unshift(@param_details.delete_at(namevar)) - end + # 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