summaryrefslogtreecommitdiffstats
path: root/lib/puppet/interface
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/interface')
-rw-r--r--lib/puppet/interface/action.rb80
-rw-r--r--lib/puppet/interface/action_manager.rb15
-rw-r--r--lib/puppet/interface/face_collection.rb95
-rw-r--r--lib/puppet/interface/option.rb19
-rw-r--r--lib/puppet/interface/option_builder.rb13
-rw-r--r--lib/puppet/interface/option_manager.rb3
6 files changed, 144 insertions, 81 deletions
diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb
index 185302b07..60ddb2ca3 100644
--- a/lib/puppet/interface/action.rb
+++ b/lib/puppet/interface/action.rb
@@ -38,6 +38,7 @@ class Puppet::Interface::Action
def to_s() "#{@face}##{@name}" end
attr_reader :name
+ attr_reader :face
attr_accessor :default
def default?
!!@default
@@ -195,14 +196,12 @@ class Puppet::Interface::Action
wrapper = <<WRAPPER
def #{@name}(#{decl.join(", ")})
#{optn}
- args = #{args}
- options = args.last
-
- action = get_action(#{name.inspect})
- action.validate_args(args)
- __invoke_decorations(:before, action, args, options)
+ args = #{args}
+ action = get_action(#{name.inspect})
+ args << action.validate_and_clean(args.pop)
+ __invoke_decorations(:before, action, args, args.last)
rval = self.__send__(#{internal_name.inspect}, *args)
- __invoke_decorations(:after, action, args, options)
+ __invoke_decorations(:after, action, args, args.last)
return rval
end
WRAPPER
@@ -227,8 +226,9 @@ WRAPPER
end
end
+ @options << option.name
+
option.aliases.each do |name|
- @options << name
@options_hash[name] = option
end
@@ -251,27 +251,61 @@ WRAPPER
option
end
- def validate_args(args)
- # Check for multiple aliases for the same option...
- args.last.keys.each do |name|
- # #7290: If this isn't actually an option, ignore it for now. We should
- # probably fail, but that wasn't our API, and I don't want to perturb
- # behaviour this late in the RC cycle. --daniel 2011-04-29
+ def validate_and_clean(original)
+ # The final set of arguments; effectively a hand-rolled shallow copy of
+ # the original, which protects the caller from the surprises they might
+ # get if they passed us a hash and we mutated it...
+ result = {}
+
+ # Check for multiple aliases for the same option, and canonicalize the
+ # name of the argument while we are about it.
+ overlap = Hash.new do |h, k| h[k] = [] end
+ unknown = []
+ original.keys.each do |name|
if option = get_option(name) then
- overlap = (option.aliases & args.last.keys)
- unless overlap.length == 1 then
- raise ArgumentError, "Multiple aliases for the same option passed: #{overlap.join(', ')}"
+ canonical = option.name
+ if result.has_key? canonical
+ overlap[canonical] << name
+ else
+ result[canonical] = original[name]
end
+ elsif Puppet.settings.include? name
+ result[name] = original[name]
+ else
+ unknown << name
+ end
+ end
+
+ unless overlap.empty?
+ msg = overlap.map {|k, v| "(#{k}, #{v.sort.join(', ')})" }.join(", ")
+ raise ArgumentError, "Multiple aliases for the same option passed: #{msg}"
+ end
+
+ unless unknown.empty?
+ msg = unknown.sort.join(", ")
+ raise ArgumentError, "Unknown options passed: #{msg}"
+ end
+
+ # Inject default arguments and check for missing mandating options.
+ missing = []
+ options.map {|x| get_option(x) }.each do |option|
+ name = option.name
+ next if result.has_key? name
+
+ if option.has_default?
+ result[name] = option.default
+ elsif option.required?
+ missing << name
end
end
- # Check for missing mandatory options.
- required = options.map do |name|
- get_option(name)
- end.select(&:required?).collect(&:name) - args.last.keys
+ unless missing.empty?
+ msg = missing.sort.join(', ')
+ raise ArgumentError, "The following options are required: #{msg}"
+ end
- return if required.empty?
- raise ArgumentError, "The following options are required: #{required.join(', ')}"
+ # All done.
+ return result
end
########################################################################
diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb
index fbf588d7d..5c9af4f96 100644
--- a/lib/puppet/interface/action_manager.rb
+++ b/lib/puppet/interface/action_manager.rb
@@ -7,13 +7,14 @@ module Puppet::Interface::ActionManager
require 'puppet/interface/action_builder'
@actions ||= {}
- @default_action ||= nil
raise "Action #{name} already defined for #{self}" if action?(name)
+
action = Puppet::Interface::ActionBuilder.build(self, name, &block)
- if action.default
- raise "Actions #{@default_action.name} and #{name} cannot both be default" if @default_action
- @default_action = action
+
+ if action.default and current = get_default_action
+ raise "Actions #{current.name} and #{name} cannot both be default"
end
+
@actions[action.name] = action
end
@@ -61,7 +62,11 @@ module Puppet::Interface::ActionManager
end
def get_default_action
- @default_action
+ default = actions.map {|x| get_action(x) }.select {|x| x.default }
+ if default.length > 1
+ raise "The actions #{default.map(&:name).join(", ")} cannot all be default"
+ end
+ default.first
end
def action?(name)
diff --git a/lib/puppet/interface/face_collection.rb b/lib/puppet/interface/face_collection.rb
index 12d3c56b1..b1f6ba398 100644
--- a/lib/puppet/interface/face_collection.rb
+++ b/lib/puppet/interface/face_collection.rb
@@ -1,8 +1,6 @@
require 'puppet/interface'
module Puppet::Interface::FaceCollection
- SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/
-
@faces = Hash.new { |hash, key| hash[key] = {} }
def self.faces
@@ -17,55 +15,36 @@ module Puppet::Interface::FaceCollection
@faces.keys.select {|name| @faces[name].length > 0 }
end
- def self.validate_version(version)
- !!(SEMVER_VERSION =~ version.to_s)
- end
-
- def self.semver_to_array(v)
- parts = SEMVER_VERSION.match(v).to_a[1..4]
- parts[0..2] = parts[0..2].map { |e| e.to_i }
- parts
- end
-
- def self.cmp_semver(a, b)
- a, b = [a, b].map do |x| semver_to_array(x) end
-
- cmp = a[0..2] <=> b[0..2]
- if cmp == 0
- cmp = a[3] <=> b[3]
- cmp = +1 if a[3].empty? && !b[3].empty?
- cmp = -1 if b[3].empty? && !a[3].empty?
- end
- cmp
+ def self.[](name, version)
+ name = underscorize(name)
+ get_face(name, version) or load_face(name, version)
end
- def self.prefix_match?(desired, target)
- # Can't meaningfully do a prefix match with current on either side.
- return false if desired == :current
- return false if target == :current
-
- # REVISIT: Should probably fail if the matcher is not valid.
- prefix = desired.split('.').map {|x| x =~ /^\d+$/ and x.to_i }
- have = semver_to_array(target)
+ def self.get_action_for_face(name, action_name, version)
+ name = underscorize(name)
- while want = prefix.shift do
- return false unless want == have.shift
+ # If the version they request specifically doesn't exist, don't search
+ # elsewhere. Usually this will start from :current and all...
+ return nil unless face = self[name, version]
+ unless action = face.get_action(action_name)
+ # ...we need to search for it bound to an o{lder,ther} version. Since
+ # we load all actions when the face is first references, this will be in
+ # memory in the known set of versions of the face.
+ (@faces[name].keys - [ :current ]).sort.reverse.each do |version|
+ break if action = @faces[name][version].get_action(action_name)
+ end
end
- return true
- end
- def self.[](name, version)
- name = underscorize(name)
- get_face(name, version) or load_face(name, version)
+ return action
end
# get face from memory, without loading.
- def self.get_face(name, desired_version)
+ def self.get_face(name, pattern)
return nil unless @faces.has_key? name
+ return @faces[name][:current] if pattern == :current
- return @faces[name][:current] if desired_version == :current
-
- found = @faces[name].keys.select {|v| prefix_match?(desired_version, v) }.sort.last
+ versions = @faces[name].keys - [ :current ]
+ found = SemVer.find_matching(pattern, versions)
return @faces[name][found]
end
@@ -77,9 +56,7 @@ module Puppet::Interface::FaceCollection
#
# We use require to avoid executing the code multiple times, like any
# other Ruby library that we might want to use. --daniel 2011-04-06
- begin
- require "puppet/face/#{name}"
-
+ if safely_require name then
# If we wanted :current, we need to index to find that; direct version
# requests just work™ as they go. --daniel 2011-04-06
if version == :current then
@@ -108,21 +85,35 @@ module Puppet::Interface::FaceCollection
# versions here and return the last item in that set.
#
# --daniel 2011-04-06
- latest_ver = @faces[name].keys.sort {|a, b| cmp_semver(a, b) }.last
+ latest_ver = @faces[name].keys.sort.last
@faces[name][:current] = @faces[name][latest_ver]
end
- rescue LoadError => e
- raise unless e.message =~ %r{-- puppet/face/#{name}$}
- # ...guess we didn't find the file; return a much better problem.
- rescue SyntaxError => e
- raise unless e.message =~ %r{puppet/face/#{name}\.rb:\d+: }
- Puppet.err "Failed to load face #{name}:\n#{e}"
- # ...but we just carry on after complaining.
+ end
+
+ unless version == :current or get_face(name, version)
+ # Try an obsolete version of the face, if needed, to see if that helps?
+ safely_require name, version
end
return get_face(name, version)
end
+ def self.safely_require(name, version = nil)
+ path = File.join 'puppet' ,'face', version.to_s, name.to_s
+ require path
+ true
+
+ rescue LoadError => e
+ raise unless e.message =~ %r{-- #{path}$}
+ # ...guess we didn't find the file; return a much better problem.
+ nil
+ rescue SyntaxError => e
+ raise unless e.message =~ %r{#{path}\.rb:\d+: }
+ Puppet.err "Failed to load face #{name}:\n#{e}"
+ # ...but we just carry on after complaining.
+ nil
+ end
+
def self.register(face)
@faces[underscorize(face.name)][face.version] = face
end
diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb
index 3cd930acf..01f6f2307 100644
--- a/lib/puppet/interface/option.rb
+++ b/lib/puppet/interface/option.rb
@@ -6,6 +6,7 @@ class Puppet::Interface::Option
def initialize(parent, *declaration, &block)
@parent = parent
@optparse = []
+ @default = nil
# Collect and sort the arguments in the declaration.
dups = {}
@@ -81,8 +82,26 @@ class Puppet::Interface::Option
!!@required
end
+ def has_default?
+ !!@default
+ end
+
+ def default=(proc)
+ required and raise ArgumentError, "#{self} can't be optional and have a default value"
+ proc.is_a? Proc or raise ArgumentError, "default value for #{self} is a #{proc.class.name.inspect}, not a proc"
+ @default = proc
+ end
+
+ def default
+ @default and @default.call
+ end
+
attr_reader :parent, :name, :aliases, :optparse
attr_accessor :required
+ def required=(value)
+ has_default? and raise ArgumentError, "#{self} can't be optional and have a default value"
+ @required = value
+ end
attr_accessor :before_action
def before_action=(proc)
diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb
index 5676ec977..c87adc2c0 100644
--- a/lib/puppet/interface/option_builder.rb
+++ b/lib/puppet/interface/option_builder.rb
@@ -51,4 +51,17 @@ class Puppet::Interface::OptionBuilder
def required(value = true)
@option.required = value
end
+
+ def default_to(&block)
+ block or raise ArgumentError, "#{@option} default_to requires a block"
+ if @option.has_default?
+ raise ArgumentError, "#{@option} already has a default value"
+ end
+ # Ruby 1.8 treats a block without arguments as accepting any number; 1.9
+ # gets this right, so we work around it for now... --daniel 2011-07-20
+ unless block.arity == 0 or (RUBY_VERSION =~ /^1\.8/ and block.arity == -1)
+ raise ArgumentError, "#{@option} default_to block should not take any arguments"
+ end
+ @option.default = block
+ end
end
diff --git a/lib/puppet/interface/option_manager.rb b/lib/puppet/interface/option_manager.rb
index 326a91d92..a1f300e8e 100644
--- a/lib/puppet/interface/option_manager.rb
+++ b/lib/puppet/interface/option_manager.rb
@@ -26,8 +26,9 @@ module Puppet::Interface::OptionManager
end
end
+ @options << option.name
+
option.aliases.each do |name|
- @options << name
@options_hash[name] = option
end