summaryrefslogtreecommitdiffstats
path: root/lib/puppet/util
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/util')
-rw-r--r--lib/puppet/util/rdoc.rb85
-rw-r--r--lib/puppet/util/rdoc/code_objects.rb219
-rw-r--r--lib/puppet/util/rdoc/generators/puppet_generator.rb829
-rw-r--r--lib/puppet/util/rdoc/generators/template/puppet/puppet.rb1051
-rw-r--r--lib/puppet/util/rdoc/parser.rb437
5 files changed, 2621 insertions, 0 deletions
diff --git a/lib/puppet/util/rdoc.rb b/lib/puppet/util/rdoc.rb
new file mode 100644
index 000000000..b33e67c71
--- /dev/null
+++ b/lib/puppet/util/rdoc.rb
@@ -0,0 +1,85 @@
+
+module Puppet::Util::RDoc
+
+ module_function
+
+ # launch a rdoc documenation process
+ # with the files/dir passed in +files+
+ def rdoc(outputdir, files)
+ begin
+ Puppet[:ignoreimport] = true
+
+ # then rdoc
+ require 'rdoc/rdoc'
+
+ # load our parser
+ require 'puppet/util/rdoc/parser'
+
+ r = RDoc::RDoc.new
+ RDoc::RDoc::GENERATORS["puppet"] = RDoc::RDoc::Generator.new("puppet/util/rdoc/generators/puppet_generator.rb",
+ "PuppetGenerator".intern,
+ "puppet")
+ # specify our own format & where to output
+ options = [ "--fmt", "puppet",
+ "--quiet",
+ "--op", outputdir ]
+
+ options += files
+
+ # launch the documentation process
+ r.document(options)
+ rescue RDoc::RDocError => e
+ raise Puppet::ParseError.new("RDoc error %s" % e)
+ end
+ end
+
+ # launch a output to console manifest doc
+ def manifestdoc(files)
+ Puppet[:ignoreimport] = true
+ files.select { |f| FileTest.file?(f) }.each do |f|
+ parser = Puppet::Parser::Parser.new(:environment => Puppet[:environment])
+ parser.file = f
+ ast = parser.parse
+ output(f, ast)
+ end
+ end
+
+ # Ouputs to the console the documentation
+ # of a manifest
+ def output(file, ast)
+ astobj = []
+ ast[:nodes].each do |name, k|
+ astobj << k if k.file == file
+ end
+ ast[:classes].each do |name, k|
+ astobj << k if k.file == file
+ end
+ ast[:definitions].each do |name, k|
+ astobj << k if k.file == file
+ end
+ astobj.sort! {|a,b| a.line <=> b.line }.each do |k|
+ output_astnode_doc(k)
+ end
+ end
+
+ def output_astnode_doc(ast)
+ puts ast.doc if !ast.doc.nil? and !ast.doc.empty?
+ if Puppet.settings[:document_all]
+ # scan each underlying resources to produce documentation
+ code = ast.code.children if ast.code.is_a?(Puppet::Parser::AST::ASTArray)
+ code ||= ast.code
+ output_resource_doc(code) unless code.nil?
+ end
+ end
+
+ def output_resource_doc(code)
+ code.sort { |a,b| a.line <=> b.line }.each do |stmt|
+ output_resource_doc(stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray)
+
+ if stmt.is_a?(Puppet::Parser::AST::Resource)
+ puts stmt.doc if !stmt.doc.nil? and !stmt.doc.empty?
+ end
+ end
+ end
+
+end \ No newline at end of file
diff --git a/lib/puppet/util/rdoc/code_objects.rb b/lib/puppet/util/rdoc/code_objects.rb
new file mode 100644
index 000000000..52df2193e
--- /dev/null
+++ b/lib/puppet/util/rdoc/code_objects.rb
@@ -0,0 +1,219 @@
+require 'rdoc/code_objects'
+
+module RDoc
+
+ # This modules contains various class that are used to hold information
+ # about the various Puppet language structures we found while parsing.
+ #
+ # Those will be mapped to their html counterparts which are defined in
+ # PuppetGenerator.
+
+ # PuppetTopLevel is a top level (usually a .pp/.rb file)
+ class PuppetTopLevel < TopLevel
+ attr_accessor :module_name, :global
+
+ # will contain all plugins
+ @@all_plugins = {}
+
+ # contains all cutoms facts
+ @@all_facts = {}
+
+ def initialize(toplevel)
+ super(toplevel.file_relative_name)
+ end
+
+ def self.all_plugins
+ @@all_plugins.values
+ end
+
+ def self.all_facts
+ @@all_facts.values
+ end
+ end
+
+ # PuppetModule holds a Puppet Module
+ # This is mapped to an HTMLPuppetModule
+ # it leverage the RDoc (ruby) module infrastructure
+ class PuppetModule < NormalModule
+ attr_accessor :facts, :plugins
+
+ def initialize(name,superclass=nil)
+ @facts = []
+ @plugins = []
+ super(name,superclass)
+ end
+
+ def initialize_classes_and_modules
+ super
+ @nodes = {}
+ end
+
+ def add_plugin(plugin)
+ add_to(@plugins, plugin)
+ end
+
+ def add_fact(fact)
+ add_to(@facts, fact)
+ end
+
+ def add_node(name,superclass)
+ cls = @nodes[name]
+ unless cls
+ cls = PuppetNode.new(name, superclass)
+ @nodes[name] = cls if !@done_documenting
+ cls.parent = self
+ cls.section = @current_section
+ end
+ cls
+ end
+
+ def each_fact
+ @facts.each {|c| yield c}
+ end
+
+ def each_plugin
+ @plugins.each {|c| yield c}
+ end
+
+ def each_node
+ @nodes.each {|c| yield c}
+ end
+
+ def nodes
+ @nodes.values
+ end
+ end
+
+ # PuppetClass holds a puppet class
+ # It is mapped to a HTMLPuppetClass for display
+ # It leverages RDoc (ruby) Class
+ class PuppetClass < ClassModule
+ attr_accessor :resource_list
+
+ def initialize(name, superclass)
+ super(name,superclass)
+ @resource_list = []
+ end
+
+ def add_resource(resource)
+ add_to(@resource_list, resource)
+ end
+
+ def is_module?
+ false
+ end
+ end
+
+ # PuppetNode holds a puppet node
+ # It is mapped to a HTMLPuppetNode for display
+ # A node is just a variation of a class
+ class PuppetNode < PuppetClass
+ def initialize(name, superclass)
+ super(name,superclass)
+ end
+
+ def is_module?
+ false
+ end
+ end
+
+ # Plugin holds a native puppet plugin (function,type...)
+ # It is mapped to a HTMLPuppetPlugin for display
+ class Plugin < Context
+ attr_accessor :name, :type
+
+ def initialize(name, type)
+ super()
+ @name = name
+ @type = type
+ @comment = ""
+ end
+
+ def <=>(other)
+ @name <=> other.name
+ end
+
+ def full_name
+ @name
+ end
+
+ def http_url(prefix)
+ path = full_name.split("::")
+ File.join(prefix, *path) + ".html"
+ end
+
+ def is_fact?
+ false
+ end
+
+ def to_s
+ res = self.class.name + ": " + @name + " (" + @type + ")\n"
+ res << @comment.to_s
+ res
+ end
+ end
+
+ # Fact holds a custom fact
+ # It is mapped to a HTMLPuppetPlugin for display
+ class Fact < Context
+ attr_accessor :name, :confine
+
+ def initialize(name, confine)
+ super()
+ @name = name
+ @confine = confine
+ @comment = ""
+ end
+
+ def <=>(other)
+ @name <=> other.name
+ end
+
+ def is_fact?
+ true
+ end
+
+ def full_name
+ @name
+ end
+
+ def to_s
+ res = self.class.name + ": " + @name + "\n"
+ res << @comment.to_s
+ res
+ end
+ end
+
+ # PuppetResource holds a puppet resource
+ # It is mapped to a HTMLPuppetResource for display
+ # A resource is defined by its "normal" form Type[title]
+ class PuppetResource < CodeObject
+ attr_accessor :type, :title, :params
+
+ def initialize(type, title, comment, params)
+ super()
+ @type = type
+ @title = title
+ @comment = comment
+ @params = params
+ end
+
+ def <=>(other)
+ full_name <=> other.full_name
+ end
+
+ def full_name
+ @type + "[" + @title + "]"
+ end
+
+ def name
+ full_name
+ end
+
+ def to_s
+ res = @type + "[" + @title + "]\n"
+ res << @comment.to_s
+ res
+ end
+ end
+end
diff --git a/lib/puppet/util/rdoc/generators/puppet_generator.rb b/lib/puppet/util/rdoc/generators/puppet_generator.rb
new file mode 100644
index 000000000..22f001164
--- /dev/null
+++ b/lib/puppet/util/rdoc/generators/puppet_generator.rb
@@ -0,0 +1,829 @@
+require 'rdoc/generators/html_generator'
+require 'puppet/util/rdoc/code_objects'
+module Generators
+
+ # This module holds all the classes needed to generate the HTML documentation
+ # of a bunch of puppet manifests.
+ #
+ # It works by traversing all the code objects defined by the Puppet RDoc::Parser
+ # and produces HTML counterparts objects that in turns are used by RDoc template engine
+ # to produce the final HTML.
+ #
+ # It is also responsible of creating the whole directory hierarchy, and various index
+ # files.
+ #
+ # It is to be noted that the whole system is built on top of ruby RDoc. As such there
+ # is an implicit mapping of puppet entities to ruby entitites:
+ #
+ # Puppet => Ruby
+ # ------------------------
+ # Module Module
+ # Class Class
+ # Definition Method
+ # Resource
+ # Node
+ # Plugin
+ # Fact
+
+ MODULE_DIR = "modules"
+ NODE_DIR = "nodes"
+ PLUGIN_DIR = "plugins"
+
+ # This is a specialized HTMLGenerator tailored to Puppet manifests
+ class PuppetGenerator < HTMLGenerator
+
+ def PuppetGenerator.for(options)
+ AllReferences::reset
+ HtmlMethod::reset
+
+ if options.all_one_file
+ PuppetGeneratorInOne.new(options)
+ else
+ PuppetGenerator.new(options)
+ end
+ end
+
+ def initialize(options) #:not-new:
+ @options = options
+ load_html_template
+ end
+
+ # loads our own html template file
+ def load_html_template
+ begin
+ require 'puppet/util/rdoc/generators/template/puppet/puppet'
+ extend RDoc::Page
+ rescue LoadError
+ $stderr.puts "Could not find Puppet template '#{template}'"
+ exit 99
+ end
+ end
+
+ def gen_method_index
+ # we don't generate an all define index
+ # as the presentation is per module/per class
+ end
+
+ # This is the central method, it generates the whole structures
+ # along with all the indices.
+ def generate_html
+ super
+ gen_into(@nodes)
+ gen_into(@plugins)
+ end
+
+ ##
+ # Generate:
+ # the list of modules
+ # the list of classes and definitions of a specific module
+ # the list of all classes
+ # the list of nodes
+ # the list of resources
+ def build_indices
+ @allfiles = []
+ @nodes = []
+ @plugins = []
+
+ # contains all the seen modules
+ @modules = {}
+ @allclasses = {}
+
+ # build the modules, classes and per modules classes and define list
+ @toplevels.each do |toplevel|
+ next unless toplevel.document_self
+ file = HtmlFile.new(toplevel, @options, FILE_DIR)
+ classes = []
+ methods = []
+ modules = []
+ nodes = []
+
+ # find all classes of this toplevel
+ # store modules if we find one
+ toplevel.each_classmodule do |k|
+ generate_class_list(classes, modules, k, toplevel, CLASS_DIR)
+ end
+
+ # find all defines belonging to this toplevel
+ HtmlMethod.all_methods.each do |m|
+ # find parent module, check this method is not already
+ # defined.
+ if m.context.parent.toplevel === toplevel
+ methods << m
+ end
+ end
+
+ classes.each do |k|
+ @allclasses[k.index_name] = k if !@allclasses.has_key?(k.index_name)
+ end
+
+ # generate nodes and plugins found
+ classes.each do |k|
+ if k.context.is_module?
+ k.context.each_node do |name,node|
+ nodes << HTMLPuppetNode.new(node, toplevel, NODE_DIR, @options)
+ @nodes << nodes.last
+ end
+ k.context.each_plugin do |plugin|
+ @plugins << HTMLPuppetPlugin.new(plugin, toplevel, PLUGIN_DIR, @options)
+ end
+ k.context.each_fact do |fact|
+ @plugins << HTMLPuppetPlugin.new(fact, toplevel, PLUGIN_DIR, @options)
+ end
+ end
+ end
+
+ @files << file
+ @allfiles << { "file" => file, "modules" => modules, "classes" => classes, "methods" => methods, "nodes" => nodes }
+ end
+
+ @classes = @allclasses.values
+ end
+
+ # produce a class/module list of HTMLPuppetModule/HTMLPuppetClass
+ # based on the code object traversal.
+ def generate_class_list(classes, modules, from, html_file, class_dir)
+ if from.is_module? and !@modules.has_key?(from.name)
+ k = HTMLPuppetModule.new(from, html_file, class_dir, @options)
+ classes << k
+ @modules[from.name] = k
+ modules << @modules[from.name]
+ elsif from.is_module?
+ modules << @modules[from.name]
+ elsif !from.is_module?
+ k = HTMLPuppetClass.new(from, html_file, class_dir, @options)
+ classes << k
+ end
+ from.each_classmodule do |mod|
+ generate_class_list(classes, modules, mod, html_file, class_dir)
+ end
+ end
+
+ # generate all the subdirectories, modules, classes and files
+ def gen_sub_directories
+ begin
+ super
+ File.makedirs(MODULE_DIR)
+ File.makedirs(NODE_DIR)
+ File.makedirs(PLUGIN_DIR)
+ rescue
+ $stderr.puts $!.message
+ exit 1
+ end
+ end
+
+ # generate the index of modules
+ def gen_file_index
+ gen_top_index(@modules.values, 'All Modules', RDoc::Page::TOP_INDEX, "fr_modules_index.html")
+ end
+
+ # generate a top index
+ def gen_top_index(collection, title, template, filename)
+ template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template)
+ res = []
+ collection.sort.each do |f|
+ if f.document_self
+ res << { "classlist" => CGI.escapeHTML("#{MODULE_DIR}/fr_#{f.index_name}.html"), "module" => CGI.escapeHTML("#{CLASS_DIR}/#{f.index_name}.html"),"name" => CGI.escapeHTML(f.index_name) }
+ end
+ end
+
+ values = {
+ "entries" => res,
+ 'list_title' => CGI.escapeHTML(title),
+ 'index_url' => main_url,
+ 'charset' => @options.charset,
+ 'style_url' => style_url('', @options.css),
+ }
+
+ File.open(filename, "w") do |f|
+ template.write_html_on(f, values)
+ end
+ end
+
+ # generate the all classes index file and the combo index
+ def gen_class_index
+ gen_an_index(@classes, 'All Classes', RDoc::Page::CLASS_INDEX, "fr_class_index.html")
+ @allfiles.each do |file|
+ unless file['file'].context.file_relative_name =~ /\.rb$/
+ gen_composite_index(file,
+ RDoc::Page::COMBO_INDEX,
+ "#{MODULE_DIR}/fr_#{file["file"].context.module_name}.html")
+ end
+ end
+ end
+
+ def gen_composite_index(collection, template, filename)\
+ return if FileTest.exists?(filename)
+
+ template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template)
+ res1 = []
+ collection['classes'].sort.each do |f|
+ if f.document_self
+ unless f.context.is_module?
+ res1 << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.index_name) }
+ end
+ end
+ end
+
+ res2 = []
+ collection['methods'].sort.each do |f|
+ if f.document_self
+ res2 << { "href" => "../"+f.path, "name" => f.index_name.sub(/\(.*\)$/,'') }
+ end
+ end
+
+ module_name = []
+ res3 = []
+ res4 = []
+ collection['modules'].sort.each do |f|
+ module_name << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.index_name) }
+ unless f.facts.nil?
+ f.facts.each do |fact|
+ res3 << {"href" => "../"+CGI.escapeHTML(AllReferences["PLUGIN(#{fact.name})"].path), "name" => CGI.escapeHTML(fact.name)}
+ end
+ end
+ unless f.plugins.nil?
+ f.plugins.each do |plugin|
+ res4 << {"href" => "../"+CGI.escapeHTML(AllReferences["PLUGIN(#{plugin.name})"].path), "name" => CGI.escapeHTML(plugin.name)}
+ end
+ end
+ end
+
+ res5 = []
+ collection['nodes'].sort.each do |f|
+ if f.document_self
+ res5 << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.name) }
+ end
+ end
+
+ values = {
+ "module" => module_name,
+ "classes" => res1,
+ 'classes_title' => CGI.escapeHTML("Classes"),
+ 'defines_title' => CGI.escapeHTML("Defines"),
+ 'facts_title' => CGI.escapeHTML("Custom Facts"),
+ 'plugins_title' => CGI.escapeHTML("Plugins"),
+ 'nodes_title' => CGI.escapeHTML("Nodes"),
+ 'index_url' => main_url,
+ 'charset' => @options.charset,
+ 'style_url' => style_url('', @options.css),
+ }
+
+ values["defines"] = res2 if res2.size>0
+ values["facts"] = res3 if res3.size>0
+ values["plugins"] = res4 if res4.size>0
+ values["nodes"] = res5 if res5.size>0
+
+ File.open(filename, "w") do |f|
+ template.write_html_on(f, values)
+ end
+ end
+
+ # returns the initial_page url
+ def main_url
+ main_page = @options.main_page
+ ref = nil
+ if main_page
+ ref = AllReferences[main_page]
+ if ref
+ ref = ref.path
+ else
+ $stderr.puts "Could not find main page #{main_page}"
+ end
+ end
+
+ unless ref
+ for file in @files
+ if file.document_self and file.context.global
+ ref = CGI.escapeHTML("#{CLASS_DIR}/#{file.context.module_name}.html")
+ break
+ end
+ end
+ end
+
+ unless ref
+ for file in @files
+ if file.document_self and !file.context.global
+ ref = CGI.escapeHTML("#{CLASS_DIR}/#{file.context.module_name}.html")
+ break
+ end
+ end
+ end
+
+ unless ref
+ $stderr.puts "Couldn't find anything to document"
+ $stderr.puts "Perhaps you've used :stopdoc: in all classes"
+ exit(1)
+ end
+
+ ref
+ end
+
+ end
+
+ # This module is used to hold/generate a list of puppet resources
+ # this is used in HTMLPuppetClass and HTMLPuppetNode
+ module ResourceContainer
+ def collect_resources
+ list = @context.resource_list
+ @resources = list.collect {|m| HTMLPuppetResource.new(m, self, @options) }
+ end
+
+ def build_resource_summary_list(path_prefix='')
+ collect_resources unless @resources
+ resources = @resources.sort
+ res = []
+ resources.each do |r|
+ res << {
+ "name" => CGI.escapeHTML(r.name),
+ "aref" => "#{path_prefix}\##{r.aref}"
+ }
+ end
+ res
+ end
+
+ def build_resource_detail_list(section)
+ outer = []
+ resources = @resources.sort
+ resources.each do |r|
+ row = {}
+ if r.section == section and r.document_self
+ row["name"] = CGI.escapeHTML(r.name)
+ desc = r.description.strip
+ row["m_desc"] = desc unless desc.empty?
+ row["aref"] = r.aref
+ row["params"] = r.params
+ outer << row
+ end
+ end
+ outer
+ end
+ end
+
+ class HTMLPuppetClass < HtmlClass
+ include ResourceContainer
+
+ def value_hash
+ super
+ rl = build_resource_summary_list
+ @values["resources"] = rl unless rl.empty?
+
+ @context.sections.each do |section|
+ secdata = @values["sections"].select { |secdata| secdata["secsequence"] == section.sequence }
+ if secdata.size == 1
+ secdata = secdata[0]
+
+ rdl = build_resource_detail_list(section)
+ secdata["resource_list"] = rdl unless rdl.empty?
+ end
+ end
+ @values
+ end
+ end
+
+ class HTMLPuppetNode < ContextUser
+ include ResourceContainer
+
+ attr_reader :path
+
+ def initialize(context, html_file, prefix, options)
+ super(context, options)
+
+ @html_file = html_file
+ @is_module = context.is_module?
+ @values = {}
+
+ context.viewer = self
+
+ if options.all_one_file
+ @path = context.full_name
+ else
+ @path = http_url(context.full_name, prefix)
+ end
+
+ AllReferences.add("NODE(#{@context.full_name})", self)
+ end
+
+ def name
+ @context.name
+ end
+
+ # return the relative file name to store this class in,
+ # which is also its url
+ def http_url(full_name, prefix)
+ path = full_name.dup
+ if path['<<']
+ path.gsub!(/<<\s*(\w*)/) { "from-#$1" }
+ end
+ File.join(prefix, path.split("::")) + ".html"
+ end
+
+ def parent_name
+ @context.parent.full_name
+ end
+
+ def index_name
+ name
+ end
+
+ def write_on(f)
+ value_hash
+ template = TemplatePage.new(RDoc::Page::BODYINC,
+ RDoc::Page::NODE_PAGE,
+ RDoc::Page::METHOD_LIST)
+ template.write_html_on(f, @values)
+ end
+
+ def value_hash
+ class_attribute_values
+ add_table_of_sections
+
+ @values["charset"] = @options.charset
+ @values["style_url"] = style_url(path, @options.css)
+
+ d = markup(@context.comment)
+ @values["description"] = d unless d.empty?
+
+ ml = build_method_summary_list
+ @values["methods"] = ml unless ml.empty?
+
+ rl = build_resource_summary_list
+ @values["resources"] = rl unless rl.empty?
+
+ il = build_include_list(@context)
+ @values["includes"] = il unless il.empty?
+
+ @values["sections"] = @context.sections.map do |section|
+
+ secdata = {
+ "sectitle" => section.title,
+ "secsequence" => section.sequence,
+ "seccomment" => markup(section.comment)
+ }
+
+ al = build_alias_summary_list(section)
+ secdata["aliases"] = al unless al.empty?
+
+ co = build_constants_summary_list(section)
+ secdata["constants"] = co unless co.empty?
+
+ al = build_attribute_list(section)
+ secdata["attributes"] = al unless al.empty?
+
+ cl = build_class_list(0, @context, section)
+ secdata["classlist"] = cl unless cl.empty?
+
+ mdl = build_method_detail_list(section)
+ secdata["method_list"] = mdl unless mdl.empty?
+
+ rdl = build_resource_detail_list(section)
+ secdata["resource_list"] = rdl unless rdl.empty?
+
+ secdata
+ end
+
+ @values
+ end
+
+ def build_attribute_list(section)
+ atts = @context.attributes.sort
+ res = []
+ atts.each do |att|
+ next unless att.section == section
+ if att.visibility == :public || att.visibility == :protected || @options.show_all
+ entry = {
+ "name" => CGI.escapeHTML(att.name),
+ "rw" => att.rw,
+ "a_desc" => markup(att.comment, true)
+ }
+ unless att.visibility == :public || att.visibility == :protected
+ entry["rw"] << "-"
+ end
+ res << entry
+ end
+ end
+ res
+ end
+
+ def class_attribute_values
+ h_name = CGI.escapeHTML(name)
+
+ @values["classmod"] = "Node"
+ @values["title"] = "#{@values['classmod']}: #{h_name}"
+
+ c = @context
+ c = c.parent while c and !c.diagram
+
+ if c && c.diagram
+ @values["diagram"] = diagram_reference(c.diagram)
+ end
+
+ @values["full_name"] = h_name
+
+ parent_class = @context.superclass
+
+ if parent_class
+ @values["parent"] = CGI.escapeHTML(parent_class)
+
+ if parent_name
+ lookup = parent_name + "::" + parent_class
+ else
+ lookup = parent_class
+ end
+ lookup = "NODE(#{lookup})"
+ parent_url = AllReferences[lookup] || AllReferences[parent_class]
+ if parent_url and parent_url.document_self
+ @values["par_url"] = aref_to(parent_url.path)
+ end
+ end
+
+ files = []
+ @context.in_files.each do |f|
+ res = {}
+ full_path = CGI.escapeHTML(f.file_absolute_name)
+
+ res["full_path"] = full_path
+ res["full_path_url"] = aref_to(f.viewer.path) if f.document_self
+
+ if @options.webcvs
+ res["cvsurl"] = cvs_url( @options.webcvs, full_path )
+ end
+
+ files << res
+ end
+
+ @values['infiles'] = files
+ end
+
+ def <=>(other)
+ self.name <=> other.name
+ end
+ end
+
+ class HTMLPuppetModule < HtmlClass
+
+ def initialize(context, html_file, prefix, options)
+ super(context, html_file, prefix, options)
+ end
+
+ def value_hash
+ @values = super
+
+ fl = build_facts_summary_list
+ @values["facts"] = fl unless fl.empty?
+
+ pl = build_plugins_summary_list
+ @values["plugins"] = pl unless pl.empty?
+
+ nl = build_nodes_list(0, @context)
+ @values["nodelist"] = nl unless nl.empty?
+
+ @values
+ end
+
+ def build_nodes_list(level, context)
+ res = ""
+ prefix = "&nbsp;&nbsp;::" * level;
+
+ context.nodes.sort.each do |node|
+ if node.document_self
+ res <<
+ prefix <<
+ "Node " <<
+ href(url(node.viewer.path), "link", node.full_name) <<
+ "<br />\n"
+ end
+ end
+ res
+ end
+
+ def build_facts_summary_list
+ potentially_referenced_list(context.facts) {|fn| ["PLUGIN(#{fn})"] }
+ end
+
+ def build_plugins_summary_list
+ potentially_referenced_list(context.plugins) {|fn| ["PLUGIN(#{fn})"] }
+ end
+
+ def facts
+ @context.facts
+ end
+
+ def plugins
+ @context.plugins
+ end
+
+ end
+
+ class HTMLPuppetPlugin < ContextUser
+ attr_reader :path
+
+ def initialize(context, html_file, prefix, options)
+ super(context, options)
+
+ @html_file = html_file
+ @is_module = false
+ @values = {}
+
+ context.viewer = self
+
+ if options.all_one_file
+ @path = context.full_name
+ else
+ @path = http_url(context.full_name, prefix)
+ end
+
+ AllReferences.add("PLUGIN(#{@context.full_name})", self)
+ end
+
+ def name
+ @context.name
+ end
+
+ # return the relative file name to store this class in,
+ # which is also its url
+ def http_url(full_name, prefix)
+ path = full_name.dup
+ if path['<<']
+ path.gsub!(/<<\s*(\w*)/) { "from-#$1" }
+ end
+ File.join(prefix, path.split("::")) + ".html"
+ end
+
+ def parent_name
+ @context.parent.full_name
+ end
+
+ def index_name
+ name
+ end
+
+ def write_on(f)
+ value_hash
+ template = TemplatePage.new(RDoc::Page::BODYINC,
+ RDoc::Page::PLUGIN_PAGE,
+ RDoc::Page::PLUGIN_LIST)
+ template.write_html_on(f, @values)
+ end
+
+ def value_hash
+ attribute_values
+ add_table_of_sections
+
+ @values["charset"] = @options.charset
+ @values["style_url"] = style_url(path, @options.css)
+
+ d = markup(@context.comment)
+ @values["description"] = d unless d.empty?
+
+ if context.is_fact?
+ unless context.confine.empty?
+ res = {}
+ res["type"] = context.confine[:type]
+ res["value"] = context.confine[:value]
+ @values["confine"] = [res]
+ end
+ else
+ @values["type"] = context.type
+ end
+
+ @values["sections"] = @context.sections.map do |section|
+ secdata = {
+ "sectitle" => section.title,
+ "secsequence" => section.sequence,
+ "seccomment" => markup(section.comment)
+ }
+ secdata
+ end
+
+ @values
+ end
+
+ def attribute_values
+ h_name = CGI.escapeHTML(name)
+
+ if @context.is_fact?
+ @values["classmod"] = "Fact"
+ else
+ @values["classmod"] = "Plugin"
+ end
+ @values["title"] = "#{@values['classmod']}: #{h_name}"
+
+ c = @context
+ @values["full_name"] = h_name
+
+ files = []
+ @context.in_files.each do |f|
+ res = {}
+ full_path = CGI.escapeHTML(f.file_absolute_name)
+
+ res["full_path"] = full_path
+ res["full_path_url"] = aref_to(f.viewer.path) if f.document_self
+
+ if @options.webcvs
+ res["cvsurl"] = cvs_url( @options.webcvs, full_path )
+ end
+
+ files << res
+ end
+
+ @values['infiles'] = files
+ end
+
+ def <=>(other)
+ self.name <=> other.name
+ end
+
+ end
+
+ class HTMLPuppetResource
+ include MarkUp
+
+ attr_reader :context
+
+ @@seq = "R000000"
+
+ def initialize(context, html_class, options)
+ @context = context
+ @html_class = html_class
+ @options = options
+ @@seq = @@seq.succ
+ @seq = @@seq
+
+ context.viewer = self
+
+ AllReferences.add(name, self)
+ end
+
+ def as_href(from_path)
+ if @options.all_one_file
+ "#" + path
+ else
+ HTMLGenerator.gen_url(from_path, path)
+ end
+ end
+
+ def name
+ @context.name
+ end
+
+ def section
+ @context.section
+ end
+
+ def index_name
+ "#{@context.name}"
+ end
+
+ def params
+ @context.params
+ end
+
+ def parent_name
+ if @context.parent.parent
+ @context.parent.parent.full_name
+ else
+ nil
+ end
+ end
+
+ def aref
+ @seq
+ end
+
+ def path
+ if @options.all_one_file
+ aref
+ else
+ @html_class.path + "#" + aref
+ end
+ end
+
+ def description
+ markup(@context.comment)
+ end
+
+ def <=>(other)
+ @context <=> other.context
+ end
+
+ def document_self
+ @context.document_self
+ end
+
+ def find_symbol(symbol, method=nil)
+ res = @context.parent.find_symbol(symbol, method)
+ if res
+ res = res.viewer
+ end
+ res
+ end
+
+ end
+
+ class PuppetGeneratorInOne < HTMLGeneratorInOne
+ def gen_method_index
+ gen_an_index(HtmlMethod.all_methods, 'Defines')
+ end
+ end
+
+end
diff --git a/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb b/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb
new file mode 100644
index 000000000..c71f81915
--- /dev/null
+++ b/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb
@@ -0,0 +1,1051 @@
+#
+# = CSS2 RDoc HTML template
+#
+# This is a template for RDoc that uses XHTML 1.0 Transitional and dictates a
+# bit more of the appearance of the output to cascading stylesheets than the
+# default. It was designed for clean inline code display, and uses DHTMl to
+# toggle the visbility of each method's source with each click on the '[source]'
+# link.
+#
+# == Authors
+#
+# * Michael Granger <ged@FaerieMUD.org>
+#
+# Copyright (c) 2002, 2003 The FaerieMUD Consortium. Some rights reserved.
+#
+# This work is licensed under the Creative Commons Attribution License. To view
+# a copy of this license, visit http://creativecommons.org/licenses/by/1.0/ or
+# send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California
+# 94305, USA.
+#
+
+module RDoc
+ module Page
+
+ FONTS = "Verdana,Arial,Helvetica,sans-serif"
+
+STYLE = %{
+body {
+ font-family: Verdana,Arial,Helvetica,sans-serif;
+ font-size: 90%;
+ margin: 0;
+ margin-left: 40px;
+ padding: 0;
+ background: white;
+}
+
+h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
+h1 { font-size: 150%; }
+h2,h3,h4 { margin-top: 1em; }
+
+a { background: #eef; color: #039; text-decoration: none; }
+a:hover { background: #039; color: #eef; }
+
+/* Override the base stylesheet's Anchor inside a table cell */
+td > a {
+ background: transparent;
+ color: #039;
+ text-decoration: none;
+}
+
+/* and inside a section title */
+.section-title > a {
+ background: transparent;
+ color: #eee;
+ text-decoration: none;
+}
+
+/* === Structural elements =================================== */
+
+div#index {
+ margin: 0;
+ margin-left: -40px;
+ padding: 0;
+ font-size: 90%;
+}
+
+
+div#index a {
+ margin-left: 0.7em;
+}
+
+div#index .section-bar {
+ margin-left: 0px;
+ padding-left: 0.7em;
+ background: #ccc;
+ font-size: small;
+}
+
+
+div#classHeader, div#fileHeader {
+ width: auto;
+ color: white;
+ padding: 0.5em 1.5em 0.5em 1.5em;
+ margin: 0;
+ margin-left: -40px;
+ border-bottom: 3px solid #006;
+}
+
+div#classHeader a, div#fileHeader a {
+ background: inherit;
+ color: white;
+}
+
+div#classHeader td, div#fileHeader td {
+ background: inherit;
+ color: white;
+}
+
+
+div#fileHeader {
+ background: #057;
+}
+
+div#classHeader {
+ background: #048;
+}
+
+div#nodeHeader {
+ background: #7f7f7f;
+}
+
+.class-name-in-header {
+ font-size: 180%;
+ font-weight: bold;
+}
+
+
+div#bodyContent {
+ padding: 0 1.5em 0 1.5em;
+}
+
+div#description {
+ padding: 0.5em 1.5em;
+ background: #efefef;
+ border: 1px dotted #999;
+}
+
+div#description h1,h2,h3,h4,h5,h6 {
+ color: #125;;
+ background: transparent;
+}
+
+div#validator-badges {
+ text-align: center;
+}
+div#validator-badges img { border: 0; }
+
+div#copyright {
+ color: #333;
+ background: #efefef;
+ font: 0.75em sans-serif;
+ margin-top: 5em;
+ margin-bottom: 0;
+ padding: 0.5em 2em;
+}
+
+
+/* === Classes =================================== */
+
+table.header-table {
+ color: white;
+ font-size: small;
+}
+
+.type-note {
+ font-size: small;
+ color: #DEDEDE;
+}
+
+.xxsection-bar {
+ background: #eee;
+ color: #333;
+ padding: 3px;
+}
+
+.section-bar {
+ color: #333;
+ border-bottom: 1px solid #999;
+ margin-left: -20px;
+}
+
+
+.section-title {
+ background: #79a;
+ color: #eee;
+ padding: 3px;
+ margin-top: 2em;
+ margin-left: -30px;
+ border: 1px solid #999;
+}
+
+.top-aligned-row { vertical-align: top }
+.bottom-aligned-row { vertical-align: bottom }
+
+/* --- Context section classes ----------------------- */
+
+.context-row { }
+.context-item-name { font-family: monospace; font-weight: bold; color: black; }
+.context-item-value { font-size: small; color: #448; }
+.context-item-desc { color: #333; padding-left: 2em; }
+
+/* --- Method classes -------------------------- */
+.method-detail {
+ background: #efefef;
+ padding: 0;
+ margin-top: 0.5em;
+ margin-bottom: 1em;
+ border: 1px dotted #ccc;
+}
+.method-heading {
+ color: black;
+ background: #ccc;
+ border-bottom: 1px solid #666;
+ padding: 0.2em 0.5em 0 0.5em;
+}
+.method-signature { color: black; background: inherit; }
+.method-name { font-weight: bold; }
+.method-args { font-style: italic; }
+.method-description { padding: 0 0.5em 0 0.5em; }
+
+/* --- Source code sections -------------------- */
+
+a.source-toggle { font-size: 90%; }
+div.method-source-code {
+ background: #262626;
+ color: #ffdead;
+ margin: 1em;
+ padding: 0.5em;
+ border: 1px dashed #999;
+ overflow: hidden;
+}
+
+div.method-source-code pre { color: #ffdead; overflow: hidden; }
+
+/* --- Ruby keyword styles --------------------- */
+
+.standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
+
+.ruby-constant { color: #7fffd4; background: transparent; }
+.ruby-keyword { color: #00ffff; background: transparent; }
+.ruby-ivar { color: #eedd82; background: transparent; }
+.ruby-operator { color: #00ffee; background: transparent; }
+.ruby-identifier { color: #ffdead; background: transparent; }
+.ruby-node { color: #ffa07a; background: transparent; }
+.ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
+.ruby-regexp { color: #ffa07a; background: transparent; }
+.ruby-value { color: #7fffd4; background: transparent; }
+}
+
+
+#####################################################################
+### H E A D E R T E M P L A T E
+#####################################################################
+
+XHTML_PREAMBLE = %{<?xml version="1.0" encoding="%charset%"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+}
+
+HEADER = XHTML_PREAMBLE + %{
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <meta http-equiv="Content-Script-Type" content="text/javascript" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
+ <script type="text/javascript">
+ // <![CDATA[
+
+ function popupCode( url ) {
+ window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
+ }
+
+ function toggleCode( id ) {
+ if ( document.getElementById )
+ elem = document.getElementById( id );
+ else if ( document.all )
+ elem = eval( "document.all." + id );
+ else
+ return false;
+
+ elemStyle = elem.style;
+
+ if ( elemStyle.display != "block" ) {
+ elemStyle.display = "block"
+ } else {
+ elemStyle.display = "none"
+ }
+
+ return true;
+ }
+
+ // Make codeblocks hidden by default
+ document.writeln( "<style type=\\"text/css\\">div.method-source-code { display: none }</style>" )
+
+ // ]]>
+ </script>
+
+</head>
+<body>
+}
+
+
+#####################################################################
+### C O N T E X T C O N T E N T T E M P L A T E
+#####################################################################
+
+CONTEXT_CONTENT = %{
+}
+
+
+#####################################################################
+### F O O T E R T E M P L A T E
+#####################################################################
+FOOTER = %{
+<div id="validator-badges">
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
+</div>
+
+</body>
+</html>
+}
+
+
+#####################################################################
+### F I L E P A G E H E A D E R T E M P L A T E
+#####################################################################
+
+FILE_PAGE = %{
+ <div id="fileHeader">
+ <h1>%short_name%</h1>
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>Path:</strong></td>
+ <td>%full_path%
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
+ENDIF:cvsurl
+ </td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>Last Update:</strong></td>
+ <td>%dtm_modified%</td>
+ </tr>
+ </table>
+ </div>
+}
+
+
+#####################################################################
+### C L A S S P A G E H E A D E R T E M P L A T E
+#####################################################################
+
+CLASS_PAGE = %{
+ <div id="classHeader">
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>%classmod%</strong></td>
+ <td class="class-name-in-header">%full_name%</td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>In:</strong></td>
+ <td>
+START:infiles
+IF:full_path_url
+ <a href="%full_path_url%">
+ENDIF:full_path_url
+ %full_path%
+IF:full_path_url
+ </a>
+ENDIF:full_path_url
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
+ENDIF:cvsurl
+ <br />
+END:infiles
+ </td>
+ </tr>
+
+IF:parent
+ <tr class="top-aligned-row">
+ <td><strong>Parent:</strong></td>
+ <td>
+IF:par_url
+ <a href="%par_url%">
+ENDIF:par_url
+ %parent%
+IF:par_url
+ </a>
+ENDIF:par_url
+ </td>
+ </tr>
+ENDIF:parent
+ </table>
+ </div>
+}
+
+NODE_PAGE = %{
+ <div id="nodeHeader">
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>%classmod%</strong></td>
+ <td class="class-name-in-header">%full_name%</td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>In:</strong></td>
+ <td>
+START:infiles
+IF:full_path_url
+ <a href="%full_path_url%">
+ENDIF:full_path_url
+ %full_path%
+IF:full_path_url
+ </a>
+ENDIF:full_path_url
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
+ENDIF:cvsurl
+ <br />
+END:infiles
+ </td>
+ </tr>
+
+IF:parent
+ <tr class="top-aligned-row">
+ <td><strong>Parent:</strong></td>
+ <td>
+IF:par_url
+ <a href="%par_url%">
+ENDIF:par_url
+ %parent%
+IF:par_url
+ </a>
+ENDIF:par_url
+ </td>
+ </tr>
+ENDIF:parent
+ </table>
+ </div>
+}
+
+PLUGIN_PAGE = %{
+ <div id="classHeader">
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>%classmod%</strong></td>
+ <td class="class-name-in-header">%full_name%</td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>In:</strong></td>
+ <td>
+START:infiles
+IF:full_path_url
+ <a href="%full_path_url%">
+ENDIF:full_path_url
+ %full_path%
+IF:full_path_url
+ </a>
+ENDIF:full_path_url
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
+ENDIF:cvsurl
+ <br />
+END:infiles
+ </td>
+ </tr>
+ </table>
+ </div>
+}
+
+
+#####################################################################
+### M E T H O D L I S T T E M P L A T E
+#####################################################################
+
+PLUGIN_LIST = %{
+
+ <div id="contextContent">
+IF:description
+ <div id="description">
+ %description%
+ </div>
+ENDIF:description
+
+
+IF:toc
+ <div id="contents-list">
+ <h3 class="section-bar">Contents</h3>
+ <ul>
+START:toc
+ <li><a href="#%href%">%secname%</a></li>
+END:toc
+ </ul>
+ENDIF:toc
+ </div>
+
+ </div>
+
+<!-- Confine -->
+IF:confine
+START:confine
+ <div id="attribute-list">
+ <h3 class="section-bar">Confine</h3>
+ %type%&nbsp;%value%
+ <div class="name-list">
+ </div>
+ </div>
+END:confine
+ENDIF:confine
+
+<!-- Type -->
+IF:type
+ <div id="attribute-list">
+ <h3 class="section-bar">Type</h3>
+ %type%
+ <div class="name-list">
+ </div>
+ </div>
+ENDIF:type
+
+START:sections
+ <div id="section">
+IF:sectitle
+ <h2 class="section-title"><a name="%secsequence%">%sectitle%</a></h2>
+IF:seccomment
+ <div class="section-comment">
+ %seccomment%
+ </div>
+ENDIF:seccomment
+ENDIF:sectitle
+END:sections
+}
+
+
+METHOD_LIST = %{
+
+ <div id="contextContent">
+IF:diagram
+ <div id="diagram">
+ %diagram%
+ </div>
+ENDIF:diagram
+
+IF:description
+ <div id="description">
+ %description%
+ </div>
+ENDIF:description
+
+IF:requires
+ <div id="requires-list">
+ <h3 class="section-bar">Required files</h3>
+
+ <div class="name-list">
+START:requires
+ HREF:aref:name:&nbsp;&nbsp;
+END:requires
+ </div>
+ </div>
+ENDIF:requires
+
+IF:toc
+ <div id="contents-list">
+ <h3 class="section-bar">Contents</h3>
+ <ul>
+START:toc
+ <li><a href="#%href%">%secname%</a></li>
+END:toc
+ </ul>
+ENDIF:toc
+ </div>
+
+IF:methods
+ <div id="method-list">
+ <h3 class="section-bar">Defines</h3>
+
+ <div class="name-list">
+START:methods
+ HREF:aref:name:&nbsp;&nbsp;
+END:methods
+ </div>
+ </div>
+ENDIF:methods
+
+IF:resources
+ <div id="method-list">
+ <h3 class="section-bar">Resources</h3>
+
+ <div class="name-list">
+START:resources
+ HREF:aref:name:&nbsp;&nbsp;
+END:resources
+ </div>
+ </div>
+ENDIF:resources
+
+ </div>
+
+
+ <!-- if includes -->
+IF:includes
+ <div id="includes">
+ <h3 class="section-bar">Included Classes</h3>
+
+ <div id="includes-list">
+START:includes
+ <span class="include-name">HREF:aref:name:</span>
+END:includes
+ </div>
+ </div>
+ENDIF:includes
+
+START:sections
+ <div id="section">
+IF:sectitle
+ <h2 class="section-title"><a name="%secsequence%">%sectitle%</a></h2>
+IF:seccomment
+ <div class="section-comment">
+ %seccomment%
+ </div>
+ENDIF:seccomment
+ENDIF:sectitle
+
+
+<!-- if facts -->
+IF:facts
+ <div id="class-list">
+ <h3 class="section-bar">Custom Facts</h3>
+START:facts
+ HREF:aref:name:&nbsp;&nbsp;
+END:facts
+ </div>
+ENDIF:facts
+
+<!-- if plugins -->
+IF:plugins
+ <div id="class-list">
+ <h3 class="section-bar">Plugins</h3>
+START:plugins
+HREF:aref:name:&nbsp;&nbsp;
+END:plugins
+ </div>
+ENDIF:plugins
+
+<!-- if nodes -->
+IF:nodelist
+ <div id="class-list">
+ <h3 class="section-bar">Nodes</h3>
+
+ %nodelist%
+ </div>
+ENDIF:nodelist
+
+<!-- if class -->
+IF:classlist
+ <div id="class-list">
+ <h3 class="section-bar">Classes and Modules</h3>
+
+ %classlist%
+ </div>
+ENDIF:classlist
+
+IF:constants
+ <div id="constants-list">
+ <h3 class="section-bar">Global Variables</h3>
+
+ <div class="name-list">
+ <table summary="Variables">
+START:constants
+ <tr class="top-aligned-row context-row">
+ <td class="context-item-name">%name%</td>
+ <td>=</td>
+ <td class="context-item-value">%value%</td>
+IF:desc
+ <td width="3em">&nbsp;</td>
+ <td class="context-item-desc">%desc%</td>
+ENDIF:desc
+ </tr>
+END:constants
+ </table>
+ </div>
+ </div>
+ENDIF:constants
+
+IF:aliases
+ <div id="aliases-list">
+ <h3 class="section-bar">External Aliases</h3>
+
+ <div class="name-list">
+ <table summary="aliases">
+START:aliases
+ <tr class="top-aligned-row context-row">
+ <td class="context-item-name">%old_name%</td>
+ <td>-&gt;</td>
+ <td class="context-item-value">%new_name%</td>
+ </tr>
+IF:desc
+ <tr class="top-aligned-row context-row">
+ <td>&nbsp;</td>
+ <td colspan="2" class="context-item-desc">%desc%</td>
+ </tr>
+ENDIF:desc
+END:aliases
+ </table>
+ </div>
+ </div>
+ENDIF:aliases
+
+
+IF:attributes
+ <div id="attribute-list">
+ <h3 class="section-bar">Attributes</h3>
+
+ <div class="name-list">
+ <table>
+START:attributes
+ <tr class="top-aligned-row context-row">
+ <td class="context-item-name">%name%</td>
+IF:rw
+ <td class="context-item-value">&nbsp;[%rw%]&nbsp;</td>
+ENDIF:rw
+IFNOT:rw
+ <td class="context-item-value">&nbsp;&nbsp;</td>
+ENDIF:rw
+ <td class="context-item-desc">%a_desc%</td>
+ </tr>
+END:attributes
+ </table>
+ </div>
+ </div>
+ENDIF:attributes
+
+
+
+ <!-- if method_list -->
+IF:method_list
+ <div id="methods">
+START:method_list
+IF:methods
+ <h3 class="section-bar">Defines</h3>
+
+START:methods
+ <div id="method-%aref%" class="method-detail">
+ <a name="%aref%"></a>
+
+ <div class="method-heading">
+IF:codeurl
+ <a href="%codeurl%" target="Code" class="method-signature"
+ onclick="popupCode('%codeurl%');return false;">
+ENDIF:codeurl
+IF:sourcecode
+ <a href="#%aref%" class="method-signature">
+ENDIF:sourcecode
+IF:callseq
+ <span class="method-name">%callseq%</span>
+ENDIF:callseq
+IFNOT:callseq
+ <span class="method-name">%name%</span><span class="method-args">%params%</span>
+ENDIF:callseq
+IF:codeurl
+ </a>
+ENDIF:codeurl
+IF:sourcecode
+ </a>
+ENDIF:sourcecode
+ </div>
+
+ <div class="method-description">
+IF:m_desc
+ %m_desc%
+ENDIF:m_desc
+IF:sourcecode
+ <p><a class="source-toggle" href="#"
+ onclick="toggleCode('%aref%-source');return false;">[Source]</a></p>
+ <div class="method-source-code" id="%aref%-source">
+<pre>
+%sourcecode%
+</pre>
+ </div>
+ENDIF:sourcecode
+ </div>
+ </div>
+
+END:methods
+ENDIF:methods
+END:method_list
+
+ </div>
+ENDIF:method_list
+
+
+ <!-- if resource_list -->
+IF:resource_list
+ <div id="resources">
+ <h3 class="section-bar">Resources</h3>
+START:resource_list
+
+ <div id="method-%aref%" class="method-detail">
+ <a name="%aref%"></a>
+
+ <div class="method-heading">
+ <span class="method-name">%name%</span><br />
+IF:params
+START:params
+ &nbsp;&nbsp;&nbsp;<span class="method-args">%name% => %value%</span><br />
+END:params
+ENDIF:params
+ </div>
+
+ <div class="method-description">
+IF:m_desc
+ %m_desc%
+ENDIF:m_desc
+ </div>
+ </div>
+END:resource_list
+
+ </div>
+ENDIF:resource_list
+
+END:sections
+}
+
+
+#####################################################################
+### B O D Y T E M P L A T E
+#####################################################################
+
+BODY = HEADER + %{
+
+!INCLUDE! <!-- banner header -->
+
+ <div id="bodyContent">
+
+} + METHOD_LIST + %{
+
+ </div>
+
+} + FOOTER
+
+BODYINC = HEADER + %{
+
+!INCLUDE! <!-- banner header -->
+
+ <div id="bodyContent">
+
+!INCLUDE!
+
+ </div>
+
+} + FOOTER
+
+
+
+#####################################################################
+### S O U R C E C O D E T E M P L A T E
+#####################################################################
+
+SRC_PAGE = XHTML_PREAMBLE + %{
+<html>
+<head>
+ <title>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
+</head>
+<body class="standalone-code">
+ <pre>%code%</pre>
+</body>
+</html>
+}
+
+
+#####################################################################
+### I N D E X F I L E T E M P L A T E S
+#####################################################################
+
+FR_INDEX_BODY = %{
+!INCLUDE!
+}
+
+FILE_INDEX = XHTML_PREAMBLE + %{
+<!--
+
+ %list_title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%list_title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" />
+ <base target="docwin" />
+</head>
+<body>
+<div id="index">
+ <h1 class="section-bar">%list_title%</h1>
+ <div id="index-entries">
+START:entries
+ <a href="%href%">%name%</a><br />
+END:entries
+ </div>
+</div>
+</body>
+</html>
+}
+
+TOP_INDEX = XHTML_PREAMBLE + %{
+<!--
+
+ %list_title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%list_title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" />
+ <base target="classes" />
+ <SCRIPT LANGUAGE="JavaScript">
+ <!--
+ function load(classlist,module) {
+ parent.classes.location.href = classlist;
+ parent.docwin.location.href = module;
+ }
+ //--></SCRIPT>
+</head>
+<body>
+<div id="index">
+ <h1 class="section-bar">%list_title%</h1>
+ <div id="index-entries">
+START:entries
+ <a href="%classlist%" onclick="load('%classlist%','%module%'); return true;">%name%</a><br />
+END:entries
+ </div>
+</div>
+</body>
+</html>
+}
+
+
+CLASS_INDEX = FILE_INDEX
+METHOD_INDEX = FILE_INDEX
+
+COMBO_INDEX = XHTML_PREAMBLE + %{
+<!--
+
+ %classes_title% &amp; %defines_title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%classes_title% &amp; %defines_title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="../%style_url%" type="text/css" />
+ <base target="docwin" />
+ <SCRIPT LANGUAGE="JavaScript">
+ <!--
+ function load(url) {
+ parent.docwin.location.href = url;
+ }
+ //--></SCRIPT>
+
+</head>
+<body>
+<div id="index">
+
+ <a href="../fr_class_index.html" target="classes">All Classes</a><br />
+
+
+<h1 class="section-bar">Module</h1>
+ <div id="index-entries">
+START:module
+ <a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:module
+ </div>
+ </div>
+<div id="index">
+
+IF:nodes
+ <h1 class="section-bar">%nodes_title%</h1>
+ <div id="index-entries">
+START:nodes
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:nodes
+ </div>
+ENDIF:nodes
+
+IF:classes
+ <h1 class="section-bar">%classes_title%</h1>
+ <div id="index-entries">
+START:classes
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:classes
+ </div>
+ENDIF:classes
+
+IF:defines
+ <h1 class="section-bar">%defines_title%</h1>
+ <div id="index-entries">
+START:defines
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:defines
+ </div>
+ENDIF:defines
+
+IF:facts
+ <h1 class="section-bar">%facts_title%</h1>
+ <div id="index-entries">
+START:facts
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:facts
+ </div>
+ENDIF:facts
+
+
+IF:plugins
+ <h1 class="section-bar">%plugins_title%</h1>
+ <div id="index-entries">
+START:plugins
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:plugins
+ </div>
+ENDIF:plugins
+
+</div>
+</body>
+</html>
+}
+
+INDEX = %{<?xml version="1.0" encoding="%charset%"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+
+<!--
+
+ %title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+</head>
+<frameset cols="20%, 80%">
+ <frameset rows="30%,70%">
+ <frame src="fr_modules_index.html" title="All Modules" />
+ <frame src="fr_class_index.html" name="classes" title="Classes & Defines" />
+ </frameset>
+ <frame src="%initial_page%" name="docwin" />
+</frameset>
+</html>
+}
+
+
+
+ end # module Page
+end # class RDoc
+
+require 'rdoc/generators/template/html/one_page_html'
diff --git a/lib/puppet/util/rdoc/parser.rb b/lib/puppet/util/rdoc/parser.rb
new file mode 100644
index 000000000..33dd6564f
--- /dev/null
+++ b/lib/puppet/util/rdoc/parser.rb
@@ -0,0 +1,437 @@
+# Puppet "parser" for the rdoc system
+# The parser uses puppet parser and traverse the AST to instruct RDoc about
+# our current structures. It also parses ruby files that could contain
+# either custom facts or puppet plugins (functions, types...)
+
+# rdoc mandatory includes
+require "rdoc/code_objects"
+require "puppet/util/rdoc/code_objects"
+require "rdoc/tokenstream"
+require "rdoc/markup/simple_markup/preprocess"
+require "rdoc/parsers/parserfactory"
+
+module RDoc
+
+class Parser
+ extend ParserFactory
+
+ # parser registration into RDoc
+ parse_files_matching(/\.(rb|pp)$/)
+
+ # called with the top level file
+ def initialize(top_level, file_name, content, options, stats)
+ @options = options
+ @stats = stats
+ @input_file_name = file_name
+ @top_level = PuppetTopLevel.new(top_level)
+ @progress = $stderr unless options.quiet
+ end
+
+ # main entry point
+ def scan
+ Puppet.info "rdoc: scanning %s" % @input_file_name
+ if @input_file_name =~ /\.pp$/
+ @parser = Puppet::Parser::Parser.new(:environment => Puppet[:environment])
+ @parser.file = @input_file_name
+ @ast = @parser.parse
+ end
+ scan_top_level(@top_level)
+ @top_level
+ end
+
+ private
+
+ # walk down the namespace and lookup/create container as needed
+ 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 = container.find_module_named(name)
+ unless container
+ container = prev_container.add_module(PuppetClass, name)
+ end
+ end
+ return [container, final_name]
+ 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.
+ def split_module(path)
+ # find a module
+ fullpath = File.expand_path(path)
+ Puppet.debug "rdoc: testing %s" % fullpath
+ if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins)\/.+\.(pp|rb)$/
+ modpath = $1
+ name = $2
+ Puppet.debug "rdoc: module %s into %s ?" % [name, modpath]
+ Puppet::Module.modulepath().each do |mp|
+ if File.identical?(modpath,mp)
+ Puppet.debug "rdoc: found module %s" % 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>
+ Puppet::Module.modulepath().each do |mp|
+ # check that fullpath is a descendant of mp
+ dirname = fullpath
+ while (dirname = File.dirname(dirname)) != '/'
+ return nil if File.identical?(dirname,mp)
+ end
+ end
+ end
+ # we are under a global manifests
+ Puppet.debug "rdoc: global manifests"
+ return "<site>"
+ end
+
+ # create documentation for the top level +container+
+ def scan_top_level(container)
+ # use the module README as documentation for the module
+ comment = ""
+ readme = File.join(File.dirname(File.dirname(@input_file_name)), "README")
+ comment = File.open(readme,"r") { |f| f.read } if FileTest.readable?(readme)
+ look_for_directives_in(container, comment) unless comment.empty?
+
+ # infer module name from directory
+ name = split_module(@input_file_name)
+ 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 %s" % name
+
+ container.module_name = name
+ container.global=true if name == "<site>"
+
+ @stats.num_modules += 1
+ container, name = get_class_or_module(container,name)
+ mod = container.add_module(PuppetModule, name)
+ mod.record_location(@top_level)
+ mod.comment = comment
+
+ if @input_file_name =~ /\.pp$/
+ parse_elements(mod)
+ elsif @input_file_name =~ /\.rb$/
+ parse_plugins(mod)
+ end
+ end
+
+ # create documentation for include statements we can find in +code+
+ # and associate it with +container+
+ def scan_for_include(container, code)
+ code.each do |stmt|
+ scan_for_include(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray)
+
+ if stmt.is_a?(Puppet::Parser::AST::Function) and stmt.name == "include"
+ stmt.arguments.each do |included|
+ Puppet.debug "found include: %s" % included.value
+ container.add_include(Include.new(included.value, stmt.doc))
+ end
+ end
+ end
+ end
+
+ # create documentation for global variables assignements we can find in +code+
+ # and associate it with +container+
+ def scan_for_vardef(container, code)
+ code.each do |stmt|
+ scan_for_vardef(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray)
+
+ if stmt.is_a?(Puppet::Parser::AST::VarDef)
+ Puppet.debug "rdoc: found constant: %s = %s" % [stmt.name.to_s, value_to_s(stmt.value)]
+ container.add_constant(Constant.new(stmt.name.to_s, value_to_s(stmt.value), stmt.doc))
+ end
+ end
+ end
+
+ # create documentation for resources we can find in +code+
+ # and associate it with +container+
+ def scan_for_resource(container, code)
+ code.each do |stmt|
+ scan_for_resource(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray)
+
+ if stmt.is_a?(Puppet::Parser::AST::Resource)
+ type = stmt.type.split("::").collect { |s| s.capitalize }.join("::")
+ title = value_to_s(stmt.title)
+ Puppet.debug "rdoc: found resource: %s[%s]" % [type,title]
+
+ param = []
+ stmt.params.children.each do |p|
+ res = {}
+ res["name"] = p.param
+ if !p.value.nil?
+ if !p.value.is_a?(Puppet::Parser::AST::ASTArray)
+ res["value"] = "'#{p.value}'"
+ else
+ res["value"] = "[%s]" % p.value.children.collect { |v| "'#{v}'" }.join(", ")
+ end
+ end
+ param << res
+ end
+
+ container.add_resource(PuppetResource.new(type, title, stmt.doc, param))
+ end
+ end
+ end
+
+ # create documentation for a class named +name+
+ def document_class(name, klass, container)
+ Puppet.debug "rdoc: found new class %s" % name
+ container, name = get_class_or_module(container, name)
+
+ superclass = klass.parentclass
+ superclass = "" if superclass.nil? or superclass.empty?
+
+ @stats.num_classes += 1
+ comment = klass.doc
+ look_for_directives_in(container, comment) unless comment.empty?
+ cls = container.add_class(PuppetClass, name, superclass)
+ cls.record_location(@top_level)
+
+ # scan class code for include
+ code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::ASTArray)
+ code ||= klass.code
+ unless code.nil?
+ scan_for_include(cls, code)
+ scan_for_resource(cls, code) if Puppet.settings[:document_all]
+ end
+
+ cls.comment = comment
+ end
+
+ # create documentation for a node
+ def document_node(name, node, container)
+ Puppet.debug "rdoc: found new node %s" % name
+ superclass = node.parentclass
+ 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::ASTArray)
+ code ||= node.code
+ unless code.nil?
+ scan_for_include(n, code)
+ scan_for_vardef(n, code)
+ scan_for_resource(n, code) if Puppet.settings[:document_all]
+ end
+
+ n.comment = comment
+ end
+
+ # create documentation for a define
+ def document_define(name, define, container)
+ Puppet.debug "rdoc: found new definition %s" % name
+ # find superclas if any
+ @stats.num_methods += 1
+
+ # find the parentclass
+ # split define name by :: to find the complete module hierarchy
+ container, name = get_class_or_module(container,name)
+
+ return if container.find_local_symbol(name)
+
+ # build up declaration
+ declaration = ""
+ define.arguments.each do |arg,value|
+ declaration << "\$#{arg}"
+ if !value.nil?
+ declaration << " => "
+ if !value.is_a?(Puppet::Parser::AST::ASTArray)
+ declaration << "'#{value.value}'"
+ else
+ declaration << "[%s]" % value.children.collect { |v| "'#{v}'" }.join(", ")
+ end
+ end
+ declaration << ", "
+ end
+ declaration.chop!.chop! if declaration.size > 1
+
+ # register method into the container
+ meth = AnyMethod.new(declaration, name)
+ container.add_method(meth)
+ meth.comment = define.doc
+ look_for_directives_in(container, meth.comment) unless meth.comment.empty?
+ meth.params = "( " + declaration + " )"
+ meth.visibility = :public
+ meth.document_self = true
+ meth.singleton = false
+ end
+
+ # Traverse the AST tree and produce code-objects node
+ # that contains the documentation
+ def parse_elements(container)
+ Puppet.debug "rdoc: scanning manifest"
+ @ast[:classes].each do |name, klass|
+ if klass.file == @input_file_name
+ unless name.empty?
+ document_class(name,klass,container)
+ else # on main class document vardefs
+ code = klass.code.children unless klass.code.is_a?(Puppet::Parser::AST::ASTArray)
+ code ||= klass.code
+ scan_for_vardef(container, code) unless code.nil?
+ end
+ end
+ end
+
+ @ast[:definitions].each do |name, define|
+ if define.file == @input_file_name
+ document_define(name,define,container)
+ end
+ end
+
+ @ast[:nodes].each do |name, node|
+ if node.file == @input_file_name
+ document_node(name,node,container)
+ end
+ end
+ end
+
+ # create documentation for plugins
+ 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 custom fact parser :-)
+ def parse_fact(container)
+ comments = ""
+ current_fact = nil
+ 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 = Fact.new($1,{})
+ container.add_fact(current_fact)
+ look_for_directives_in(container, comments) unless comments.empty?
+ current_fact.comment = comments
+ current_fact.record_location(@top_level)
+ comments = ""
+ Puppet.debug "rdoc: found custom fact %s" % 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
+ end
+
+ # this is a poor man puppet plugin parser :-)
+ # it doesn't extract doc nor desc :-(
+ 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]*newfunction[ \t]*\([ \t]*:(.*?)[ \t]*,[ \t]*:type[ \t]*=>[ \t]*(:rvalue|:lvalue)\)/
+ current_plugin = Plugin.new($1, "function")
+ container.add_plugin(current_plugin)
+ look_for_directives_in(container, comments) unless comments.empty?
+ current_plugin.comment = comments
+ current_plugin.record_location(@top_level)
+ comments = ""
+ Puppet.debug "rdoc: found new function plugins %s" % current_plugin.name
+ elsif line =~ /^[ \t]*Puppet::Type.newtype[ \t]*\([ \t]*:(.*?)\)/
+ current_plugin = Plugin.new($1, "type")
+ container.add_plugin(current_plugin)
+ look_for_directives_in(container, comments) unless comments.empty?
+ current_plugin.comment = comments
+ current_plugin.record_location(@top_level)
+ comments = ""
+ Puppet.debug "rdoc: found new type plugins %s" % current_plugin.name
+ elsif line =~ /module Puppet::Parser::Functions/
+ # skip
+ else # unknown line type
+ comments =""
+ end
+ end
+ end
+ end
+
+ # look_for_directives_in scans the current +comment+ for RDoc directives
+ def look_for_directives_in(context, comment)
+ preprocess = SM::PreProcess.new(@input_file_name, @options.rdoc_include)
+
+ 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
+
+ def remove_private_comments(comment)
+ comment.gsub!(/^#--.*?^#\+\+/m, '')
+ comment.sub!(/^#--.*/m, '')
+ end
+
+ # convert an AST value to a string
+ def value_to_s(value)
+ value = value.children if value.is_a?(Puppet::Parser::AST::ASTArray)
+ if value.is_a?(Array)
+ "['#{value.join(", ")}']"
+ elsif [:true, true, "true"].include?(value)
+ "true"
+ elsif [:false, false, "false"].include?(value)
+ "false"
+ else
+ value.to_s
+ end
+ end
+end
+end \ No newline at end of file