module Poise::Utils

Public Instance Methods

ancestor_send(obj, msg, *args, default: nil, ignore: [default]) click to toggle source

Try to find an ancestor to call a method on.

@since 2.2.3 @since 2.3.0

Added ignore parameter.

@param obj [Object] Self from the caller. @param msg [Symbol] Method to try to call. @param args [Array<Object>] Method arguments. @param default [Object] Default return value if no valid ancestor exists. @param ignore [Array<Object>] Return value to ignore when scanning ancesors. @return [Object] @example

val = @val || Poise::Utils.ancestor_send(self, :val)
# File lib/poise/utils.rb, line 87
def ancestor_send(obj, msg, *args, default: nil, ignore: [default])
  # Class is a subclass of Module, if we get something else use its class.
  obj = obj.class unless obj.is_a?(Module)
  ancestors = []
  if obj.respond_to?(:superclass)
    # Check the superclass first if present.
    ancestors << obj.superclass
  end
  # Make sure we don't check obj itself.
  ancestors.concat(obj.ancestors.drop(1))
  ancestors.each do |mod|
    if mod.respond_to?(msg)
      val = mod.send(msg, *args)
      # If we get the default back, assume we should keep trying.
      return val unless ignore.include?(val)
    end
  end
  # Nothing valid found, use the default.
  default
end
find_cookbook_name(run_context, filename) click to toggle source

Find the cookbook name for a given filename. The can used to find the cookbook that corresponds to a caller of a file.

@param run_context [Chef::RunContext] Context to check. @param filename [String] Absolute filename to check for. @return [String] @example

def my_thing
  caller_filename = caller.first.split(':').first
  cookbook = Poise::Utils.find_cookbook_name(run_context, caller_filename)
  # ...
end
# File lib/poise/utils.rb, line 40
def find_cookbook_name(run_context, filename)
  possibles = {}
  Poise.debug("[Poise] Checking cookbook for #{filename.inspect}")
  run_context.cookbook_collection.each do |name, ver|
    # This special method is added by Halite::Gem#as_cookbook_version.
    if ver.respond_to?(:halite_root)
      # The join is there because ../poise-ruby/lib starts with ../poise so
      # we want a trailing /.
      if filename.start_with?(File.join(ver.halite_root, ''))
        Poise.debug("[Poise] Found matching halite_root in #{name}: #{ver.halite_root.inspect}")
        possibles[ver.halite_root] = name
      end
    else
      Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |seg|
        ver.segment_filenames(seg).each do |file|
          if ::File::ALT_SEPARATOR
            file = file.gsub(::File::ALT_SEPARATOR, ::File::SEPARATOR)
          end
          # Put this behind an environment variable because it is verbose
          # even for normal debugging-level output.
          Poise.debug("[Poise] Checking #{seg} in #{name}: #{file.inspect}")
          if file == filename
            Poise.debug("[Poise] Found matching #{seg} in #{name}: #{file.inspect}")
            possibles[file] = name
          end
        end
      end
    end
  end
  raise Poise::Error.new("Unable to find cookbook for file #{filename.inspect}") if possibles.empty?
  # Sort the items by matching path length, pick the name attached to the longest.
  possibles.sort_by{|key, value| key.length }.last[1]
end
parameterized_module(mod, &block) click to toggle source

Create a helper to invoke a module with some parameters.

@since 2.3.0 @param mod [Module] The module to wrap. @param block [Proc] The module to implement to parameterization. @return [void] @example

module MyMixin
  def self.my_mixin_name(name)
    # ...
  end
end

Poise::Utils.parameterized_module(MyMixin) do |name|
  my_mixin_name(name)
end
Calls superclass method
# File lib/poise/utils.rb, line 124
def parameterized_module(mod, &block)
  raise Poise::Error.new("Cannot parameterize an anonymous module") unless mod.name && !mod.name.empty?
  parent_name_parts = mod.name.split(/::/)
  # Grab the last piece which will be the method name.
  mod_name = parent_name_parts.pop
  # Find the enclosing module or class object.
  parent = parent_name_parts.inject(Object) {|memo, name| memo.const_get(name) }
  # Object is a special case since we need #define_method instead.
  method_type = if parent == Object
    :define_method
  else
    :define_singleton_method
  end
  # Scoping hack.
  self_ = self
  # Construct the method.
  parent.send(method_type, mod_name) do |*args|
    self_.send(:check_block_arity!, block, args)
    # Create a new anonymous module to be returned from the method.
    Module.new do
      # Fake the name.
      define_singleton_method(:name) do
        super() || mod.name
      end

      # When the stub module gets included, activate our behaviors.
      define_singleton_method(:included) do |klass|
        super(klass)
        klass.send(:include, mod)
        klass.instance_exec(*args, &block)
      end
    end
  end
end

Private Instance Methods

check_block_arity!(block, args) click to toggle source

Check that the given arguments match the given block. This is needed because Ruby will nil-pad mismatched argspecs on blocks rather than error.

@since 2.3.0 @param block [Proc] Block to check. @param args [Array<Object>] Arguments to check. @return [void]

# File lib/poise/utils.rb, line 168
def check_block_arity!(block, args)
  # Convert the block to a lambda-style proc. You can't make this shit up.
  obj = Object.new
  obj.define_singleton_method(:block, &block)
  block  = obj.method(:block).to_proc
  # Check
  required_args = block.arity < 0 ? ~block.arity : block.arity
  if args.length < required_args || (block.arity >= 0 && args.length > block.arity)
    raise ArgumentError.new("wrong number of arguments (#{args.length} for #{required_args}#{block.arity < 0 ? '+' : ''})")
  end
end