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
|
module Vagrant
|
||||||
class Action
|
class Action
|
||||||
class MultiStep < Step
|
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
|
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
|
end
|
||||||
|
|
||||||
def step(step_class, *extra_inputs)
|
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
|
# Get the options hash and set the defaults
|
||||||
options = {}
|
maps = {}
|
||||||
options = extra_inputs.pop if extra_inputs.last.kind_of?(Hash)
|
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
|
# Append the step
|
||||||
@steps << [step_class, extra_inputs, options]
|
@step_names << step_name
|
||||||
|
@steps[step_name] = [step_class, maps]
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(params=nil)
|
def call(params=nil)
|
||||||
params ||= {}
|
params ||= {}
|
||||||
|
|
||||||
# Instantiate all the steps
|
# 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
|
# For each step, call it with proper inputs, using the output
|
||||||
# of that call as inputs to the next.
|
# of that call as inputs to the next.
|
||||||
instances.inject(params) do |inputs, data|
|
step_outputs = {}
|
||||||
step, extra_inputs, options = data
|
steps.inject(params) do |inputs, data|
|
||||||
|
name, step, mappings = 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
|
|
||||||
|
|
||||||
# If we have inputs to remap, remap them.
|
# If we have inputs to remap, remap them.
|
||||||
if options[:map]
|
mappings.each do |from, to|
|
||||||
options[:map].each do |from, to|
|
if from.kind_of?(GroupInput)
|
||||||
# This sets the input to the new key while removing the
|
# Group inputs get their data from the initial parameters given
|
||||||
# the old key from the same hash. Kind of sneaky, but
|
# to this group.
|
||||||
# hopefully this comment makes it clear.
|
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)
|
inputs[to] = inputs.delete(from)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Call the actual step, using the results for the next
|
# Call the actual step, using the results for the next
|
||||||
# iteration.
|
# iteration.
|
||||||
step.call(inputs)
|
step_outputs[name] = step.call(inputs)
|
||||||
|
|
||||||
|
# Return a shallow dup of the results
|
||||||
|
step_outputs[name].dup
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -76,7 +76,64 @@ describe Vagrant::Action::MultiStep do
|
||||||
|
|
||||||
ms = described_class.new
|
ms = described_class.new
|
||||||
ms.step step_A
|
ms.step step_A
|
||||||
ms.step step_B, :map => { :foo => :from }
|
ms.step step_B, :foo => :from
|
||||||
ms.call.should == { :value => "A" }
|
ms.call.should == { :value => "A" }
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Reference in New Issue