Functionality common to both our RDoc version 1 and 2 parsers.
New instance of the appropriate PreProcess for our RDoc version.
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 432 def create_rdoc_preprocess raise(NotImplementedError, "This method must be overwritten for whichever version of RDoc this parser is working with") end
create documentation for a class named name
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 228 def document_class(name, klass, container) Puppet.debug "rdoc: found new class #{name}" container, name = get_class_or_module(container, name) superclass = klass.parent superclass = "" if superclass.nil? or superclass.empty? comment = klass.doc look_for_directives_in(container, comment) unless comment.empty? cls = container.add_class(RDoc::PuppetClass, name, superclass) # it is possible we already encountered this class, while parsing some namespaces # from other classes of other files. But at that time we couldn't know this class superclass # so, now we know it and force it. cls.superclass = superclass cls.record_location(@top_level) # scan class code for include code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::BlockExpression) code ||= klass.code unless code.nil? scan_for_include_or_require(cls, code) scan_for_realize(cls, code) scan_for_resource(cls, code) if Puppet.settings[:document_all] end cls.add_comment(comment, klass.file) rescue => detail raise Puppet::ParseError, "impossible to parse class '#{name}' in #{klass.file} at line #{klass.line}: #{detail}", detail.backtrace end
create documentation for a define
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 284 def document_define(name, define, container) Puppet.debug "rdoc: found new definition #{name}" # find superclas if any # find the parent # split define name by :: to find the complete module hierarchy container, name = get_class_or_module(container,name) # build up declaration declaration = "" define.arguments.each do |arg,value| declaration << "\$#{arg}" unless value.nil? declaration << " => " case value when Puppet::Parser::AST::Leaf declaration << "'#{value.value}'" when Puppet::Parser::AST::BlockExpression declaration << "[#{value.children.collect { |v| "'#{v}'" }.join(", ")}]" else declaration << "#{value.to_s}" end end declaration << ", " end declaration.chop!.chop! if declaration.size > 1 # register method into the container meth = RDoc::AnyMethod.new(declaration, name) meth.comment = define.doc container.add_method(meth) look_for_directives_in(container, meth.comment) unless meth.comment.empty? meth.params = "( #{declaration} )" meth.visibility = :public meth.document_self = true meth.singleton = false rescue => detail raise Puppet::ParseError, "impossible to parse definition '#{name}' in #{define.file} at line #{define.line}: #{detail}", detail.backtrace end
create documentation for a node
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 259 def document_node(name, node, container) Puppet.debug "rdoc: found new node #{name}" superclass = node.parent superclass = "" if superclass.nil? or superclass.empty? comment = node.doc look_for_directives_in(container, comment) unless comment.empty? n = container.add_node(name, superclass) n.record_location(@top_level) code = node.code.children if node.code.is_a?(Puppet::Parser::AST::BlockExpression) code ||= node.code unless code.nil? scan_for_include_or_require(n, code) scan_for_realize(n, code) scan_for_vardef(n, code) scan_for_resource(n, code) if Puppet.settings[:document_all] end n.add_comment(comment, node.file) rescue => detail raise Puppet::ParseError, "impossible to parse node '#{name}' in #{node.file} at line #{node.line}: #{detail}", detail.backtrace end
Due to a bug in RDoc, we need to roll our own find_module_named The issue is that RDoc tries harder by asking the parent for a class/module of the name. But by doing so, it can mistakenly use a module of same name but from which we are not descendant.
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 48 def find_object_named(container, name) return container if container.name == name container.each_classmodule do |m| return m if m.name == name end nil end
walk down the namespace and lookup/create container as needed
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 57 def get_class_or_module(container, name) # class ::A -> A is in the top level if name =~ /^::/ container = @top_level end names = name.split('::') final_name = names.pop names.each do |name| prev_container = container container = find_object_named(container, name) container ||= prev_container.add_class(RDoc::PuppetClass, name, nil) end [container, final_name] end
#look_for_directives_in
scans the current comment for RDoc
directives
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 437 def look_for_directives_in(context, comment) preprocess = create_rdoc_preprocess preprocess.handle(comment) do |directive, param| case directive when "stopdoc" context.stop_doc "" when "startdoc" context.start_doc context.force_documentation = true "" when "enddoc" #context.done_documenting = true #"" throw :enddoc when "main" options = Options.instance options.main_page = param "" when "title" options = Options.instance options.title = param "" when "section" context.set_current_section(param, comment) comment.replace("") # 1.8 doesn't support #clear break else warn "Unrecognized directive '#{directive}'" break end end remove_private_comments(comment) end
Traverse the AST tree and produce code-objects node that contains the documentation
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 326 def parse_elements(container, known_resource_types) Puppet.debug "rdoc: scanning manifest" known_resource_types.hostclasses.values.sort { |a,b| a.name <=> b.name }.each do |klass| name = klass.name if klass.file == @input_file_name unless name.empty? document_class(name,klass,container) else # on main class document vardefs code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::BlockExpression) code ||= klass.code scan_for_vardef(container, code) unless code.nil? end end end known_resource_types.definitions.each do |name, define| if define.file == @input_file_name document_define(name,define,container) end end known_resource_types.nodes.each do |name, node| if node.file == @input_file_name document_node(name.to_s,node,container) end end end
this is a poor man custom fact parser :-)
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 366 def parse_fact(container) comments = "" current_fact = nil parsed_facts = [] File.open(@input_file_name) do |of| of.each do |line| # fetch comments if line =~ /^[ \t]*# ?(.*)$/ comments += $1 + "\n" elsif line =~ /^[ \t]*Facter.add\(['"](.*?)['"]\)/ current_fact = RDoc::Fact.new($1,{}) look_for_directives_in(container, comments) unless comments.empty? current_fact.comment = comments parsed_facts << current_fact comments = "" Puppet.debug "rdoc: found custom fact #{current_fact.name}" elsif line =~ /^[ \t]*confine[ \t]*:(.*?)[ \t]*=>[ \t]*(.*)$/ current_fact.confine = { :type => $1, :value => $2 } unless current_fact.nil? else # unknown line type comments ="" end end end parsed_facts.each do |f| container.add_fact(f) f.record_location(@top_level) end end
create documentation for plugins
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 356 def parse_plugins(container) Puppet.debug "rdoc: scanning plugin or fact" if @input_file_name =~ /\/facter\/[^\/]+\.rb$/ parse_fact(container) else parse_puppet_plugin(container) end end
this is a poor man puppet plugin parser :-) it doesn’t extract doc nor desc :-(
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 397 def parse_puppet_plugin(container) comments = "" current_plugin = nil File.open(@input_file_name) do |of| of.each do |line| # fetch comments if line =~ /^[ \t]*# ?(.*)$/ comments += $1 + "\n" elsif line =~ /^[ \t]*(?:Puppet::Parser::Functions::)?newfunction[ \t]*\([ \t]*:(.*?)[ \t]*,[ \t]*:type[ \t]*=>[ \t]*(:rvalue|:lvalue)/ current_plugin = RDoc::Plugin.new($1, "function") look_for_directives_in(container, comments) unless comments.empty? current_plugin.comment = comments current_plugin.record_location(@top_level) container.add_plugin(current_plugin) comments = "" Puppet.debug "rdoc: found new function plugins #{current_plugin.name}" elsif line =~ /^[ \t]*Puppet::Type.newtype[ \t]*\([ \t]*:(.*?)\)/ current_plugin = RDoc::Plugin.new($1, "type") look_for_directives_in(container, comments) unless comments.empty? current_plugin.comment = comments current_plugin.record_location(@top_level) container.add_plugin(current_plugin) comments = "" Puppet.debug "rdoc: found new type plugins #{current_plugin.name}" elsif line =~ /module Puppet::Parser::Functions/ # skip else # unknown line type comments ="" end end end end
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 473 def remove_private_comments(comment) comment.gsub!(/^#--.*?^#\+\+/, '') comment.sub!(/^#--.*/, '') end
main entry point
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 26 def scan environment = Puppet.lookup(:current_environment) known_resource_types = environment.known_resource_types unless known_resource_types.watching_file?(@input_file_name) Puppet.info "rdoc: scanning #{@input_file_name}" if @input_file_name =~ /\.pp$/ @parser = Puppet::Parser::Parser.new(environment) @parser.file = @input_file_name @parser.parse.instantiate('').each do |type| known_resource_types.add type end end end scan_top_level(@top_level, environment) @top_level end
create documentation for include statements we can find in
code and associate it with container
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 151 def scan_for_include_or_require(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_include_or_require(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::BlockExpression) if stmt.is_a?(Puppet::Parser::AST::Function) and ['include','require'].include?(stmt.name) stmt.arguments.each do |included| Puppet.debug "found #{stmt.name}: #{included}" container.send("add_#{stmt.name}", RDoc::Include.new(included.to_s, stmt.doc)) end end end end
create documentation for realize statements we can find in
code and associate it with container
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 167 def scan_for_realize(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_realize(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::BlockExpression) if stmt.is_a?(Puppet::Parser::AST::Function) and stmt.name == 'realize' stmt.arguments.each do |realized| Puppet.debug "found #{stmt.name}: #{realized}" container.add_realize( RDoc::Include.new(realized.to_s, stmt.doc)) end end end end
create documentation for resources we can find in code and
associate it with container
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 197 def scan_for_resource(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_resource(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::BlockExpression) if stmt.is_a?(Puppet::Parser::AST::Resource) and !stmt.type.nil? begin type = stmt.type.split("::").collect { |s| s.capitalize }.join("::") stmt.instances.each do |inst| title = inst.title.is_a?(Puppet::Parser::AST::ASTArray) ? inst.title.to_s.gsub(/\[(.*)\]/,'\1') : inst.title.to_s Puppet.debug "rdoc: found resource: #{type}[#{title}]" param = [] inst.parameters.children.each do |p| res = {} res["name"] = p.param res["value"] = "#{p.value.to_s}" unless p.value.nil? param << res end container.add_resource(RDoc::PuppetResource.new(type, title, stmt.doc, param)) end rescue => detail raise Puppet::ParseError, "impossible to parse resource in #{stmt.file} at line #{stmt.line}: #{detail}", detail.backtrace end end end end
create documentation for global variables assignements we can find in
code and associate it with container
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 183 def scan_for_vardef(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_vardef(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::BlockExpression) if stmt.is_a?(Puppet::Parser::AST::VarDef) Puppet.debug "rdoc: found constant: #{stmt.name} = #{stmt.value}" container.add_constant(RDoc::Constant.new(stmt.name.to_s, stmt.value.to_s, stmt.doc)) end end end
create documentation for the top level container
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 114 def scan_top_level(container, environment) # use the module README as documentation for the module comment = "" %w{README README.rdoc}.each do |rfile| readme = File.join(File.dirname(File.dirname(@input_file_name)), rfile) comment = File.open(readme,"r") { |f| f.read } if FileTest.readable?(readme) end look_for_directives_in(container, comment) unless comment.empty? # infer module name from directory name = split_module(@input_file_name, environment) if name.nil? # skip .pp files that are not in manifests directories as we can't guarantee they're part # of a module or the global configuration. container.document_self = false return end Puppet.debug "rdoc: scanning for #{name}" container.module_name = name container.global=true if name == SITE container, name = get_class_or_module(container,name) mod = container.add_module(RDoc::PuppetModule, name) mod.record_location(@top_level) mod.add_comment(comment, @input_file_name) if @input_file_name =~ /\.pp$/ parse_elements(mod, environment.known_resource_types) elsif @input_file_name =~ /\.rb$/ parse_plugins(mod) end end
#split_module
tries to find if path belongs to the module path if it does,
it returns the module name, otherwise if we are sure it is part of the
global manifest path, “__site__” is returned. And finally if this path
couldn’t be mapped anywhere, nil is returned.
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 79 def split_module(path, environment) # find a module fullpath = File.expand_path(path) Puppet.debug "rdoc: testing #{fullpath}" if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins|lib)\/.+\.(pp|rb)$/ modpath = $1 name = $2 Puppet.debug "rdoc: module #{name} into #{modpath} ?" environment.modulepath.each do |mp| if File.identical?(modpath,mp) Puppet.debug "rdoc: found module #{name}" return name end end end if fullpath =~ /\.(pp|rb)$/ # there can be paths we don't want to scan under modules # imagine a ruby or manifest that would be distributed as part as a module # but we don't want those to be hosted under <site> environment.modulepath.each do |mp| # check that fullpath is a descendant of mp dirname = fullpath previous = dirname while (dirname = File.dirname(previous)) != previous previous = dirname return nil if File.identical?(dirname,mp) end end end # we are under a global manifests Puppet.debug "rdoc: global manifests" SITE end
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 6 def self.included(base) base.class_eval do attr_accessor :input_file_name, :top_level # parser registration into RDoc parse_files_matching(/\.(rb|pp)$/) end end
called with the top level file
# File lib/puppet/util/rdoc/parser/puppet_parser_core.rb, line 16 def initialize(top_level, file_name, body, options, stats) @options = options @stats = stats @input_file_name = file_name @top_level = top_level @top_level.extend(RDoc::PuppetTopLevel) @progress = $stderr unless options.quiet end