diff --git a/features/tasks/condition.feature b/features/tasks/condition.feature index 85f4c24..78aeb59 100644 --- a/features/tasks/condition.feature +++ b/features/tasks/condition.feature @@ -1,13 +1,12 @@ Feature: `condition' task keyword - Scenario: prevents task evaluation when condition is not met + Scenario: prevents task actions application when condition is not met Given a recipe with: """ task :hello do condition { false } - puts 'evaluated' - exit 70 + echo 'evaluated' end """ When I execute the recipe diff --git a/lib/producer/core.rb b/lib/producer/core.rb index ee5df3b..8215060 100644 --- a/lib/producer/core.rb +++ b/lib/producer/core.rb @@ -2,6 +2,8 @@ require 'producer/core/action' require 'producer/core/actions/echo' require 'producer/core/actions/shell_command' require 'producer/core/cli' +require 'producer/core/condition' +require 'producer/core/condition/dsl' require 'producer/core/env' require 'producer/core/errors' require 'producer/core/interpreter' diff --git a/lib/producer/core/condition.rb b/lib/producer/core/condition.rb new file mode 100644 index 0000000..73f0779 --- /dev/null +++ b/lib/producer/core/condition.rb @@ -0,0 +1,19 @@ +module Producer + module Core + class Condition + class << self + def evaluate(env, &block) + DSL.evaluate(env, &block) + end + end + + def initialize(expression) + @expression = expression + end + + def ! + !@expression + end + end + end +end diff --git a/lib/producer/core/condition/dsl.rb b/lib/producer/core/condition/dsl.rb new file mode 100644 index 0000000..9347352 --- /dev/null +++ b/lib/producer/core/condition/dsl.rb @@ -0,0 +1,22 @@ +module Producer + module Core + class Condition + class DSL + class << self + def evaluate(env, &block) + dsl = new(&block) + Condition.new(dsl.evaluate) + end + end + + def initialize(&block) + @block = block + end + + def evaluate + @block.call + end + end + end + end +end diff --git a/lib/producer/core/interpreter.rb b/lib/producer/core/interpreter.rb index c24dc81..42be8e9 100644 --- a/lib/producer/core/interpreter.rb +++ b/lib/producer/core/interpreter.rb @@ -6,7 +6,7 @@ module Producer end def process_task(task) - task.actions.each(&:apply) + task.actions.each(&:apply) if task.condition_met? end end end diff --git a/lib/producer/core/task.rb b/lib/producer/core/task.rb index e63ff99..5559cb2 100644 --- a/lib/producer/core/task.rb +++ b/lib/producer/core/task.rb @@ -1,15 +1,20 @@ module Producer module Core class Task - attr_reader :name, :actions + attr_reader :name, :actions, :condition def self.evaluate(name, env, &block) DSL.evaluate(name, env, &block) end - def initialize(name, actions = []) - @name = name - @actions = actions + def initialize(name, actions = [], condition = true) + @name = name + @actions = actions + @condition = condition + end + + def condition_met? + !!@condition end end end diff --git a/lib/producer/core/task/dsl.rb b/lib/producer/core/task/dsl.rb index 1103604..4d4b545 100644 --- a/lib/producer/core/task/dsl.rb +++ b/lib/producer/core/task/dsl.rb @@ -6,7 +6,7 @@ module Producer def evaluate(name, env, &block) dsl = new(&block) dsl.evaluate(env) - Task.new(name, dsl.actions) + Task.new(name, dsl.actions, dsl.condition) end def define_action(keyword, klass) @@ -22,20 +22,19 @@ module Producer attr_accessor :actions def initialize(&block) - @block = block - @actions = [] + @block = block + @actions = [] + @condition = true end def evaluate(env) @env = env instance_eval &@block - rescue ConditionNotMetError end - private - def condition(&block) - fail ConditionNotMetError unless block.call + @condition = Condition.evaluate(@env, &block) if block + @condition end end end diff --git a/spec/producer/core/condition/dsl_spec.rb b/spec/producer/core/condition/dsl_spec.rb new file mode 100644 index 0000000..62ac89e --- /dev/null +++ b/spec/producer/core/condition/dsl_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +module Producer::Core + describe Condition::DSL do + let(:block) { proc { :some_condition_code } } + let(:env) { double('env') } + subject(:dsl) { Condition::DSL.new(&block) } + + describe '.evaluate' do + it 'builds a new DSL sandbox with given code' do + expect(Condition::DSL).to receive(:new).with(&block).and_call_original + Condition::DSL.evaluate(env, &block) + end + + it 'evaluates the DSL sandbox code' do + dsl = double('dsl') + allow(Condition::DSL).to receive(:new) { dsl } + expect(dsl).to receive(:evaluate) + Condition::DSL.evaluate(env, &block) + end + + it 'builds a condition with value returned from DSL evaluation' do + expect(Condition) + .to receive(:new).with(dsl.evaluate) + Condition::DSL.evaluate(env, &block) + end + + it 'returns the condition' do + condition = double('task') + allow(Condition).to receive(:new) { condition } + expect(Condition::DSL.evaluate(env, &block)).to be condition + end + end + + describe '#initialize' do + it 'assigns the code' do + expect(dsl.instance_eval { @block }).to be block + end + end + + describe '#evaluate' do + it 'returns the value returned by the assigned block' do + expect(dsl.evaluate).to eq block.call + end + end + end +end diff --git a/spec/producer/core/condition_spec.rb b/spec/producer/core/condition_spec.rb new file mode 100644 index 0000000..057fb64 --- /dev/null +++ b/spec/producer/core/condition_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +module Producer::Core + describe Condition do + let(:expression) { double('expression') } + let(:condition) { Condition.new(expression) } + + describe '.evaluate' do + let(:env) { double('env') } + let(:block) { proc { :some_condition_code } } + + it 'delegates to DSL.evaluate' do + expect(Condition::DSL) + .to receive(:evaluate).with(env) do |&b| + expect(b.call).to eq :some_condition_code + end + Condition.evaluate(env, &block) + end + + it 'returns the evaluated condition' do + condition = double('condition') + allow(Condition::DSL).to receive(:evaluate) { condition } + expect(Condition.evaluate(env, &block)).to be condition + end + end + + describe '#initialize' do + it 'assigns the expression' do + expect(condition.instance_eval { @expression }).to be expression + end + end + + describe '#!' do + it 'returns the negated expression' do + expect(condition.!).to be !expression + end + end + end +end diff --git a/spec/producer/core/interpreter_spec.rb b/spec/producer/core/interpreter_spec.rb index 24d2ad4..d668568 100644 --- a/spec/producer/core/interpreter_spec.rb +++ b/spec/producer/core/interpreter_spec.rb @@ -12,12 +12,29 @@ module Producer::Core end describe '#process_task' do - it 'applies the task actions' do - action = double('action') - task = double('task') + let(:action) { double('action') } + let(:task) { double('task').as_null_object } + + before do allow(task).to receive(:actions) { [action] } - expect(action).to receive(:apply) - interpreter.process_task(task) + end + + context 'when task condition is met' do + it 'applies the actions' do + expect(action).to receive(:apply) + interpreter.process_task(task) + end + end + + context 'when task condition is not met' do + before do + allow(task).to receive(:condition_met?) { false } + end + + it 'does not apply the actions' do + expect(action).not_to receive(:apply) + interpreter.process_task(task) + end end end end diff --git a/spec/producer/core/task/dsl_spec.rb b/spec/producer/core/task/dsl_spec.rb index 38b508d..c1dcebb 100644 --- a/spec/producer/core/task/dsl_spec.rb +++ b/spec/producer/core/task/dsl_spec.rb @@ -27,11 +27,13 @@ module Producer::Core Task::DSL.evaluate(name, env, &block) end - it 'builds a task with its name and registered actions' do + it 'builds a task with its name, actions and condition' do dsl = double('dsl').as_null_object allow(Task::DSL).to receive(:new) { dsl } - allow(dsl).to receive(:actions) { [:some_action]} - expect(Task).to receive(:new).with(:some_task, [:some_action]) + allow(dsl).to receive(:actions) { [:some_action] } + allow(dsl).to receive(:condition) { :some_condition } + expect(Task) + .to receive(:new).with(:some_task, [:some_action], :some_condition) Task::DSL.evaluate(name, env, &block) end @@ -53,6 +55,10 @@ module Producer::Core it 'assigns no action' do expect(dsl.actions).to be_empty end + + it 'assigns true as the condition' do + expect(dsl.instance_eval { @condition }).to be_true + end end describe '#actions' do @@ -89,27 +95,35 @@ module Producer::Core end end - describe '#condition' do - context 'when met (block evals to true)' do - let(:block) { proc { - condition { true } - throw :after_condition - } } + context 'DSL specific methods' do + subject(:dsl) { Task::DSL.new(&block).evaluate(env) } - it 'evaluates all the block' do - expect { dsl.evaluate(env) } - .to throw_symbol :after_condition + describe '#condition' do + context 'when a block is given' do + let(:block) { proc { condition { :some_value } } } + + it 'builds a new evaluated condition' do + expect(Condition) + .to receive(:evaluate).with(env) do |&b| + expect(b.call).to eq :some_value + end + dsl + end + + it 'assigns the new condition' do + condition = double('condition').as_null_object + allow(Condition).to receive(:evaluate) { condition } + expect(dsl.condition).to be condition + end end end + end - context 'when not met (block evals to false)' do - let(:block) { proc { - condition { false } - throw :after_condition - } } - - it 'stops block evaluation' do - expect { dsl.evaluate(env) }.not_to throw_symbol :after_condition + describe '#condition' do + context 'without block' do + it 'returns the assigned condition' do + dsl.instance_eval { @condition = :some_condition } + expect(dsl.condition).to be :some_condition end end end diff --git a/spec/producer/core/task_spec.rb b/spec/producer/core/task_spec.rb index 3838ec7..41cd7e0 100644 --- a/spec/producer/core/task_spec.rb +++ b/spec/producer/core/task_spec.rb @@ -4,7 +4,8 @@ module Producer::Core describe Task do let(:name) { :some_task } let(:action) { double('action') } - subject(:task) { Task.new(name, [action]) } + let(:condition) { double('condition') } + subject(:task) { Task.new(name, [action], condition) } describe '.evaluate' do let(:env) { double('env') } @@ -34,12 +35,20 @@ module Producer::Core expect(task.instance_eval { @actions }).to eq [action] end + it 'assigns the condition' do + expect(task.instance_eval { @condition }).to eq condition + end + context 'when only the name is given as argument' do subject(:task) { Task.new(name) } it 'assigns no action' do expect(task.actions).to be_empty end + + it 'assigns a truthy condition' do + expect(task.condition).to be_true + end end end @@ -54,5 +63,29 @@ module Producer::Core expect(task.actions).to eq [action] end end + + describe '#condition' do + it 'returns the assigned condition' do + expect(task.condition).to be condition + end + end + + describe '#condition_met?' do + context 'when condition is truthy' do + let(:condition) { Condition.new(true) } + + it 'returns true' do + expect(task.condition_met?).to be true + end + end + + context 'when condition is falsy' do + let(:condition) { Condition.new(false) } + + it 'returns false' do + expect(task.condition_met?).to be false + end + end + end end end