#!/usr/bin/env ruby

#
# = Synopsis
#
# Generate a reference for all Puppet types.  Largely meant for internal Reductive
# Labs use.
#
# = Usage
#
#   puppetdoc [-a|--all] [-h|--help] [-o|--outputdir <rdoc outputdir>] [-m|--mode <text|pdf|trac|rdoc>] 
#             [-r|--reference <[type]|configuration|..>] [manifest-file]
#
# = Description
#
# If mode is not 'rdoc', then this command generates a restructured-text document describing all installed
# Puppet types or all allowable arguments to puppet executables.  It is largely
# meant for internal use and is used to generate the reference document
# available on the Reductive Labs web site.
#
# In 'rdoc' mode, this command generates an html RDoc hierarchy describing the manifests that
# are in 'manifestdir' and 'modulepath' configuration directives. 
# The generated documentation directory is doc by default but can be changed with the 'outputdir' option.
#
# If the command is started with 'manifest-file' command-line arguments, puppetdoc generate a single
# manifest documentation that is output on stdout.
#
# = Options
#
# all::
#   Output the docs for all of the reference types. In 'rdoc' modes, this also outputs documentation for all resources
#
# help::
#   Print this help message
#
# outputdir::
#   Specifies the directory where to output the rdoc documentation in 'rdoc' mode.
#
# mode::
#   Determine the output mode.  Valid modes are 'text', 'trac', 'pdf' and 'rdoc'.  Note that 'trac' mode only works on Reductive Labs servers.  The default mode is 'text'.  In 'rdoc' mode you must provide 'manifests-path'
#
# reference::
#   Build a particular reference.  Get a list of references by running +puppetdoc --list+.
#
# = Example
#
#   $ puppetdoc -r type > /tmp/type_reference.rst
# or
#   $ puppetdoc --outputdir /tmp/rdoc --mode rdoc /path/to/manifests
# or
#   $ puppetdoc /etc/puppet/manifests/site.pp
#
# = Author
#
# Luke Kanies
#
# = Copyright
#
# Copyright (c) 2005-2007 Reductive Labs, LLC
# Licensed under the GNU Public License

require 'puppet'
require 'puppet/util/reference'
require 'puppet/network/handler'
require 'puppet/util/rdoc'
require 'getoptlong'

options = [
    [ "--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 ],
    [ "--outputdir",    "-o",   GetoptLong::REQUIRED_ARGUMENT ],
    [ "--verbose",      "-v",   GetoptLong::NO_ARGUMENT ],
    [ "--debug",        "-d",   GetoptLong::NO_ARGUMENT ]
]

# Add all of the config parameters as valid options.
Puppet.settings.addargs(options)
result = GetoptLong.new(*options)

debug = false

$tab = "    "
options = {:references => [], :mode => :text, :format => :to_rest}

Reference = Puppet::Util::Reference

begin
    unknown_args = []
    result.each { |opt,arg|
        case opt
        when "--outputdir"
            options[:outputdir] = arg
        when "--all"
            options[:all] = true
        when "--format"
            method = "to_%s" % arg
            if Reference.method_defined?(method)
                options[:format] = method
            else
                raise "Invalid output format %s" % arg
            end
        when "--mode"
            if Reference.modes.include?(arg) or arg.intern==:rdoc
                options[:mode] = arg.intern
            else
                raise "Invalid output mode %s" % arg
            end
        when "--list"
            puts Reference.references.collect { |r| Reference.reference(r).doc }.join("\n")
            exit(0)
        when "--reference"
            options[:references] << arg.intern
        when "--verbose"
            options[:verbose] = true
        when "--debug"
            options[:debug] = true
        when "--help"
            if Puppet.features.usage?
                RDoc::usage && exit
            else
                puts "No help available unless you have RDoc::usage installed"
                exit
            end
        else
            unknown_args << {:opt => opt, :arg => arg }
        end
    }

    # sole manifest documentation
    if ARGV.size > 0
        options[:mode] = :rdoc
        manifest = true
    end

    # consume the remaining unknown options
    # and feed them as settings, but only for rdoc mode
    if options[:mode] == :rdoc and unknown_args.size > 0
        unknown_args.each do |option|
            # force absolute path for modulepath when passed on commandline
            if option[:opt]=="--modulepath" or option[:opt] == "--manifestdir"
                option[:arg] = option[:arg].split(':').collect { |p| File.expand_path(p) }.join(':')
            end
            Puppet.settings.handlearg(option[:opt], option[:arg])
        end
    end
rescue GetoptLong::InvalidOption => detail
    $stderr.puts "Try '#{$0} --help'"
    exit(1)
end

if options[:mode] == :rdoc # rdoc mode
    # hack to get access to puppetmasterd modulepath and manifestdir
    Puppet[:name] = "puppetmasterd"
    # Now parse the config
    Puppet.parse_config

    # Handle the logging settings.
    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)
    end
end

if options[:all] and options[:mode] != :rdoc
    # Don't add dynamic references to the "all" list.
    options[:references] = Reference.references.reject do |ref|
        Reference.reference(ref).dynamic?
    end
end

if options[:references].empty?
    options[:references] << :type
end

case options[:mode]
when :rdoc # rdoc or sole manifest mode
    exit_code = 0
    files = []
    unless manifest
        files += Puppet[:modulepath].split(':').collect { |p| File.expand_path(p) }
        files += Puppet[:manifestdir].split(':').collect { |p| File.expand_path(p) }
    end
    files += ARGV
    Puppet.info "scanning: %s" % files.inspect
    Puppet.settings.setdefaults("puppetdoc",
        "document_all" => [false, "Document all resources"]
    )
    Puppet.settings[:document_all] = options[:all] || false
    begin
        if manifest
            Puppet::Util::RDoc.manifestdoc(files)
        else
            Puppet::Util::RDoc.rdoc(options[:outputdir], files)
        end
    rescue => detail
        if Puppet[:trace]
            puts detail.backtrace
        end
        $stderr.puts "Could not generate documentation: %s" % detail
        exit_code = 1
    end
    exit exit_code
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
    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

    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

