diff --git a/lib/puppet-strings/yard.rb b/lib/puppet-strings/yard.rb index 38f2415..90d0be0 100644 --- a/lib/puppet-strings/yard.rb +++ b/lib/puppet-strings/yard.rb @@ -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 diff --git a/lib/puppet-strings/yard/parsers.rb b/lib/puppet-strings/yard/parsers.rb index 3d049d0..6916fee 100644 --- a/lib/puppet-strings/yard/parsers.rb +++ b/lib/puppet-strings/yard/parsers.rb @@ -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 diff --git a/lib/puppet-strings/yard/parsers/puppet/parser.rb b/lib/puppet-strings/yard/parsers/puppet/parser.rb new file mode 100644 index 0000000..a74675b --- /dev/null +++ b/lib/puppet-strings/yard/parsers/puppet/parser.rb @@ -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 diff --git a/lib/puppet-strings/yard/parsers/puppet/statement.rb b/lib/puppet-strings/yard/parsers/puppet/statement.rb new file mode 100644 index 0000000..8dd9868 --- /dev/null +++ b/lib/puppet-strings/yard/parsers/puppet/statement.rb @@ -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] 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