I misunderstood your earlier comment about missing tests -- it's fine
if this file doesn't have any tests.
I think it's time to refactor the file, though -- the case-statement-
based selection nearly always means it should be refactored into
simple classes with a dispatch method (e.g., a DocGenerator class,
with an instance that can generate trac docs and html docs etc.).
It also seems like there are a lot of whitespace changes here; did you
change the indentation method?
Otherwise, awesome.
And if anyone wants to help make that CSS look a bit less awful, it'd
be great to differentiate ourselves from rdoc in how the generated
docs look.
On Oct 31, 2008, at 5:40 PM, Brice Figureau wrote:
>
> 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
>
>
> >
--
Computers are not intelligent. They only think they are.
---------------------------------------------------------------------
Luke Kanies | http://reductivelabs.com | http://madstop.com
--~--~---------~--~----~------------~-------~--~----~
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
-~----------~----~----~----~------~----~------~--~---