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
+        &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning 
System">CVS</acronym></a>)
+ENDIF:cvsurl
+      </td>
+    </tr>
+    <tr class="top-aligned-row">
+      <td><strong>Last Update:</strong></td>
+      <td>%dtm_modified%</td>
+    </tr>
+    </table>
+  </div>
+}
+
+
+#####################################################################
+### C L A S S   P A G E   H E A D E R   T E M P L A T E
+#####################################################################
+
+CLASS_PAGE = %{
+    <div id="classHeader">
+        <table class="header-table">
+        <tr class="top-aligned-row">
+          <td><strong>%classmod%</strong></td>
+          <td class="class-name-in-header">%full_name%</td>
+        </tr>
+        <tr class="top-aligned-row">
+            <td><strong>In:</strong></td>
+            <td>
+START:infiles
+IF:full_path_url
+                <a href="%full_path_url%">
+ENDIF:full_path_url
+                %full_path%
+IF:full_path_url
+                </a>
+ENDIF:full_path_url
+IF:cvsurl
+        &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning 
System">CVS</acronym></a>)
+ENDIF:cvsurl
+        <br />
+END:infiles
+            </td>
+        </tr>
+
+IF:parent
+        <tr class="top-aligned-row">
+            <td><strong>Parent:</strong></td>
+            <td>
+IF:par_url
+                <a href="%par_url%">
+ENDIF:par_url
+                %parent%
+IF:par_url
+               </a>
+ENDIF:par_url
+            </td>
+        </tr>
+ENDIF:parent
+        </table>
+    </div>
+}
+
+
+#####################################################################
+### 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:&nbsp;&nbsp;
+END:requires
+      </div>
+    </div>
+ENDIF:requires
+
+IF:toc
+    <div id="contents-list">
+      <h3 class="section-bar">Contents</h3>
+      <ul>
+START:toc
+      <li><a href="#%href%">%secname%</a></li>
+END:toc
+     </ul>
+ENDIF:toc
+   </div>
+
+IF:methods
+    <div id="method-list">
+      <h3 class="section-bar">Defines</h3>
+
+      <div class="name-list">
+START:methods
+      HREF:aref:name:&nbsp;&nbsp;
+END:methods
+      </div>
+    </div>
+ENDIF:methods
+
+  </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">&nbsp;</td>
+          <td class="context-item-desc">%desc%</td>
+ENDIF:desc
+        </tr>
+END:constants
+        </table>
+      </div>
+    </div>
+ENDIF:constants
+
+IF:aliases
+    <div id="aliases-list">
+      <h3 class="section-bar">External Aliases</h3>
+
+      <div class="name-list">
+                        <table summary="aliases">
+START:aliases
+        <tr class="top-aligned-row context-row">
+          <td class="context-item-name">%old_name%</td>
+          <td>-&gt;</td>
+          <td class="context-item-value">%new_name%</td>
+        </tr>
+IF:desc
+      <tr class="top-aligned-row context-row">
+        <td>&nbsp;</td>
+        <td colspan="2" class="context-item-desc">%desc%</td>
+      </tr>
+ENDIF:desc
+END:aliases
+                        </table>
+      </div>
+    </div>
+ENDIF:aliases
+
+
+IF:attributes
+    <div id="attribute-list">
+      <h3 class="section-bar">Attributes</h3>
+
+      <div class="name-list">
+        <table>
+START:attributes
+        <tr class="top-aligned-row context-row">
+          <td class="context-item-name">%name%</td>
+IF:rw
+          <td class="context-item-value">&nbsp;[%rw%]&nbsp;</td>
+ENDIF:rw
+IFNOT:rw
+          <td class="context-item-value">&nbsp;&nbsp;</td>
+ENDIF:rw
+          <td class="context-item-desc">%a_desc%</td>
+        </tr>
+END:attributes
+        </table>
+      </div>
+    </div>
+ENDIF:attributes
+      
+
+
+    <!-- if method_list -->
+IF:method_list
+    <div id="methods">
+START:method_list
+IF:methods
+      <h3 class="section-bar">Defines</h3>
+
+START:methods
+      <div id="method-%aref%" class="method-detail">
+        <a name="%aref%"></a>
+
+        <div class="method-heading">
+IF:codeurl
+          <a href="%codeurl%" target="Code" class="method-signature"
+            onclick="popupCode('%codeurl%');return false;">
+ENDIF:codeurl
+IF:sourcecode
+          <a href="#%aref%" class="method-signature">
+ENDIF:sourcecode
+IF:callseq
+          <span class="method-name">%callseq%</span>
+ENDIF:callseq
+IFNOT:callseq
+          <span class="method-name">%name%</span><span 
class="method-args">%params%</span>
+ENDIF:callseq
+IF:codeurl
+          </a>
+ENDIF:codeurl
+IF:sourcecode
+          </a>
+ENDIF:sourcecode
+        </div>
+      
+        <div class="method-description">
+IF:m_desc
+          %m_desc%
+ENDIF:m_desc
+IF:sourcecode
+          <p><a class="source-toggle" href="#"
+            onclick="toggleCode('%aref%-source');return 
false;">[Source]</a></p>
+          <div class="method-source-code" id="%aref%-source">
+<pre>
+%sourcecode%
+</pre>
+          </div>
+ENDIF:sourcecode
+        </div>
+      </div>
+
+END:methods
+ENDIF:methods
+END:method_list
+
+    </div>
+ENDIF:method_list
+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% &amp; %list_title2% 
+
+  -->
+<html xmlns="http://www.w3.org/1999/xhtml"; xml:lang="en" lang="en">
+<head>
+  <title>%list_title1% &amp; %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
-~----------~----~----~----~------~----~------~--~---

Reply via email to