lib/cany/recipe.rb in cany-0.0.2 vs lib/cany/recipe.rb in cany-0.1.0
- old
+ new
@@ -9,27 +9,45 @@
# name the later one will overwrite the earlier one and therefore used by Cany.
# @param [Symbol] name A ruby symbol as name for the recipe. It should be short and recognizable
def self.register_as(name)
@@recipes ||= {}
@@recipes[name] = self
+ module_eval(<<-EOS, __FILE__, __LINE__)
+ def name
+ :#{name}
+ end
+ EOS
end
# @api public
# Looks for the class registered for the given name
# @param [Symbol] name the name the class is search for
- # @return [Cany::Recipe, nil] Returns the found class or nil if no class is registered on this
- # name
+ # @return [Cany::Recipe] Returns the found class or nil
+ # @raise UnknownRecipe if there is no recipe registered for this name.
def self.from_name(name)
+ raise UnknownRecipe.new(name) unless @@recipes[name]
@@recipes[name]
end
# Creates a new instance of this recipe
# @param [Cany::Specification] spec Specification object
+ def initialize(spec, &configure_block)
+ @spec = spec
+ @inner = nil
+ @hooks = Hash[(self.class.defined_hooks || []).map do |name|
+ [name, Cany.hash_with_array_as_default]
+ end]
+ @options = Hash[(self.class.defined_options || []).map do |name|
+ [name, Cany.hash_with_array_as_default]
+ end]
+ self.class.const_get(:DSL).new(self).exec(&configure_block) if configure_block
+ end
+
+ # Specify the inner recipe for the current one.
# @param [Cany::Recipe, nil] inner Inner recipes should should be call between the pre and post
# actions of this class. Nil means most inner recipes.
- def initialize(spec, inner)
- @spec = spec
+ def inner=(inner)
@inner = inner
end
attr_reader :spec, :inner
@@ -44,32 +62,39 @@
# The arguments can be group with arguments, but must not:
# @example
# exec 'echo', %w(a b)
# exec ['echo', 'a', 'b']
# exec 'echo', 'a', 'b'
+ # @raise [CommandExecutionFailed] if the executed program exists with a
+ # non-zero exit code.
def exec(*args)
- puts " #{args.flatten.join(' ')}"
- system *args.flatten
+ args.flatten!
+ Cany.logger.info args.join(' ')
+ unless system(*args)
+ raise CommandExecutionFailed.new args
+ end
end
+ # exec is special name in same situations it may no work but this alias should work always
+ alias :exec_ :exec
# @api public
- # Run a ruby task (like gem, bundler, rake ...)
+ # Run a ruby task (like gem, bundle, rake ...)
#
# The method expects as arguments the program name and additional parameters for the program.
# See exec for more examples
def ruby_bin(*args)
exec RbConfig.ruby, '-S', *args
end
# @api public
# Install files or directory from the build directory
- # @param [String] source The relative file name to a filename or directory inside the build
+ # @param source[String] The relative file name to a filename or directory inside the build
# directory that should be installed/copied into the destination package
- # @param [String] destination The diretory name into that the file or directory should be
+ # @param destination[String] The diretory name into that the file or directory should be
# installed
- def install(src, dest_dir)
- exec 'dh_install', src, dest_dir
+ def install(source, destination)
+ exec 'dh_install', source, destination
end
# @api public
# Install a file. The content is passed as argument. This method is designed to be used by
# recipes to create files dynamically.
@@ -94,22 +119,128 @@
def install_link(source, destination)
exec 'dh_link', source, destination
end
# @api public
+ # Specify a command call (program + args) that should be installed as service and started
+ # automatically.
+ # This method should be only call inside the binary step.
+ # @param name[Symbol] A short identifier. Used to separate different services. E.g. the name
+ # of the web server that is launched by this command (like puma, unicorn, thin)
+ # @param command[Array<String>] The command that should be started and its parameter. The first
+ # element is the command name - can be absolute or relative path name (than searched in path)
+ # @param opts[Hash] Service behavior options
+ # @option opts[String] :user As which user should the command executed (default is root)
+ # @option opts[String] :group As which group should the command executed (default is root)
+ def install_service(*args)
+ recipe(:system).install_service(*args)
+ end
+
+ # @api public
# Ensure that the given files or directories are no present. Directories are removed
# recursively.
def rmtree(*args)
args.flatten.each do |path|
::FileUtils.remove_entry path if File.exists? path
end
end
+ class << self
+ attr_accessor :defined_hooks, :defined_options
+
+ # @api public
+ # Define a new hook
+ # @param name[Symbol]
+ def hook(name)
+ @defined_hooks ||= []
+ @defined_hooks << name
+ end
+
+ # @api public
+ # Define a configure option. These kind of option are design for other
+ # recipes not for the user. See Recipe::DSL for this.
+ # @param name[Symbol] The name of the option. The option name is scoped
+ # inside a recipe.
+ def option(name)
+ @defined_options ||= []
+ @defined_options << name
+ end
+ end
+
+ def hook(name)
+ @hooks[name].tap do |hook|
+ raise UnknownHook.new name unless hook
+ end
+ end
+
+ # @api public
+ # Ask for the current values for a defined option
+ def option(name)
+ @options[name].tap do |option|
+ raise UnknownOption.new name unless option
+ end
+ end
+
+ # @api public
+ # Configure an other recipe
+ # @param name[Symbol] The option name
+ # @param options[Hash] The configuration data itself.
+ def configure(name, options)
+ option(name).merge! options
+ end
+
+ # @api public
+ # Run defined actions for a hook
+ # @param name[Symbol] hook identification, no error is raised on unknown hooks
+ # @param state[Symbol] state that should be executed (:before, :after or :around)
+ def run_hook(name, state)
+ hook(name)[state].each do |block|
+ Cany.logger.info "run #{block} for hook #{name} in state #{state} ..."
+ instance_eval(&block)
+ end
+ end
+
+ # @api public
+ # Access the recipe instance from another loaded recipe of this
+ # specification
+ # @param name[Symbol] recipe name
+ def recipe(name)
+ return spec.system_recipe if name == :system
+ recipe_class = Recipe.from_name(name)
+ @spec.recipes.each do |one_recipe|
+ return one_recipe if one_recipe.instance_of? recipe_class
+ end
+ raise UnloadedRecipe.new name
+ end
+
+ # @api public
+ # Adds a new dependency for the software. See Cany::Dependency for a more
+ # abstract description about dependencies
+ # See Cany::Mixins::DependMixin for parameter description
+ include Cany::Mixins::DependMixin
+ def depend(*args)
+ @spec.dependencies << create_dep(*args)
+ end
+
# default implementation:
#########################
+ # @!group Recipe Steps - to be overridden in subclass
+
# @api public
+ # Prepares the recipes to run things. This is call exactly once for all recipes before
+ # recipes actions are executed.
+ def prepare
+ end
+
+ # @api public
+ # This step is executed to create the distribution specific packages from canspec. The recipe
+ # can e.g. add additional dependencies or adjust the package meta data.
+ def create(creator)
+ end
+
+ # @api public
# clean the build directory from all temporary and created files
def clean
inner.clean
end
@@ -121,8 +252,48 @@
# @api public
# create binary (package) version of this file (means make install)
def binary
inner.binary
+ end
+
+ # @!endgroup
+
+
+ # This superclass helps recipes to create easily an own mini DSL to let the user configure the
+ # recipe with it.
+ class DSL
+ def initialize(recipe)
+ @recipe = recipe
+ end
+
+ # Evaluate a given block inside the dsl.
+ def exec(&block)
+ instance_eval(&block)
+ end
+
+ # This is a simple delegate helper. It can be used to pass option directly to recipe instance.
+ # @param [Symbol] param1 Multiple symbol names
+ def self.delegate(*methods)
+ methods.each do |method|
+ module_eval(<<-EOS, __FILE__, __LINE__)
+ def #{method}(*args, &block)
+ @recipe.send :'#{method}=', *args, &block
+ end
+ EOS
+ end
+ end
+
+ def before(hook_name, &block)
+ @recipe.hook(hook_name)[:before] << block
+ end
+
+ def after(hook_name, &block)
+ @recipe.hook(hook_name)[:after] << block
+ end
+
+ def around(hook_name, &block)
+ @recipe.hook(hook_name)[:around] << block
+ end
end
end
end