diff options
author | Daniel Pittman <daniel@puppetlabs.com> | 2011-04-26 17:18:09 -0700 |
---|---|---|
committer | Daniel Pittman <daniel@puppetlabs.com> | 2011-04-26 23:35:31 -0700 |
commit | 092ab09d00474d69361ee757efde2b28c89b39eb (patch) | |
tree | 4972d871be768411316e357198ee910e536f7324 | |
parent | 67a86554fff18cd75744ea987ea2361f495efc4c (diff) | |
download | puppet-092ab09d00474d69361ee757efde2b28c89b39eb.tar.gz puppet-092ab09d00474d69361ee757efde2b28c89b39eb.tar.xz puppet-092ab09d00474d69361ee757efde2b28c89b39eb.zip |
(#6962) Extend documentation API for Faces.
This adds the remaining documentation mechanisms to the Face instances,
allowing them to build and report correct documentation, licensing and
ownership for the help face to build on.
-rw-r--r-- | lib/puppet/face/help/action.erb | 48 | ||||
-rw-r--r-- | lib/puppet/face/help/face.erb | 46 | ||||
-rw-r--r-- | lib/puppet/interface.rb | 178 | ||||
-rw-r--r-- | spec/shared_behaviours/documentation_on_faces.rb | 183 | ||||
-rwxr-xr-x | spec/unit/interface_spec.rb | 6 |
5 files changed, 433 insertions, 28 deletions
diff --git a/lib/puppet/face/help/action.erb b/lib/puppet/face/help/action.erb index eaf131464..7a9b87117 100644 --- a/lib/puppet/face/help/action.erb +++ b/lib/puppet/face/help/action.erb @@ -1,3 +1,47 @@ -Use: puppet <%= face.name %> [options] <%= action.name %> [options] +puppet <%= face.name %><%= action.default? ? '' : " #{action.name}" %>(1) -- <%= action.summary || face.summary %> +<%= '=' * (_erbout.length - 1) %> -Summary: <%= action.summary %> +% if action.synopsis +SYNOPSIS +-------- + +<%= action.synopsis %> + +% end +% if action.description +DESCRIPTION +----------- +<%= action.description %> + +%end +% unless action.options.empty? +OPTIONS +------- +% action.options.sort.each do |name| +% option = action.get_option name +<%= " " + option.optparse.join(" |" ) %> +<%= option.desc and option.desc.gsub(/^/, ' ') %> + +% end +% end +% if action.examples +EXAMPLES +-------- +<%= action.examples %> +% end +% if action.notes +NOTES +----- +<%= action.notes %> + +% end +% unless action.authors.empty? +AUTHOR +------ +<%= action.authors.map {|x| " * " + x } .join("\n") %> + +%end +COPYRIGHT AND LICENSE +--------------------- +<%= action.copyright %> +<%= action.license %> diff --git a/lib/puppet/face/help/face.erb b/lib/puppet/face/help/face.erb index efe5fd809..944f7a96b 100644 --- a/lib/puppet/face/help/face.erb +++ b/lib/puppet/face/help/face.erb @@ -1,7 +1,47 @@ -Use: puppet <%= face.name %> [options] <action> [options] +NAME + <%= face.name %> -- <%= face.summary || "unknown face..." %> -Available actions: +% if face.synopsis +SYNOPSIS +<%= face.synopsis.gsub(/^/, ' ') %> + +% end +% if face.description +DESCRIPTION +<%= face.description.chomp.gsub(/^/, ' ') %> + +%end +% unless face.options.empty? +OPTIONS +% face.options.sort.each do |name| +% option = face.get_option name +<%= " " + option.optparse.join(" |" ) %> +<%= option.desc and option.desc.gsub(/^/, ' ') %> + +% end +% end +ACTIONS +% padding = face.actions.map{|x| x.to_s.length}.max + 2 % face.actions.each do |actionname| % action = face.get_action(actionname) - <%= action.name.to_s.ljust(16) %> <%= action.summary %> + <%= action.name.to_s.ljust(padding) %> <%= action.summary %> % end + +% if face.examples +EXAMPLES +<%= face.examples %> +% end +% if face.notes +NOTES +<%= face.notes %> + +% end +% unless face.authors.empty? +AUTHOR +<%= face.authors.join("\n").gsub(/^/, ' * ') %> + +%end +COPYRIGHT AND LICENSE +<%= face.copyright.gsub(/^/, ' ') %> +<%= face.license.gsub(/^/, ' ') %> + diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb index ced00863d..adf6c991c 100644 --- a/lib/puppet/interface.rb +++ b/lib/puppet/interface.rb @@ -1,5 +1,6 @@ require 'puppet' require 'puppet/util/autoload' +require 'prettyprint' class Puppet::Interface require 'puppet/interface/face_collection' @@ -70,7 +71,7 @@ class Puppet::Interface # the same instance between build-time and the runtime instance. When that # splits out this should merge into a module that both the action and face # include. --daniel 2011-04-17 - attr_accessor :summary, :description + attr_accessor :summary def summary(value = nil) self.summary = value unless value.nil? @summary @@ -83,11 +84,178 @@ class Puppet::Interface @summary = value end + attr_accessor :description def description(value = nil) self.description = value unless value.nil? @description end + attr_accessor :examples + def examples(value = nil) + self.examples = value unless value.nil? + @examples + end + + attr_accessor :short_description + def short_description(value = nil) + self.short_description = value unless value.nil? + if @short_description.nil? then + fail "REVISIT: Extract this..." + end + @short_description + end + + def author(value = nil) + unless value.nil? then + unless value.is_a? String + raise ArgumentError, 'author must be a string; use multiple statements for multiple authors' + end + + if value =~ /\n/ then + raise ArgumentError, 'author should be a single line; use multiple statements for multiple authors' + end + @authors.push(value) + end + @authors.empty? ? nil : @authors.join("\n") + end + def author=(value) + if Array(value).any? {|x| x =~ /\n/ } then + raise ArgumentError, 'author should be a single line; use multiple statements' + end + @authors = Array(value) + end + def authors + @authors + end + def authors=(value) + if Array(value).any? {|x| x =~ /\n/ } then + raise ArgumentError, 'author should be a single line; use multiple statements' + end + @authors = Array(value) + end + + attr_accessor :notes + def notes(value = nil) + @notes = value unless value.nil? + @notes + end + + attr_accessor :license + def license(value = nil) + @license = value unless value.nil? + @license + end + + def copyright(owner = nil, years = nil) + if years.nil? and not owner.nil? then + raise ArgumentError, 'copyright takes the owners names, then the years covered' + end + self.copyright_owner = owner unless owner.nil? + self.copyright_years = years unless years.nil? + + if self.copyright_years or self.copyright_owner then + "Copyright #{self.copyright_years} by #{self.copyright_owner}" + else + "Unknown copyright owner and years." + end + end + + attr_accessor :copyright_owner + def copyright_owner=(value) + case value + when String then @copyright_owner = value + when Array then @copyright_owner = value.join(", ") + else + raise ArgumentError, "copyright owner must be a string or an array of strings" + end + @copyright_owner + end + + attr_accessor :copyright_years + def copyright_years=(value) + years = munge_copyright_year value + years = (years.is_a?(Array) ? years : [years]). + sort_by do |x| x.is_a?(Range) ? x.first : x end + + @copyright_years = years.map do |year| + if year.is_a? Range then + "#{year.first}-#{year.last}" + else + year + end + end.join(", ") + end + + def munge_copyright_year(input) + case input + when Range then input + when Integer then + if input < 1970 then + fault = "before 1970" + elsif input > (future = Time.now.year + 2) then + fault = "after #{future}" + end + if fault then + raise ArgumentError, "copyright with a year #{fault} is very strange; did you accidentally add or subtract two years?" + end + + input + + when String then + input.strip.split(/,/).map do |part| + part = part.strip + if part =~ /^\d+$/ then + part.to_i + elsif found = part.split(/-/) then + unless found.length == 2 and found.all? {|x| x.strip =~ /^\d+$/ } + raise ArgumentError, "#{part.inspect} is not a good copyright year or range" + end + Range.new(found[0].to_i, found[1].to_i) + else + raise ArgumentError, "#{part.inspect} is not a good copyright year or range" + end + end + + when Array then + result = [] + input.each do |item| + item = munge_copyright_year item + if item.is_a? Array + result.concat item + else + result << item + end + end + result + + else + raise ArgumentError, "#{input.inspect} is not a good copyright year, set, or range" + end + end + + def synopsis + output = PrettyPrint.format do |s| + s.text("puppet #{name} <action>") + s.breakable + + options.each do |option| + option = get_option(option) + wrap = option.required? ? %w{ < > } : %w{ [ ] } + + s.group(0, *wrap) do + option.optparse.each do |item| + unless s.current_group.first? + s.breakable + s.text '|' + s.breakable + end + s.text item + end + end + end + end + end + ######################################################################## attr_reader :name, :version @@ -97,9 +265,15 @@ class Puppet::Interface raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!" end - @name = Puppet::Interface::FaceCollection.underscorize(name) + @name = Puppet::Interface::FaceCollection.underscorize(name) @version = version + # The few bits of documentation we actually demand. The default license + # is a favour to our end users; if you happen to get that in a core face + # report it as a bug, please. --daniel 2011-04-26 + @authors = [] + @license = 'All Rights Reserved' + instance_eval(&block) if block_given? end diff --git a/spec/shared_behaviours/documentation_on_faces.rb b/spec/shared_behaviours/documentation_on_faces.rb index 41b4015c9..ef2645620 100644 --- a/spec/shared_behaviours/documentation_on_faces.rb +++ b/spec/shared_behaviours/documentation_on_faces.rb @@ -1,34 +1,177 @@ # encoding: UTF-8 shared_examples_for "documentation on faces" do - context "description" do - describe "#summary" do - it "should accept a summary" do - text = "this is my summary" - expect { subject.summary = text }.to_not raise_error - subject.summary.should == text + defined?(Attrs) or + Attrs = [:summary, :description, :examples, :short_description, :notes, :author] + + defined?(SingleLineAttrs) or + SingleLineAttrs = [:summary, :author] + + # Simple, procedural tests that apply to a bunch of methods. + Attrs.each do |attr| + it "should accept a #{attr}" do + expect { subject.send("#{attr}=", "hello") }.not_to raise_error + subject.send(attr).should == "hello" + end + + it "should accept a long (single line) value for #{attr}" do + text = "I never know when to stop with the word banana" + ("na" * 1000) + expect { subject.send("#{attr}=", text) }.to_not raise_error + subject.send(attr).should == text + end + end + + # Should they accept multiple lines? + Attrs.each do |attr| + text = "with\nnewlines" + + if SingleLineAttrs.include? attr then + it "should not accept multiline values for #{attr}" do + expect { subject.send("#{attr}=", text) }. + to raise_error ArgumentError, /#{attr} should be a single line/ + subject.send(attr).should be_nil + end + else + it "should accept multiline values for #{attr}" do + expect { subject.send("#{attr}=", text) }.not_to raise_error + subject.send(attr).should == text + end + end + end + + describe "multiple authors" do + authors = %w{John Paul George Ringo} + + context "in the DSL" do + it "should support multiple authors" do + + authors.each {|name| subject.author name } + subject.authors.should =~ authors + + subject.author.should == authors.join("\n") + end + + it "should reject author as an array" do + expect { subject.author ["Foo", "Bar"] }. + to raise_error ArgumentError, /author must be a string/ + end + end + + context "#author=" do + it "should accept a single name" do + subject.author = "Fred" + subject.author.should == "Fred" + end + + it "should accept an array of names" do + subject.author = authors + subject.authors.should =~ authors + subject.author.should == authors.join("\n") + end + + it "should not append when set multiple times" do + subject.author = "Fred" + subject.author = "John" + subject.author.should == "John" + end + + it "should reject arrays with embedded newlines" do + expect { subject.author = ["Fred\nJohn"] }. + to raise_error ArgumentError, /author should be a single line/ end + end + end + + describe "#license" do + it "should default to reserving rights" do + subject.license.should =~ /All Rights Reserved/ + end + + it "should accept an arbitrary license string in the DSL" do + subject.license("foo") + subject.license.should == "foo" + end + + it "should accept an arbitrary license string on the object" do + subject.license = "foo" + subject.license.should == "foo" + end + + it "should accept symbols to specify existing licenses..." + end - it "should accept a long, long, long summary" do - text = "I never know when to stop with the word banana" + ("na" * 1000) - expect { subject.summary = text }.to_not raise_error - subject.summary.should == text + describe "#copyright" do + it "should fail with just a name" do + expect { subject.copyright("invalid") }. + to raise_error ArgumentError, /copyright takes the owners names, then the years covered/ + end + + [1997, "1997"].each do |year| + it "should accept an entity name and a #{year.class.name} year" do + subject.copyright("me", year) + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /#{year}/ end - it "should reject a summary with a newline" do - expect { subject.summary = "with\nnewlines" }. - to raise_error ArgumentError, /summary should be a single line/ + it "should accept multiple entity names and a #{year.class.name} year" do + subject.copyright ["me", "you"], year + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /\byou\b/ + subject.copyright.should =~ /#{year}/ end end - describe "#description" do - it "should accept a description" do - subject.description = "hello" - subject.description.should == "hello" + ["1997-2003", "1997 - 2003", 1997..2003].each do |range| + it "should accept a #{range.class.name} range of years" do + subject.copyright("me", range) + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /1997-2003/ + end + + it "should accept a #{range.class.name} range of years" do + subject.copyright ["me", "you"], range + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /\byou\b/ + subject.copyright.should =~ /1997-2003/ + end + end + + [[1997, 2003], ["1997", 2003], ["1997", "2003"]].each do |input| + it "should accept the set of years #{input.inspect} in an array" do + subject.copyright "me", input + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /1997, 2003/ + end + + it "should accept the set of years #{input.inspect} in an array" do + subject.copyright ["me", "you"], input + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /\byou\b/ + subject.copyright.should =~ /1997, 2003/ + end + end + + it "should warn if someone does math accidentally on the range of years" do + expect { subject.copyright "me", 1997-2003 }. + to raise_error ArgumentError, /copyright with a year before 1970 is very strange; did you accidentally add or subtract two years\?/ + end + + it "should accept complex copyright years" do + years = [1997, 1999, 2000..2002, 2005].reverse + subject.copyright "me", years + subject.copyright.should =~ /\bme\b/ + subject.copyright.should =~ /1997, 1999, 2000-2002, 2005/ + end + end + + # Things that are automatically generated. + [:name, :options, :synopsis].each do |attr| + describe "##{attr}" do + it "should not allow you to set #{attr}" do + subject.should_not respond_to :"#{attr}=" end - it "should accept a description with a newline" do - subject.description = "hello \n my \n fine \n friend" - subject.description.should == "hello \n my \n fine \n friend" + it "should have a #{attr}" do + subject.send(attr).should_not be_nil end end end diff --git a/spec/unit/interface_spec.rb b/spec/unit/interface_spec.rb index a1d70cf64..27da39766 100755 --- a/spec/unit/interface_spec.rb +++ b/spec/unit/interface_spec.rb @@ -76,7 +76,11 @@ describe Puppet::Interface do # Required documentation methods... { :summary => "summary", - :description => "This is the description of the stuff\n\nWhee" + :description => "This is the description of the stuff\n\nWhee", + :examples => "This is my example", + :short_description => "This is my custom short description", + :notes => "These are my notes...", + :author => "This is my authorship data", }.each do |attr, value| it "should support #{attr} in the builder" do face = subject.new(:builder, '1.0.0') do |