diff --git a/features/recipe_ask.feature b/features/task_ask.feature similarity index 93% rename from features/recipe_ask.feature rename to features/task_ask.feature index ae091d7..7255928 100644 --- a/features/recipe_ask.feature +++ b/features/task_ask.feature @@ -1,4 +1,4 @@ -Feature: `ask' recipe keyword +Feature: `ask' task keyword @exec Scenario: prompts user with a list of choices on standard output diff --git a/lib/producer/core.rb b/lib/producer/core.rb index 0d29d39..10758ee 100644 --- a/lib/producer/core.rb +++ b/lib/producer/core.rb @@ -38,6 +38,5 @@ require 'producer/core/remote' require 'producer/core/remote/environment' require 'producer/core/remote/fs' require 'producer/core/task' -require 'producer/core/task/dsl' require 'producer/core/version' require 'producer/core/worker' diff --git a/lib/producer/core/recipe.rb b/lib/producer/core/recipe.rb index b39da34..60704f8 100644 --- a/lib/producer/core/recipe.rb +++ b/lib/producer/core/recipe.rb @@ -23,7 +23,7 @@ module Producer end def task(name, *args, &block) - @tasks << Task.evaluate(name, env, *args, &block) + @tasks << Task.evaluate(env, name, *args, &block) end def macro(name, &block) diff --git a/lib/producer/core/task.rb b/lib/producer/core/task.rb index 18ff815..c5e2dcc 100644 --- a/lib/producer/core/task.rb +++ b/lib/producer/core/task.rb @@ -2,28 +2,54 @@ module Producer module Core class Task class << self - def evaluate(name, env, *args, &block) - dsl = DSL.new(env, &block) - dsl.evaluate(*args) - Task.new(name, dsl.actions, dsl.condition) + def define_action(keyword, klass) + define_method(keyword) do |*args| + @actions << klass.new(@env, *args) + end + end + + def evaluate(env, name, *args, &block) + new(env, name).tap { |o| o.instance_exec *args, &block } end end + define_action :echo, Actions::Echo + define_action :sh, Actions::ShellCommand + + define_action :mkdir, Actions::Mkdir + define_action :file_append, Actions::FileAppend + define_action :file_replace_content, Actions::FileReplaceContent + define_action :file_write, Actions::FileWriter + attr_reader :name, :actions, :condition - def initialize(name, actions = [], condition = true) + def initialize(env, name, actions = [], condition = true) + @env = env @name = name @actions = actions @condition = condition end def to_s - name.to_s + @name.to_s end def condition_met? !!@condition end + + def condition(&block) + @condition = Condition.evaluate(@env, &block) if block + @condition + end + + def ask(question, choices, prompter: Prompter.new(@env.input, @env.output)) + prompter.prompt(question, choices) + end + + def get(key) + @env[key] + end end end end diff --git a/lib/producer/core/task/dsl.rb b/lib/producer/core/task/dsl.rb deleted file mode 100644 index 17839af..0000000 --- a/lib/producer/core/task/dsl.rb +++ /dev/null @@ -1,50 +0,0 @@ -module Producer - module Core - class Task - class DSL - class << self - def define_action(keyword, klass) - define_method(keyword) do |*args| - @actions << klass.new(@env, *args) - end - end - end - - define_action :echo, Actions::Echo - define_action :sh, Actions::ShellCommand - - define_action :mkdir, Actions::Mkdir - - define_action :file_append, Actions::FileAppend - define_action :file_replace_content, Actions::FileReplaceContent - define_action :file_write, Actions::FileWriter - - attr_reader :env, :block, :actions - - def initialize(env, &block) - @env = env - @block = block - @actions = [] - @condition = true - end - - def evaluate(*args) - instance_exec *args, &@block - end - - def condition(&block) - @condition = Condition.evaluate(@env, &block) if block - @condition - end - - def ask(question, choices, prompter: Prompter) - prompter.new(env.input, env.output).prompt(question, choices) - end - - def get(key) - env[key] - end - end - end - end -end diff --git a/lib/producer/core/worker.rb b/lib/producer/core/worker.rb index 62c6869..453ea52 100644 --- a/lib/producer/core/worker.rb +++ b/lib/producer/core/worker.rb @@ -4,27 +4,25 @@ module Producer DRY_RUN_WARNING = 'running in dry run mode, actions will NOT be applied'.freeze - attr_accessor :env - def initialize(env) @env = env end def process(tasks) - env.log DRY_RUN_WARNING, :warn if env.dry_run? + @env.log DRY_RUN_WARNING, :warn if @env.dry_run? tasks.each { |t| process_task t } end def process_task(task) if task.condition_met? - env.log "Task: `#{task}' applying..." + @env.log "Task: `#{task}' applying..." task.actions.each do |e| - env.log " action: #{e}" - e.apply unless env.dry_run? + @env.log " action: #{e}" + e.apply unless @env.dry_run? end else - env.log "Task: `#{task}' skipped" + @env.log "Task: `#{task}' skipped" end end end diff --git a/spec/producer/core/task/dsl_spec.rb b/spec/producer/core/task/dsl_spec.rb deleted file mode 100644 index fec6ba8..0000000 --- a/spec/producer/core/task/dsl_spec.rb +++ /dev/null @@ -1,123 +0,0 @@ -require 'spec_helper' - -module Producer::Core - class Task - describe DSL do - let(:block) { proc {} } - let(:env) { Env.new } - subject(:dsl) { DSL.new(env, &block) } - - %w[ - echo - sh - mkdir - file_append - file_replace_content - file_write - ].each do |action| - it "has `#{action}' action defined" do - expect(dsl).to respond_to action.to_sym - end - end - - describe '.define_action' do - let(:some_action_class) { Class.new(Action) } - - before { described_class.define_action(:some_action, some_action_class) } - - it 'defines a new action keyword' do - expect(dsl).to respond_to :some_action - end - - context 'when an action keyword is called' do - it 'registers the action' do - expect { dsl.some_action }.to change { dsl.actions.count }.by 1 - end - - it 'registers the action with current env' do - dsl.some_action - expect(dsl.actions.first.env).to be env - end - - it 'registers the action with given arguments' do - dsl.some_action :some, :args - expect(dsl.actions.first.arguments).to eq [:some, :args] - end - end - end - - describe '#initialize' do - it 'assigns the given env' do - expect(dsl.env).to be env - end - - it 'assigns the given block' do - expect(dsl.block).to be block - end - - it 'assigns no action' do - expect(dsl.actions).to be_empty - end - - it 'assigns true as the condition' do - expect(dsl.condition).to be true - end - end - - describe '#evaluate' do - let(:block) { proc { throw :task_code } } - - it 'evaluates its code' do - expect { dsl.evaluate } - .to throw_symbol :task_code - end - - context 'when arguments are given' do - let(:block) { proc { |e| throw e } } - - it 'passes arguments as block parameters' do - expect { dsl.evaluate :some_argument } - .to throw_symbol :some_argument - end - end - end - - describe '#condition' do - context 'when a block is given' do - it 'assigns a new evaluated condition' do - dsl.condition { :some_return_value } - expect(dsl.condition.return_value).to eq :some_return_value - end - end - end - - describe '#ask' do - let(:question) { 'Which letter?' } - let(:choices) { [[:a, ?A], [:b, ?B]] } - let(:prompter_class) { double('prompter class').as_null_object } - subject(:ask) { dsl.ask question, choices, - prompter: prompter_class } - - it 'builds a prompter' do - expect(prompter_class).to receive(:new).with(env.input, env.output) - ask - end - - it 'prompts and returns the choice' do - prompter = double 'prompter' - allow(prompter_class).to receive(:new) { prompter } - allow(prompter).to receive(:prompt) { :choice } - expect(ask).to eq :choice - end - end - - describe '#get' do - let(:env) { Env.new(registry: { some_key: :some_value }) } - - it 'fetches a value from the registry at given index' do - expect(dsl.get :some_key).to eq :some_value - end - end - end - end -end diff --git a/spec/producer/core/task_spec.rb b/spec/producer/core/task_spec.rb index 3d2236c..fbffa53 100644 --- a/spec/producer/core/task_spec.rb +++ b/spec/producer/core/task_spec.rb @@ -2,62 +2,90 @@ require 'spec_helper' module Producer::Core describe Task do + class SomeAction < Action; end + + let(:env) { Env.new } let(:name) { :some_task } - let(:action) { double 'action' } - let(:condition) { double 'condition' } - subject(:task) { Task.new(name, [action], condition) } + let(:condition) { :some_condition } + subject(:task) { described_class.new(env, name, [], condition) } - describe '.evaluate' do - let(:name) { :some_task } - let(:env) { double 'env' } - let(:block) { proc { condition { :condition }; some_action } } - let(:some_action_class) { Class.new(Action) } - subject(:task) { Task.evaluate(name, env, :some, :args, &block) } + %w[ + echo + sh + mkdir + file_append + file_replace_content + file_write + ].each do |action| + it "has `#{action}' action defined" do + expect(task).to respond_to action.to_sym + end + end - before { Task::DSL.define_action(:some_action, some_action_class) } + describe '.define_action' do + before { described_class.define_action(:some_action, SomeAction) } - it 'returns an evaluated task' do - expect(task).to be_kind_of Task + it 'defines a new action keyword' do + expect(task).to respond_to :some_action end - context 'evaluated task' do - it 'has the requested name' do - expect(task.name).to eq name + context 'when an action keyword is called' do + it 'registers the action' do + expect { task.some_action }.to change { task.actions.count }.by 1 end - it 'has the requested actions' do - expect(task.actions.first).to be_kind_of some_action_class + it 'registers the action with current env' do + task.some_action + expect(task.actions.first.env).to be env end - it 'has the requested condition' do - expect(task.condition.return_value).to be :condition + it 'registers the action with given arguments' do + task.some_action :foo, :bar + expect(task.actions.first.arguments).to eq %i[foo bar] + end + end + end + + describe '.evaluate' do + let(:code) { proc { condition { :condition }; some_action } } + let(:arguments) { [] } + subject(:task) { described_class.evaluate(env, name, *arguments, &code) } + + before { described_class.define_action(:some_action, SomeAction) } + + it 'returns an evaluated task' do + expect(task).to be_a Task + end + + it 'evaluates the task condition' do + expect(task.condition).to be_a Condition + end + + it 'evaluates the task actions' do + expect(task.actions).to match [ + an_instance_of(SomeAction) + ] + end + + context 'when task arguments are given' do + let(:code) { proc { |a, b| throw a } } + let(:arguments) { %i[foo bar] } + + it 'passes arguments as block parameters during evaluation' do + expect { task }.to throw_symbol :foo end end end describe '#initialize' do - it 'assigns the name' do - expect(task.name).to eq name + subject(:task) { described_class.new(env, name) } + + it 'assigns no action' do + expect(task.actions).to be_empty end - it 'assigns the actions' do - expect(task.actions).to eq [action] - end - - it 'assigns the condition' do - expect(task.condition).to eq condition - end - - context 'when only the name is given as argument' do - subject(:task) { described_class.new(name) } - - it 'assigns no action' do - expect(task.actions).to be_empty - end - - it 'assigns a true condition' do - expect(task.condition).to be true - end + it 'assigns a truthy condition' do + expect(task.condition).to be_truthy end end @@ -69,7 +97,7 @@ module Producer::Core describe '#condition_met?' do context 'when condition is truthy' do - let(:condition) { Condition.new([], true) } + let(:condition) { true } it 'returns true' do expect(task.condition_met?).to be true @@ -77,12 +105,50 @@ module Producer::Core end context 'when condition is falsy' do - let(:condition) { Condition.new([], false) } + let(:condition) { false } it 'returns false' do expect(task.condition_met?).to be false end end end + + describe '#condition' do + it 'returns current condition' do + expect(task.condition).to eq :some_condition + end + + context 'when a block is given' do + it 'assigns a new evaluated condition' do + task.condition { :some_new_condition } + expect(task.condition.return_value).to eq :some_new_condition + end + end + end + + describe '#ask' do + let(:question) { 'Which letter?' } + let(:choices) { [[:a, ?A], [:b, ?B]] } + let(:prompter) { instance_spy Prompter } + subject(:ask) { task.ask question, choices, prompter: prompter } + + it 'prompts for choices' do + ask + expect(prompter).to have_received(:prompt).with(question, choices) + end + + it 'returns selected choice' do + allow(prompter).to receive(:prompt) { :choice } + expect(ask).to eq :choice + end + end + + describe '#get' do + before { env[:some_key] = :some_value } + + it 'fetches a value from the registry at given index' do + expect(task.get :some_key).to eq :some_value + end + end end end diff --git a/spec/producer/core/worker_spec.rb b/spec/producer/core/worker_spec.rb index 8ce7a8b..8ba1c0d 100644 --- a/spec/producer/core/worker_spec.rb +++ b/spec/producer/core/worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Producer::Core describe Worker do - let(:env) { double 'env', log: nil, dry_run?: false } + let(:env) { Env.new } subject(:worker) { described_class.new(env) } describe '#process' do @@ -12,7 +12,7 @@ module Producer::Core end context 'when dry run is enabled' do - before { allow(env).to receive(:dry_run?) { true } } + before { env.dry_run = true } it 'warns dry run is enabled' do expect(env).to receive(:log).with( @@ -25,12 +25,12 @@ module Producer::Core end describe '#process_task' do + let(:env) { instance_spy Env, dry_run?: false } let(:action) { double('action', to_s: 'echo').as_null_object } - let(:task_name) { 'some_task' } - let(:task) { Task.new(task_name, [action]) } + let(:task) { Task.new(env, :some_task, [action]) } it 'logs task info' do - expect(env).to receive(:log).with /\ATask: `#{task_name}'/ + expect(env).to receive(:log).with /\ATask: `some_task'/ worker.process_task task end @@ -41,7 +41,7 @@ module Producer::Core end it 'logs the task as beeing applied' do - expect(env).to receive(:log).with /#{task_name}.+applying\.\.\.\z/ + expect(env).to receive(:log).with /some_task.+applying\.\.\.\z/ worker.process_task task end @@ -61,7 +61,7 @@ module Producer::Core end context 'when task condition is not met' do - let(:task) { Task.new(task_name, [action], false) } + before { task.condition { false } } it 'does not apply the actions' do expect(action).not_to receive :apply @@ -69,16 +69,10 @@ module Producer::Core end it 'logs the task as beeing skipped' do - expect(env).to receive(:log).with /#{task_name}.+skipped\z/ + expect(env).to receive(:log).with /some_task.+skipped\z/ worker.process_task task end end end - - describe '#env' do - it 'returns the assigned env' do - expect(worker.env).to be env - end - end end end