Project

General

Profile

Download (7.41 KB) Statistics
| Branch: | Tag: | Revision:
module ForemanMaintain
module Concerns
module Metadata
# limit of steps dependent on each other, to avoid endless recursion
MAX_PREPARATION_STEPS_DEPTH = 20

class << self
# modules not to be included in autogenerated labels
attr_accessor :top_level_modules
end

class DSL
attr_reader :data

def initialize(data = {})
@data = data
end

def label(label)
@data[:label] = label
end

def tags(*tags)
@data[:tags].concat(tags)
end

def description(description)
@data[:description] = description
end

def confine(&block)
@data[:confine_blocks] << block
end

def before(*step_labels)
raise Error::MultipleBeforeDetected, step_labels if step_labels.count > 1
@data[:before].concat(step_labels)
end

def after(*step_labels)
@data[:after].concat(step_labels)
end

# Parametrize the definition.
#
# == Arguments
#
# +name+: Name (Symbol) of the attribute
# +description_or_options+: Description string or a Hash with options
# +options+: Hash with options (unless specified in +descriptions_or_options+)
# +&block+: block to be called when processing the data: can be used for validation
# and type-casing of the value: expected to return the value to be used
#
# == Options
#
# +:description+: String describing the parameter
# +:required+: true if required
# +:flag+: param is just a true/false value: not expecting other values
#
def param(name, descripiton_or_options = {}, options = {}, &block)
case descripiton_or_options
when String
description = descripiton_or_options
when Hash
options = options.merge(descripiton_or_options) if descripiton_or_options.is_a?(Hash)
end
@data[:params][name] = Param.new(name, description, options, &block)
end

# Mark the class as manual: this means the instance
# of class will not be initialized by detector to check the confine block
# to determine if it's valid on the system or not.
# The classes marked for manual detect need to be initialized
# in from other places (such as `additional_features` in Feature)
def manual_detection
@data[:autodetect] = false
end

# in the block, define one or more preparation steps needed
# before executing this definition
def preparation_steps(&block)
@data[:preparation_steps_blocks] << block
end

# Specify what feature the definition related to.
def for_feature(feature_label)
@data[:for_feature] = feature_label
confine do
feature(feature_label)
end
end

# Ensure to not run the step twice: expects the scenario to be persisted
# between runs to work properly
def run_once
@data[:run_once] = true
end

def self.eval_dsl(metadata, &block)
new(metadata).tap do |dsl|
dsl.instance_eval(&block)
end.data
end
end

module ClassMethods
include Finders

def inherited(klass)
sub_classes << klass
end

# Override if the class should be used as parent class only.
# By default, we assume the class that does't inherit from class with
# Metadata is abstract = the base class of particular concept
def abstract_class
!(superclass < Metadata)
end

def sub_classes
@sub_classes ||= []
end

def autodetect?
metadata.fetch(:autodetect, true)
end

def all_sub_classes(ignore_abstract_classes = true)
ret = []
ret << self if !ignore_abstract_classes || !abstract_class
sub_classes.each do |sub_class|
ret.concat(sub_class.all_sub_classes(ignore_abstract_classes))
end
ret
end

def metadata(&block)
@metadata ||= initialize_metadata
if block
metadata_class.eval_dsl(@metadata, &block)
end
@metadata
end

def metadata_class
DSL
end

def label
metadata[:label] || generate_label
end

def description
metadata[:description] || to_s
end

def tags
metadata[:tags]
end

def params
metadata[:params] || {}
end

def before
metadata[:before] || []
end

def after
metadata[:after] || []
end

def run_once?
metadata[:run_once]
end

def initialize_metadata
{ :tags => [],
:confine_blocks => [],
:params => {},
:preparation_steps_blocks => [],
:before => [],
:after => [] }.tap do |metadata|
if superclass.respond_to?(:metadata)
metadata[:label] = superclass.metadata[:label]
end
end
end

def present?
evaluate_confines
end

def preparation_steps(recursion_depth = 0, trace = [])
raise "Too many dependent steps #{trace}" if recursion_depth > MAX_PREPARATION_STEPS_DEPTH
return @preparation_steps if defined?(@preparation_steps)
preparation_steps = metadata[:preparation_steps_blocks].map(&:call)
preparation_steps.each { |step| raise ArgumentError unless step.is_a?(Executable) }
all_preparation_steps = []
preparation_steps.each do |step|
all_preparation_steps.concat(
step.preparation_steps(recursion_depth + 1, trace + [step])
)
all_preparation_steps << step
end
@preparation_steps = all_preparation_steps
end

private

def evaluate_confines
raise 'Recursive confine block call detected' if @confines_evaluation_in_progress
@confines_evaluation_in_progress = true
metadata[:confine_blocks].all? do |block|
instance_exec(&block)
end
ensure
@confines_evaluation_in_progress = false
end

def generate_label
label_parts = []
name.split('::').reduce(Object) do |parent_constant, name|
constant = parent_constant.const_get(name)
unless Metadata.top_level_modules.include?(constant)
# CamelCase -> camel_case
label_parts << name.split(/(?=[A-Z])/).map(&:downcase)
end
constant
end
label_parts.join('_').to_sym
end
end

def self.included(klass)
klass.extend(ClassMethods)
end

def metadata
self.class.metadata
end

def label
self.class.label
end

def label_dashed
label.to_s.tr('_', '-')
end

def description
self.class.description
end

def runtime_message
description
end

def tags
self.class.tags
end

def params
self.class.params
end

def run_once?
self.class.run_once?
end

def preparation_steps(*args)
self.class.preparation_steps(*args)
end
end
end
end
(4-4/6)