summaryrefslogtreecommitdiffstats
path: root/lib/puppet/util
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2008-12-02 16:26:54 -0600
committerLuke Kanies <luke@madstop.com>2008-12-02 16:26:54 -0600
commit99a9b5a045af6f1c68619792a45603cbe450652d (patch)
tree579963d464c0529d4e9250f937d495e415f1e867 /lib/puppet/util
parentf73e13e7464edab73443857d628602b89361c220 (diff)
parent278bfe83015312292360f727d6532a143610db0d (diff)
Merge branch '0.24.x'
Conflicts: bin/puppetca lib/puppet/type/group.rb lib/puppet/type/tidy.rb lib/puppet/util/settings.rb Also edited the following files so tests will pass: lib/puppet/type/component.rb spec/unit/ssl/certificate_request.rb spec/unit/type/computer.rb spec/unit/type/mcx.rb spec/unit/type/resources.rb spec/unit/util/settings.rb spec/unit/util/storage.rb test/ral/type/zone.rb
Diffstat (limited to 'lib/puppet/util')
-rwxr-xr-xlib/puppet/util/filetype.rb6
-rw-r--r--lib/puppet/util/metric.rb5
-rw-r--r--lib/puppet/util/rdoc.rb85
-rw-r--r--lib/puppet/util/rdoc/code_objects.rb219
-rw-r--r--lib/puppet/util/rdoc/generators/puppet_generator.rb829
-rw-r--r--lib/puppet/util/rdoc/generators/template/puppet/puppet.rb1051
-rw-r--r--lib/puppet/util/rdoc/parser.rb437
-rw-r--r--lib/puppet/util/selinux.rb198
-rw-r--r--lib/puppet/util/settings.rb154
9 files changed, 2839 insertions, 145 deletions
diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb
index b10b50fb7..a58b9a23b 100755
--- a/lib/puppet/util/filetype.rb
+++ b/lib/puppet/util/filetype.rb
@@ -218,10 +218,10 @@ class Puppet::Util::FileType
begin
output = Puppet::Util.execute(%w{crontab -l}, :uid => @path)
return "" if output.include?("can't open your crontab")
+ raise Puppet::Error, "User %s not authorized to use cron" % @path if output.include?("you are not authorized to use cron")
return output
- rescue
- # If there's a failure, treat it like an empty file.
- return ""
+ rescue => detail
+ raise Puppet::Error, "Could not read crontab for %s: %s" % [@path, detail]
end
end
diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb
index e6d7678aa..f352462cd 100644
--- a/lib/puppet/util/metric.rb
+++ b/lib/puppet/util/metric.rb
@@ -4,11 +4,6 @@ require 'puppet'
# A class for handling metrics. This is currently ridiculously hackish.
class Puppet::Util::Metric
- # Load the library as a feature, so we can test its presence.
- # It's only used by this class, so there's no reason to move it
- # to the main feature list.
- Puppet.features.add :rrd, :libs => 'RRDtool'
-
attr_accessor :type, :name, :value, :label
attr_writer :values
diff --git a/lib/puppet/util/rdoc.rb b/lib/puppet/util/rdoc.rb
new file mode 100644
index 000000000..b33e67c71
--- /dev/null
+++ b/lib/puppet/util/rdoc.rb
@@ -0,0 +1,85 @@
+
+module Puppet::Util::RDoc
+
+ module_function
+
+ # launch a rdoc documenation process
+ # with the files/dir passed in +files+
+ def rdoc(outputdir, files)
+ begin
+ Puppet[:ignoreimport] = true
+
+ # then rdoc
+ require 'rdoc/rdoc'
+
+ # load our parser
+ require 'puppet/util/rdoc/parser'
+
+ r = RDoc::RDoc.new
+ RDoc::RDoc::GENERATORS["puppet"] = RDoc::RDoc::Generator.new("puppet/util/rdoc/generators/puppet_generator.rb",
+ "PuppetGenerator".intern,
+ "puppet")
+ # specify our own format & where to output
+ options = [ "--fmt", "puppet",
+ "--quiet",
+ "--op", outputdir ]
+
+ options += files
+
+ # launch the documentation process
+ r.document(options)
+ rescue RDoc::RDocError => e
+ raise Puppet::ParseError.new("RDoc error %s" % e)
+ end
+ end
+
+ # launch a output to console manifest doc
+ def manifestdoc(files)
+ Puppet[:ignoreimport] = true
+ files.select { |f| FileTest.file?(f) }.each do |f|
+ parser = Puppet::Parser::Parser.new(:environment => Puppet[:environment])
+ parser.file = f
+ ast = parser.parse
+ output(f, ast)
+ end
+ end
+
+ # Ouputs to the console the documentation
+ # of a manifest
+ def output(file, ast)
+ astobj = []
+ ast[:nodes].each do |name, k|
+ astobj << k if k.file == file
+ end
+ ast[:classes].each do |name, k|
+ astobj << k if k.file == file
+ end
+ ast[:definitions].each do |name, k|
+ astobj << k if k.file == file
+ end
+ astobj.sort! {|a,b| a.line <=> b.line }.each do |k|
+ output_astnode_doc(k)
+ end
+ end
+
+ def output_astnode_doc(ast)
+ puts ast.doc if !ast.doc.nil? and !ast.doc.empty?
+ if Puppet.settings[:document_all]
+ # scan each underlying resources to produce documentation
+ code = ast.code.children if ast.code.is_a?(Puppet::Parser::AST::ASTArray)
+ code ||= ast.code
+ output_resource_doc(code) unless code.nil?
+ end
+ end
+
+ def output_resource_doc(code)
+ code.sort { |a,b| a.line <=> b.line }.each do |stmt|
+ output_resource_doc(stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray)
+
+ if stmt.is_a?(Puppet::Parser::AST::Resource)
+ puts stmt.doc if !stmt.doc.nil? and !stmt.doc.empty?
+ end
+ end
+ end
+
+end \ No newline at end of file
diff --git a/lib/puppet/util/rdoc/code_objects.rb b/lib/puppet/util/rdoc/code_objects.rb
new file mode 100644
index 000000000..52df2193e
--- /dev/null
+++ b/lib/puppet/util/rdoc/code_objects.rb
@@ -0,0 +1,219 @@
+require 'rdoc/code_objects'
+
+module RDoc
+
+ # This modules contains various class that are used to hold information
+ # about the various Puppet language structures we found while parsing.
+ #
+ # Those will be mapped to their html counterparts which are defined in
+ # PuppetGenerator.
+
+ # PuppetTopLevel is a top level (usually a .pp/.rb file)
+ class PuppetTopLevel < TopLevel
+ attr_accessor :module_name, :global
+
+ # will contain all plugins
+ @@all_plugins = {}
+
+ # contains all cutoms facts
+ @@all_facts = {}
+
+ def initialize(toplevel)
+ super(toplevel.file_relative_name)
+ end
+
+ def self.all_plugins
+ @@all_plugins.values
+ end
+
+ def self.all_facts
+ @@all_facts.values
+ end
+ end
+
+ # PuppetModule holds a Puppet Module
+ # This is mapped to an HTMLPuppetModule
+ # it leverage the RDoc (ruby) module infrastructure
+ class PuppetModule < NormalModule
+ attr_accessor :facts, :plugins
+
+ def initialize(name,superclass=nil)
+ @facts = []
+ @plugins = []
+ super(name,superclass)
+ end
+
+ def initialize_classes_and_modules
+ super
+ @nodes = {}
+ end
+
+ def add_plugin(plugin)
+ add_to(@plugins, plugin)
+ end
+
+ def add_fact(fact)
+ add_to(@facts, fact)
+ end
+
+ def add_node(name,superclass)
+ cls = @nodes[name]
+ unless cls
+ cls = PuppetNode.new(name, superclass)
+ @nodes[name] = cls if !@done_documenting
+ cls.parent = self
+ cls.section = @current_section
+ end
+ cls
+ end
+
+ def each_fact
+ @facts.each {|c| yield c}
+ end
+
+ def each_plugin
+ @plugins.each {|c| yield c}
+ end
+
+ def each_node
+ @nodes.each {|c| yield c}
+ end
+
+ def nodes
+ @nodes.values
+ end
+ end
+
+ # PuppetClass holds a puppet class
+ # It is mapped to a HTMLPuppetClass for display
+ # It leverages RDoc (ruby) Class
+ class PuppetClass < ClassModule
+ attr_accessor :resource_list
+
+ def initialize(name, superclass)
+ super(name,superclass)
+ @resource_list = []
+ end
+
+ def add_resource(resource)
+ add_to(@resource_list, resource)
+ end
+
+ def is_module?
+ false
+ end
+ end
+
+ # PuppetNode holds a puppet node
+ # It is mapped to a HTMLPuppetNode for display
+ # A node is just a variation of a class
+ class PuppetNode < PuppetClass
+ def initialize(name, superclass)
+ super(name,superclass)
+ end
+
+ def is_module?
+ false
+ end
+ end
+
+ # Plugin holds a native puppet plugin (function,type...)
+ # It is mapped to a HTMLPuppetPlugin for display
+ class Plugin < Context
+ attr_accessor :name, :type
+
+ def initialize(name, type)
+ super()
+ @name = name
+ @type = type
+ @comment = ""
+ end
+
+ def <=>(other)
+ @name <=> other.name
+ end
+
+ def full_name
+ @name
+ end
+
+ def http_url(prefix)
+ path = full_name.split("::")
+ File.join(prefix, *path) + ".html"
+ end
+
+ def is_fact?
+ false
+ end
+
+ def to_s
+ res = self.class.name + ": " + @name + " (" + @type + ")\n"
+ res << @comment.to_s
+ res
+ end
+ end
+
+ # Fact holds a custom fact
+ # It is mapped to a HTMLPuppetPlugin for display
+ class Fact < Context
+ attr_accessor :name, :confine
+
+ def initialize(name, confine)
+ super()
+ @name = name
+ @confine = confine
+ @comment = ""
+ end
+
+ def <=>(other)
+ @name <=> other.name
+ end
+
+ def is_fact?
+ true
+ end
+
+ def full_name
+ @name
+ end
+
+ def to_s
+ res = self.class.name + ": " + @name + "\n"
+ res << @comment.to_s
+ res
+ end
+ end
+
+ # PuppetResource holds a puppet resource
+ # It is mapped to a HTMLPuppetResource for display
+ # A resource is defined by its "normal" form Type[title]
+ class PuppetResource < CodeObject
+ attr_accessor :type, :title, :params
+
+ def initialize(type, title, comment, params)
+ super()
+ @type = type
+ @title = title
+ @comment = comment
+ @params = params
+ end
+
+ def <=>(other)
+ full_name <=> other.full_name
+ end
+
+ def full_name
+ @type + "[" + @title + "]"
+ end
+
+ def name
+ full_name
+ end
+
+ def to_s
+ res = @type + "[" + @title + "]\n"
+ res << @comment.to_s
+ res
+ end
+ end
+end
diff --git a/lib/puppet/util/rdoc/generators/puppet_generator.rb b/lib/puppet/util/rdoc/generators/puppet_generator.rb
new file mode 100644
index 000000000..22f001164
--- /dev/null
+++ b/lib/puppet/util/rdoc/generators/puppet_generator.rb
@@ -0,0 +1,829 @@
+require 'rdoc/generators/html_generator'
+require 'puppet/util/rdoc/code_objects'
+module Generators
+
+ # This module holds all the classes needed to generate the HTML documentation
+ # of a bunch of puppet manifests.
+ #
+ # It works by traversing all the code objects defined by the Puppet RDoc::Parser
+ # and produces HTML counterparts objects that in turns are used by RDoc template engine
+ # to produce the final HTML.
+ #
+ # It is also responsible of creating the whole directory hierarchy, and various index
+ # files.
+ #
+ # It is to be noted that the whole system is built on top of ruby RDoc. As such there
+ # is an implicit mapping of puppet entities to ruby entitites:
+ #
+ # Puppet => Ruby
+ # ------------------------
+ # Module Module
+ # Class Class
+ # Definition Method
+ # Resource
+ # Node
+ # Plugin
+ # Fact
+
+ MODULE_DIR = "modules"
+ NODE_DIR = "nodes"
+ PLUGIN_DIR = "plugins"
+
+ # This is a specialized HTMLGenerator tailored to Puppet manifests
+ class PuppetGenerator < HTMLGenerator
+
+ def PuppetGenerator.for(options)
+ AllReferences::reset
+ HtmlMethod::reset
+
+ if options.all_one_file
+ PuppetGeneratorInOne.new(options)
+ else
+ PuppetGenerator.new(options)
+ end
+ end
+
+ def initialize(options) #:not-new:
+ @options = options
+ load_html_template
+ end
+
+ # loads our own html template file
+ def load_html_template
+ begin
+ require 'puppet/util/rdoc/generators/template/puppet/puppet'
+ extend RDoc::Page
+ rescue LoadError
+ $stderr.puts "Could not find Puppet template '#{template}'"
+ exit 99
+ end
+ end
+
+ def gen_method_index
+ # we don't generate an all define index
+ # as the presentation is per module/per class
+ end
+
+ # This is the central method, it generates the whole structures
+ # along with all the indices.
+ def generate_html
+ super
+ gen_into(@nodes)
+ gen_into(@plugins)
+ end
+
+ ##
+ # Generate:
+ # the list of modules
+ # the list of classes and definitions of a specific module
+ # the list of all classes
+ # the list of nodes
+ # the list of resources
+ def build_indices
+ @allfiles = []
+ @nodes = []
+ @plugins = []
+
+ # contains all the seen modules
+ @modules = {}
+ @allclasses = {}
+
+ # build the modules, classes and per modules classes and define list
+ @toplevels.each do |toplevel|
+ next unless toplevel.document_self
+ file = HtmlFile.new(toplevel, @options, FILE_DIR)
+ classes = []
+ methods = []
+ modules = []
+ nodes = []
+
+ # find all classes of this toplevel
+ # store modules if we find one
+ toplevel.each_classmodule do |k|
+ generate_class_list(classes, modules, k, toplevel, CLASS_DIR)
+ end
+
+ # find all defines belonging to this toplevel
+ HtmlMethod.all_methods.each do |m|
+ # find parent module, check this method is not already
+ # defined.
+ if m.context.parent.toplevel === toplevel
+ methods << m
+ end
+ end
+
+ classes.each do |k|
+ @allclasses[k.index_name] = k if !@allclasses.has_key?(k.index_name)
+ end
+
+ # generate nodes and plugins found
+ classes.each do |k|
+ if k.context.is_module?
+ k.context.each_node do |name,node|
+ nodes << HTMLPuppetNode.new(node, toplevel, NODE_DIR, @options)
+ @nodes << nodes.last
+ end
+ k.context.each_plugin do |plugin|
+ @plugins << HTMLPuppetPlugin.new(plugin, toplevel, PLUGIN_DIR, @options)
+ end
+ k.context.each_fact do |fact|
+ @plugins << HTMLPuppetPlugin.new(fact, toplevel, PLUGIN_DIR, @options)
+ end
+ end
+ end
+
+ @files << file
+ @allfiles << { "file" => file, "modules" => modules, "classes" => classes, "methods" => methods, "nodes" => nodes }
+ end
+
+ @classes = @allclasses.values
+ end
+
+ # produce a class/module list of HTMLPuppetModule/HTMLPuppetClass
+ # based on the code object traversal.
+ def generate_class_list(classes, modules, from, html_file, class_dir)
+ if from.is_module? and !@modules.has_key?(from.name)
+ k = HTMLPuppetModule.new(from, html_file, class_dir, @options)
+ classes << k
+ @modules[from.name] = k
+ modules << @modules[from.name]
+ elsif from.is_module?
+ modules << @modules[from.name]
+ elsif !from.is_module?
+ k = HTMLPuppetClass.new(from, html_file, class_dir, @options)
+ classes << k
+ end
+ from.each_classmodule do |mod|
+ generate_class_list(classes, modules, mod, html_file, class_dir)
+ end
+ end
+
+ # generate all the subdirectories, modules, classes and files
+ def gen_sub_directories
+ begin
+ super
+ File.makedirs(MODULE_DIR)
+ File.makedirs(NODE_DIR)
+ File.makedirs(PLUGIN_DIR)
+ rescue
+ $stderr.puts $!.message
+ exit 1
+ end
+ end
+
+ # generate the index of modules
+ def gen_file_index
+ gen_top_index(@modules.values, 'All Modules', RDoc::Page::TOP_INDEX, "fr_modules_index.html")
+ end
+
+ # generate a top index
+ def gen_top_index(collection, title, template, filename)
+ template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template)
+ res = []
+ collection.sort.each do |f|
+ if f.document_self
+ res << { "classlist" => CGI.escapeHTML("#{MODULE_DIR}/fr_#{f.index_name}.html"), "module" => CGI.escapeHTML("#{CLASS_DIR}/#{f.index_name}.html"),"name" => CGI.escapeHTML(f.index_name) }
+ end
+ end
+
+ values = {
+ "entries" => res,
+ 'list_title' => CGI.escapeHTML(title),
+ 'index_url' => main_url,
+ 'charset' => @options.charset,
+ 'style_url' => style_url('', @options.css),
+ }
+
+ File.open(filename, "w") do |f|
+ template.write_html_on(f, values)
+ end
+ end
+
+ # generate the all classes index file and the combo index
+ def gen_class_index
+ gen_an_index(@classes, 'All Classes', RDoc::Page::CLASS_INDEX, "fr_class_index.html")
+ @allfiles.each do |file|
+ unless file['file'].context.file_relative_name =~ /\.rb$/
+ gen_composite_index(file,
+ RDoc::Page::COMBO_INDEX,
+ "#{MODULE_DIR}/fr_#{file["file"].context.module_name}.html")
+ end
+ end
+ end
+
+ def gen_composite_index(collection, template, filename)\
+ return if FileTest.exists?(filename)
+
+ template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template)
+ res1 = []
+ collection['classes'].sort.each do |f|
+ if f.document_self
+ unless f.context.is_module?
+ res1 << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.index_name) }
+ end
+ end
+ end
+
+ res2 = []
+ collection['methods'].sort.each do |f|
+ if f.document_self
+ res2 << { "href" => "../"+f.path, "name" => f.index_name.sub(/\(.*\)$/,'') }
+ end
+ end
+
+ module_name = []
+ res3 = []
+ res4 = []
+ collection['modules'].sort.each do |f|
+ module_name << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.index_name) }
+ unless f.facts.nil?
+ f.facts.each do |fact|
+ res3 << {"href" => "../"+CGI.escapeHTML(AllReferences["PLUGIN(#{fact.name})"].path), "name" => CGI.escapeHTML(fact.name)}
+ end
+ end
+ unless f.plugins.nil?
+ f.plugins.each do |plugin|
+ res4 << {"href" => "../"+CGI.escapeHTML(AllReferences["PLUGIN(#{plugin.name})"].path), "name" => CGI.escapeHTML(plugin.name)}
+ end
+ end
+ end
+
+ res5 = []
+ collection['nodes'].sort.each do |f|
+ if f.document_self
+ res5 << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.name) }
+ end
+ end
+
+ values = {
+ "module" => module_name,
+ "classes" => res1,
+ 'classes_title' => CGI.escapeHTML("Classes"),
+ 'defines_title' => CGI.escapeHTML("Defines"),
+ 'facts_title' => CGI.escapeHTML("Custom Facts"),
+ 'plugins_title' => CGI.escapeHTML("Plugins"),
+ 'nodes_title' => CGI.escapeHTML("Nodes"),
+ 'index_url' => main_url,
+ 'charset' => @options.charset,
+ 'style_url' => style_url('', @options.css),
+ }
+
+ values["defines"] = res2 if res2.size>0
+ values["facts"] = res3 if res3.size>0
+ values["plugins"] = res4 if res4.size>0
+ values["nodes"] = res5 if res5.size>0
+
+ File.open(filename, "w") do |f|
+ template.write_html_on(f, values)
+ end
+ end
+
+ # returns the initial_page url
+ def main_url
+ main_page = @options.main_page
+ ref = nil
+ if main_page
+ ref = AllReferences[main_page]
+ if ref
+ ref = ref.path
+ else
+ $stderr.puts "Could not find main page #{main_page}"
+ end
+ end
+
+ unless ref
+ for file in @files
+ if file.document_self and file.context.global
+ ref = CGI.escapeHTML("#{CLASS_DIR}/#{file.context.module_name}.html")
+ break
+ end
+ end
+ end
+
+ unless ref
+ for file in @files
+ if file.document_self and !file.context.global
+ ref = CGI.escapeHTML("#{CLASS_DIR}/#{file.context.module_name}.html")
+ break
+ end
+ end
+ end
+
+ unless ref
+ $stderr.puts "Couldn't find anything to document"
+ $stderr.puts "Perhaps you've used :stopdoc: in all classes"
+ exit(1)
+ end
+
+ ref
+ end
+
+ end
+
+ # This module is used to hold/generate a list of puppet resources
+ # this is used in HTMLPuppetClass and HTMLPuppetNode
+ module ResourceContainer
+ def collect_resources
+ list = @context.resource_list
+ @resources = list.collect {|m| HTMLPuppetResource.new(m, self, @options) }
+ end
+
+ def build_resource_summary_list(path_prefix='')
+ collect_resources unless @resources
+ resources = @resources.sort
+ res = []
+ resources.each do |r|
+ res << {
+ "name" => CGI.escapeHTML(r.name),
+ "aref" => "#{path_prefix}\##{r.aref}"
+ }
+ end
+ res
+ end
+
+ def build_resource_detail_list(section)
+ outer = []
+ resources = @resources.sort
+ resources.each do |r|
+ row = {}
+ if r.section == section and r.document_self
+ row["name"] = CGI.escapeHTML(r.name)
+ desc = r.description.strip
+ row["m_desc"] = desc unless desc.empty?
+ row["aref"] = r.aref
+ row["params"] = r.params
+ outer << row
+ end
+ end
+ outer
+ end
+ end
+
+ class HTMLPuppetClass < HtmlClass
+ include ResourceContainer
+
+ def value_hash
+ super
+ rl = build_resource_summary_list
+ @values["resources"] = rl unless rl.empty?
+
+ @context.sections.each do |section|
+ secdata = @values["sections"].select { |secdata| secdata["secsequence"] == section.sequence }
+ if secdata.size == 1
+ secdata = secdata[0]
+
+ rdl = build_resource_detail_list(section)
+ secdata["resource_list"] = rdl unless rdl.empty?
+ end
+ end
+ @values
+ end
+ end
+
+ class HTMLPuppetNode < ContextUser
+ include ResourceContainer
+
+ attr_reader :path
+
+ def initialize(context, html_file, prefix, options)
+ super(context, options)
+
+ @html_file = html_file
+ @is_module = context.is_module?
+ @values = {}
+
+ context.viewer = self
+
+ if options.all_one_file
+ @path = context.full_name
+ else
+ @path = http_url(context.full_name, prefix)
+ end
+
+ AllReferences.add("NODE(#{@context.full_name})", self)
+ end
+
+ def name
+ @context.name
+ end
+
+ # return the relative file name to store this class in,
+ # which is also its url
+ def http_url(full_name, prefix)
+ path = full_name.dup
+ if path['<<']
+ path.gsub!(/<<\s*(\w*)/) { "from-#$1" }
+ end
+ File.join(prefix, path.split("::")) + ".html"
+ end
+
+ def parent_name
+ @context.parent.full_name
+ end
+
+ def index_name
+ name
+ end
+
+ def write_on(f)
+ value_hash
+ template = TemplatePage.new(RDoc::Page::BODYINC,
+ RDoc::Page::NODE_PAGE,
+ RDoc::Page::METHOD_LIST)
+ template.write_html_on(f, @values)
+ end
+
+ def value_hash
+ class_attribute_values
+ add_table_of_sections
+
+ @values["charset"] = @options.charset
+ @values["style_url"] = style_url(path, @options.css)
+
+ d = markup(@context.comment)
+ @values["description"] = d unless d.empty?
+
+ ml = build_method_summary_list
+ @values["methods"] = ml unless ml.empty?
+
+ rl = build_resource_summary_list
+ @values["resources"] = rl unless rl.empty?
+
+ il = build_include_list(@context)
+ @values["includes"] = il unless il.empty?
+
+ @values["sections"] = @context.sections.map do |section|
+
+ secdata = {
+ "sectitle" => section.title,
+ "secsequence" => section.sequence,
+ "seccomment" => markup(section.comment)
+ }
+
+ al = build_alias_summary_list(section)
+ secdata["aliases"] = al unless al.empty?
+
+ co = build_constants_summary_list(section)
+ secdata["constants"] = co unless co.empty?
+
+ al = build_attribute_list(section)
+ secdata["attributes"] = al unless al.empty?
+
+ cl = build_class_list(0, @context, section)
+ secdata["classlist"] = cl unless cl.empty?
+
+ mdl = build_method_detail_list(section)
+ secdata["method_list"] = mdl unless mdl.empty?
+
+ rdl = build_resource_detail_list(section)
+ secdata["resource_list"] = rdl unless rdl.empty?
+
+ secdata
+ end
+
+ @values
+ end
+
+ def build_attribute_list(section)
+ atts = @context.attributes.sort
+ res = []
+ atts.each do |att|
+ next unless att.section == section
+ if att.visibility == :public || att.visibility == :protected || @options.show_all
+ entry = {
+ "name" => CGI.escapeHTML(att.name),
+ "rw" => att.rw,
+ "a_desc" => markup(att.comment, true)
+ }
+ unless att.visibility == :public || att.visibility == :protected
+ entry["rw"] << "-"
+ end
+ res << entry
+ end
+ end
+ res
+ end
+
+ def class_attribute_values
+ h_name = CGI.escapeHTML(name)
+
+ @values["classmod"] = "Node"
+ @values["title"] = "#{@values['classmod']}: #{h_name}"
+
+ c = @context
+ c = c.parent while c and !c.diagram
+
+ if c && c.diagram
+ @values["diagram"] = diagram_reference(c.diagram)
+ end
+
+ @values["full_name"] = h_name
+
+ parent_class = @context.superclass
+
+ if parent_class
+ @values["parent"] = CGI.escapeHTML(parent_class)
+
+ if parent_name
+ lookup = parent_name + "::" + parent_class
+ else
+ lookup = parent_class
+ end
+ lookup = "NODE(#{lookup})"
+ parent_url = AllReferences[lookup] || AllReferences[parent_class]
+ if parent_url and parent_url.document_self
+ @values["par_url"] = aref_to(parent_url.path)
+ end
+ end
+
+ files = []
+ @context.in_files.each do |f|
+ res = {}
+ full_path = CGI.escapeHTML(f.file_absolute_name)
+
+ res["full_path"] = full_path
+ res["full_path_url"] = aref_to(f.viewer.path) if f.document_self
+
+ if @options.webcvs
+ res["cvsurl"] = cvs_url( @options.webcvs, full_path )
+ end
+
+ files << res
+ end
+
+ @values['infiles'] = files
+ end
+
+ def <=>(other)
+ self.name <=> other.name
+ end
+ end
+
+ class HTMLPuppetModule < HtmlClass
+
+ def initialize(context, html_file, prefix, options)
+ super(context, html_file, prefix, options)
+ end
+
+ def value_hash
+ @values = super
+
+ fl = build_facts_summary_list
+ @values["facts"] = fl unless fl.empty?
+
+ pl = build_plugins_summary_list
+ @values["plugins"] = pl unless pl.empty?
+
+ nl = build_nodes_list(0, @context)
+ @values["nodelist"] = nl unless nl.empty?
+
+ @values
+ end
+
+ def build_nodes_list(level, context)
+ res = ""
+ prefix = "&nbsp;&nbsp;::" * level;
+
+ context.nodes.sort.each do |node|
+ if node.document_self
+ res <<
+ prefix <<
+ "Node " <<
+ href(url(node.viewer.path), "link", node.full_name) <<
+ "<br />\n"
+ end
+ end
+ res
+ end
+
+ def build_facts_summary_list
+ potentially_referenced_list(context.facts) {|fn| ["PLUGIN(#{fn})"] }
+ end
+
+ def build_plugins_summary_list
+ potentially_referenced_list(context.plugins) {|fn| ["PLUGIN(#{fn})"] }
+ end
+
+ def facts
+ @context.facts
+ end
+
+ def plugins
+ @context.plugins
+ end
+
+ end
+
+ class HTMLPuppetPlugin < ContextUser
+ attr_reader :path
+
+ def initialize(context, html_file, prefix, options)
+ super(context, options)
+
+ @html_file = html_file
+ @is_module = false
+ @values = {}
+
+ context.viewer = self
+
+ if options.all_one_file
+ @path = context.full_name
+ else
+ @path = http_url(context.full_name, prefix)
+ end
+
+ AllReferences.add("PLUGIN(#{@context.full_name})", self)
+ end
+
+ def name
+ @context.name
+ end
+
+ # return the relative file name to store this class in,
+ # which is also its url
+ def http_url(full_name, prefix)
+ path = full_name.dup
+ if path['<<']
+ path.gsub!(/<<\s*(\w*)/) { "from-#$1" }
+ end
+ File.join(prefix, path.split("::")) + ".html"
+ end
+
+ def parent_name
+ @context.parent.full_name
+ end
+
+ def index_name
+ name
+ end
+
+ def write_on(f)
+ value_hash
+ template = TemplatePage.new(RDoc::Page::BODYINC,
+ RDoc::Page::PLUGIN_PAGE,
+ RDoc::Page::PLUGIN_LIST)
+ template.write_html_on(f, @values)
+ end
+
+ def value_hash
+ attribute_values
+ add_table_of_sections
+
+ @values["charset"] = @options.charset
+ @values["style_url"] = style_url(path, @options.css)
+
+ d = markup(@context.comment)
+ @values["description"] = d unless d.empty?
+
+ if context.is_fact?
+ unless context.confine.empty?
+ res = {}
+ res["type"] = context.confine[:type]
+ res["value"] = context.confine[:value]
+ @values["confine"] = [res]
+ end
+ else
+ @values["type"] = context.type
+ end
+
+ @values["sections"] = @context.sections.map do |section|
+ secdata = {
+ "sectitle" => section.title,
+ "secsequence" => section.sequence,
+ "seccomment" => markup(section.comment)
+ }
+ secdata
+ end
+
+ @values
+ end
+
+ def attribute_values
+ h_name = CGI.escapeHTML(name)
+
+ if @context.is_fact?
+ @values["classmod"] = "Fact"
+ else
+ @values["classmod"] = "Plugin"
+ end
+ @values["title"] = "#{@values['classmod']}: #{h_name}"
+
+ c = @context
+ @values["full_name"] = h_name
+
+ files = []
+ @context.in_files.each do |f|
+ res = {}
+ full_path = CGI.escapeHTML(f.file_absolute_name)
+
+ res["full_path"] = full_path
+ res["full_path_url"] = aref_to(f.viewer.path) if f.document_self
+
+ if @options.webcvs
+ res["cvsurl"] = cvs_url( @options.webcvs, full_path )
+ end
+
+ files << res
+ end
+
+ @values['infiles'] = files
+ end
+
+ def <=>(other)
+ self.name <=> other.name
+ end
+
+ end
+
+ class HTMLPuppetResource
+ include MarkUp
+
+ attr_reader :context
+
+ @@seq = "R000000"
+
+ def initialize(context, html_class, options)
+ @context = context
+ @html_class = html_class
+ @options = options
+ @@seq = @@seq.succ
+ @seq = @@seq
+
+ context.viewer = self
+
+ AllReferences.add(name, self)
+ end
+
+ def as_href(from_path)
+ if @options.all_one_file
+ "#" + path
+ else
+ HTMLGenerator.gen_url(from_path, path)
+ end
+ end
+
+ def name
+ @context.name
+ end
+
+ def section
+ @context.section
+ end
+
+ def index_name
+ "#{@context.name}"
+ end
+
+ def params
+ @context.params
+ end
+
+ def parent_name
+ if @context.parent.parent
+ @context.parent.parent.full_name
+ else
+ nil
+ end
+ end
+
+ def aref
+ @seq
+ end
+
+ def path
+ if @options.all_one_file
+ aref
+ else
+ @html_class.path + "#" + aref
+ end
+ end
+
+ def description
+ markup(@context.comment)
+ end
+
+ def <=>(other)
+ @context <=> other.context
+ end
+
+ def document_self
+ @context.document_self
+ end
+
+ def find_symbol(symbol, method=nil)
+ res = @context.parent.find_symbol(symbol, method)
+ if res
+ res = res.viewer
+ end
+ res
+ end
+
+ end
+
+ class PuppetGeneratorInOne < HTMLGeneratorInOne
+ def gen_method_index
+ gen_an_index(HtmlMethod.all_methods, 'Defines')
+ end
+ end
+
+end
diff --git a/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb b/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb
new file mode 100644
index 000000000..c71f81915
--- /dev/null
+++ b/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb
@@ -0,0 +1,1051 @@
+#
+# = CSS2 RDoc HTML template
+#
+# This is a template for RDoc that uses XHTML 1.0 Transitional and dictates a
+# bit more of the appearance of the output to cascading stylesheets than the
+# default. It was designed for clean inline code display, and uses DHTMl to
+# toggle the visbility of each method's source with each click on the '[source]'
+# link.
+#
+# == Authors
+#
+# * Michael Granger <ged@FaerieMUD.org>
+#
+# Copyright (c) 2002, 2003 The FaerieMUD Consortium. Some rights reserved.
+#
+# This work is licensed under the Creative Commons Attribution License. To view
+# a copy of this license, visit http://creativecommons.org/licenses/by/1.0/ or
+# send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California
+# 94305, USA.
+#
+
+module RDoc
+ module Page
+
+ FONTS = "Verdana,Arial,Helvetica,sans-serif"
+
+STYLE = %{
+body {
+ font-family: Verdana,Arial,Helvetica,sans-serif;
+ font-size: 90%;
+ margin: 0;
+ margin-left: 40px;
+ padding: 0;
+ background: white;
+}
+
+h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
+h1 { font-size: 150%; }
+h2,h3,h4 { margin-top: 1em; }
+
+a { background: #eef; color: #039; text-decoration: none; }
+a:hover { background: #039; color: #eef; }
+
+/* Override the base stylesheet's Anchor inside a table cell */
+td > a {
+ background: transparent;
+ color: #039;
+ text-decoration: none;
+}
+
+/* and inside a section title */
+.section-title > a {
+ background: transparent;
+ color: #eee;
+ text-decoration: none;
+}
+
+/* === Structural elements =================================== */
+
+div#index {
+ margin: 0;
+ margin-left: -40px;
+ padding: 0;
+ font-size: 90%;
+}
+
+
+div#index a {
+ margin-left: 0.7em;
+}
+
+div#index .section-bar {
+ margin-left: 0px;
+ padding-left: 0.7em;
+ background: #ccc;
+ font-size: small;
+}
+
+
+div#classHeader, div#fileHeader {
+ width: auto;
+ color: white;
+ padding: 0.5em 1.5em 0.5em 1.5em;
+ margin: 0;
+ margin-left: -40px;
+ border-bottom: 3px solid #006;
+}
+
+div#classHeader a, div#fileHeader a {
+ background: inherit;
+ color: white;
+}
+
+div#classHeader td, div#fileHeader td {
+ background: inherit;
+ color: white;
+}
+
+
+div#fileHeader {
+ background: #057;
+}
+
+div#classHeader {
+ background: #048;
+}
+
+div#nodeHeader {
+ background: #7f7f7f;
+}
+
+.class-name-in-header {
+ font-size: 180%;
+ font-weight: bold;
+}
+
+
+div#bodyContent {
+ padding: 0 1.5em 0 1.5em;
+}
+
+div#description {
+ padding: 0.5em 1.5em;
+ background: #efefef;
+ border: 1px dotted #999;
+}
+
+div#description h1,h2,h3,h4,h5,h6 {
+ color: #125;;
+ background: transparent;
+}
+
+div#validator-badges {
+ text-align: center;
+}
+div#validator-badges img { border: 0; }
+
+div#copyright {
+ color: #333;
+ background: #efefef;
+ font: 0.75em sans-serif;
+ margin-top: 5em;
+ margin-bottom: 0;
+ padding: 0.5em 2em;
+}
+
+
+/* === Classes =================================== */
+
+table.header-table {
+ color: white;
+ font-size: small;
+}
+
+.type-note {
+ font-size: small;
+ color: #DEDEDE;
+}
+
+.xxsection-bar {
+ background: #eee;
+ color: #333;
+ padding: 3px;
+}
+
+.section-bar {
+ color: #333;
+ border-bottom: 1px solid #999;
+ margin-left: -20px;
+}
+
+
+.section-title {
+ background: #79a;
+ color: #eee;
+ padding: 3px;
+ margin-top: 2em;
+ margin-left: -30px;
+ border: 1px solid #999;
+}
+
+.top-aligned-row { vertical-align: top }
+.bottom-aligned-row { vertical-align: bottom }
+
+/* --- Context section classes ----------------------- */
+
+.context-row { }
+.context-item-name { font-family: monospace; font-weight: bold; color: black; }
+.context-item-value { font-size: small; color: #448; }
+.context-item-desc { color: #333; padding-left: 2em; }
+
+/* --- Method classes -------------------------- */
+.method-detail {
+ background: #efefef;
+ padding: 0;
+ margin-top: 0.5em;
+ margin-bottom: 1em;
+ border: 1px dotted #ccc;
+}
+.method-heading {
+ color: black;
+ background: #ccc;
+ border-bottom: 1px solid #666;
+ padding: 0.2em 0.5em 0 0.5em;
+}
+.method-signature { color: black; background: inherit; }
+.method-name { font-weight: bold; }
+.method-args { font-style: italic; }
+.method-description { padding: 0 0.5em 0 0.5em; }
+
+/* --- Source code sections -------------------- */
+
+a.source-toggle { font-size: 90%; }
+div.method-source-code {
+ background: #262626;
+ color: #ffdead;
+ margin: 1em;
+ padding: 0.5em;
+ border: 1px dashed #999;
+ overflow: hidden;
+}
+
+div.method-source-code pre { color: #ffdead; overflow: hidden; }
+
+/* --- Ruby keyword styles --------------------- */
+
+.standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
+
+.ruby-constant { color: #7fffd4; background: transparent; }
+.ruby-keyword { color: #00ffff; background: transparent; }
+.ruby-ivar { color: #eedd82; background: transparent; }
+.ruby-operator { color: #00ffee; background: transparent; }
+.ruby-identifier { color: #ffdead; background: transparent; }
+.ruby-node { color: #ffa07a; background: transparent; }
+.ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
+.ruby-regexp { color: #ffa07a; background: transparent; }
+.ruby-value { color: #7fffd4; background: transparent; }
+}
+
+
+#####################################################################
+### H E A D E R T E M P L A T E
+#####################################################################
+
+XHTML_PREAMBLE = %{<?xml version="1.0" encoding="%charset%"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+}
+
+HEADER = XHTML_PREAMBLE + %{
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <meta http-equiv="Content-Script-Type" content="text/javascript" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
+ <script type="text/javascript">
+ // <![CDATA[
+
+ function popupCode( url ) {
+ window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
+ }
+
+ function toggleCode( id ) {
+ if ( document.getElementById )
+ elem = document.getElementById( id );
+ else if ( document.all )
+ elem = eval( "document.all." + id );
+ else
+ return false;
+
+ elemStyle = elem.style;
+
+ if ( elemStyle.display != "block" ) {
+ elemStyle.display = "block"
+ } else {
+ elemStyle.display = "none"
+ }
+
+ return true;
+ }
+
+ // Make codeblocks hidden by default
+ document.writeln( "<style type=\\"text/css\\">div.method-source-code { display: none }</style>" )
+
+ // ]]>
+ </script>
+
+</head>
+<body>
+}
+
+
+#####################################################################
+### C O N T E X T C O N T E N T T E M P L A T E
+#####################################################################
+
+CONTEXT_CONTENT = %{
+}
+
+
+#####################################################################
+### F O O T E R T E M P L A T E
+#####################################################################
+FOOTER = %{
+<div id="validator-badges">
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
+</div>
+
+</body>
+</html>
+}
+
+
+#####################################################################
+### F I L E P A G E H E A D E R T E M P L A T E
+#####################################################################
+
+FILE_PAGE = %{
+ <div id="fileHeader">
+ <h1>%short_name%</h1>
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>Path:</strong></td>
+ <td>%full_path%
+IF:cvsurl
+ &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>
+}
+
+NODE_PAGE = %{
+ <div id="nodeHeader">
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>%classmod%</strong></td>
+ <td class="class-name-in-header">%full_name%</td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>In:</strong></td>
+ <td>
+START:infiles
+IF:full_path_url
+ <a href="%full_path_url%">
+ENDIF:full_path_url
+ %full_path%
+IF:full_path_url
+ </a>
+ENDIF:full_path_url
+IF:cvsurl
+ &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>
+}
+
+PLUGIN_PAGE = %{
+ <div id="classHeader">
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>%classmod%</strong></td>
+ <td class="class-name-in-header">%full_name%</td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>In:</strong></td>
+ <td>
+START:infiles
+IF:full_path_url
+ <a href="%full_path_url%">
+ENDIF:full_path_url
+ %full_path%
+IF:full_path_url
+ </a>
+ENDIF:full_path_url
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
+ENDIF:cvsurl
+ <br />
+END:infiles
+ </td>
+ </tr>
+ </table>
+ </div>
+}
+
+
+#####################################################################
+### M E T H O D L I S T T E M P L A T E
+#####################################################################
+
+PLUGIN_LIST = %{
+
+ <div id="contextContent">
+IF:description
+ <div id="description">
+ %description%
+ </div>
+ENDIF:description
+
+
+IF:toc
+ <div id="contents-list">
+ <h3 class="section-bar">Contents</h3>
+ <ul>
+START:toc
+ <li><a href="#%href%">%secname%</a></li>
+END:toc
+ </ul>
+ENDIF:toc
+ </div>
+
+ </div>
+
+<!-- Confine -->
+IF:confine
+START:confine
+ <div id="attribute-list">
+ <h3 class="section-bar">Confine</h3>
+ %type%&nbsp;%value%
+ <div class="name-list">
+ </div>
+ </div>
+END:confine
+ENDIF:confine
+
+<!-- Type -->
+IF:type
+ <div id="attribute-list">
+ <h3 class="section-bar">Type</h3>
+ %type%
+ <div class="name-list">
+ </div>
+ </div>
+ENDIF:type
+
+START:sections
+ <div id="section">
+IF:sectitle
+ <h2 class="section-title"><a name="%secsequence%">%sectitle%</a></h2>
+IF:seccomment
+ <div class="section-comment">
+ %seccomment%
+ </div>
+ENDIF:seccomment
+ENDIF:sectitle
+END:sections
+}
+
+
+METHOD_LIST = %{
+
+ <div id="contextContent">
+IF:diagram
+ <div id="diagram">
+ %diagram%
+ </div>
+ENDIF:diagram
+
+IF:description
+ <div id="description">
+ %description%
+ </div>
+ENDIF:description
+
+IF:requires
+ <div id="requires-list">
+ <h3 class="section-bar">Required files</h3>
+
+ <div class="name-list">
+START:requires
+ HREF:aref:name:&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
+
+IF:resources
+ <div id="method-list">
+ <h3 class="section-bar">Resources</h3>
+
+ <div class="name-list">
+START:resources
+ HREF:aref:name:&nbsp;&nbsp;
+END:resources
+ </div>
+ </div>
+ENDIF:resources
+
+ </div>
+
+
+ <!-- if includes -->
+IF:includes
+ <div id="includes">
+ <h3 class="section-bar">Included Classes</h3>
+
+ <div id="includes-list">
+START:includes
+ <span class="include-name">HREF:aref:name:</span>
+END:includes
+ </div>
+ </div>
+ENDIF:includes
+
+START:sections
+ <div id="section">
+IF:sectitle
+ <h2 class="section-title"><a name="%secsequence%">%sectitle%</a></h2>
+IF:seccomment
+ <div class="section-comment">
+ %seccomment%
+ </div>
+ENDIF:seccomment
+ENDIF:sectitle
+
+
+<!-- if facts -->
+IF:facts
+ <div id="class-list">
+ <h3 class="section-bar">Custom Facts</h3>
+START:facts
+ HREF:aref:name:&nbsp;&nbsp;
+END:facts
+ </div>
+ENDIF:facts
+
+<!-- if plugins -->
+IF:plugins
+ <div id="class-list">
+ <h3 class="section-bar">Plugins</h3>
+START:plugins
+HREF:aref:name:&nbsp;&nbsp;
+END:plugins
+ </div>
+ENDIF:plugins
+
+<!-- if nodes -->
+IF:nodelist
+ <div id="class-list">
+ <h3 class="section-bar">Nodes</h3>
+
+ %nodelist%
+ </div>
+ENDIF:nodelist
+
+<!-- if class -->
+IF:classlist
+ <div id="class-list">
+ <h3 class="section-bar">Classes and Modules</h3>
+
+ %classlist%
+ </div>
+ENDIF:classlist
+
+IF:constants
+ <div id="constants-list">
+ <h3 class="section-bar">Global Variables</h3>
+
+ <div class="name-list">
+ <table summary="Variables">
+START:constants
+ <tr class="top-aligned-row context-row">
+ <td class="context-item-name">%name%</td>
+ <td>=</td>
+ <td class="context-item-value">%value%</td>
+IF:desc
+ <td width="3em">&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
+
+
+ <!-- if resource_list -->
+IF:resource_list
+ <div id="resources">
+ <h3 class="section-bar">Resources</h3>
+START:resource_list
+
+ <div id="method-%aref%" class="method-detail">
+ <a name="%aref%"></a>
+
+ <div class="method-heading">
+ <span class="method-name">%name%</span><br />
+IF:params
+START:params
+ &nbsp;&nbsp;&nbsp;<span class="method-args">%name% => %value%</span><br />
+END:params
+ENDIF:params
+ </div>
+
+ <div class="method-description">
+IF:m_desc
+ %m_desc%
+ENDIF:m_desc
+ </div>
+ </div>
+END:resource_list
+
+ </div>
+ENDIF:resource_list
+
+END:sections
+}
+
+
+#####################################################################
+### B O D Y T E M P L A T E
+#####################################################################
+
+BODY = HEADER + %{
+
+!INCLUDE! <!-- banner header -->
+
+ <div id="bodyContent">
+
+} + METHOD_LIST + %{
+
+ </div>
+
+} + FOOTER
+
+BODYINC = HEADER + %{
+
+!INCLUDE! <!-- banner header -->
+
+ <div id="bodyContent">
+
+!INCLUDE!
+
+ </div>
+
+} + FOOTER
+
+
+
+#####################################################################
+### S O U R C E C O D E T E M P L A T E
+#####################################################################
+
+SRC_PAGE = XHTML_PREAMBLE + %{
+<html>
+<head>
+ <title>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
+</head>
+<body class="standalone-code">
+ <pre>%code%</pre>
+</body>
+</html>
+}
+
+
+#####################################################################
+### I N D E X F I L E T E M P L A T E S
+#####################################################################
+
+FR_INDEX_BODY = %{
+!INCLUDE!
+}
+
+FILE_INDEX = XHTML_PREAMBLE + %{
+<!--
+
+ %list_title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%list_title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" />
+ <base target="docwin" />
+</head>
+<body>
+<div id="index">
+ <h1 class="section-bar">%list_title%</h1>
+ <div id="index-entries">
+START:entries
+ <a href="%href%">%name%</a><br />
+END:entries
+ </div>
+</div>
+</body>
+</html>
+}
+
+TOP_INDEX = XHTML_PREAMBLE + %{
+<!--
+
+ %list_title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%list_title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" />
+ <base target="classes" />
+ <SCRIPT LANGUAGE="JavaScript">
+ <!--
+ function load(classlist,module) {
+ parent.classes.location.href = classlist;
+ parent.docwin.location.href = module;
+ }
+ //--></SCRIPT>
+</head>
+<body>
+<div id="index">
+ <h1 class="section-bar">%list_title%</h1>
+ <div id="index-entries">
+START:entries
+ <a href="%classlist%" onclick="load('%classlist%','%module%'); return true;">%name%</a><br />
+END:entries
+ </div>
+</div>
+</body>
+</html>
+}
+
+
+CLASS_INDEX = FILE_INDEX
+METHOD_INDEX = FILE_INDEX
+
+COMBO_INDEX = XHTML_PREAMBLE + %{
+<!--
+
+ %classes_title% &amp; %defines_title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%classes_title% &amp; %defines_title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="../%style_url%" type="text/css" />
+ <base target="docwin" />
+ <SCRIPT LANGUAGE="JavaScript">
+ <!--
+ function load(url) {
+ parent.docwin.location.href = url;
+ }
+ //--></SCRIPT>
+
+</head>
+<body>
+<div id="index">
+
+ <a href="../fr_class_index.html" target="classes">All Classes</a><br />
+
+
+<h1 class="section-bar">Module</h1>
+ <div id="index-entries">
+START:module
+ <a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:module
+ </div>
+ </div>
+<div id="index">
+
+IF:nodes
+ <h1 class="section-bar">%nodes_title%</h1>
+ <div id="index-entries">
+START:nodes
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:nodes
+ </div>
+ENDIF:nodes
+
+IF:classes
+ <h1 class="section-bar">%classes_title%</h1>
+ <div id="index-entries">
+START:classes
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:classes
+ </div>
+ENDIF:classes
+
+IF:defines
+ <h1 class="section-bar">%defines_title%</h1>
+ <div id="index-entries">
+START:defines
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:defines
+ </div>
+ENDIF:defines
+
+IF:facts
+ <h1 class="section-bar">%facts_title%</h1>
+ <div id="index-entries">
+START:facts
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:facts
+ </div>
+ENDIF:facts
+
+
+IF:plugins
+ <h1 class="section-bar">%plugins_title%</h1>
+ <div id="index-entries">
+START:plugins
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:plugins
+ </div>
+ENDIF:plugins
+
+</div>
+</body>
+</html>
+}
+
+INDEX = %{<?xml version="1.0" encoding="%charset%"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+
+<!--
+
+ %title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+</head>
+<frameset cols="20%, 80%">
+ <frameset rows="30%,70%">
+ <frame src="fr_modules_index.html" title="All Modules" />
+ <frame src="fr_class_index.html" name="classes" title="Classes & Defines" />
+ </frameset>
+ <frame src="%initial_page%" name="docwin" />
+</frameset>
+</html>
+}
+
+
+
+ end # module Page
+end # class RDoc
+
+require 'rdoc/generators/template/html/one_page_html'
diff --git a/lib/puppet/util/rdoc/parser.rb b/lib/puppet/util/rdoc/parser.rb
new file mode 100644
index 000000000..33dd6564f
--- /dev/null
+++ b/lib/puppet/util/rdoc/parser.rb
@@ -0,0 +1,437 @@
+# Puppet "parser" for the rdoc system
+# The parser uses puppet parser and traverse the AST to instruct RDoc about
+# our current structures. It also parses ruby files that could contain
+# either custom facts or puppet plugins (functions, types...)
+
+# rdoc mandatory includes
+require "rdoc/code_objects"
+require "puppet/util/rdoc/code_objects"
+require "rdoc/tokenstream"
+require "rdoc/markup/simple_markup/preprocess"
+require "rdoc/parsers/parserfactory"
+
+module RDoc
+
+class Parser
+ extend ParserFactory
+
+ # parser registration into RDoc
+ parse_files_matching(/\.(rb|pp)$/)
+
+ # called with the top level file
+ def initialize(top_level, file_name, content, options, stats)
+ @options = options
+ @stats = stats
+ @input_file_name = file_name
+ @top_level = PuppetTopLevel.new(top_level)
+ @progress = $stderr unless options.quiet
+ end
+
+ # main entry point
+ def scan
+ Puppet.info "rdoc: scanning %s" % @input_file_name
+ if @input_file_name =~ /\.pp$/
+ @parser = Puppet::Parser::Parser.new(:environment => Puppet[:environment])
+ @parser.file = @input_file_name
+ @ast = @parser.parse
+ end
+ scan_top_level(@top_level)
+ @top_level
+ end
+
+ private
+
+ # walk down the namespace and lookup/create container as needed
+ def get_class_or_module(container, name)
+
+ # class ::A -> A is in the top level
+ if name =~ /^::/
+ container = @top_level
+ end
+
+ names = name.split('::')
+
+ final_name = names.pop
+ names.each do |name|
+ prev_container = container
+ container = container.find_module_named(name)
+ unless container
+ container = prev_container.add_module(PuppetClass, name)
+ end
+ end
+ return [container, final_name]
+ end
+
+ # split_module tries to find if +path+ belongs to the module path
+ # if it does, it returns the module name, otherwise if we are sure
+ # it is part of the global manifest path, "<site>" is returned.
+ # And finally if this path couldn't be mapped anywhere, nil is returned.
+ def split_module(path)
+ # find a module
+ fullpath = File.expand_path(path)
+ Puppet.debug "rdoc: testing %s" % fullpath
+ if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins)\/.+\.(pp|rb)$/
+ modpath = $1
+ name = $2
+ Puppet.debug "rdoc: module %s into %s ?" % [name, modpath]
+ Puppet::Module.modulepath().each do |mp|
+ if File.identical?(modpath,mp)
+ Puppet.debug "rdoc: found module %s" % name
+ return name
+ end
+ end
+ end
+ if fullpath =~ /\.(pp|rb)$/
+ # there can be paths we don't want to scan under modules
+ # imagine a ruby or manifest that would be distributed as part as a module
+ # but we don't want those to be hosted under <site>
+ Puppet::Module.modulepath().each do |mp|
+ # check that fullpath is a descendant of mp
+ dirname = fullpath
+ while (dirname = File.dirname(dirname)) != '/'
+ return nil if File.identical?(dirname,mp)
+ end
+ end
+ end
+ # we are under a global manifests
+ Puppet.debug "rdoc: global manifests"
+ return "<site>"
+ end
+
+ # create documentation for the top level +container+
+ def scan_top_level(container)
+ # use the module README as documentation for the module
+ comment = ""
+ readme = File.join(File.dirname(File.dirname(@input_file_name)), "README")
+ comment = File.open(readme,"r") { |f| f.read } if FileTest.readable?(readme)
+ look_for_directives_in(container, comment) unless comment.empty?
+
+ # infer module name from directory
+ name = split_module(@input_file_name)
+ if name.nil?
+ # skip .pp files that are not in manifests directories as we can't guarantee they're part
+ # of a module or the global configuration.
+ container.document_self = false
+ return
+ end
+
+ Puppet.debug "rdoc: scanning for %s" % name
+
+ container.module_name = name
+ container.global=true if name == "<site>"
+
+ @stats.num_modules += 1
+ container, name = get_class_or_module(container,name)
+ mod = container.add_module(PuppetModule, name)
+ mod.record_location(@top_level)
+ mod.comment = comment
+
+ if @input_file_name =~ /\.pp$/
+ parse_elements(mod)
+ elsif @input_file_name =~ /\.rb$/
+ parse_plugins(mod)
+ end
+ end
+
+ # create documentation for include statements we can find in +code+
+ # and associate it with +container+
+ def scan_for_include(container, code)
+ code.each do |stmt|
+ scan_for_include(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray)
+
+ if stmt.is_a?(Puppet::Parser::AST::Function) and stmt.name == "include"
+ stmt.arguments.each do |included|
+ Puppet.debug "found include: %s" % included.value
+ container.add_include(Include.new(included.value, stmt.doc))
+ end
+ end
+ end
+ end
+
+ # create documentation for global variables assignements we can find in +code+
+ # and associate it with +container+
+ def scan_for_vardef(container, code)
+ code.each do |stmt|
+ scan_for_vardef(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray)
+
+ if stmt.is_a?(Puppet::Parser::AST::VarDef)
+ Puppet.debug "rdoc: found constant: %s = %s" % [stmt.name.to_s, value_to_s(stmt.value)]
+ container.add_constant(Constant.new(stmt.name.to_s, value_to_s(stmt.value), stmt.doc))
+ end
+ end
+ end
+
+ # create documentation for resources we can find in +code+
+ # and associate it with +container+
+ def scan_for_resource(container, code)
+ code.each do |stmt|
+ scan_for_resource(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray)
+
+ if stmt.is_a?(Puppet::Parser::AST::Resource)
+ type = stmt.type.split("::").collect { |s| s.capitalize }.join("::")
+ title = value_to_s(stmt.title)
+ Puppet.debug "rdoc: found resource: %s[%s]" % [type,title]
+
+ param = []
+ stmt.params.children.each do |p|
+ res = {}
+ res["name"] = p.param
+ if !p.value.nil?
+ if !p.value.is_a?(Puppet::Parser::AST::ASTArray)
+ res["value"] = "'#{p.value}'"
+ else
+ res["value"] = "[%s]" % p.value.children.collect { |v| "'#{v}'" }.join(", ")
+ end
+ end
+ param << res
+ end
+
+ container.add_resource(PuppetResource.new(type, title, stmt.doc, param))
+ end
+ end
+ end
+
+ # create documentation for a class named +name+
+ def document_class(name, klass, container)
+ Puppet.debug "rdoc: found new class %s" % name
+ container, name = get_class_or_module(container, name)
+
+ superclass = klass.parentclass
+ superclass = "" if superclass.nil? or superclass.empty?
+
+ @stats.num_classes += 1
+ comment = klass.doc
+ look_for_directives_in(container, comment) unless comment.empty?
+ cls = container.add_class(PuppetClass, name, superclass)
+ cls.record_location(@top_level)
+
+ # scan class code for include
+ code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::ASTArray)
+ code ||= klass.code
+ unless code.nil?
+ scan_for_include(cls, code)
+ scan_for_resource(cls, code) if Puppet.settings[:document_all]
+ end
+
+ cls.comment = comment
+ end
+
+ # create documentation for a node
+ def document_node(name, node, container)
+ Puppet.debug "rdoc: found new node %s" % name
+ superclass = node.parentclass
+ superclass = "" if superclass.nil? or superclass.empty?
+
+ comment = node.doc
+ look_for_directives_in(container, comment) unless comment.empty?
+ n = container.add_node(name, superclass)
+ n.record_location(@top_level)
+
+ code = node.code.children if node.code.is_a?(Puppet::Parser::AST::ASTArray)
+ code ||= node.code
+ unless code.nil?
+ scan_for_include(n, code)
+ scan_for_vardef(n, code)
+ scan_for_resource(n, code) if Puppet.settings[:document_all]
+ end
+
+ n.comment = comment
+ end
+
+ # create documentation for a define
+ def document_define(name, define, container)
+ Puppet.debug "rdoc: found new definition %s" % name
+ # find superclas if any
+ @stats.num_methods += 1
+
+ # find the parentclass
+ # split define name by :: to find the complete module hierarchy
+ container, name = get_class_or_module(container,name)
+
+ return if container.find_local_symbol(name)
+
+ # build up declaration
+ declaration = ""
+ define.arguments.each do |arg,value|
+ declaration << "\$#{arg}"
+ if !value.nil?
+ declaration << " => "
+ if !value.is_a?(Puppet::Parser::AST::ASTArray)
+ declaration << "'#{value.value}'"
+ else
+ declaration << "[%s]" % value.children.collect { |v| "'#{v}'" }.join(", ")
+ end
+ end
+ declaration << ", "
+ end
+ declaration.chop!.chop! if declaration.size > 1
+
+ # register method into the container
+ meth = AnyMethod.new(declaration, name)
+ container.add_method(meth)
+ meth.comment = define.doc
+ look_for_directives_in(container, meth.comment) unless meth.comment.empty?
+ meth.params = "( " + declaration + " )"
+ meth.visibility = :public
+ meth.document_self = true
+ meth.singleton = false
+ end
+
+ # Traverse the AST tree and produce code-objects node
+ # that contains the documentation
+ def parse_elements(container)
+ Puppet.debug "rdoc: scanning manifest"
+ @ast[:classes].each do |name, klass|
+ if klass.file == @input_file_name
+ unless name.empty?
+ document_class(name,klass,container)
+ else # on main class document vardefs
+ code = klass.code.children unless klass.code.is_a?(Puppet::Parser::AST::ASTArray)
+ code ||= klass.code
+ scan_for_vardef(container, code) unless code.nil?
+ end
+ end
+ end
+
+ @ast[:definitions].each do |name, define|
+ if define.file == @input_file_name
+ document_define(name,define,container)
+ end
+ end
+
+ @ast[:nodes].each do |name, node|
+ if node.file == @input_file_name
+ document_node(name,node,container)
+ end
+ end
+ end
+
+ # create documentation for plugins
+ def parse_plugins(container)
+ Puppet.debug "rdoc: scanning plugin or fact"
+ if @input_file_name =~ /\/facter\/[^\/]+\.rb$/
+ parse_fact(container)
+ else
+ parse_puppet_plugin(container)
+ end
+ end
+
+ # this is a poor man custom fact parser :-)
+ def parse_fact(container)
+ comments = ""
+ current_fact = nil
+ File.open(@input_file_name) do |of|
+ of.each do |line|
+ # fetch comments
+ if line =~ /^[ \t]*# ?(.*)$/
+ comments += $1 + "\n"
+ elsif line =~ /^[ \t]*Facter.add\(['"](.*?)['"]\)/
+ current_fact = Fact.new($1,{})
+ container.add_fact(current_fact)
+ look_for_directives_in(container, comments) unless comments.empty?
+ current_fact.comment = comments
+ current_fact.record_location(@top_level)
+ comments = ""
+ Puppet.debug "rdoc: found custom fact %s" % current_fact.name
+ elsif line =~ /^[ \t]*confine[ \t]*:(.*?)[ \t]*=>[ \t]*(.*)$/
+ current_fact.confine = { :type => $1, :value => $2 } unless current_fact.nil?
+ else # unknown line type
+ comments =""
+ end
+ end
+ end
+ end
+
+ # this is a poor man puppet plugin parser :-)
+ # it doesn't extract doc nor desc :-(
+ def parse_puppet_plugin(container)
+ comments = ""
+ current_plugin = nil
+
+ File.open(@input_file_name) do |of|
+ of.each do |line|
+ # fetch comments
+ if line =~ /^[ \t]*# ?(.*)$/
+ comments += $1 + "\n"
+ elsif line =~ /^[ \t]*newfunction[ \t]*\([ \t]*:(.*?)[ \t]*,[ \t]*:type[ \t]*=>[ \t]*(:rvalue|:lvalue)\)/
+ current_plugin = Plugin.new($1, "function")
+ container.add_plugin(current_plugin)
+ look_for_directives_in(container, comments) unless comments.empty?
+ current_plugin.comment = comments
+ current_plugin.record_location(@top_level)
+ comments = ""
+ Puppet.debug "rdoc: found new function plugins %s" % current_plugin.name
+ elsif line =~ /^[ \t]*Puppet::Type.newtype[ \t]*\([ \t]*:(.*?)\)/
+ current_plugin = Plugin.new($1, "type")
+ container.add_plugin(current_plugin)
+ look_for_directives_in(container, comments) unless comments.empty?
+ current_plugin.comment = comments
+ current_plugin.record_location(@top_level)
+ comments = ""
+ Puppet.debug "rdoc: found new type plugins %s" % current_plugin.name
+ elsif line =~ /module Puppet::Parser::Functions/
+ # skip
+ else # unknown line type
+ comments =""
+ end
+ end
+ end
+ end
+
+ # look_for_directives_in scans the current +comment+ for RDoc directives
+ def look_for_directives_in(context, comment)
+ preprocess = SM::PreProcess.new(@input_file_name, @options.rdoc_include)
+
+ preprocess.handle(comment) do |directive, param|
+ case directive
+ when "stopdoc"
+ context.stop_doc
+ ""
+ when "startdoc"
+ context.start_doc
+ context.force_documentation = true
+ ""
+ when "enddoc"
+ #context.done_documenting = true
+ #""
+ throw :enddoc
+ when "main"
+ options = Options.instance
+ options.main_page = param
+ ""
+ when "title"
+ options = Options.instance
+ options.title = param
+ ""
+ when "section"
+ context.set_current_section(param, comment)
+ comment.replace("") # 1.8 doesn't support #clear
+ break
+ else
+ warn "Unrecognized directive '#{directive}'"
+ break
+ end
+ end
+ remove_private_comments(comment)
+ end
+
+ def remove_private_comments(comment)
+ comment.gsub!(/^#--.*?^#\+\+/m, '')
+ comment.sub!(/^#--.*/m, '')
+ end
+
+ # convert an AST value to a string
+ def value_to_s(value)
+ value = value.children if value.is_a?(Puppet::Parser::AST::ASTArray)
+ if value.is_a?(Array)
+ "['#{value.join(", ")}']"
+ elsif [:true, true, "true"].include?(value)
+ "true"
+ elsif [:false, false, "false"].include?(value)
+ "false"
+ else
+ value.to_s
+ end
+ end
+end
+end \ No newline at end of file
diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb
index 148748950..70f244507 100644
--- a/lib/puppet/util/selinux.rb
+++ b/lib/puppet/util/selinux.rb
@@ -1,74 +1,67 @@
# Provides utility functions to help interfaces Puppet to SELinux.
#
-# Currently this is implemented via the command line tools. At some
-# point support should be added to use the new SELinux ruby bindings
-# as that will be faster and more reliable then shelling out when they
-# are available. At this time (2008-09-26) these bindings aren't bundled on
-# any SELinux-using distribution I know of.
+# This requires the very new SELinux Ruby bindings. These bindings closely
+# mirror the SELinux C library interface.
+#
+# Support for the command line tools is not provided because the performance
+# was abysmal. At this time (2008-11-02) the only distribution providing
+# these Ruby SELinux bindings which I am aware of is Fedora (in libselinux-ruby).
-require 'puppet/util'
+begin
+ require 'selinux'
+rescue LoadError
+ # Nothing
+end
module Puppet::Util::SELinux
- include Puppet::Util
-
def selinux_support?
- FileTest.exists?("/selinux/enforce")
+ unless defined? Selinux
+ return false
+ end
+ if Selinux.is_selinux_enabled == 1
+ return true
+ end
+ return false
end
# Retrieve and return the full context of the file. If we don't have
- # SELinux support or if the stat call fails then return nil.
+ # SELinux support or if the SELinux call fails then return nil.
def get_selinux_current_context(file)
unless selinux_support?
return nil
end
- context = ""
- begin
- execpipe("/usr/bin/stat -c %C #{file}") do |out|
- out.each do |line|
- context << line
- end
- end
- rescue Puppet::ExecutionFailure
- return nil
- end
- context.chomp!
- # Handle the case that the system seems to have SELinux support but
- # stat finds unlabled files.
- if context == "(null)"
+ retval = Selinux.lgetfilecon(file)
+ if retval == -1
return nil
end
- return context
+ return retval[1]
end
- # Use the matchpathcon command, if present, to return the SELinux context
- # which the SELinux policy on the system expects the file to have. We can
- # use this to obtain a good default context. If the command does not
- # exist or the call fails return nil.
- #
- # Note: For this command to work a full, non-relative, filesystem path
- # should be given.
+ # Retrieve and return the default context of the file. If we don't have
+ # SELinux support or if the SELinux call fails to file a default then return nil.
def get_selinux_default_context(file)
unless selinux_support?
return nil
end
- unless FileTest.executable?("/usr/sbin/matchpathcon")
+ # If the filesystem has no support for SELinux labels, return a default of nil
+ # instead of what matchpathcon would return
+ unless selinux_label_support?(file)
return nil
end
- context = ""
+ # If the file exists we should pass the mode to matchpathcon for the most specific
+ # matching. If not, we can pass a mode of 0.
begin
- execpipe("/usr/sbin/matchpathcon #{file}") do |out|
- out.each do |line|
- context << line
- end
- end
- rescue Puppet::ExecutionFailure
+ filestat = File.lstat(file)
+ mode = filestat.mode
+ rescue Errno::ENOENT
+ mode = 0
+ end
+ retval = Selinux.matchpathcon(file, mode)
+ if retval == -1
return nil
end
- # For a successful match, matchpathcon returns two fields separated by
- # a variable amount of whitespace. The second field is the full context.
- context = context.split(/\s/)[1]
- return context
+ return retval[1]
end
# Take the full SELinux context returned from the tools and parse it
@@ -91,32 +84,52 @@ module Puppet::Util::SELinux
end
# This updates the actual SELinux label on the file. You can update
- # only a single component or update the entire context. It is just a
- # wrapper around the chcon command.
+ # only a single component or update the entire context.
+ # The caveat is that since setting a partial context makes no sense the
+ # file has to already exist. Puppet (via the File resource) will always
+ # just try to set components, even if all values are specified by the manifest.
+ # I believe that the OS should always provide at least a fall-through context
+ # though on any well-running system.
def set_selinux_context(file, value, component = false)
unless selinux_support?
return nil
end
- case component
- when :seluser
- flag = "-u"
- when :selrole
- flag = "-r"
- when :seltype
- flag = "-t"
- when :selrange
- flag = "-l"
- else
- flag = nil
- end
- if flag.nil?
- cmd = ["/usr/bin/chcon","-h",value,file]
+ if component
+ # Must first get existing context to replace a single component
+ context = Selinux.lgetfilecon(file)[1]
+ if context == -1
+ # We can't set partial context components when no context exists
+ # unless/until we can find a way to make Puppet call this method
+ # once for all selinux file label attributes.
+ Puppet.warning "Can't set SELinux context on file unless the file already has some kind of context"
+ return nil
+ end
+ context = context.split(':')
+ case component
+ when :seluser
+ context[0] = value
+ when :selrole
+ context[1] = value
+ when :seltype
+ context[2] = value
+ when :selrange
+ context[3] = value
+ else
+ raise ArguementError, "set_selinux_context component must be one of :seluser, :selrole, :seltype, or :selrange"
+ end
+ context = context.join(':')
+ else
+ context = value
+ end
+
+ retval = Selinux.lsetfilecon(file, context)
+ if retval == 0
+ return true
else
- cmd = ["/usr/bin/chcon","-h",flag,value,file]
+ Puppet.warning "Failed to set SELinux context %s on %s" % [context, file]
+ return false
end
- execute(cmd)
- return true
end
# Since this call relies on get_selinux_default_context it also needs a
@@ -136,4 +149,63 @@ module Puppet::Util::SELinux
end
return nil
end
+
+ # Internal helper function to read and parse /proc/mounts
+ def read_mounts
+ begin
+ mounts = File.read("/proc/mounts")
+ rescue
+ return nil
+ end
+
+ mntpoint = {}
+
+ # Read all entries in /proc/mounts. The second column is the
+ # mountpoint and the third column is the filesystem type.
+ # We skip rootfs because it is always mounted at /
+ mounts.collect do |line|
+ params = line.split(' ')
+ next if params[2] == 'rootfs'
+ mntpoint[params[1]] = params[2]
+ end
+ return mntpoint
+ end
+
+ # Internal helper function to return which type of filesystem a
+ # given file path resides on
+ def find_fs(file)
+ unless mnts = read_mounts()
+ return nil
+ end
+
+ # For a given file:
+ # Check if the filename is in the data structure;
+ # return the fstype if it is.
+ # Just in case: return something if you're down to "/" or ""
+ # Remove the last slash and everything after it,
+ # and repeat with that as the file for the next loop through.
+ ary = file.split('/')
+ while not ary.empty? do
+ path = ary.join('/')
+ if mnts.has_key?(path)
+ return mnts[path]
+ end
+ ary.pop
+ end
+ return mnts['/']
+ end
+
+ # Check filesystem a path resides on for SELinux support against
+ # whitelist of known-good filesystems.
+ # Returns true if the filesystem can support SELinux labels and
+ # false if not.
+ def selinux_label_support?(file)
+ fstype = find_fs(file)
+ if fstype.nil?
+ return false
+ end
+ filesystems = ['ext2', 'ext3', 'ext4', 'gfs', 'gfs2', 'xfs', 'jfs']
+ return filesystems.include?(fstype)
+ end
+
end
diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb
index d0c16ec92..976c80a82 100644
--- a/lib/puppet/util/settings.rb
+++ b/lib/puppet/util/settings.rb
@@ -9,8 +9,6 @@ class Puppet::Util::Settings
include Enumerable
include Puppet::Util
- @@sync = Sync.new
-
attr_accessor :file
attr_reader :timer
@@ -21,22 +19,22 @@ class Puppet::Util::Settings
# Set a config value. This doesn't set the defaults, it sets the value itself.
def []=(param, value)
- @@sync.synchronize do # yay, thread-safe
- param = symbolize(param)
- unless element = @config[param]
- raise ArgumentError,
- "Attempt to assign a value to unknown configuration parameter %s" % param.inspect
- end
- if element.respond_to?(:munge)
- value = element.munge(value)
- end
- if element.respond_to?(:handle)
- element.handle(value)
- end
- # Reset the name, so it's looked up again.
- if param == :name
- @name = nil
- end
+ param = symbolize(param)
+ unless element = @config[param]
+ raise ArgumentError,
+ "Attempt to assign a value to unknown configuration parameter %s" % param.inspect
+ end
+ if element.respond_to?(:munge)
+ value = element.munge(value)
+ end
+ if element.respond_to?(:handle)
+ element.handle(value)
+ end
+ # Reset the name, so it's looked up again.
+ if param == :name
+ @name = nil
+ end
+ @sync.synchronize do # yay, thread-safe
@values[:memory][param] = value
@cache.clear
@@ -60,7 +58,6 @@ class Puppet::Util::Settings
return options
end
- # Turn the config into a Puppet configuration and apply it
def apply
trans = self.to_transportable
begin
@@ -87,25 +84,21 @@ class Puppet::Util::Settings
# Remove all set values, potentially skipping cli values.
def clear(exceptcli = false)
- @config.each { |name, obj|
- unless exceptcli and obj.setbycli
- obj.clear
+ @sync.synchronize do
+ @values.each do |name, values|
+ @values.delete(name) unless exceptcli and name == :cli
end
- }
- @values.each do |name, values|
- next if name == :cli and exceptcli
- @values.delete(name)
- end
- # Don't clear the 'used' in this case, since it's a config file reparse,
- # and we want to retain this info.
- unless exceptcli
- @used = []
- end
+ # Don't clear the 'used' in this case, since it's a config file reparse,
+ # and we want to retain this info.
+ unless exceptcli
+ @used = []
+ end
- @cache.clear
+ @cache.clear
- @name = nil
+ @name = nil
+ end
end
# This is mostly just used for testing.
@@ -178,10 +171,12 @@ class Puppet::Util::Settings
end
str = str.intern
if self.valid?(str)
- if self.boolean?(str)
- @values[:cli][str] = bool
- else
- @values[:cli][str] = value
+ @sync.synchronize do
+ if self.boolean?(str)
+ @values[:cli][str] = bool
+ else
+ @values[:cli][str] = value
+ end
end
else
raise ArgumentError, "Invalid argument %s" % opt
@@ -199,14 +194,17 @@ class Puppet::Util::Settings
@shortnames.include?(short)
end
- # Create a new config object
+ # Create a new collection of config settings.
def initialize
@config = {}
@shortnames = {}
-
+
@created = []
@searchpath = nil
+ # Mutex-like thing to protect @values
+ @sync = Sync.new
+
# Keep track of set values.
@values = Hash.new { |hash, key| hash[key] = {} }
@@ -310,7 +308,10 @@ class Puppet::Util::Settings
end
searchpath.each do |source|
next if source == :name
- break if @name = @values[source][:name]
+ @sync.synchronize do
+ @name = @values[source][:name]
+ end
+ break if @name
end
unless @name
@name = convert(@config[:name].default).intern
@@ -333,14 +334,24 @@ class Puppet::Util::Settings
end
end
- # Parse the configuration file.
+ # Parse the configuration file. Just provides
+ # thread safety.
def parse(file)
+ # We have to clear outside of the sync, because it's
+ # also using synchronize().
clear(true)
+ @sync.synchronize do
+ unsafe_parse(file)
+ end
+ end
+
+ # Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly.
+ def unsafe_parse(file)
parse_file(file).each do |area, values|
@values[area] = values
end
-
+
# Determine our environment, if we have one.
if @config[:environment]
env = self.value(:environment).to_sym
@@ -377,12 +388,11 @@ class Puppet::Util::Settings
# what kind of element we're creating, but the value itself might be either
# a default or a value, so we can't actually assign it.
def newelement(hash)
- value = hash[:value] || hash[:default]
klass = nil
if hash[:section]
hash[:section] = symbolize(hash[:section])
end
- case value
+ case hash[:default]
when true, false, "true", "false":
klass = CBoolean
when /^\$\w+\//, /^\//:
@@ -392,7 +402,7 @@ class Puppet::Util::Settings
else
raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, hash[:name]]
end
- hash[:parent] = self
+ hash[:settings] = self
element = klass.new(hash)
return element
@@ -415,7 +425,7 @@ class Puppet::Util::Settings
def reparse
if defined? @file and @file.changed?
Puppet.notice "Reparsing %s" % @file.file
- @@sync.synchronize do
+ @sync.synchronize do
parse(@file)
end
reuse()
@@ -424,7 +434,7 @@ class Puppet::Util::Settings
def reuse
return unless defined? @used
- @@sync.synchronize do # yay, thread-safe
+ @sync.synchronize do # yay, thread-safe
@used.each do |section|
@used.delete(section)
self.use(section)
@@ -508,7 +518,6 @@ class Puppet::Util::Settings
name = symbolize(name)
hash[:name] = name
hash[:section] = section
- name = hash[:name]
if @config.include?(name)
raise ArgumentError, "Parameter %s is already defined" % name
end
@@ -621,7 +630,7 @@ Generated on #{Time.now}.
# Create the necessary objects to use a section. This is idempotent;
# you can 'use' a section as many times as you want.
def use(*sections)
- @@sync.synchronize do # yay, thread-safe
+ @sync.synchronize do # yay, thread-safe
sections = sections.reject { |s| @used.include?(s.to_sym) }
return if sections.empty?
@@ -680,16 +689,19 @@ Generated on #{Time.now}.
end
# See if we can find it within our searchable list of values
- val = nil
- each_source(environment) do |source|
- # Look for the value. We have to test the hash for whether
- # it exists, because the value might be false.
- if @values[source].include?(param)
- val = @values[source][param]
- break
+ val = catch :foundval do
+ each_source(environment) do |source|
+ # Look for the value. We have to test the hash for whether
+ # it exists, because the value might be false.
+ @sync.synchronize do
+ if @values[source].include?(param)
+ throw :foundval, @values[source][param]
+ end
+ end
end
+ throw :foundval, nil
end
-
+
# If we didn't get a value, use the default
val = @config[param].default if val.nil?
@@ -975,14 +987,9 @@ Generated on #{Time.now}.
# The base element type.
class CElement
- attr_accessor :name, :section, :default, :parent, :setbycli, :call_on_define
+ attr_accessor :name, :section, :default, :setbycli, :call_on_define
attr_reader :desc, :short
- # Unset any set value.
- def clear
- @value = nil
- end
-
def desc=(value)
@desc = value.gsub(/^\s*/, '')
end
@@ -1002,10 +1009,9 @@ Generated on #{Time.now}.
# Create the new element. Pretty much just sets the name.
def initialize(args = {})
- if args.include?(:parent)
- self.parent = args[:parent]
- args.delete(:parent)
- end
+ @settings = args.delete(:settings)
+ raise ArgumentError.new("You must refer to a settings object") if @settings.nil? or !@settings.is_a?(Puppet::Util::Settings)
+
args.each do |param, value|
method = param.to_s + "="
unless self.respond_to? method
@@ -1060,7 +1066,7 @@ Generated on #{Time.now}.
# If the value has not been overridden, then print it out commented
# and unconverted, so it's clear that that's the default and how it
# works.
- value = @parent.value(self.name)
+ value = @settings.value(self.name)
if value != @default
line = "%s = %s" % [@name, value]
@@ -1075,7 +1081,7 @@ Generated on #{Time.now}.
# Retrieves the value, or if it's not set, retrieves the default.
def value
- @parent.value(self.name)
+ @settings.value(self.name)
end
end
@@ -1086,7 +1092,7 @@ Generated on #{Time.now}.
def group
if defined? @group
- return @parent.convert(@group)
+ return @settings.convert(@group)
else
return nil
end
@@ -1094,7 +1100,7 @@ Generated on #{Time.now}.
def owner
if defined? @owner
- return @parent.convert(@owner)
+ return @settings.convert(@owner)
else
return nil
end
@@ -1117,7 +1123,7 @@ Generated on #{Time.now}.
# Return the appropriate type.
def type
- value = @parent.value(self.name)
+ value = @settings.value(self.name)
if @name.to_s =~ /dir/
return :directory
elsif value.to_s =~ /\/$/
@@ -1187,7 +1193,7 @@ Generated on #{Time.now}.
return true unless value.is_a? String
value.scan(/\$(\w+)/) { |name|
name = $1
- unless @parent.include?(name)
+ unless @settings.include?(name)
raise ArgumentError,
"Settings parameter '%s' is undefined" %
name