Merge pull request #7 from hkenney/PDOC-3_refactor-prototype-code

(PDOC-3) Refactor prototype code to make it more readable
This commit is contained in:
Andrew Parker 2014-09-30 14:57:35 -07:00
commit 970b60481f
25 changed files with 539 additions and 551 deletions

View File

@ -3,6 +3,7 @@ require 'puppet/face'
Puppet::Face.define(:strings, '0.0.1') do Puppet::Face.define(:strings, '0.0.1') do
summary "Generate Puppet documentation with YARD." summary "Generate Puppet documentation with YARD."
# Ensures that the user has the needed features to use puppet strings
def check_required_features def check_required_features
unless Puppet.features.yard? unless Puppet.features.yard?
raise RuntimeError, "The 'yard' gem must be installed in order to use this face." raise RuntimeError, "The 'yard' gem must be installed in order to use this face."
@ -17,14 +18,6 @@ Puppet::Face.define(:strings, '0.0.1') do
end end
end end
# Maps things like the Puppet `--debug` flag to YARD options.
def merge_puppet_args!(yard_args)
yard_args.unshift '--debug' if Puppet[:debug]
yard_args.unshift '--backtrace' if Puppet[:trace]
yard_args
end
# A list of globs that generates the default list of module files from which # A list of globs that generates the default list of module files from which
# documentation can be extracted. # documentation can be extracted.
# #
@ -40,6 +33,9 @@ Puppet::Face.define(:strings, '0.0.1') do
when_invoked do |*args| when_invoked do |*args|
check_required_features check_required_features
require 'puppetx/puppetlabs/strings/actions'
yardoc_actions = Puppetx::PuppetLabs::Strings::Actions.new(Puppet[:debug], Puppet[:trace])
# The last element of the argument array should be the options hash. # The last element of the argument array should be the options hash.
# #
@ -52,12 +48,8 @@ Puppet::Face.define(:strings, '0.0.1') do
# For now, assume the remaining positional args are a list of manifest # For now, assume the remaining positional args are a list of manifest
# and ruby files to parse. # and ruby files to parse.
yard_args = (args.empty? ? MODULE_SOURCEFILES : args) yard_args = (args.empty? ? MODULE_SOURCEFILES : args)
merge_puppet_args!(yard_args)
require 'puppetx/puppetlabs/strings/yard/plugin' yardoc_actions.generate_documentation(*yard_args)
# Hand off to YARD for further processing.
YARD::CLI::Yardoc.run(*yard_args)
end end
end end
@ -65,59 +57,29 @@ Puppet::Face.define(:strings, '0.0.1') do
# (.yardoc directories) for Ruby Gems. Currently lacks the fine-grained # (.yardoc directories) for Ruby Gems. Currently lacks the fine-grained
# control over where these indicies are created and just dumps them in the # control over where these indicies are created and just dumps them in the
# module roots. # module roots.
action(:modules) do
summary "Generate YARD indices for a list of modules."
arguments "[module-name ...]"
when_invoked do |*args|
check_required_features
require 'puppetx/puppetlabs/strings/yard/plugin'
opts = args.pop
# NOTE: The retrun value of the `module` Face seems to have changed in
# 3.6.x. This part of the code will blow up if run under an earlier
# version of Puppet.
modules = Puppet::Face[:module, :current].list
module_list = modules[:modules_by_path].values.flatten
# TODO: Can use select! if Ruby 1.8.7 support is dropped.
module_list = module_list.select {|m| args.include? m.name} unless args.empty?
# Invoke `yardoc` with -n so that it doesn't generate any HTML output but
# does build a `.yardoc` index that other tools can generate output from.
yard_args = %w[--no-stats -n] + MODULE_SOURCEFILES
merge_puppet_args!(yard_args)
module_list.each do |m|
Dir.chdir(m.path) do
YARD::CLI::Yardoc.run(*yard_args)
# Cear the global Registry so that objects from one module don't
# bleed into the next.
YARD::Registry.clear
end
end
end
end
action(:server) do action(:server) do
summary "Serve YARD documentation for modules." summary "Serve YARD documentation for modules."
when_invoked do |*args| when_invoked do |*args|
check_required_features check_required_features
require 'puppetx/puppetlabs/strings/yard/plugin' require 'puppetx/puppetlabs/strings/actions'
server_actions = Puppetx::PuppetLabs::Strings::Actions.new(Puppet[:debug], Puppet[:trace])
opts = args.pop opts = args.pop
module_names = args
# FIXME: This is pretty inefficient as it forcibly re-generates the YARD # FIXME: This is pretty inefficient as it forcibly re-generates the YARD
# indicies each time the server is started. However, it ensures things are # indicies each time the server is started. However, it ensures things are
# generated properly. # generated properly.
module_list = Puppet::Face[:strings, :current].modules module_list = server_actions.index_documentation_for_modules(module_names, MODULE_SOURCEFILES)
module_tuples = module_list.map do |mod| module_tuples = server_actions.generate_module_tuples(module_list)
name = (mod.forge_name || mod.name).gsub('/', '-')
yard_index = File.join(mod.path, '.yardoc')
[name, yard_index] module_tuples.map! do |mod|
[mod[:name], mod[:index_path]]
end end
# The `-m` flag means a list of name/path pairs will follow. The name is # The `-m` flag means a list of name/path pairs will follow. The name is
@ -126,7 +88,8 @@ Puppet::Face.define(:strings, '0.0.1') do
yard_args = %w[-m -q] + module_tuples.flatten yard_args = %w[-m -q] + module_tuples.flatten
merge_puppet_args!(yard_args) merge_puppet_args!(yard_args)
YARD::CLI::Server.run(*yard_args) server_actions.serve_documentation(*yard_args)
end end
end end
end end

View File

@ -1,4 +0,0 @@
require 'puppet/util/feature'
# Support require_relative under Ruby 1.8.7.
Puppet.features.add(:require_relative, :libs => ['backports/1.9.1/kernel/require_relative'])

View File

@ -1,16 +1,53 @@
require 'puppet'
require 'puppetx' require 'puppetx'
require 'puppet/pops'
require 'puppet/util/docs'
require 'yard'
# Nothing to see here except forward declarations. # Nothing to see here except forward declarations.
module Puppetx::PuppetLabs module Puppetx::PuppetLabs
module Strings module Strings
# This submodule contains bits that interface with the YARD plugin system.
module YARD
end
# This submodule contains bits that operate on the Pops module produced by # This submodule contains bits that operate on the Pops module produced by
# the Future parser. # the Future parser.
module Pops module Pops
require 'puppetx/puppetlabs/strings/pops/yard_statement'
require 'puppetx/puppetlabs/strings/pops/yard_transformer'
end
# This submodule contains bits that interface with the YARD plugin system.
module YARD
require 'puppetx/puppetlabs/strings/yard/monkey_patches'
require 'puppetx/puppetlabs/strings/yard/parser'
# This submodule contains code objects which are used to represent relevant
# aspects of puppet code in YARD's Registry
module CodeObjects
require 'puppetx/puppetlabs/strings/yard/code_objects/puppet_namespace_object'
require 'puppetx/puppetlabs/strings/yard/code_objects/defined_type_object'
require 'puppetx/puppetlabs/strings/yard/code_objects/host_class_object'
end
# This submodule contains handlers which are used to extract relevant data about
# puppet code from the ASTs produced by the Ruby and Puppet parsers
module Handlers
# This utility library contains some tools for working with Puppet docstrings
require 'puppetx/puppetlabs/strings/yard/handlers/base'
require 'puppetx/puppetlabs/strings/yard/handlers/defined_type_handler'
require 'puppetx/puppetlabs/strings/yard/handlers/host_class_handler'
require 'puppetx/puppetlabs/strings/yard/handlers/puppet_3x_function_handler'
require 'puppetx/puppetlabs/strings/yard/handlers/puppet_4x_function_handler'
end
::YARD::Parser::SourceParser.register_parser_type(:puppet,
Puppetx::PuppetLabs::Strings::YARD::PuppetParser,
['pp'])
::YARD::Handlers::Processor.register_handler_namespace(:puppet,
Puppetx::PuppetLabs::Strings::YARD::Handlers)
# FIXME: Might not be the best idea to have the template code on the Ruby
# LOAD_PATH as the contents of this directory really aren't library code.
::YARD::Templates::Engine.register_template_path(
File.join(File.dirname(__FILE__), 'strings', 'yard', 'templates'))
end end
end end
end end

View File

@ -0,0 +1,92 @@
require 'puppetx/puppetlabs/strings'
class Puppetx::PuppetLabs::Strings::Actions
# Creates a new instance of the Actions class by determining
# whether or not debug and backtrace arguments should be sent
# to YARD
def initialize(puppet_debug, puppet_backtrace)
@debug = puppet_debug
@backtrace = puppet_backtrace
end
# Holds the name of a module and the file path to its YARD index
ModuleIndex = Struct.new(:name, :index_path)
# Maps things like the Puppet `--debug` flag to YARD options.
def merge_puppet_args!(yard_args)
yard_args.unshift '--debug' if @debug
yard_args.unshift '--backtrace' if @backtrace
yard_args
end
# Builds doc indices (.yardoc directories) for modules.
# Currently lacks the fine-grained control over where these
# indices are created and just dumps them in the module roots.
#
# @return [Array<Module>] the modules to be documented
#
# @param [Array<String>] module_names a list of the module source files
# @param [Array<String>] module_sourcefiles default list of module files
def index_documentation_for_modules(module_names, module_sourcefiles)
# NOTE: The return value of the `module` Face seems to have changed in
# 3.6.x. This part of the code will blow up if run under an earlier
# version of Puppet.
modules = Puppet::Face[:module, :current].list
module_list = modules[:modules_by_path].values.flatten
module_list.select! {|m| module_names.include? m.name} unless module_names.empty?
# Invoke `yardoc` with -n so that it doesn't generate any HTML output but
# does build a `.yardoc` index that other tools can generate output from.
yard_args = %w[--no-stats -n] + module_sourcefiles
merge_puppet_args!(yard_args)
module_list.each do |m|
Dir.chdir(m.path) do
YARD::CLI::Yardoc.run(*yard_args)
# Clear the global Registry so that objects from one module don't
# bleed into the next.
YARD::Registry.clear
end
end
end
# Extracts the needed information of the modules we're documenting
#
# @return [Array<ModuleIndex>] An array of representation of the modules
# to produce documentation for. Each ModuleIndex contains the module name
# and the path to its YARD index
#
# @param [Array<String>] module_list a list of the module source files
def generate_module_tuples(module_list)
module_list.map do |mod|
name = (mod.forge_name || mod.name).gsub('/', '-')
yard_index = File.join(mod.path, '.yardoc')
ModuleIndex.new(name, yard_index)
end
end
# Hands off the needed information to YARD so it may
# serve the documentation
#
# @param [Array<String>] yard_args a list of all the arguments to pass to YARD
def serve_documentation(*yard_args)
merge_puppet_args!(yard_args)
YARD::CLI::Server.run(*yard_args)
end
# Hands off the needed information to YARD so it may
# generate the documentation
#
# @param [Array<String>] yard_args a list of all the arguments to pass to YARD
def generate_documentation(*yard_args)
merge_puppet_args!(yard_args)
YARD::CLI::Yardoc.run(*yard_args)
end
end

View File

@ -1,78 +1,72 @@
require 'ostruct' require 'ostruct'
require 'puppet/pops'
require_relative '../../strings' # An adapter class that conforms a Pops model instance + adapters to the
# interface expected by YARD handlers.
#
# FIXME: Inhertiting from OpenStruct is a bit of a hack. It allows attributes
# to be declared as needed but in the long run understandibility of the code
# would be improved by having a concrete model.
class Puppetx::PuppetLabs::Strings::Pops::YARDStatement < OpenStruct
attr_reader :pops_obj, :comments
module Puppetx::PuppetLabs::Strings::Pops def initialize(pops_obj)
# An adapter class that conforms a Pops model instance + adapters to the # Initialize OpenStruct
# interface expected by YARD handlers. super({})
#
# FIXME: Inhertiting from OpenStruct is a bit of a hack. It allows attributes
# to be declared as needed but in the long run understandibility of the code
# would be improved by having a concrete model.
class YARDStatement < OpenStruct
attr_reader :pops_obj, :comments
def initialize(pops_obj) unless pops_obj.is_a? Puppet::Pops::Model::PopsObject
# Initialize OpenStruct raise ArgumentError, "A YARDStatement can only be initialized from a PopsObject. Got a: #{pops_obj.class}"
super({}) end
unless pops_obj.is_a? Puppet::Pops::Model::PopsObject @pops_obj = pops_obj
raise ArgumentError, "A YARDStatement can only be initialized from a PopsObject. Got a: #{pops_obj.class}" @pos_adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(@pops_obj)
# FIXME: Perhaps this should be a seperate adapter?
@comments = extract_comments
end
def type
pops_obj.class
end
def line
@line ||= @pos_adapter.line
end
def source
@source ||= @pos_adapter.extract_text
end
# FIXME: I don't know quite what these are supposed to do, but they show up
# quite often in the YARD handler code. Figure out whether they are
# necessary.
alias_method :show, :source
def comments_hash_flag; nil end
def comments_range; nil end
private
# TODO: This stuff should probably be part of a separate class/adapter.
COMMENT_PATTERN = /^\s*#.*\n/
def extract_comments
comments = []
program = pops_obj.eAllContainers.find {|c| c.is_a?(Puppet::Pops::Model::Program) }
# FIXME: Horribly inefficient. Multiple copies. Generator pattern would
# be much better.
source_text = program.source_text.lines.to_a
source_text.slice(0, line-1).reverse.each do |line|
if COMMENT_PATTERN.match(line)
# FIXME: The gsub trims comments, but is extremely optimistic: It
# assumes only one space separates the comment body from the
# comment character.
comments.unshift line.gsub(/^\s*#\s/, '')
else
# No comment found on this line. We must be done piecing together a
# comment block.
break
end end
@pops_obj = pops_obj
@pos_adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(@pops_obj)
# FIXME: Perhaps this should be a seperate adapter?
@comments = extract_comments
end
def type
pops_obj.class
end
def line
@line ||= @pos_adapter.line
end
def source
@source ||= @pos_adapter.extract_text
end
# FIXME: I don't know quite what these are supposed to do, but they show up
# quite often in the YARD handler code. Figure out whether they are
# necessary.
alias_method :show, :source
def comments_hash_flag; nil end
def comments_range; nil end
private
# TODO: This stuff should probably be part of a separate class/adapter.
COMMENT_PATTERN = /^\s*#.*\n/
def extract_comments
comments = []
program = pops_obj.eAllContainers.find {|c| c.is_a?(Puppet::Pops::Model::Program) }
# FIXME: Horribly inefficient. Multiple copies. Generator pattern would
# be much better.
source_text = program.source_text.lines.to_a
source_text.slice(0, line-1).reverse.each do |line|
if COMMENT_PATTERN.match(line)
# FIXME: The gsub trims comments, but is extremely optimistic: It
# assumes only one space separates the comment body from the
# comment character.
comments.unshift line.gsub(/^\s*#\s/, '')
else
# No comment found on this line. We must be done piecing together a
# comment block.
break
end
end
# Stick everything back together.
comments.join
end end
# Stick everything back together.
comments.join
end end
end end

View File

@ -1,54 +1,47 @@
require 'puppet/pops' # Loosely based on the TreeDumper classes in Pops::Model. The responsibility of
# this class is to walk a Pops::Model and output objects that can be consumed
# by YARD handlers.
#
# @note Currently, this class only extracts node, host class and type
# definitions.
class Puppetx::PuppetLabs::Strings::Pops::YARDTransformer
def initialize
@transform_visitor = Puppet::Pops::Visitor.new(self, 'transform')
end
require_relative '../../strings' def transform(o)
require_relative 'yard_statement' @transform_visitor.visit(o)
end
module Puppetx::PuppetLabs::Strings::Pops private
# Loosely based on the TreeDumper classes in Pops::Model. The responsibility of
# this class is to walk a Pops::Model and output objects that can be consumed def transform_Factory(o)
# by YARD handlers. transform(o.current)
# end
# @note Currently, this class only extracts node, host class and type
# definitions. def transform_Program(o)
class YARDTransformer o.definitions.map{|d| transform(d)}
def initialize end
@transform_visitor = Puppet::Pops::Visitor.new(self, 'transform')
# Extract comments from type definitions and class definitions. Wrap them
# into YARDStatement objects that provide an interface for YARD handlers.
def transform_NamedDefinition(o)
obj = Puppetx::PuppetLabs::Strings::Pops::YARDStatement.new(o)
obj.parameters = o.parameters.map do |p|
param_tuple = [transform(p)]
param_tuple << ( p.value.nil? ? nil : transform(p.value) )
end end
def transform(o) obj
@transform_visitor.visit(o) end
end
private # Catch-all visitor.
def transform_Positioned(o)
Puppetx::PuppetLabs::Strings::Pops::YARDStatement.new(o)
end
def transform_Factory(o) # nil in... nil out!
transform(o.current) def transform_NilClass(o)
end nil
def transform_Program(o)
o.definitions.map{|d| transform(d)}
end
# Extract comments from type definitions and class definitions. Wrap them
# into YARDStatement objects that provide an interface for YARD handlers.
def transform_NamedDefinition(o)
obj = YARDStatement.new(o)
obj.parameters = o.parameters.map do |p|
param_tuple = [transform(p)]
param_tuple << ( p.value.nil? ? nil : transform(p.value) )
end
obj
end
# Catch-all visitor.
def transform_Positioned(o)
YARDStatement.new(o)
end
# nil in... nil out!
def transform_NilClass(o)
nil
end
end end
end end

View File

@ -1,3 +0,0 @@
require_relative 'code_objects/puppet_namespace_object'
require_relative 'code_objects/defined_type_object'
require_relative 'code_objects/host_class_object'

View File

@ -1,11 +1,5 @@
require 'puppet/pops' class Puppetx::PuppetLabs::Strings::YARD::CodeObjects::DefinedTypeObject < Puppetx::PuppetLabs::Strings::YARD::CodeObjects::PuppetNamespaceObject
# A list of parameters attached to this class.
require_relative 'puppet_namespace_object' # @return [Array<Array(String, String)>]
attr_accessor :parameters
module Puppetx::PuppetLabs::Strings::YARD::CodeObjects
class DefinedTypeObject < PuppetNamespaceObject
# A list of parameters attached to this class.
# @return [Array<Array(String, String)>]
attr_accessor :parameters
end
end end

View File

@ -1,26 +1,22 @@
require_relative 'defined_type_object' class Puppetx::PuppetLabs::Strings::YARD::CodeObjects::HostClassObject < Puppetx::PuppetLabs::Strings::YARD::CodeObjects::DefinedTypeObject
# The {HostClassObject} that this class inherits from, if any.
# @return [HostClassObject, Proxy, nil]
attr_accessor :parent_class
module Puppetx::PuppetLabs::Strings::YARD::CodeObjects # NOTE: `include_mods` is never used as it makes no sense for Puppet, but
class HostClassObject < DefinedTypeObject # this is called by `YARD::Registry` and it will pass a parameter.
# The {HostClassObject} that this class inherits from, if any. def inheritance_tree(include_mods = false)
# @return [HostClassObject, Proxy, nil] if parent_class.is_a?(Puppetx::PuppetLabs::Strings::YARD::CodeObjects::HostClassObject)
attr_accessor :parent_class # Cool. We got a host class. Return self + parent inheritance tree.
[self] + parent_class.inheritance_tree
# NOTE: `include_mods` is never used as it makes no sense for Puppet, but elsif parent_class.is_a?(YARD::CodeObjects::Proxy)
# this is called by `YARD::Registry` and it will pass a parameter. # We have a reference to a parent that has not been created yet. Just
def inheritance_tree(include_mods = false) # return it.
if parent_class.is_a?(HostClassObject) [self, parent_class]
# Cool. We got a host class. Return self + parent inheritance tree. else
[self] + parent_class.inheritance_tree # Most likely no parent class. But also possibly an object that we
elsif parent_class.is_a?(YARD::CodeObjects::Proxy) # shouldn't inherit from. Just return self.
# We have a reference to a parent that has not been created yet. Just [self]
# return it.
[self, parent_class]
else
# Most likely no parent class. But also possibly an object that we
# shouldn't inherit from. Just return self.
[self]
end
end end
end end
end end

View File

@ -1,36 +1,30 @@
require 'yard' class Puppetx::PuppetLabs::Strings::YARD::CodeObjects::PuppetNamespaceObject < YARD::CodeObjects::NamespaceObject
# NOTE: `YARD::Registry#resolve` requires a method with this signature to
require_relative '../../../strings' # be present on all subclasses of `NamespaceObject`.
def inheritance_tree(include_mods = false)
module Puppetx::PuppetLabs::Strings::YARD::CodeObjects [self]
class PuppetNamespaceObject < YARD::CodeObjects::NamespaceObject
# NOTE: `YARD::Registry#resolve` requires a method with this signature to
# be present on all subclasses of `NamespaceObject`.
def inheritance_tree(include_mods = false)
[self]
end
# FIXME: We used to override `self.new` to ensure no YARD proxies were
# created for namespaces segments that did not map to a host class or
# defined type. Fighting the system in this way turned out to be
# counter-productive.
#
# However, if a proxy is left in, YARD will drop back to namspace-mangling
# heuristics that are very specific to Ruby and which produce ugly paths in
# the resulting output. Need to find a way to address this.
#
# Tried:
#
# - Overriding self.new in the code object. Failed because self.new
# overrides are gross and difficult to pull off. Especially when
# replacing an existing override.
#
# - Adding functionality to the base handler to ensure something other
# than a proxy occupies each namespace segment. Failed because once a
# code object is created with a namespace, it will never update.
# Unless that namespace is set to a Proxy.
#
# def self.new(namespace, name, *args, &block)
end end
# FIXME: We used to override `self.new` to ensure no YARD proxies were
# created for namespaces segments that did not map to a host class or
# defined type. Fighting the system in this way turned out to be
# counter-productive.
#
# However, if a proxy is left in, YARD will drop back to namspace-mangling
# heuristics that are very specific to Ruby and which produce ugly paths in
# the resulting output. Need to find a way to address this.
#
# Tried:
#
# - Overriding self.new in the code object. Failed because self.new
# overrides are gross and difficult to pull off. Especially when
# replacing an existing override.
#
# - Adding functionality to the base handler to ensure something other
# than a proxy occupies each namespace segment. Failed because once a
# code object is created with a namespace, it will never update.
# Unless that namespace is set to a Proxy.
#
# def self.new(namespace, name, *args, &block)
end end

View File

@ -1,6 +0,0 @@
require_relative 'handlers/base'
require_relative 'handlers/defined_type_handler'
require_relative 'handlers/host_class_handler'
require_relative 'handlers/puppet_3x_function_handler'
require_relative 'handlers/puppet_4x_function_handler'

View File

@ -1,19 +1,11 @@
require 'yard' class Puppetx::PuppetLabs::Strings::YARD::Handlers::Base < YARD::Handlers::Base
require 'puppet/pops' # Easy access to Pops model objects for handler matching.
include Puppet::Pops::Model
require_relative '../../../strings' # Easy access to custom code objects from which documentation is generated.
require_relative '../code_objects' include Puppetx::PuppetLabs::Strings::YARD::CodeObjects
module Puppetx::PuppetLabs::Strings::YARD::Handlers
class Base < YARD::Handlers::Base
# Easy access to Pops model objects for handler matching.
include Puppet::Pops::Model
# Easy access to custom code objects from which documentation is generated.
include Puppetx::PuppetLabs::Strings::YARD::CodeObjects
def self.handles?(statement)
handlers.any? {|h| h == statement.type}
end
def self.handles?(statement)
handlers.any? {|h| h == statement.type}
end end
end end

View File

@ -1,18 +1,14 @@
require_relative 'base' class Puppetx::PuppetLabs::Strings::YARD::Handlers::DefinedTypeHandler < Puppetx::PuppetLabs::Strings::YARD::Handlers:: Base
handles ResourceTypeDefinition
module Puppetx::PuppetLabs::Strings::YARD::Handlers process do
class DefinedTypeHandler < Base obj = DefinedTypeObject.new(:root, statement.pops_obj.name) do |o|
handles ResourceTypeDefinition o.parameters = statement.parameters.map do |a|
param_tuple = [a[0].pops_obj.name]
process do param_tuple << ( a[1].nil? ? nil : a[1].source )
obj = DefinedTypeObject.new(:root, statement.pops_obj.name) do |o|
o.parameters = statement.parameters.map do |a|
param_tuple = [a[0].pops_obj.name]
param_tuple << ( a[1].nil? ? nil : a[1].source )
end
end end
register obj
end end
register obj
end end
end end

View File

@ -1,24 +1,20 @@
require_relative 'base' class Puppetx::PuppetLabs::Strings::YARD::Handlers::HostClassHandler < Puppetx::PuppetLabs::Strings::YARD::Handlers::Base
handles HostClassDefinition
module Puppetx::PuppetLabs::Strings::YARD::Handlers process do
class HostClassHandler < Base obj = HostClassObject.new(:root, statement.pops_obj.name) do |o|
handles HostClassDefinition o.parameters = statement.parameters.map do |a|
param_tuple = [a[0].pops_obj.name]
process do param_tuple << ( a[1].nil? ? nil : a[1].source )
obj = HostClassObject.new(:root, statement.pops_obj.name) do |o|
o.parameters = statement.parameters.map do |a|
param_tuple = [a[0].pops_obj.name]
param_tuple << ( a[1].nil? ? nil : a[1].source )
end
end end
statement.pops_obj.tap do |o|
if o.parent_class
obj.parent_class = P(:root, o.parent_class)
end
end
register obj
end end
statement.pops_obj.tap do |o|
if o.parent_class
obj.parent_class = P(:root, o.parent_class)
end
end
register obj
end end
end end

View File

@ -1,89 +1,83 @@
# This utility library contains some tools for working with Puppet docstrings. class Puppetx::PuppetLabs::Strings::YARD::Handlers::Puppet3xFunctionHandler < YARD::Handlers::Ruby::Base
require 'puppet/util/docs' include Puppetx::PuppetLabs::Strings::YARD::CodeObjects
require_relative '../code_objects' handles method_call(:newfunction)
module Puppetx::PuppetLabs::Strings::YARD::Handlers process do
class Puppet3xFunctionHandler < YARD::Handlers::Ruby::Base name, options = process_parameters
include Puppetx::PuppetLabs::Strings::YARD::CodeObjects
handles method_call(:newfunction) obj = MethodObject.new(function_namespace, name)
process do register obj
name, options = process_parameters if options['doc']
register_docstring(obj, options['doc'], nil)
obj = MethodObject.new(function_namespace, name)
register obj
if options['doc']
register_docstring(obj, options['doc'], nil)
end
# This has to be done _after_ register_docstring as all tags on the
# object are overwritten by tags parsed out of the docstring.
return_type = options['type']
return_type ||= 'statement' # Default for newfunction
obj.add_tag YARD::Tags::Tag.new(:return, '', return_type)
# FIXME: This is a hack that allows us to document the Puppet Core which
# uses `--no-transitive-tag api` and then only shows things explicitly
# tagged with `public` or `private` api. This is kind of insane and
# should be fixed upstream.
obj.add_tag YARD::Tags::Tag.new(:api, 'public')
end end
private # This has to be done _after_ register_docstring as all tags on the
# object are overwritten by tags parsed out of the docstring.
return_type = options['type']
return_type ||= 'statement' # Default for newfunction
obj.add_tag YARD::Tags::Tag.new(:return, '', return_type)
# Returns a {PuppetNamespaceObject} for holding functions. Creates this # FIXME: This is a hack that allows us to document the Puppet Core which
# object if necessary. # uses `--no-transitive-tag api` and then only shows things explicitly
# # tagged with `public` or `private` api. This is kind of insane and
# @return [PuppetNamespaceObject] # should be fixed upstream.
def function_namespace obj.add_tag YARD::Tags::Tag.new(:api, 'public')
# NOTE: This tricky. If there is ever a Ruby class or module with the end
# name ::Puppet3xFunctions, then there will be a clash. Hopefully the name
# is sufficiently uncommon.
obj = P(:root, 'Puppet3xFunctions')
if obj.is_a? Proxy
namespace_obj = PuppetNamespaceObject.new(:root, 'Puppet3xFunctions')
namespace_obj.add_tag YARD::Tags::Tag.new(:api, 'public')
register namespace_obj private
end
obj # Returns a {PuppetNamespaceObject} for holding functions. Creates this
# object if necessary.
#
# @return [PuppetNamespaceObject]
def function_namespace
# NOTE: This tricky. If there is ever a Ruby class or module with the
# name ::Puppet3xFunctions, then there will be a clash. Hopefully the name
# is sufficiently uncommon.
obj = P(:root, 'Puppet3xFunctions')
if obj.is_a? Proxy
namespace_obj = PuppetNamespaceObject.new(:root, 'Puppet3xFunctions')
namespace_obj.add_tag YARD::Tags::Tag.new(:api, 'public')
register namespace_obj
end end
# NOTE: The following methods duplicate functionality from obj
# Puppet::Util::Reference and Puppet::Parser::Functions.functiondocs end
#
# 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 # NOTE: The following methods duplicate functionality from
# definition. # Puppet::Util::Reference and Puppet::Parser::Functions.functiondocs
# #
# @return [(String, Hash{String => String})] # However, implementing this natively in YARD is a good test for the
def process_parameters # feasibility of extracting custom Ruby documentation. In the end, the
# Passing `false` to prameters excludes the block param from the returned # existing approach taken by Puppet::Util::Reference may be the best due to
# list. # the heavy use of metaprogramming in Types and Providers.
name, opts = statement.parameters(false).compact
name = process_element(name) # 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
opts = opts.map do |tuple| name = process_element(name)
# 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
[name, Hash[opts]] 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 end
# Sometimes the YARD parser returns Heredoc strings that start with `<-` [name, Hash[opts]]
# instead of `<<-`. end
HEREDOC_START = /^<?<-/
# Sometimes the YARD parser returns Heredoc strings that start with `<-`
# instead of `<<-`.
HEREDOC_START = /^<?<-/
# Turns an entry in the method parameter list into a string. # Turns an entry in the method parameter list into a string.
# #
@ -105,20 +99,19 @@ module Puppetx::PuppetLabs::Strings::YARD::Handlers
end end
end end
# Cleans up and formats Heredoc contents parsed by YARD. # Cleans up and formats Heredoc contents parsed by YARD.
# #
# @param source [String] # @param source [String]
# @return [String] # @return [String]
def process_heredoc(source) def process_heredoc(source)
source = source.lines.to_a source = source.lines.to_a
# YARD adds a line of source context on either side of the Heredoc # YARD adds a line of source context on either side of the Heredoc
# contents. # contents.
source.shift source.shift
source.pop source.pop
# This utility method normalizes indentation and trims whitespace. # This utility method normalizes indentation and trims whitespace.
Puppet::Util::Docs.scrub(source.join) Puppet::Util::Docs.scrub(source.join)
end
end end
end end

View File

@ -1,98 +1,95 @@
require_relative '../code_objects' # 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::Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
include Puppetx::PuppetLabs::Strings::YARD::CodeObjects
module Puppetx::PuppetLabs::Strings::YARD::Handlers handles method_call(:dispatch)
# 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 Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
include Puppetx::PuppetLabs::Strings::YARD::CodeObjects
handles method_call(:dispatch) process do
return unless owner.is_a?(MethodObject) && owner['puppet_4x_function']
return unless statement.docstring
process do docstring = ::YARD::Docstring.new(statement.docstring, nil)
return unless owner.is_a?(MethodObject) && owner['puppet_4x_function']
return unless statement.docstring
docstring = ::YARD::Docstring.new(statement.docstring, nil) # FIXME: This does a wholesale copy of all possible tags. But, we're only
# interested in the @overload tag.
owner.add_tag *docstring.tags
end
end
# FIXME: This does a wholesale copy of all possible tags. But, we're only class Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
# interested in the @overload tag. include Puppetx::PuppetLabs::Strings::YARD::CodeObjects
owner.add_tag *docstring.tags
end handles method_call(:create_function)
process do
name = process_parameters
obj = MethodObject.new(function_namespace, name)
obj['puppet_4x_function'] = true
register obj
obj.add_tag YARD::Tags::Tag.new(:api, 'public')
blk = statement.block.children.first
parse_block(blk, :owner => obj)
end end
class Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base private
include Puppetx::PuppetLabs::Strings::YARD::CodeObjects
handles method_call(:create_function) # Returns a {PuppetNamespaceObject} for holding functions. Creates this
# object if necessary.
#
# @return [PuppetNamespaceObject]
def function_namespace
# NOTE: This tricky. If there is ever a Ruby class or module with the
# name ::Puppet4xFunctions, then there will be a clash. Hopefully the name
# is sufficiently uncommon.
obj = P(:root, 'Puppet4xFunctions')
if obj.is_a? Proxy
namespace_obj = PuppetNamespaceObject.new(:root, 'Puppet4xFunctions')
process do register namespace_obj
name = process_parameters # FIXME: The docstring has to be cleared. Otherwise, the namespace
# object will be registered using the docstring of the
obj = MethodObject.new(function_namespace, name) # `create_function` call that is currently being processed.
obj['puppet_4x_function'] = true #
# Figure out how to properly register the namespace without using the
register obj # function handler object.
register_docstring(namespace_obj, '', nil)
obj.add_tag YARD::Tags::Tag.new(:api, 'public') namespace_obj.add_tag YARD::Tags::Tag.new(:api, 'public')
blk = statement.block.children.first
parse_block(blk, :owner => obj)
end end
private obj
end
# Returns a {PuppetNamespaceObject} for holding functions. Creates this # NOTE: The following methods duplicate functionality from
# object if necessary. # Puppet::Util::Reference and Puppet::Parser::Functions.functiondocs
# #
# @return [PuppetNamespaceObject] # However, implementing this natively in YARD is a good test for the
def function_namespace # feasibility of extracting custom Ruby documentation. In the end, the
# NOTE: This tricky. If there is ever a Ruby class or module with the # existing approach taken by Puppet::Util::Reference may be the best due to
# name ::Puppet4xFunctions, then there will be a clash. Hopefully the name # the heavy use of metaprogramming in Types and Providers.
# is sufficiently uncommon.
obj = P(:root, 'Puppet4xFunctions')
if obj.is_a? Proxy
namespace_obj = PuppetNamespaceObject.new(:root, 'Puppet4xFunctions')
register namespace_obj # Extracts the Puppet function name and options hash from the parsed
# FIXME: The docstring has to be cleared. Otherwise, the namespace # definition.
# object will be registered using the docstring of the #
# `create_function` call that is currently being processed. # @return [(String, Hash{String => String})]
# def process_parameters
# Figure out how to properly register the namespace without using the # Passing `false` to prameters excludes the block param from the returned
# function handler object. # list.
register_docstring(namespace_obj, '', nil) name, _ = statement.parameters(false).compact
namespace_obj.add_tag YARD::Tags::Tag.new(:api, 'public')
end
obj name = process_element(name)
end
# NOTE: The following methods duplicate functionality from name
# Puppet::Util::Reference and Puppet::Parser::Functions.functiondocs end
#
# 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 # Sometimes the YARD parser returns Heredoc strings that start with `<-`
# definition. # instead of `<<-`.
# HEREDOC_START = /^<?<-/
# @return [(String, Hash{String => String})]
def process_parameters
# Passing `false` to prameters excludes the block param from the returned
# list.
name, _ = statement.parameters(false).compact
name = process_element(name)
name
end
# Sometimes the YARD parser returns Heredoc strings that start with `<-`
# instead of `<<-`.
HEREDOC_START = /^<?<-/
# Turns an entry in the method parameter list into a string. # Turns an entry in the method parameter list into a string.
# #
@ -114,20 +111,19 @@ module Puppetx::PuppetLabs::Strings::YARD::Handlers
end end
end end
# Cleans up and formats Heredoc contents parsed by YARD. # Cleans up and formats Heredoc contents parsed by YARD.
# #
# @param source [String] # @param source [String]
# @return [String] # @return [String]
def process_heredoc(source) def process_heredoc(source)
source = source.lines.to_a source = source.lines.to_a
# YARD adds a line of source context on either side of the Heredoc # YARD adds a line of source context on either side of the Heredoc
# contents. # contents.
source.shift source.shift
source.pop source.pop
# This utility method normalizes indentation and trims whitespace. # This utility method normalizes indentation and trims whitespace.
Puppet::Util::Docs.scrub(source.join) Puppet::Util::Docs.scrub(source.join)
end
end end
end end

View File

@ -1,32 +1,30 @@
require 'yard' require 'yard'
require 'puppet/pops' require 'puppet/pops'
require_relative '../../strings' require 'puppetx/puppetlabs/strings'
require_relative '../pops/yard_transformer' require 'puppetx/puppetlabs/strings//pops/yard_transformer'
module Puppetx::PuppetLabs::Strings::YARD class Puppetx::PuppetLabs::Strings::YARD::PuppetParser < YARD::Parser::Base
class PuppetParser < YARD::Parser::Base attr_reader :file, :source
attr_reader :file, :source
def initialize(source, filename) def initialize(source, filename)
@source = source @source = source
@file = filename @file = filename
@parser = Puppet::Pops::Parser::Parser.new()
@transformer = Puppetx::PuppetLabs::Strings::Pops::YARDTransformer.new()
end
def parse
@parse_result ||= @parser.parse_string(source)
self
end
def enumerator
statements = @transformer.transform(@parse_result)
# Ensure an array is returned and prune any nil values.
Array(statements).compact
end
@parser = Puppet::Pops::Parser::Parser.new()
@transformer = Puppetx::PuppetLabs::Strings::Pops::YARDTransformer.new()
end end
def parse
@parse_result ||= @parser.parse_string(source)
self
end
def enumerator
statements = @transformer.transform(@parse_result)
# Ensure an array is returned and prune any nil values.
Array(statements).compact
end
end end

View File

@ -1,22 +0,0 @@
# TODO: Decide if supporting 1.8.7 is really worth it.
if RUBY_VERSION < '1.9'
require 'backports/1.9.1/kernel/require_relative'
end
require 'puppet'
require_relative 'monkey_patches'
require_relative 'parser'
require_relative 'handlers'
YARD::Parser::SourceParser.register_parser_type(:puppet,
Puppetx::PuppetLabs::Strings::YARD::PuppetParser,
['pp'])
YARD::Handlers::Processor.register_handler_namespace(:puppet,
Puppetx::PuppetLabs::Strings::YARD::Handlers)
# FIXME: Might not be the best idea to have the template code on the Ruby
# LOAD_PATH as the contents of this directory really aren't library code.
YARD::Templates::Engine.register_template_path(File.join(
File.dirname(__FILE__),
'templates'))

View File

@ -3,11 +3,17 @@ require 'spec_helper'
module StringsSpec module StringsSpec
module Parsing module Parsing
# Cleans up the Registry and gives YARD some source code
# to generate documentation for
def parse(string, parser = :ruby) def parse(string, parser = :ruby)
Registry.clear YARD::Registry.clear
YARD::Parser::SourceParser.parse_string(string, parser) YARD::Parser::SourceParser.parse_string(string, parser)
end end
# A custom matcher that allows us to compare aspects of a
# Code Objects to the specified values. This gives us a
# simplified way to ensure that the Code Object added to the
# Registry is what we expect when testing handlers
RSpec::Matchers.define :document_a do |arguments| RSpec::Matchers.define :document_a do |arguments|
match do |actual| match do |actual|
compare_values(actual).empty? compare_values(actual).empty?

View File

@ -5,9 +5,7 @@ require 'mocha'
require 'puppet' require 'puppet'
require 'rspec' require 'rspec'
# This is neeeded so we can access a Registry if YARD creates one require 'puppetx/puppetlabs/strings'
require 'puppetx/puppetlabs/strings/yard/plugin'
include YARD
RSpec.configure do |config| RSpec.configure do |config|
config.mock_with :mocha config.mock_with :mocha

View File

@ -67,22 +67,6 @@ describe Puppet::Face do
end end
end end
describe "modules action" do
it "should raise an error if yard is absent" do
Puppet.features.stubs(:yard?).returns(false)
expect{Puppet::Face[:strings, :current].modules}.to raise_error(RuntimeError, "The 'yard' gem must be installed in order to use this face.")
end
it "should raise an error if rgen is absent" do
Puppet.features.stubs(:rgen?).returns(false)
expect{Puppet::Face[:strings, :current].modules}.to raise_error(RuntimeError, "The 'rgen' gem must be installed in order to use this face.")
end
it "should raise an error if the Ruby version is less than 1.9", :if => RUBY_VERSION.match(/^1\.8/) do
expect{Puppet::Face[:strings, :current].modules}.to raise_error(RuntimeError, "This face requires Ruby 1.9 or greater.")
end
end
describe "server action" do describe "server action" do
it "should raise an error if yard is absent" do it "should raise an error if yard is absent" do
Puppet.features.stubs(:yard?).returns(false) Puppet.features.stubs(:yard?).returns(false)
@ -118,3 +102,4 @@ describe Puppet::Face do
File.read(File.join(dir, modulename, 'doc', file)) File.read(File.join(dir, modulename, 'doc', file))
end end
end end

View File

@ -3,11 +3,11 @@ require 'puppetx/puppetlabs/strings/yard/handlers/defined_type_handler'
require 'strings_spec/parsing' require 'strings_spec/parsing'
describe "DefinedTypeHanlder" do describe Puppetx::PuppetLabs::Strings::YARD::Handlers::DefinedTypeHandler do
include StringsSpec::Parsing include StringsSpec::Parsing
def the_definedtype() def the_definedtype()
Registry.at("foo::bar") YARD::Registry.at("foo::bar")
end end
it "should parse single-line documentation strings before a given defined type" do it "should parse single-line documentation strings before a given defined type" do
@ -56,6 +56,6 @@ describe "DefinedTypeHanlder" do
parse(puppet_code, :puppet) parse(puppet_code, :puppet)
expect(Registry.all).to be_empty expect(YARD::Registry.all).to be_empty
end end
end end

View File

@ -2,11 +2,11 @@ require 'spec_helper'
require 'puppetx/puppetlabs/strings/yard/handlers/host_class_handler' require 'puppetx/puppetlabs/strings/yard/handlers/host_class_handler'
require 'strings_spec/parsing' require 'strings_spec/parsing'
describe "HostClassDefintion" do describe Puppetx::PuppetLabs::Strings::YARD::Handlers::HostClassHandler do
include StringsSpec::Parsing include StringsSpec::Parsing
def the_hostclass() def the_hostclass()
Registry.at("foo::bar") YARD::Registry.at("foo::bar")
end end
it "should parse single-line documentation strings before a given class" do it "should parse single-line documentation strings before a given class" do

View File

@ -2,15 +2,15 @@ require 'spec_helper'
require 'puppetx/puppetlabs/strings/yard/handlers/puppet_3x_function_handler' require 'puppetx/puppetlabs/strings/yard/handlers/puppet_3x_function_handler'
require 'strings_spec/parsing' require 'strings_spec/parsing'
describe "Puppet3xFunctionHanlder" do describe Puppetx::PuppetLabs::Strings::YARD::Handlers::Puppet3xFunctionHandler do
include StringsSpec::Parsing include StringsSpec::Parsing
def the_method() def the_method()
Registry.at("Puppet3xFunctions#the_function") YARD::Registry.at("Puppet3xFunctions#the_function")
end end
def the_namespace() def the_namespace()
Registry.at("Puppet3xFunctions") YARD::Registry.at("Puppet3xFunctions")
end end
it "should parse single-line documentation strings before a given function" do it "should parse single-line documentation strings before a given function" do

View File

@ -2,15 +2,15 @@ require 'spec_helper'
require 'puppetx/puppetlabs/strings/yard/handlers/puppet_4x_function_handler' require 'puppetx/puppetlabs/strings/yard/handlers/puppet_4x_function_handler'
require 'strings_spec/parsing' require 'strings_spec/parsing'
describe "Pupet4xFunctionHandler" do describe Puppetx::PuppetLabs::Strings::YARD::Handlers::Puppet4xFunctionHandler do
include StringsSpec::Parsing include StringsSpec::Parsing
def the_method() def the_method()
Registry.at("Puppet4xFunctions#the_function") YARD::Registry.at("Puppet4xFunctions#the_function")
end end
def the_namespace() def the_namespace()
Registry.at("Puppet4xFunctions") YARD::Registry.at("Puppet4xFunctions")
end end
it "should parse single-line documentation strings before a given function" do it "should parse single-line documentation strings before a given function" do
@ -60,6 +60,6 @@ describe "Pupet4xFunctionHandler" do
This is not ruby code This is not ruby code
RUBY RUBY
expect(Registry.all).to be_empty expect(YARD::Registry.all).to be_empty
end end
end end