diff options
Diffstat (limited to 'lib/puppet/util')
-rw-r--r-- | lib/puppet/util/rdoc.rb | 85 | ||||
-rw-r--r-- | lib/puppet/util/rdoc/code_objects.rb | 219 | ||||
-rw-r--r-- | lib/puppet/util/rdoc/generators/puppet_generator.rb | 829 | ||||
-rw-r--r-- | lib/puppet/util/rdoc/generators/template/puppet/puppet.rb | 1051 | ||||
-rw-r--r-- | lib/puppet/util/rdoc/parser.rb | 437 |
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 = " ::" * 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 + (<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 + (<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 + (<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 + (<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% %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: +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: +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: +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: +END:facts + </div> +ENDIF:facts + +<!-- if plugins --> +IF:plugins + <div id="class-list"> + <h3 class="section-bar">Plugins</h3> +START:plugins +HREF:aref:name: +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"> </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>-></td> + <td class="context-item-value">%new_name%</td> + </tr> +IF:desc + <tr class="top-aligned-row context-row"> + <td> </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"> [%rw%] </td> +ENDIF:rw +IFNOT:rw + <td class="context-item-value"> </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 + <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% & %defines_title% + + --> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <title>%classes_title% & %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 |