(PDOC-63) Implement the Puppet parser.

This commit implements the Puppet language parser that future handlers will use
to generate YARD code objects for classes, defined types, and functions written
in Puppet.
This commit is contained in:
Peter Huene 2016-09-11 11:04:17 -07:00
parent d4bab8a5ab
commit f2e41a9c23
No known key found for this signature in database
GPG Key ID: 6B585C1742BE3C3C
4 changed files with 223 additions and 0 deletions

View File

@ -12,6 +12,9 @@ module PuppetStrings::Yard
def self.setup!
# Register the template path
YARD::Templates::Engine.register_template_path(File.join(File.dirname(__FILE__), 'yard', 'templates'))
# Register the Puppet parser
YARD::Parser::SourceParser.register_parser_type(:puppet, PuppetStrings::Yard::Parsers::Puppet::Parser, ['pp'])
end
end

View File

@ -1,3 +1,7 @@
# The module for custom YARD parsers.
module PuppetStrings::Yard::Parsers
# The module for custom YARD parsers for the Puppet language.
module Puppet
require 'puppet-strings/yard/parsers/puppet/parser'
end
end

View File

@ -0,0 +1,70 @@
require 'puppet'
require 'puppet/pops'
require 'puppet-strings/yard/parsers/puppet/statement'
# Implements the Puppet language parser.
class PuppetStrings::Yard::Parsers::Puppet::Parser < YARD::Parser::Base
attr_reader :file, :source
# Initializes the parser.
# @param [String] source The source being parsed.
# @param [String] filename The file name of the file being parsed.
# @return [void]
def initialize(source, filename)
@source = source
@file = filename
@visitor = ::Puppet::Pops::Visitor.new(self, 'transform')
end
# Parses the source.
# @return [void]
def parse
begin
@statements ||= (@visitor.visit(::Puppet::Pops::Parser::Parser.new.parse_string(source)) || []).compact
rescue ::Puppet::ParseError => ex
log.error "Failed to parse #{@file}: #{ex.message}"
@statements = []
end
@statements.freeze
self
end
# Gets an enumerator for the statements that were parsed.
# @return Returns an enumerator for the statements that were parsed.
def enumerator
@statements
end
private
def transform_Program(o)
# Cache the lines of the source text; we'll use this to locate comments
@lines = o.source_text.lines.to_a
o.definitions.map { |d| @visitor.visit(d) }
end
def transform_Factory(o)
@visitor.visit(o.current)
end
def transform_HostClassDefinition(o)
statement = PuppetStrings::Yard::Parsers::Puppet::ClassStatement.new(o, @file)
statement.extract_docstring(@lines)
statement
end
def transform_ResourceTypeDefinition(o)
statement = PuppetStrings::Yard::Parsers::Puppet::DefinedTypeStatement.new(o, @file)
statement.extract_docstring(@lines)
statement
end
def transform_FunctionDefinition(o)
statement = PuppetStrings::Yard::Parsers::Puppet::FunctionStatement.new(o, @file)
statement.extract_docstring(@lines)
statement
end
def transform_Object(o)
# Ignore anything else (will be compacted out of the resulting array)
end
end

View File

@ -0,0 +1,146 @@
require 'puppet'
require 'puppet/pops'
module PuppetStrings::Yard::Parsers::Puppet
# Represents the base Puppet language statement.
class Statement
# The pattern for parsing docstring comments.
COMMENT_REGEX = /^\s*#+\s?/
attr_reader :source
attr_reader :file
attr_reader :line
attr_reader :docstring
attr_reader :comments_range
# Initializes the Puppet language statement.
# @param object The Puppet parser model object for the statement.
# @param [String] file The file name of the file containing the statement.
def initialize(object, file)
@file = file
adapter = ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(object)
@source = adapter.extract_text
@line = adapter.line
@comments_range = nil
end
# Extracts the docstring for the statement given the source lines.
# @param [Array<String>] lines The source lines for the file containing the statement.
# @return [void]
def extract_docstring(lines)
comment = []
(0..@line-2).reverse_each do |index|
break unless index <= lines.count
line = lines[index].strip
count = line.size
line.gsub!(COMMENT_REGEX, '')
# Break out if nothing was removed (wasn't a comment line)
break unless line.size < count
comment << line
end
@comments_range = (@line - comment.size - 1..@line - 1)
@docstring = YARD::Docstring.new(comment.reverse.join("\n"))
end
# Shows the first line context for the statement.
# @return [String] Returns the first line context for the statement.
def show
"\t#{@line}: #{first_line}"
end
# Gets the full comments of the statement.
# @return [String] Returns the full comments of the statement.
def comments
@docstring.all
end
# Determines if the comments have hash flag.
# @return [Boolean] Returns true if the comments have a hash flag or false if not.
def comments_hash_flag
false
end
private
def first_line
@source.split(/\r?\n/).first.strip
end
end
# Implements a parameterized statement (a statement that takes parameters).
class ParameterizedStatement < Statement
# Implements a parameter for a parameterized statement.
class Parameter
attr_reader :name
attr_reader :type
attr_reader :value
# Initializes the parameter.
# @param [Puppet::Pops::Model::Parameter] parameter The parameter model object.
def initialize(parameter)
@name = parameter.name
# Take the exact text for the type expression
if parameter.type_expr
adapter = ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(parameter.type_expr)
@type = adapter.extract_text
end
# Take the exact text for the default value expression
if parameter.value
adapter = ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(parameter.value)
@value = adapter.extract_text
end
end
end
attr_reader :parameters
# Initializes the parameterized statement.
# @param object The Puppet parser model object that has parameters.
# @param [String] file The file containing the statement.
def initialize(object, file)
super(object, file)
@parameters = object.parameters.map { |parameter| Parameter.new(parameter) }
end
end
# Implements the Puppet class statement.
class ClassStatement < ParameterizedStatement
attr_reader :name
attr_reader :parent_class
# Initializes the Puppet class statement.
# @param [Puppet::Pops::Model::HostClassDefinition] object The model object for the class statement.
# @param [String] file The file containing the statement.
def initialize(object, file)
super(object, file)
@name = object.name
@parent_class = object.parent_class
end
end
# Implements the Puppet defined type statement.
class DefinedTypeStatement < ParameterizedStatement
attr_reader :name
# Initializes the Puppet defined type statement.
# @param [Puppet::Pops::Model::ResourceTypeDefinition] object The model object for the defined type statement.
# @param [String] file The file containing the statement.
def initialize(object, file)
super(object, file)
@name = object.name
end
end
# Implements the Puppet function statement.
class FunctionStatement < ParameterizedStatement
attr_reader :name
# Initializes the Puppet function statement.
# @param [Puppet::Pops::Model::FunctionDefinition] object The model object for the function statement.
# @param [String] file The file containing the statement.
def initialize(object, file)
super(object, file)
@name = object.name
end
end
end