diff --git a/lib/puppet/util/docs.rb b/lib/puppet/util/docs.rb index efd054d85..1746ef3fc 100644 --- a/lib/puppet/util/docs.rb +++ b/lib/puppet/util/docs.rb @@ -1,114 +1,108 @@ # Some simple methods for helping manage automatic documentation generation. module Puppet::Util::Docs # Specify the actual doc string. def desc(str) @doc = str end # Add a new autodoc block. We have to define these as class methods, # rather than just sticking them in a hash, because otherwise they're # too difficult to do inheritance with. def dochook(name, &block) method = "dochook_#{name}" meta_def method, &block end attr_writer :doc # Generate the full doc string. def doc extra = methods.find_all { |m| m.to_s =~ /^dochook_.+/ }.sort.collect { |m| self.send(m) }.join(" ") if @doc @doc + extra else extra end end # Build a table def doctable(headers, data) str = "\n\n" lengths = [] # Figure out the longest field for all columns data.each do |name, values| [name, values].flatten.each_with_index do |value, i| lengths[i] ||= 0 lengths[i] = value.to_s.length if value.to_s.length > lengths[i] end end # The headers could also be longest headers.each_with_index do |value, i| lengths[i] = value.to_s.length if value.to_s.length > lengths[i] end - # Add the top header row - str += lengths.collect { |num| "=" * num }.join(" ") + "\n" + # Add the header names + str += headers.zip(lengths).collect { |value, num| pad(value, num) }.join(" | ") + " |" + "\n" - # And the header names - str += headers.zip(lengths).collect { |value, num| pad(value, num) }.join(" ") + "\n" - - # And the second header row - str += lengths.collect { |num| "=" * num }.join(" ") + "\n" + # And the header row + str += lengths.collect { |num| "-" * num }.join(" | ") + " |" + "\n" # Now each data row data.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |name, rows| str += [name, rows].flatten.zip(lengths).collect do |value, length| pad(value, length) - end.join(" ") + "\n" + end.join(" | ") + " |" + "\n" end - # And the bottom line row - str += lengths.collect { |num| "=" * num }.join(" ") + "\n" - str + "\n" end attr_reader :nodoc def nodoc? nodoc end # Pad a field with spaces def pad(value, length) value.to_s + (" " * (length - value.to_s.length)) end # Handle the inline indentation in the docs. def scrub(text) # Stupid markdown #text = text.gsub("<%=", "<%=") # For text with no carriage returns, there's nothing to do. return text if text !~ /\n/ indent = nil # If we can match an indentation, then just remove that same level of # indent from every line. However, ignore any indentation on the # first line, since that can be inconsistent. text = text.lstrip text.gsub!(/^([\t]+)/) { |s| " "*8*s.length; } # Expand leading tabs # Find first non-empty line after the first line: line2start = (text =~ /(\n?\s*\n)/) line2start += $1.length if (text[line2start..-1] =~ /^([ ]+)\S/) == 0 indent = Regexp.quote($1) begin return text.gsub(/^#{indent}/,'') rescue => detail puts detail.backtrace puts detail end else return text end end module_function :scrub end diff --git a/lib/puppet/util/provider_features.rb b/lib/puppet/util/provider_features.rb index ac294d20d..30e8dcb39 100644 --- a/lib/puppet/util/provider_features.rb +++ b/lib/puppet/util/provider_features.rb @@ -1,169 +1,169 @@ # Provides feature definitions. require 'puppet/util/methodhelper' require 'puppet/util/docs' require 'puppet/util' module Puppet::Util::ProviderFeatures include Puppet::Util::Docs # The class that models the features and handles checking whether the features # are present. class ProviderFeature include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Docs attr_accessor :name, :docs, :methods # Are all of the requirements met? def available?(obj) if self.methods return !!methods_available?(obj) else # In this case, the provider has to declare support for this # feature, and that's been checked before we ever get to the # method checks. return false end end def initialize(name, docs, hash) self.name = symbolize(name) self.docs = docs hash = symbolize_options(hash) set_options(hash) end private # Are all of the required methods available? def methods_available?(obj) methods.each do |m| if obj.is_a?(Class) return false unless obj.public_method_defined?(m) else return false unless obj.respond_to?(m) end end true end end # Define one or more features. At a minimum, features require a name # and docs, and at this point they should also specify a list of methods # required to determine if the feature is present. def feature(name, docs, hash = {}) @features ||= {} raise(Puppet::DevError, "Feature #{name} is already defined") if @features.include?(name) begin obj = ProviderFeature.new(name, docs, hash) @features[obj.name] = obj rescue ArgumentError => detail error = ArgumentError.new( "Could not create feature #{name}: #{detail}" ) error.set_backtrace(detail.backtrace) raise error end end # Return a hash of all feature documentation. def featuredocs str = "" @features ||= {} return nil if @features.empty? names = @features.keys.sort { |a,b| a.to_s <=> b.to_s } names.each do |name| doc = @features[name].docs.gsub(/\n\s+/, " ") - str += "- **#{name}**: #{doc}\n" + str += "- *#{name}*: #{doc}\n" end if providers.length > 0 headers = ["Provider", names].flatten data = {} providers.each do |provname| data[provname] = [] prov = provider(provname) names.each do |name| if prov.feature?(name) - data[provname] << "**X**" + data[provname] << "*X*" else data[provname] << "" end end end str += doctable(headers, data) end str end # Return a list of features. def features @features ||= {} @features.keys end # Generate a module that sets up the boolean methods to test for given # features. def feature_module unless defined?(@feature_module) @features ||= {} @feature_module = ::Module.new const_set("FeatureModule", @feature_module) features = @features # Create a feature? method that can be passed a feature name and # determine if the feature is present. @feature_module.send(:define_method, :feature?) do |name| method = name.to_s + "?" return !!(respond_to?(method) and send(method)) end # Create a method that will list all functional features. @feature_module.send(:define_method, :features) do return false unless defined?(features) features.keys.find_all { |n| feature?(n) }.sort { |a,b| a.to_s <=> b.to_s } end # Create a method that will determine if a provided list of # features are satisfied by the curred provider. @feature_module.send(:define_method, :satisfies?) do |*needed| ret = true needed.flatten.each do |feature| unless feature?(feature) ret = false break end end ret end # Create a boolean method for each feature so you can test them # individually as you might need. @features.each do |name, feature| method = name.to_s + "?" @feature_module.send(:define_method, method) do (is_a?(Class) ? declared_feature?(name) : self.class.declared_feature?(name)) or feature.available?(self) end end # Allow the provider to declare that it has a given feature. @feature_module.send(:define_method, :has_features) do |*names| @declared_features ||= [] names.each do |name| name = symbolize(name) @declared_features << name end end # Aaah, grammatical correctness @feature_module.send(:alias_method, :has_feature, :has_features) end @feature_module end # Return the actual provider feature instance. Really only used for testing. def provider_feature(name) return nil unless defined?(@features) @features[name] end end diff --git a/lib/puppet/util/reference.rb b/lib/puppet/util/reference.rb index 3fdd37f68..00390746e 100644 --- a/lib/puppet/util/reference.rb +++ b/lib/puppet/util/reference.rb @@ -1,184 +1,184 @@ require 'puppet/util/instance_loader' require 'fileutils' # Manage Reference Documentation. class Puppet::Util::Reference include Puppet::Util include Puppet::Util::Docs extend Puppet::Util::InstanceLoader instance_load(:reference, 'puppet/reference') def self.footer "\n\n----------------\n\n*This page autogenerated on #{Time.now}*\n" end def self.modes %w{pdf text markdown} end def self.newreference(name, options = {}, &block) ref = self.new(name, options, &block) instance_hash(:reference)[symbolize(name)] = ref ref end def self.page(*sections) depth = 4 # Use the minimum depth sections.each do |name| section = reference(name) or raise "Could not find section #{name}" depth = section.depth if section.depth < depth end - text = ".. contents:: :depth: 2\n\n" + text = "{:toc}\n\n" end def self.pdf(text) puts "creating pdf" Puppet::Util.secure_open("/tmp/puppetdoc.txt", "w") do |f| f.puts text end rst2latex = %x{which rst2latex} if $CHILD_STATUS != 0 or rst2latex =~ /no / rst2latex = %x{which rst2latex.py} end if $CHILD_STATUS != 0 or rst2latex =~ /no / raise "Could not find rst2latex" end rst2latex.chomp! cmd = %{#{rst2latex} /tmp/puppetdoc.txt > /tmp/puppetdoc.tex} Puppet::Util.secure_open("/tmp/puppetdoc.tex","w") do |f| # If we get here without an error, /tmp/puppetdoc.tex isn't a tricky cracker's symlink end output = %x{#{cmd}} unless $CHILD_STATUS == 0 $stderr.puts "rst2latex failed" $stderr.puts output exit(1) end $stderr.puts output # Now convert to pdf Dir.chdir("/tmp") do %x{texi2pdf puppetdoc.tex >/dev/null 2>/dev/null} end end def self.markdown(name, text) puts "Creating markdown for #{name} reference." dir = "/tmp/#{Puppet::PUPPETVERSION}" FileUtils.mkdir(dir) unless File.directory?(dir) Puppet::Util.secure_open(dir + "/#{name}.rst", "w") do |f| f.puts text end pandoc = %x{which pandoc} if $CHILD_STATUS != 0 or pandoc =~ /no / pandoc = %x{which pandoc} end if $CHILD_STATUS != 0 or pandoc =~ /no / raise "Could not find pandoc" end pandoc.chomp! cmd = %{#{pandoc} -s -r rst -w markdown #{dir}/#{name}.rst -o #{dir}/#{name}.mdwn} output = %x{#{cmd}} unless $CHILD_STATUS == 0 $stderr.puts "Pandoc failed to create #{name} reference." $stderr.puts output exit(1) end File.unlink(dir + "/#{name}.rst") end def self.references instance_loader(:reference).loadall loaded_instances(:reference).sort { |a,b| a.to_s <=> b.to_s } end HEADER_LEVELS = [nil, "=", "-", "+", "'", "~"] attr_accessor :page, :depth, :header, :title, :dynamic attr_writer :doc def doc if defined?(@doc) return "#{@name} - #{@doc}" else return @title end end def dynamic? self.dynamic end def h(name, level) "#{name}\n#{HEADER_LEVELS[level] * name.to_s.length}\n\n" end def initialize(name, options = {}, &block) @name = name options.each do |option, value| send(option.to_s + "=", value) end meta_def(:generate, &block) # Now handle the defaults @title ||= "#{@name.to_s.capitalize} Reference" @page ||= @title.gsub(/\s+/, '') @depth ||= 2 @header ||= "" end # Indent every line in the chunk except those which begin with '..'. def indent(text, tab) text.gsub(/(^|\A)/, tab).gsub(/^ +\.\./, "..") end def option(name, value) ":#{name.to_s.capitalize}: #{value}\n" end def paramwrap(name, text, options = {}) options[:level] ||= 5 #str = "#{name} : " str = h(name, options[:level]) str += "- **namevar**\n\n" if options[:namevar] str += text #str += text.gsub(/\n/, "\n ") str += "\n\n" end # Remove all trac links. def strip_trac(text) text.gsub(/`\w+\s+([^`]+)`:trac:/) { |m| $1 } end def text puts output end def to_rest(withcontents = true) # First the header text = h(@title, 1) text += "\n\n**This page is autogenerated; any changes will get overwritten** *(last generated on #{Time.now.to_s})*\n\n" - text += ".. contents:: :depth: #{@depth}\n\n" if withcontents + text += "{:toc}\n\n" if withcontents text += @header text += generate text += self.class.footer if withcontents text end def to_text(withcontents = true) strip_trac(to_rest(withcontents)) end end