From b1a15bd43aa04dbd0c4da715065f1c98d89f3535 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sun, 11 Sep 2016 11:47:33 -0700 Subject: [PATCH] (PDOC-63) Implement a Puppet provider YARD handler. This commit implements a YARD handler for Puppet providers and the associated code object and templates. --- lib/puppet-strings/yard.rb | 5 + lib/puppet-strings/yard/code_objects.rb | 1 + .../yard/code_objects/provider.rb | 77 ++++++++++++ lib/puppet-strings/yard/handlers.rb | 1 + .../yard/handlers/ruby/provider_handler.rb | 112 ++++++++++++++++++ .../html/full_list_puppet_provider.erb | 10 ++ .../templates/default/fulldoc/html/setup.rb | 9 ++ .../templates/default/layout/html/objects.erb | 2 + .../templates/default/layout/html/setup.rb | 19 ++- .../default/puppet_provider/html/box_info.erb | 14 +++ .../puppet_provider/html/collection.erb | 10 ++ .../default/puppet_provider/html/features.erb | 12 ++ .../default/puppet_provider/html/header.erb | 1 + .../default/puppet_provider/html/overview.erb | 6 + .../default/puppet_provider/html/setup.rb | 29 +++++ .../default/puppet_type/html/setup.rb | 2 +- 16 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 lib/puppet-strings/yard/code_objects/provider.rb create mode 100644 lib/puppet-strings/yard/handlers/ruby/provider_handler.rb create mode 100644 lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_provider/html/box_info.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_provider/html/collection.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_provider/html/features.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_provider/html/header.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_provider/html/overview.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_provider/html/setup.rb diff --git a/lib/puppet-strings/yard.rb b/lib/puppet-strings/yard.rb index 8c92acd..ce3106b 100644 --- a/lib/puppet-strings/yard.rb +++ b/lib/puppet-strings/yard.rb @@ -41,6 +41,7 @@ class YARD::CLI::Yardoc :puppet_class, :puppet_defined_type, :puppet_type, + :puppet_provider, ) end end @@ -61,6 +62,10 @@ class YARD::CLI::Stats output 'Puppet Types', *type_statistics_all(:puppet_type) end + def stats_for_puppet_providers + output 'Puppet Providers', *type_statistics_all(:puppet_provider) + end + def output(name, data, undoc = nil) # Monkey patch output to accommodate our larger header widths @total += data if data.is_a?(Integer) && undoc diff --git a/lib/puppet-strings/yard/code_objects.rb b/lib/puppet-strings/yard/code_objects.rb index dc6b9e3..d2c60db 100644 --- a/lib/puppet-strings/yard/code_objects.rb +++ b/lib/puppet-strings/yard/code_objects.rb @@ -3,4 +3,5 @@ module PuppetStrings::Yard::CodeObjects require 'puppet-strings/yard/code_objects/class' require 'puppet-strings/yard/code_objects/defined_type' require 'puppet-strings/yard/code_objects/type' + require 'puppet-strings/yard/code_objects/provider' end diff --git a/lib/puppet-strings/yard/code_objects/provider.rb b/lib/puppet-strings/yard/code_objects/provider.rb new file mode 100644 index 0000000..8a30075 --- /dev/null +++ b/lib/puppet-strings/yard/code_objects/provider.rb @@ -0,0 +1,77 @@ +require 'puppet-strings/yard/code_objects/group' + +# Implements the group for Puppet providers. +class PuppetStrings::Yard::CodeObjects::Providers < PuppetStrings::Yard::CodeObjects::Group + # Gets the singleton instance of the group. + # @param [String] type The resource type name for the provider. + # @return Returns the singleton instance of the group. + def self.instance(type) + super("puppet_providers_#{type}".intern) + end + + # Gets the display name of the group. + # @param [Boolean] prefix whether to show a prefix. Ignored for Puppet group namespaces. + # @return [String] Returns the display name of the group. + def name(prefix = false) + 'Providers' + end +end + +# Implements the Puppet provider code object. +class PuppetStrings::Yard::CodeObjects::Provider < PuppetStrings::Yard::CodeObjects::Base + attr_reader :type_name, :confines, :features, :defaults, :commands + + # Initializes a Puppet provider code object. + # @param [String] type_name The resource type name for the provider. + # @param [String] name The name of the provider.s + # @return [void] + def initialize(type_name, name) + @type_name = type_name + super(PuppetStrings::Yard::CodeObjects::Providers.instance(type_name), name) + end + + # Gets the type of the code object. + # @return Returns the type of the code object. + def type + :puppet_provider + end + + # Adds a confine to the provider. + # @param [String] key The confine's key. + # @param [String] value The confine's value. + # @return [void] + def add_confine(key, value) + return unless key && value + @confines ||= {} + @confines[key] = value + end + + # Adds a feature to the provider. + # @param [String] feature The feature to add to the provider. + # @return [void] + def add_feature(feature) + return unless feature + @features ||= [] + @features << feature + end + + # Adds a default to the provider. + # @param [String] key The default's key. + # @param [String] value The default's value. + # @return [void] + def add_default(key, value) + return unless key && value + @defaults ||= {} + @defaults[key] = value + end + + # Adds a command to the provider. + # @param [String] key The command's key. + # @param [String] value The command's value. + # @return [void] + def add_command(key, value) + return unless key && value + @commands ||= {} + @commands[key] = value + end +end diff --git a/lib/puppet-strings/yard/handlers.rb b/lib/puppet-strings/yard/handlers.rb index f3c683c..655e309 100644 --- a/lib/puppet-strings/yard/handlers.rb +++ b/lib/puppet-strings/yard/handlers.rb @@ -3,6 +3,7 @@ module PuppetStrings::Yard::Handlers # The module for custom Ruby YARD handlers. module Ruby require 'puppet-strings/yard/handlers/ruby/type_handler' + require 'puppet-strings/yard/handlers/ruby/provider_handler' end # The module for custom Puppet YARD handlers. diff --git a/lib/puppet-strings/yard/handlers/ruby/provider_handler.rb b/lib/puppet-strings/yard/handlers/ruby/provider_handler.rb new file mode 100644 index 0000000..89929c9 --- /dev/null +++ b/lib/puppet-strings/yard/handlers/ruby/provider_handler.rb @@ -0,0 +1,112 @@ +require 'puppet-strings/yard/handlers/ruby/base' +require 'puppet-strings/yard/code_objects' +require 'puppet/util/docs' + +# Implements the handler for Puppet providers written in Ruby. +class PuppetStrings::Yard::Handlers::Ruby::ProviderHandler < PuppetStrings::Yard::Handlers::Ruby::Base + namespace_only + handles method_call(:provide) + + process do + return unless statement.count >= 2 + + # Check that provide is being called on Puppet::Type.type() + type_call = statement[0] + return unless type_call.is_a?(YARD::Parser::Ruby::MethodCallNode) && type_call.count >= 3 + return unless type_call[0].source == 'Puppet::Type' + return unless type_call[2].source == 'type' + + # Extract the type name + type_call_parameters = type_call.parameters(false) + return unless type_call_parameters.count >= 1 + type_name = node_as_string(type_call_parameters.first) + raise YARD::Parser::UndocumentableError, "Could not determine the resource type name for the provider defined at #{statement.file}:#{statement.line}." unless type_name + + # Register the object + object = PuppetStrings::Yard::CodeObjects::Provider.new(type_name, get_name) + register object + + # Extract the docstring + register_provider_docstring object + + # Populate the provider data + populate_provider_data object + + # Mark the provider as public if it doesn't already have an api tag + object.add_tag YARD::Tags::Tag.new(:api, 'public') unless object.has_tag? :api + end + + private + def get_name + parameters = statement.parameters(false) + raise YARD::Parser::UndocumentableError, "Expected at least one parameter to 'provide' at #{statement.file}:#{statement.line}." if parameters.empty? + name = node_as_string(parameters.first) + raise YARD::Parser::UndocumentableError, "Expected a symbol or string literal for first parameter but found '#{parameters.first.type}' at #{statement.file}:#{statement.line}." unless name + name + end + + def register_provider_docstring(object) + # Walk the tree searching for assignments or calls to desc/doc= + statement.traverse do |child| + if child.type == :assign + ivar = child.jump(:ivar) + next unless ivar != child && ivar.source == '@doc' + docstring = node_as_string(child[1]) + log.error "Failed to parse docstring for Puppet provider '#{object.name}' (resource type '#{object.type_name}') near #{child.file}:#{child.line}." and return nil unless docstring + register_docstring(object, Puppet::Util::Docs.scrub(docstring), nil) + return nil + elsif child.is_a?(YARD::Parser::Ruby::MethodCallNode) + # Look for a call to a dispatch method with a block + next unless child.method_name && + (child.method_name.source == 'desc' || child.method_name.source == 'doc=') && + child.parameters(false).count == 1 + + docstring = node_as_string(child.parameters[0]) + log.error "Failed to parse docstring for Puppet provider '#{object.name}' (resource type '#{object.type_name}') near #{child.file}:#{child.line}." and return nil unless docstring + register_docstring(object, Puppet::Util::Docs.scrub(docstring), nil) + return nil + end + end + log.warn "Missing a description for Puppet provider '#{object.name}' (resource type '#{object.type_name}') at #{statement.file}:#{statement.line}." + end + + def populate_provider_data(object) + # Traverse the block looking for confines/defaults/commands + block = statement.block + return unless block && block.count >= 2 + block[1].children.each do |node| + next unless node.is_a?(YARD::Parser::Ruby::MethodCallNode) && node.method_name + + method_name = node.method_name.source + parameters = node.parameters(false) + + if method_name == 'confine' + # Add a confine to the object + next unless parameters.count >= 1 + parameters[0].each do |kvp| + next unless kvp.count == 2 + object.add_confine(node_as_string(kvp[0]) || kvp[0].source, node_as_string(kvp[1]) || kvp[1].source) + end + elsif method_name == 'has_feature' || method_name == 'has_features' + # Add the features to the object + parameters.each do |parameter| + object.add_feature(node_as_string(parameter) || parameter.source) + end + elsif method_name == 'defaultfor' + # Add a default to the object + next unless parameters.count >= 1 + parameters[0].each do |kvp| + next unless kvp.count == 2 + object.add_default(node_as_string(kvp[0]) || kvp[0].source, node_as_string(kvp[1]) || kvp[1].source) + end + elsif method_name == 'commands' + # Add the commands to the object + next unless parameters.count >= 1 + parameters[0].each do |kvp| + next unless kvp.count == 2 + object.add_command(node_as_string(kvp[0]) || kvp[0].source, node_as_string(kvp[1]) || kvp[1].source) + end + end + end + end +end diff --git a/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb b/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb new file mode 100644 index 0000000..ec95e02 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb @@ -0,0 +1,10 @@ +<% even = false %> +<% @items.each do |item| %> +
  • +
    + <%= linkify item, h(item.name(true)) %> + Resource type: <%=item.type_name%> +
    +
  • + <% even = !even %> +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb b/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb index 86b3e10..cdc18c5 100644 --- a/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb @@ -25,6 +25,15 @@ def generate_puppet_type_list generate_list_contents end +# Generates the searchable Puppet provider list. +# @return [void] +def generate_puppet_provider_list + @items = Registry.all(:puppet_provider).sort_by {|p| p.name.to_s } + @list_title = 'Provider List' + @list_type = 'puppet_provider' + generate_list_contents +end + # Generates the searchable Ruby method list. # @return [void] def generate_method_list diff --git a/lib/puppet-strings/yard/templates/default/layout/html/objects.erb b/lib/puppet-strings/yard/templates/default/layout/html/objects.erb index 9395340..ac5c815 100644 --- a/lib/puppet-strings/yard/templates/default/layout/html/objects.erb +++ b/lib/puppet-strings/yard/templates/default/layout/html/objects.erb @@ -19,6 +19,8 @@ <%= linkify obj, obj.name %> <% if (obj.type == :module || obj.type == :class) && !obj.namespace.root? %> (<%= obj.namespace.path %>) + <% elsif obj.type == :puppet_provider %> + (Resource type: <%= obj.type_name %>) <% end %> <% end %> diff --git a/lib/puppet-strings/yard/templates/default/layout/html/setup.rb b/lib/puppet-strings/yard/templates/default/layout/html/setup.rb index 11ea423..91a79ac 100644 --- a/lib/puppet-strings/yard/templates/default/layout/html/setup.rb +++ b/lib/puppet-strings/yard/templates/default/layout/html/setup.rb @@ -4,7 +4,7 @@ def init case object when '_index.html' @page_title = options.title - sections :layout, [:index, [:listing, [:classes, :defined_types, :types, :files, :objects]]] + sections :layout, [:index, [:listing, [:classes, :defined_types, :types, :providers, :files, :objects]]] else super end @@ -38,6 +38,10 @@ def layout @nav_url = url_for_list('puppet_type') @page_title = "Resource Type: #{object.name}" @path = object.path + when PuppetStrings::Yard::CodeObjects::Provider + @nav_url = url_for_list('puppet_provider') + @page_title = "Provider: #{object.name}" + @path = object.path else @path = object.path end @@ -64,6 +68,11 @@ def create_menu_lists title: 'Resource Types', search_title: 'Resource Types' }, + { + type: 'puppet_provider', + title: 'Providers', + search_title: 'Providers' + }, { type: 'class', title: 'Ruby Classes', @@ -129,6 +138,14 @@ def types erb(:objects) end +# Renders the providers section. +# @return [String] Returns the rendered section. +def providers + @title = 'Puppet Provider Listing A-Z' + @objects_by_letter = objects_by_letter(:puppet_provider) + erb(:objects) +end + # Renders the objects section. # @return [String] Returns the rendered section. def objects diff --git a/lib/puppet-strings/yard/templates/default/puppet_provider/html/box_info.erb b/lib/puppet-strings/yard/templates/default/puppet_provider/html/box_info.erb new file mode 100644 index 0000000..6dc586f --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_provider/html/box_info.erb @@ -0,0 +1,14 @@ +
    +
    +
    Defined in:
    +
    + <%= object.file %><% if object.files.size > 1 %>,
    + <%= object.files[1..-1].map {|f| f.first }.join(",
    ") %>
    + <% end %> + + +
    +
    Resource type:
    +
    <%= linkify(Registry["puppet_types::#{object.type_name}"], object.type_name) %>
    +
    + diff --git a/lib/puppet-strings/yard/templates/default/puppet_provider/html/collection.erb b/lib/puppet-strings/yard/templates/default/puppet_provider/html/collection.erb new file mode 100644 index 0000000..ec1bdf8 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_provider/html/collection.erb @@ -0,0 +1,10 @@ +<% if @collection && !@collection.empty? %> +
    +

    <%= @title %>

    +
      + <% @collection.each do |key, value| %> +
    • <%= key %> — <%= value %>
    • + <% end %> +
    +
    +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_provider/html/features.erb b/lib/puppet-strings/yard/templates/default/puppet_provider/html/features.erb new file mode 100644 index 0000000..fa3fa3e --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_provider/html/features.erb @@ -0,0 +1,12 @@ +<% if object.features && !object.features.empty? %> +
    +

    Features

    +
      + <% object.features.each do |feature| %> +
    • + <%= feature %> +
    • + <% end %> +
    +
    +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_provider/html/header.erb b/lib/puppet-strings/yard/templates/default/puppet_provider/html/header.erb new file mode 100644 index 0000000..005785c --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_provider/html/header.erb @@ -0,0 +1 @@ +

    Provider: <%= object.name %>

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

    Overview

    +
    +
    + <%= htmlify(object.docstring) %> +
    +
    diff --git a/lib/puppet-strings/yard/templates/default/puppet_provider/html/setup.rb b/lib/puppet-strings/yard/templates/default/puppet_provider/html/setup.rb new file mode 100644 index 0000000..57a69a4 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_provider/html/setup.rb @@ -0,0 +1,29 @@ +# Initializes the template. +# @return [void] +def init + sections :header, :box_info, :overview, T('tags'), :features, :confines, :defaults, :commands +end + +# Renders the confines section. +# @return [String] Returns the rendered section. +def confines + @title = 'Confines' + @collection = object.confines + erb(:collection) +end + +# Renders the defaults section. +# @return [String] Returns the rendered section. +def defaults + @title = 'Default Provider For' + @collection = object.defaults + erb(:collection) +end + +# Renders the commands section. +# @return [String] Returns the rendered section. +def commands + @title = 'Commands' + @collection = object.commands + erb(:collection) +end diff --git a/lib/puppet-strings/yard/templates/default/puppet_type/html/setup.rb b/lib/puppet-strings/yard/templates/default/puppet_type/html/setup.rb index 35c0b24..d0a3043 100644 --- a/lib/puppet-strings/yard/templates/default/puppet_type/html/setup.rb +++ b/lib/puppet-strings/yard/templates/default/puppet_type/html/setup.rb @@ -7,7 +7,7 @@ end # Renders the box_info section. # @return [String] Returns the rendered section. def box_info - @providers = [] + @providers = PuppetStrings::Yard::CodeObjects::Providers.instance(object.name).children erb(:box_info) end