From 7c3cd5463c73e36486d5ba7ada67d40c76545456 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sun, 11 Sep 2016 11:20:28 -0700 Subject: [PATCH] (PDOC-63) Implement a Puppet class YARD handler. This commit implements a YARD handler for Puppet classes and the associated code object and templates. --- lib/puppet-strings/yard.rb | 10 +- lib/puppet-strings/yard/code_objects.rb | 1 + lib/puppet-strings/yard/code_objects/base.rb | 14 +++ lib/puppet-strings/yard/code_objects/class.rb | 44 ++++++++ lib/puppet-strings/yard/code_objects/group.rb | 30 +++++ lib/puppet-strings/yard/handlers.rb | 4 + .../yard/handlers/puppet/base.rb | 46 ++++++++ .../yard/handlers/puppet/class_handler.rb | 23 ++++ .../fulldoc/html/full_list_puppet_class.erb | 9 ++ .../templates/default/fulldoc/html/setup.rb | 28 +++++ .../templates/default/layout/html/objects.erb | 31 ++++++ .../templates/default/layout/html/setup.rb | 104 ++++++++++++++++++ .../default/puppet_class/html/box_info.erb | 26 +++++ .../default/puppet_class/html/header.erb | 1 + .../default/puppet_class/html/overview.erb | 6 + .../default/puppet_class/html/setup.rb | 14 +++ .../default/puppet_class/html/source.erb | 12 ++ .../yard/templates/default/tags/setup.rb | 8 ++ 18 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 lib/puppet-strings/yard/code_objects/base.rb create mode 100644 lib/puppet-strings/yard/code_objects/class.rb create mode 100644 lib/puppet-strings/yard/code_objects/group.rb create mode 100644 lib/puppet-strings/yard/handlers/puppet/base.rb create mode 100644 lib/puppet-strings/yard/handlers/puppet/class_handler.rb create mode 100644 lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_class.erb create mode 100644 lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb create mode 100644 lib/puppet-strings/yard/templates/default/layout/html/objects.erb create mode 100644 lib/puppet-strings/yard/templates/default/layout/html/setup.rb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_class/html/box_info.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_class/html/header.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_class/html/overview.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_class/html/setup.rb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_class/html/source.erb create mode 100644 lib/puppet-strings/yard/templates/default/tags/setup.rb diff --git a/lib/puppet-strings/yard.rb b/lib/puppet-strings/yard.rb index 90d0be0..2a03364 100644 --- a/lib/puppet-strings/yard.rb +++ b/lib/puppet-strings/yard.rb @@ -15,6 +15,9 @@ module PuppetStrings::Yard # Register the Puppet parser YARD::Parser::SourceParser.register_parser_type(:puppet, PuppetStrings::Yard::Parsers::Puppet::Parser, ['pp']) + + # Register our handlers + YARD::Handlers::Processor.register_handler_namespace(:puppet, PuppetStrings::Yard::Handlers::Puppet) end end @@ -25,7 +28,8 @@ class YARD::CLI::Yardoc YARD::Registry.all( :root, :module, - :class + :class, + :puppet_class, ) end end @@ -34,6 +38,10 @@ end # This is the recommended way to add custom stats. # @private class YARD::CLI::Stats + def stats_for_puppet_classes + output 'Puppet Classes', *type_statistics_all(:puppet_class) + 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 d346ef7..93a5a0f 100644 --- a/lib/puppet-strings/yard/code_objects.rb +++ b/lib/puppet-strings/yard/code_objects.rb @@ -1,3 +1,4 @@ # The module for custom YARD code objects. module PuppetStrings::Yard::CodeObjects + require 'puppet-strings/yard/code_objects/class' end diff --git a/lib/puppet-strings/yard/code_objects/base.rb b/lib/puppet-strings/yard/code_objects/base.rb new file mode 100644 index 0000000..ba9840e --- /dev/null +++ b/lib/puppet-strings/yard/code_objects/base.rb @@ -0,0 +1,14 @@ +# Implements the base code object. +class PuppetStrings::Yard::CodeObjects::Base < YARD::CodeObjects::NamespaceObject + # Allocates a new code object. + # @param [Array] args The arguments to initialize the code object with. + # @return Returns the code object. + def self.new(*args) + # Skip the super class' implementation because it detects :: in names and this will cause namespaces in the output we don't want + object = Object.class.instance_method(:new).bind(self).call(*args) + existing = YARD::Registry.at(object.path) + object = existing if existing && existing.class == self + yield(object) if block_given? + object + end +end diff --git a/lib/puppet-strings/yard/code_objects/class.rb b/lib/puppet-strings/yard/code_objects/class.rb new file mode 100644 index 0000000..71461c5 --- /dev/null +++ b/lib/puppet-strings/yard/code_objects/class.rb @@ -0,0 +1,44 @@ +require 'puppet-strings/yard/code_objects/group' + +# Implements the group for Puppet classes. +class PuppetStrings::Yard::CodeObjects::Classes < PuppetStrings::Yard::CodeObjects::Group + # Gets the singleton instance of the group. + # @return Returns the singleton instance of the group. + def self.instance + super(:puppet_classes) + end + + # Gets the display name of the group. + # @param [Boolean] prefix whether to show a prefix. Ignored for Puppet group namespaces. + # @return [String] Returns the display name of the group. + def name(prefix = false) + 'Puppet Classes' + end +end + +# Implements the Puppet class code object. +class PuppetStrings::Yard::CodeObjects::Class < PuppetStrings::Yard::CodeObjects::Base + attr_reader :statement + attr_reader :parameters + + # Initializes a Puppet class code object. + # @param [PuppetStrings::Parsers::ClassStatement] statement The class statement that was parsed. + # @return [void] + def initialize(statement) + @statement = statement + @parameters = statement.parameters.map { |p| [p.name, p.value] } + super(PuppetStrings::Yard::CodeObjects::Classes.instance, statement.name) + end + + # Gets the type of the code object. + # @return Returns the type of the code object. + def type + :puppet_class + end + + # Gets the source of the code object. + # @return Returns the source of the code object. + def source + @statement.source + end +end diff --git a/lib/puppet-strings/yard/code_objects/group.rb b/lib/puppet-strings/yard/code_objects/group.rb new file mode 100644 index 0000000..f3becac --- /dev/null +++ b/lib/puppet-strings/yard/code_objects/group.rb @@ -0,0 +1,30 @@ +require 'puppet-strings/yard/code_objects/base' + +# Implements the base class for "groups". +# +# A group behaves like a YARD namespace object, but displays differently in the HTML output. +class PuppetStrings::Yard::CodeObjects::Group < PuppetStrings::Yard::CodeObjects::Base + # Gets the singleton instance of the group. + # @param [Symbol] key The key to lookup the group for. + # @return Returns the singleton instance of the group. + def self.instance(key) + instance = P(:root, key) + return instance unless instance.is_a?(YARD::CodeObjects::Proxy) + instance = self.new(:root, key) + instance.visibility = :hidden + P(:root).children << instance + instance + end + + # Gets the path to the group. + # @return [String] Returns the path to the group. + def path + @name.to_s + end + + # Gets the type of the group. + # @return [Symbol] Returns the type of the group. + def type + @name + end +end diff --git a/lib/puppet-strings/yard/handlers.rb b/lib/puppet-strings/yard/handlers.rb index e502343..df90be1 100644 --- a/lib/puppet-strings/yard/handlers.rb +++ b/lib/puppet-strings/yard/handlers.rb @@ -1,3 +1,7 @@ # The module for custom YARD handlers. module PuppetStrings::Yard::Handlers + # The module for custom Puppet YARD handlers. + module Puppet + require 'puppet-strings/yard/handlers/puppet/class_handler' + end end diff --git a/lib/puppet-strings/yard/handlers/puppet/base.rb b/lib/puppet-strings/yard/handlers/puppet/base.rb new file mode 100644 index 0000000..0315b3c --- /dev/null +++ b/lib/puppet-strings/yard/handlers/puppet/base.rb @@ -0,0 +1,46 @@ +# Implements the base handler for Puppet language handlers. +class PuppetStrings::Yard::Handlers::Puppet::Base < YARD::Handlers::Base + # Determine sif the handler handles the given statement. + # @param statement The statement that was parsed. + # @return [Boolean] Returns true if the statement is handled by this handler or false if not. + def self.handles?(statement) + handlers.any? {|handler| statement.is_a?(handler)} + end + + protected + # Sets the parameter tag types for the given code object. + # This also performs some validation on the parameter tags. + # @param object The code object to set the parameter tag types for. + # @return [void] + def set_parameter_types(object) + # Ensure there is an actual parameter for each parameter tag + tags = object.tags(:param) + tags.each do |tag| + next if statement.parameters.find { |p| tag.name == p.name } + log.warn "The @param tag for parameter '#{tag.name}' has no matching parameter at #{statement.file}:#{statement.line}." + end + + # Assign the types for the parameter + statement.parameters.each do |parameter| + tag = tags.find { |t| t.name == parameter.name } + + unless tag + log.warn "Missing @param tag for parameter '#{parameter.name}' near #{statement.file}:#{statement.line}." unless object.docstring.empty? + + # Add a tag with an empty docstring + object.add_tag YARD::Tags::Tag.new(:param, '', [parameter.type || 'Any'], parameter.name) + object.parameters << [parameter.name, parameter.value] + next + end + + # Warn if the parameter is typed and the tag also has a type + log.warn "The @param tag for parameter '#{parameter.name}' should not contain a type specification near #{statement.file}:#{statement.line}: ignoring in favor of parameter type information." if parameter.type && tag.types && !tag.types.empty? + + if parameter.type + tag.types = [parameter.type] + elsif !tag.types + tag.types = ['Any'] + end + end + end +end diff --git a/lib/puppet-strings/yard/handlers/puppet/class_handler.rb b/lib/puppet-strings/yard/handlers/puppet/class_handler.rb new file mode 100644 index 0000000..9415bc5 --- /dev/null +++ b/lib/puppet-strings/yard/handlers/puppet/class_handler.rb @@ -0,0 +1,23 @@ +require 'puppet-strings/yard/handlers/puppet/base' +require 'puppet-strings/yard/parsers' +require 'puppet-strings/yard/code_objects' + +# Implements the handler for Puppet classes. +class PuppetStrings::Yard::Handlers::Puppet::ClassHandler < PuppetStrings::Yard::Handlers::Puppet::Base + handles PuppetStrings::Yard::Parsers::Puppet::ClassStatement + + process do + # Register the object + object = PuppetStrings::Yard::CodeObjects::Class.new(statement) + register object + + # Log a warning if missing documentation + log.warn "Missing documentation for Puppet class '#{object.name}' at #{statement.file}:#{statement.line}." if object.docstring.empty? + + # Set the parameter types + set_parameter_types(object) + + # Mark the class as public if it doesn't already have an api tag + object.add_tag YARD::Tags::Tag.new(:api, 'public') unless object.has_tag? :api + end +end diff --git a/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_class.erb b/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_class.erb new file mode 100644 index 0000000..095188f --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_class.erb @@ -0,0 +1,9 @@ +<% even = false %> +<% @items.each do |item| %> +
  • +
    + <%= linkify item, h(item.name(true)) %> +
    +
  • + <% even = !even %> +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb b/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb new file mode 100644 index 0000000..0c58c0f --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb @@ -0,0 +1,28 @@ +# Generates the searchable Puppet class list. +# @return [void] +def generate_puppet_class_list + @items = Registry.all(:puppet_class).sort_by { |c| c.name.to_s } + @list_title = 'Puppet Class List' + @list_type = 'puppet_class' + generate_list_contents +end + +# Generates the searchable Ruby method list. +# @return [void] +def generate_method_list + @items = prune_method_listing(Registry.all(:method), false) + @items = @items.reject {|m| m.name.to_s =~ /=$/ && m.is_attribute? } + @items = @items.sort_by {|m| m.name.to_s } + @list_title = 'Ruby Method List' + @list_type = 'method' + generate_list_contents +end + +# Generate a searchable Ruby class list in the output. +# @return [void] +def generate_class_list + @items = options.objects if options.objects + @list_title = 'Ruby Class List' + @list_type = 'class' + generate_list_contents +end diff --git a/lib/puppet-strings/yard/templates/default/layout/html/objects.erb b/lib/puppet-strings/yard/templates/default/layout/html/objects.erb new file mode 100644 index 0000000..9395340 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/layout/html/objects.erb @@ -0,0 +1,31 @@ +<% unless @objects_by_letter.empty? %> +

    <%= @title %>

    + + <% i = 0 %> + + + + + +
    + <% @objects_by_letter.sort_by {|l,o| l.to_s }.each do |letter, objects| %> + <% if (i += 1) % 8 == 0 %> + + <% i = 0 %> + <% end %> +
      +
    • <%= letter %>
    • +
        + <% objects.each do |obj| %> +
      • + <%= linkify obj, obj.name %> + <% if (obj.type == :module || obj.type == :class) && !obj.namespace.root? %> + (<%= obj.namespace.path %>) + <% end %> +
      • + <% end %> +
      +
    + <% 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 new file mode 100644 index 0000000..bd5a48e --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/layout/html/setup.rb @@ -0,0 +1,104 @@ +# Initializes the template. +# @return [void] +def init + case object + when '_index.html' + @page_title = options.title + sections :layout, [:index, [:listing, [:classes, :files, :objects]]] + else + super + end +end + +# Renders the layout section. +# @return [String] Returns the rendered section. +def layout + @nav_url = url_for_list(!@file || options.index ? menu_lists.first[:type] : 'file') + + case object + when nil, String + @path = nil + when @file + @path = @file.path + when !object.is_a?(YARD::CodeObjects::NamespaceObject) + @path = object.parent.path + @nav_url = url_for_list('class') + when YARD::CodeObjects::ClassObject + @path = object.path + @nav_url = url_for_list('class') + when PuppetStrings::Yard::CodeObjects::Class + @nav_url = url_for_list('puppet_class') + @page_title = "Puppet Class: #{object.name}" + @path = object.path + else + @path = object.path + end + + erb(:layout) +end + +# Creates the dynamic menu lists. +# @return [Array] Returns the dynamic menu list. +def create_menu_lists + menu_lists = [ + { + type: 'puppet_class', + title: 'Puppet Classes', + search_title: 'Puppet Classes' + }, + { + type: 'class', + title: 'Ruby Classes', + search_title: 'Class List' + }, + { + type: 'method', + title: 'Ruby Methods', + search_title: 'Method List' + }, + ] + + menu_lists.delete_if { |e| YARD::Registry.all(e[:type].intern).empty? } + + # We must always return at least one group, so always keep the files list + menu_lists << { + type: 'file', + title: 'Files', + search_title: 'File List' + } if menu_lists.empty? || !YARD::Registry.all(:file).empty? + + menu_lists +end + +# Gets the menu lists to use. +# @return [Array + <% if object.statement.parent_class %> +
    +
    Inherits:
    +
    <%= linkify(Registry["puppet_classes::#{object.statement.parent_class}"], object.statement.parent_class.dup) %>
    +
    + <% end %> + <% if @subclasses && !@subclasses.empty? %> +
    +
    Inherited by:
    +
    + <% @subclasses.each do |subclass| %> + <%= linkify(subclass, subclass.name.to_s) %>
    + <% end %> +
    +
    + <% end %> +
    +
    Defined in:
    +
    + <%= object.file %><% if object.files.size > 1 %>,
    + <%= object.files[1..-1].map {|f| f.first }.join(",
    ") %> + <% end %> +
    +
    + diff --git a/lib/puppet-strings/yard/templates/default/puppet_class/html/header.erb b/lib/puppet-strings/yard/templates/default/puppet_class/html/header.erb new file mode 100644 index 0000000..a7e847c --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_class/html/header.erb @@ -0,0 +1 @@ +

    Puppet Class: <%= object.name %>

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

    Overview

    +
    +
    + <%= htmlify(object.docstring) %> +
    +
    diff --git a/lib/puppet-strings/yard/templates/default/puppet_class/html/setup.rb b/lib/puppet-strings/yard/templates/default/puppet_class/html/setup.rb new file mode 100644 index 0000000..2250d2f --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_class/html/setup.rb @@ -0,0 +1,14 @@ +# Initializes the template. +# @return [void] +def init + sections :header, :box_info, :overview, T('tags'), :source +end + +# Renders the box_info section. +# @return [String] Returns the rendered section. +def box_info + @subclasses = Registry.all(:puppet_class).find_all { |c| + c.statement.parent_class == object.name.to_s + } + erb(:box_info) +end diff --git a/lib/puppet-strings/yard/templates/default/puppet_class/html/source.erb b/lib/puppet-strings/yard/templates/default/puppet_class/html/source.erb new file mode 100644 index 0000000..0fd3c5e --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_class/html/source.erb @@ -0,0 +1,12 @@ +
    + + + + + +
    +
    <%= "\n\n\n" %><%= h format_lines(object) %>
    +
    +
    # File '<%= h object.file %>'<% if object.line %>, line <%= object.line %><% end %><%= "\n\n" %><%= html_syntax_highlight object.source %>
    +
    +
    diff --git a/lib/puppet-strings/yard/templates/default/tags/setup.rb b/lib/puppet-strings/yard/templates/default/tags/setup.rb new file mode 100644 index 0000000..be92cf0 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/tags/setup.rb @@ -0,0 +1,8 @@ +# Called to return parameter tags. +# @return [Array] Returns the parameter tags if the object should have parameters. +def param + tag(:param) if + object.type == :method || + object.type == :puppet_class +end +