There is currently two type of documentation generation
for manifests (module or modulepath):
* RDoc integration
* Markdown (using rdiscount)
Both version only handle classes and defines and produce html.
The usage is the following:
For the rdoc variant:
$ puppetdoc --rdoc --outputdir doc /path/to/manifests
For the markdown version:
$ puppetdoc --manifests --outputdir doc /path/to/manifests
Note: the RDoc integration only scans modules. It skips manifests
not below <module>/manifests directory. It doesn't support ruby
plugins.
Signed-off-by: Brice Figureau <[EMAIL PROTECTED]>
---
bin/puppetdoc | 135 +++--
lib/puppet/doc.rb | 89 +++
lib/puppet/parser/ast/function.rb | 28 +-
lib/puppet/rdoc/generators/puppet_generator.rb | 229 ++++++
.../rdoc/generators/template/puppet/puppet.rb | 791 ++++++++++++++++++++
lib/puppet/rdoc/parser.rb | 208 +++++
6 files changed, 1418 insertions(+), 62 deletions(-)
create mode 100644 lib/puppet/doc.rb
create mode 100644 lib/puppet/rdoc/generators/puppet_generator.rb
create mode 100644 lib/puppet/rdoc/generators/template/puppet/puppet.rb
create mode 100644 lib/puppet/rdoc/parser.rb
diff --git a/bin/puppetdoc b/bin/puppetdoc
index 82e4c07..a7e16fd 100755
--- a/bin/puppetdoc
+++ b/bin/puppetdoc
@@ -47,15 +47,19 @@
require 'puppet'
require 'puppet/util/reference'
require 'puppet/network/handler'
+require 'puppet/doc'
require 'getoptlong'
result = GetoptLong.new(
- [ "--all", "-a", GetoptLong::NO_ARGUMENT ],
- [ "--list", "-l", GetoptLong::NO_ARGUMENT ],
- [ "--format", "-f", GetoptLong::REQUIRED_ARGUMENT ],
- [ "--mode", "-m", GetoptLong::REQUIRED_ARGUMENT ],
- [ "--reference", "-r",
GetoptLong::REQUIRED_ARGUMENT ],
- [ "--help", "-h", GetoptLong::NO_ARGUMENT
]
+ [ "--all", "-a", GetoptLong::NO_ARGUMENT ],
+ [ "--list", "-l", GetoptLong::NO_ARGUMENT ],
+ [ "--format", "-f", GetoptLong::REQUIRED_ARGUMENT ],
+ [ "--mode", "-m", GetoptLong::REQUIRED_ARGUMENT ],
+ [ "--reference", "-r", GetoptLong::REQUIRED_ARGUMENT ],
+ [ "--help", "-h", GetoptLong::NO_ARGUMENT ],
+ [ "--manifests", "-p", GetoptLong::NO_ARGUMENT ],
+ [ "--rdoc", GetoptLong::NO_ARGUMENT ],
+ [ "--outputdir", "-o", GetoptLong::REQUIRED_ARGUMENT ]
)
debug = false
@@ -68,6 +72,15 @@ Reference = Puppet::Util::Reference
begin
result.each { |opt,arg|
case opt
+ when "--manifests"
+ options[:manifests] = true
+ when "--rdoc"
+ # force manifests
+ options[:manifests] = true
+ options[:rdoc] = true
+ when "--outputdir"
+ # force manifests
+ options[:outputdir] = arg
when "--all"
options[:all] = true
when "--format"
@@ -102,61 +115,83 @@ rescue GetoptLong::InvalidOption => detail
exit(1)
end
-if options[:all]
- # Don't add dynamic references to the "all" list.
- options[:references] = Reference.references.reject do |ref|
- Reference.reference(ref).dynamic?
+if options[:manifests]
+ Puppet.parse_config
+ if options[:debug] or options[:verbose]
+ if options[:debug]
+ Puppet::Util::Log.level = :debug
+ else
+ Puppet::Util::Log.level = :info
+ end
+
+ Puppet::Util::Log.newdestination(:console)
+ options[:setdest] = true
end
-end
-if options[:references].empty?
- options[:references] << :type
-end
+ files = []
+ files += ARGV
-case options[:mode]
-when :trac
- options[:references].each do |name|
- section = Puppet::Util::Reference.reference(name) or raise "Could not
find section %s" % name
- unless options[:mode] == :pdf
- section.trac
- end
- end
-else
- text = ""
- if options[:references].length > 1
- with_contents = false
+ if options[:rdoc]
+ Puppet::Doc.rdoc(options[:outputdir], files)
else
- with_contents = true
+ Puppet::Doc.doc(options[:outputdir], files)
end
- exit_code = 0
- options[:references].sort { |a,b| a.to_s <=> b.to_s }.each do |name|
- raise "Could not find reference %s" % name unless section =
Puppet::Util::Reference.reference(name)
-
- begin
- # Add the per-section text, but with no ToC
- text += section.send(options[:format], with_contents)
- rescue => detail
- puts detail.backtrace
- $stderr.puts "Could not generate reference %s: %s" % [name, detail]
- exit_code = 1
- next
+else
+ if options[:all]
+ # Don't add dynamic references to the "all" list.
+ options[:references] = Reference.references.reject do |ref|
+ Reference.reference(ref).dynamic?
end
end
- unless with_contents # We've only got one reference
- text += Puppet::Util::Reference.footer
+ if options[:references].empty?
+ options[:references] << :type
end
- # Replace the trac links, since they're invalid everywhere else
- text.gsub!(/`\w+\s+([^`]+)`:trac:/) { |m| $1 }
-
- if options[:mode] == :pdf
- Puppet::Util::Reference.pdf(text)
+ case options[:mode]
+ when :trac
+ options[:references].each do |name|
+ section = Puppet::Util::Reference.reference(name) or raise "Could
not find section %s" % name
+ unless options[:mode] == :pdf
+ section.trac
+ end
+ end
else
- puts text
- end
+ text = ""
+ if options[:references].length > 1
+ with_contents = false
+ else
+ with_contents = true
+ end
+ exit_code = 0
+ options[:references].sort { |a,b| a.to_s <=> b.to_s }.each do |name|
+ raise "Could not find reference %s" % name unless section =
Puppet::Util::Reference.reference(name)
+
+ begin
+ # Add the per-section text, but with no ToC
+ text += section.send(options[:format], with_contents)
+ rescue => detail
+ puts detail.backtrace
+ $stderr.puts "Could not generate reference %s: %s" % [name,
detail]
+ exit_code = 1
+ next
+ end
+ end
- exit exit_code
-end
+ unless with_contents # We've only got one reference
+ text += Puppet::Util::Reference.footer
+ end
+ # Replace the trac links, since they're invalid everywhere else
+ text.gsub!(/`\w+\s+([^`]+)`:trac:/) { |m| $1 }
+
+ if options[:mode] == :pdf
+ Puppet::Util::Reference.pdf(text)
+ else
+ puts text
+ end
+
+ exit exit_code
+ end
+end
diff --git a/lib/puppet/doc.rb b/lib/puppet/doc.rb
new file mode 100644
index 0000000..64b3948
--- /dev/null
+++ b/lib/puppet/doc.rb
@@ -0,0 +1,89 @@
+
+class Puppet::Doc
+
+ # launch a rdoc documenation process
+ # with the files/dir passed in +files+
+ def self.rdoc(outputdir, files)
+ begin
+ # load our parser first
+ require 'puppet/rdoc/parser'
+
+ # then rdoc
+ require 'rdoc/rdoc'
+ r = RDoc::RDoc.new
+ RDoc::RDoc::GENERATORS["puppet"] =
RDoc::RDoc::Generator.new("puppet/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
+
+ # Entry point for "regular" documentation mode
+ # still to be refined.
+ def self.doc(outputdir, files)
+ # if outputdir is omitted
+ outputdir ||= "doc"
+ Dir.mkdir(outputdir) unless FileTest.directory?(outputdir)
+
+ # scan every files from files, if directory descend
+ manifests = []
+ files.each do |file|
+ if FileTest.directory?(file)
+ files.concat(Dir.glob(File.join(file, "*")))
+ elsif file =~ /\.pp$/ # got a manifest
+ manifests << file
+ end
+ end
+
+ # parse and document
+ environment = "development"
+ manifests.each do |manifest|
+ self.parse(outputdir, manifest)
+ # if we have a module, produce a module directory
+ # then a file per class and per defines
+ end
+ end
+
+ def self.write_doc(outputdir, name, doc)
+ f = File.new(File.join(outputdir,"#{name}.html"), "w")
+ require 'rdiscount'
+ markdown = RDiscount.new(doc)
+ f.puts("<html><body>")
+ f.puts(markdown.to_html)
+ f.puts("</body></html>")
+ end
+
+ def self.parse(outputdir, manifest)
+ parser = Puppet::Parser::Parser.new(:environment => "development")
+ parser.file = manifest
+ ast = parser.parse
+
+ if manifest =~ /([^\/]+)\/manifests\/[^\/]+\.pp/
+ module_name = $1
+ outputdir = File.join(outputdir,module_name)
+ end
+
+ unless module_name.nil?
+ Dir.mkdir(outputdir) unless FileTest.directory?(outputdir)
+ end
+
+ ast[:classes].each do |name, klass|
+ self.write_doc(outputdir, name, klass.doc) unless name.empty?
+ end
+
+ ast[:definitions].each do |name, define|
+ self.write_doc(outputdir, name, define.doc) unless name.empty?
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/lib/puppet/parser/ast/function.rb
b/lib/puppet/parser/ast/function.rb
index eb36fa9..d46ed8f 100644
--- a/lib/puppet/parser/ast/function.rb
+++ b/lib/puppet/parser/ast/function.rb
@@ -8,20 +8,9 @@ class Puppet::Parser::AST
@settor = true
def evaluate(scope)
- # We don't need to evaluate the name, because it's plaintext
- args = @arguments.safeevaluate(scope)
-
- return scope.send("function_" + @name, args)
- end
-
- def initialize(hash)
- @ftype = hash[:ftype] || :rvalue
- hash.delete(:ftype) if hash.include? :ftype
-
- super(hash)
# Make sure it's a defined function
- unless @fname = Puppet::Parser::Functions.function(@name)
+ unless @fname
raise Puppet::ParseError, "Unknown function %s" % @name
end
@@ -42,6 +31,21 @@ class Puppet::Parser::AST
raise Puppet::DevError, "Invalid function type %s" %
@ftype.inspect
end
+
+
+ # We don't need to evaluate the name, because it's plaintext
+ args = @arguments.safeevaluate(scope)
+
+ return scope.send("function_" + @name, args)
+ end
+
+ def initialize(hash)
+ @ftype = hash[:ftype] || :rvalue
+ hash.delete(:ftype) if hash.include? :ftype
+
+ super(hash)
+
+ @fname = Puppet::Parser::Functions.function(@name)
# Lastly, check the parity
end
end
diff --git a/lib/puppet/rdoc/generators/puppet_generator.rb
b/lib/puppet/rdoc/generators/puppet_generator.rb
new file mode 100644
index 0000000..902ad59
--- /dev/null
+++ b/lib/puppet/rdoc/generators/puppet_generator.rb
@@ -0,0 +1,229 @@
+require 'rdoc/generators/html_generator'
+module Generators
+
+ MODULE_DIR = "modules"
+
+ # This is a special 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
+
+ def load_html_template
+ begin
+ require 'puppet/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
+
+ ##
+ # Generate:
+ # the list of modules
+ # the list of classes and defines of a specific module
+ # the list of all classes
+ def build_indices
+ @allfiles = []
+
+ # 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 = []
+
+ # 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 [EMAIL
PROTECTED](k.index_name)
+ end
+
+ @files << file
+ @allfiles << { "file" => file, "modules" => modules, "classes"
=> classes, "methods" => methods }
+ end
+
+ @classes = @allclasses.values
+ end
+
+ def generate_class_list(classes, modules, from, html_file, class_dir)
+ if from.is_module? and [EMAIL PROTECTED](from.name)
+ k = HtmlClass.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 = HtmlClass.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)
+ 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" =>
"#{MODULE_DIR}/fr_#{f.index_name}.html", "module" =>
"#{CLASS_DIR}/#{f.index_name}.html","name" => 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 class 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|
+ gen_composite_index(file["classes"],file["methods"],
file['modules'], 'Classes', 'Defines',
+ RDoc::Page::COMBO_INDEX,
+
"#{MODULE_DIR}/fr_#{file["file"].context.file_relative_name}.html")
+ end
+ end
+
+ def gen_composite_index(coll1, coll2, coll3, title1, title2, template,
filename)
+ template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template)
+ res1 = []
+ coll1.sort.each do |f|
+ if f.document_self
+ unless f.context.is_module?
+ res1 << { "href" => "../"+f.path, "name" =>
f.index_name }
+ end
+ end
+ end
+
+ res2 = []
+ coll2.sort.each do |f|
+ if f.document_self
+ res2 << { "href" => "../"+f.path, "name" =>
f.index_name.sub(/\(.*\)$/,'') }
+ end
+ end
+
+ module_name = []
+ coll3.sort.each do |f|
+ module_name << { "href" => "../"+f.path, "name" =>
f.index_name }
+ end
+
+ values = {
+ "module" => module_name,
+ "entries1" => res1,
+ 'list_title1' => CGI.escapeHTML(title1),
+ "entries2" => res2,
+ 'list_title2' => CGI.escapeHTML(title2),
+ '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
+
+ # 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
+ ref = "#{CLASS_DIR}/#{file.index_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
+
+ class PuppetGeneratorInOne < HTMLGeneratorInOne
+ def gen_method_index
+ gen_an_index(HtmlMethod.all_methods, 'Defines')
+ end
+ end
+
+ end
\ No newline at end of file
diff --git a/lib/puppet/rdoc/generators/template/puppet/puppet.rb
b/lib/puppet/rdoc/generators/template/puppet/puppet.rb
new file mode 100644
index 0000000..7228ce1
--- /dev/null
+++ b/lib/puppet/rdoc/generators/template/puppet/puppet.rb
@@ -0,0 +1,791 @@
+#
+# = 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 <[EMAIL PROTECTED]>
+#
+# 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;
+}
+
+
+.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>
+}
+
+
+#####################################################################
+### M E T H O D L I S T T E M P L A T E
+#####################################################################
+
+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
+
+ </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: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">Constants</h3>
+
+ <div class="name-list">
+ <table summary="Constants">
+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
+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
+
+
+
+#####################################################################
+### 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 + %{
+<!--
+
+ %list_title1% & %list_title2%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%list_title1% & %list_title2%</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">
+<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">
+ <h1 class="section-bar">%list_title1%</h1>
+ <div id="index-entries">
+START:entries1
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:entries1
+ </div>
+ <h1 class="section-bar">%list_title2%</h1>
+ <div id="index-entries">
+START:entries2
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:entries2
+ </div>
+</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/rdoc/parser.rb b/lib/puppet/rdoc/parser.rb
new file mode 100644
index 0000000..87da2f6
--- /dev/null
+++ b/lib/puppet/rdoc/parser.rb
@@ -0,0 +1,208 @@
+# Puppet "parser" for the rdoc system
+# The parser uses puppet parser and traverse the AST to instruct RDoc about
+# our current structures.
+
+# rdoc mandatory includes
+require "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(/\.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 = top_level
+ @progress = $stderr unless options.quiet
+ end
+
+ # main entry point
+ def scan
+ environment = "development"
+ @parser = Puppet::Parser::Parser.new(:environment => environment)
+ @parser.file = @input_file_name
+ @ast = @parser.parse
+ 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)
+ if !container
+ container = prev_container.add_module(NormalClass, name)
+ end
+ end
+ return [container, final_name]
+ end
+
+ # create documentation
+ 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)
+
+ # infer module name from directory
+ if @input_file_name =~ /([^\/]+)\/manifests\/.+\.pp/
+ name = $1
+ else
+ # skip .pp files that are not in manifests as we can't guarantee
they're part
+ # of a module and we only know how to scan modules
+ container.document_self = false
+ return
+ end
+
+ @top_level.file_relative_name = name
+ @stats.num_modules += 1
+ container, name = get_class_or_module(container,name)
+ mod = container.add_module(NormalModule, name)
+ mod.record_location(@top_level)
+ mod.comment = comment
+
+ parse_elements(mod)
+ end
+
+ def scan_for_include(container, code)
+ code.each do |stmt|
+ scan_for_include(container,code) 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|
+ container.add_include(Include.new(included.value,
stmt.doc))
+ end
+ end
+ end
+ end
+
+ # create documentation for a class
+ def document_class(name, klass, container)
+ 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(NormalClass, name, superclass)
+ cls.record_location(@top_level)
+
+ # scan class code for include
+ code = [klass.code] unless
klass.code.is_a?(Puppet::Parser::AST::ASTArray)
+ scan_for_include(cls, code) unless code.nil?
+
+ cls.comment = comment
+ end
+
+ # create documentation for a define
+ def document_define(name, define, container)
+ # 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
+ unless value.nil?
+ declaration << " => "
+ declaration << "'#{value.value}'"
+ 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
+ meth.params = "( " + declaration + " )"
+ meth.visibility = :public
+ meth.document_self = true
+ meth.singleton = false
+ end
+
+ def parse_elements(container)
+ @ast[:classes].each do |name, klass|
+ if klass.file == @input_file_name
+ document_class(name,klass,container) unless name.empty?
+ end
+ end
+
+ @ast[:definitions].each do |name, define|
+ if define.file == @input_file_name
+ document_define(name,define,container)
+ end
+ end
+ end
+
+ 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
+end
+end
\ No newline at end of file
--
1.6.0.2
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Puppet Developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/puppet-dev?hl=en
-~----------~----~----~----~------~----~------~--~---