Multistep can now take parameters from any arbitrary step prior.
This commit is contained in:
parent
5b87165e97
commit
7d3746b292
|
@ -1,49 +1,123 @@
|
|||
module Vagrant
|
||||
class Action
|
||||
class MultiStep < Step
|
||||
# This class is used as a placeholder to represent a parameter that
|
||||
# needs to be replaced at runtime. For example: A step might take a
|
||||
# parameter B that is outputted as A from a previous step. An instance
|
||||
# of this class can represent that the previous A should be remapped
|
||||
# to this new B.
|
||||
class Param
|
||||
attr_reader :name
|
||||
attr_reader :variable
|
||||
|
||||
def initialize(name, variable)
|
||||
@name = name
|
||||
@variable = variable
|
||||
end
|
||||
end
|
||||
|
||||
# Represents a remapping that comes from the group inputs.
|
||||
class GroupInput < Param; end
|
||||
|
||||
# Represents a remapping that comes from the output of a previous step.
|
||||
class StepOutput < Param; end
|
||||
|
||||
def initialize
|
||||
@steps = []
|
||||
@step_names = []
|
||||
@steps = {}
|
||||
end
|
||||
|
||||
# This returns a custom object that represents an input parameter
|
||||
# given to this group.
|
||||
#
|
||||
# @param [Symbol] key Parameter name of the input to use from the group.
|
||||
# @return [GroupInput] A `param` type that can be used for remappings.
|
||||
def input(key)
|
||||
return GroupInput.new(:global, key)
|
||||
end
|
||||
|
||||
# This returns a custom object that represents an output parameter
|
||||
# from another step in this group.
|
||||
#
|
||||
# @param [Object] name Name of the step. This is either an explicit name
|
||||
# like a symbol or the class for the step if it is unique.
|
||||
# @param [Symbol] output The output variable name from the step.
|
||||
# @return [StepOutput] A `param` type that can be used for remappings.
|
||||
def output(name, output)
|
||||
return StepOutput.new(name, output)
|
||||
end
|
||||
|
||||
def step(step_class, *extra_inputs)
|
||||
# Determine the name for this step.
|
||||
step_name = nil
|
||||
if step_class.is_a?(Symbol)
|
||||
step_name = step_class
|
||||
step_class = extra_inputs.shift
|
||||
else
|
||||
step_name = step_class
|
||||
end
|
||||
|
||||
if @steps.has_key?(step_name)
|
||||
raise NameError, "Step with name #{step_name} already exists."
|
||||
elsif !step_class.is_a?(Class)
|
||||
raise ArgumentError, "Step class must be a class."
|
||||
end
|
||||
|
||||
# Get the options hash and set the defaults
|
||||
options = {}
|
||||
options = extra_inputs.pop if extra_inputs.last.kind_of?(Hash)
|
||||
maps = {}
|
||||
maps = extra_inputs.pop if extra_inputs.last.kind_of?(Hash)
|
||||
|
||||
extra_inputs.each do |direct|
|
||||
if direct.is_a?(Symbol)
|
||||
# Symbols are assumed to be inputs to this group
|
||||
direct = input(direct)
|
||||
end
|
||||
|
||||
maps[direct] = direct.variable
|
||||
end
|
||||
|
||||
# Append the step
|
||||
@steps << [step_class, extra_inputs, options]
|
||||
@step_names << step_name
|
||||
@steps[step_name] = [step_class, maps]
|
||||
end
|
||||
|
||||
def call(params=nil)
|
||||
params ||= {}
|
||||
|
||||
# Instantiate all the steps
|
||||
instances = @steps.map { |s, inputs, options| [s.new, inputs, options] }
|
||||
steps = @step_names.map do |name|
|
||||
step_class, maps = @steps[name]
|
||||
[name, step_class.new, maps]
|
||||
end
|
||||
|
||||
# For each step, call it with proper inputs, using the output
|
||||
# of that call as inputs to the next.
|
||||
instances.inject(params) do |inputs, data|
|
||||
step, extra_inputs, options = data
|
||||
|
||||
# If there are extra inputs for this step, add them to the
|
||||
# parameters based on the initial parameters.
|
||||
extra_inputs.each do |extra_input|
|
||||
inputs[extra_input] = params[extra_input]
|
||||
end
|
||||
step_outputs = {}
|
||||
steps.inject(params) do |inputs, data|
|
||||
name, step, mappings = data
|
||||
|
||||
# If we have inputs to remap, remap them.
|
||||
if options[:map]
|
||||
options[:map].each do |from, to|
|
||||
# This sets the input to the new key while removing the
|
||||
# the old key from the same hash. Kind of sneaky, but
|
||||
# hopefully this comment makes it clear.
|
||||
mappings.each do |from, to|
|
||||
if from.kind_of?(GroupInput)
|
||||
# Group inputs get their data from the initial parameters given
|
||||
# to this group.
|
||||
inputs[to] = params[from.variable]
|
||||
elsif from.kind_of?(StepOutput)
|
||||
# Step outputs get their data from a previous step's output.
|
||||
inputs[to] = step_outputs[from.name][from.variable]
|
||||
else
|
||||
# A basic remapping remaps the previous steps outputs to an
|
||||
# input for this step.
|
||||
inputs[to] = inputs.delete(from)
|
||||
end
|
||||
end
|
||||
|
||||
# Call the actual step, using the results for the next
|
||||
# iteration.
|
||||
step.call(inputs)
|
||||
step_outputs[name] = step.call(inputs)
|
||||
|
||||
# Return a shallow dup of the results
|
||||
step_outputs[name].dup
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -76,7 +76,64 @@ describe Vagrant::Action::MultiStep do
|
|||
|
||||
ms = described_class.new
|
||||
ms.step step_A
|
||||
ms.step step_B, :map => { :foo => :from }
|
||||
ms.step step_B, :foo => :from
|
||||
ms.call.should == { :value => "A" }
|
||||
end
|
||||
|
||||
it "should be able to reference variables from steps before other steps" do
|
||||
step_A = Class.new(Vagrant::Action::Step) do
|
||||
output :foo
|
||||
|
||||
def enter
|
||||
return :foo => 10
|
||||
end
|
||||
end
|
||||
|
||||
step_B = Class.new(Vagrant::Action::Step) do
|
||||
input :from
|
||||
output :value
|
||||
|
||||
def enter
|
||||
return :value => @from
|
||||
end
|
||||
end
|
||||
|
||||
step_C = Class.new(Vagrant::Action::Step) do
|
||||
input :number
|
||||
output :value
|
||||
|
||||
def enter
|
||||
return :value => @number * 2
|
||||
end
|
||||
end
|
||||
|
||||
obj = []
|
||||
|
||||
g = described_class.new
|
||||
g.step step_A
|
||||
g.step step_B, :foo => :from
|
||||
g.step step_C, g.output(step_A, :foo) => :number
|
||||
g.call.should == { :value => 20 }
|
||||
end
|
||||
|
||||
it "should error if multiple steps of the same class are given without explicit names" do
|
||||
step_A = Class.new(Vagrant::Action::Step)
|
||||
|
||||
g = described_class.new
|
||||
g.step step_A
|
||||
expect { g.step step_A }.to raise_error(NameError)
|
||||
end
|
||||
|
||||
it "should not error if multiple steps of the same class are given with custom names" do
|
||||
step_A = Class.new(Vagrant::Action::Step)
|
||||
|
||||
g = described_class.new
|
||||
g.step step_A
|
||||
expect { g.step :another, step_A }.to_not raise_error
|
||||
end
|
||||
|
||||
it "should error if a step is not a class" do
|
||||
g = described_class.new
|
||||
expect { g.step :foo }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue