module Spec module Story module Runner class IllegalStepError < StandardError def initialize(state, event) super("Illegal attempt to create a #{event} after a #{state}") end end class StoryParser def initialize(story_mediator) @story_mediator = story_mediator @current_story_lines = [] transition_to(:starting_state) end def parse(lines) lines.reject! {|line| line == ""} until lines.empty? process_line(lines.shift) end @state.eof end def process_line(line) line.strip! case line when /^Story: / then @state.story(line) when /^Scenario: / then @state.scenario(line) when /^Given:? / then @state.given(line) when /^GivenScenario:? / then @state.given_scenario(line) when /^When:? / then @state.event(line) when /^Then:? / then @state.outcome(line) when /^And:? / then @state.one_more_of_the_same(line) else @state.other(line) end end def init_story(title) @current_story_lines.clear add_story_line(title) end def add_story_line(line) @current_story_lines << line end def create_story() unless @current_story_lines.empty? @story_mediator.create_story(@current_story_lines[0].gsub("Story: ",""), @current_story_lines[1..-1].join("\n")) @current_story_lines.clear end end def create_scenario(title) @story_mediator.create_scenario(title.gsub("Scenario: ","")) end def create_given(name) @story_mediator.create_given(name) end def create_given_scenario(name) @story_mediator.create_given_scenario(name) end def create_when(name) @story_mediator.create_when(name) end def create_then(name) @story_mediator.create_then(name) end def transition_to(key) @state = states[key] end def states @states ||= { :starting_state => StartingState.new(self), :story_state => StoryState.new(self), :scenario_state => ScenarioState.new(self), :given_state => GivenState.new(self), :when_state => WhenState.new(self), :then_state => ThenState.new(self) } end class State def initialize(parser) @parser = parser end def story(line) @parser.init_story(line) @parser.transition_to(:story_state) end def scenario(line) @parser.create_scenario(line) @parser.transition_to(:scenario_state) end def given(line) @parser.create_given(remove_tag_from(:given, line)) @parser.transition_to(:given_state) end def given_scenario(line) @parser.create_given_scenario(remove_tag_from(:givenscenario, line)) @parser.transition_to(:given_state) end def event(line) @parser.create_when(remove_tag_from(:when, line)) @parser.transition_to(:when_state) end def outcome(line) @parser.create_then(remove_tag_from(:then, line)) @parser.transition_to(:then_state) end def remove_tag_from(tag, line) tokens = line.split # validation of tag can go here tokens[0].downcase.match(/#{tag.to_s}:?/) ? (tokens[1..-1].join(' ')) : line end def eof end def other(line) # no-op - supports header text before the first story in a file end end class StartingState < State def initialize(parser) @parser = parser end end class StoryState < State def one_more_of_the_same(line) other(line) end def story(line) @parser.create_story @parser.add_story_line(line) end def scenario(line) @parser.create_story @parser.create_scenario(line) @parser.transition_to(:scenario_state) end def given(line) other(line) end def event(line) other(line) end def outcome(line) other(line) end def other(line) @parser.add_story_line(line) end def eof @parser.create_story end end class ScenarioState < State def one_more_of_the_same(line) raise IllegalStepError.new("Scenario", "And") end def scenario(line) @parser.create_scenario(line) end end class GivenState < State def one_more_of_the_same(line) @parser.create_given(remove_tag_from(:and, line)) end def given(line) @parser.create_given(remove_tag_from(:given, line)) end end class WhenState < State def one_more_of_the_same(line) @parser.create_when(remove_tag_from(:and ,line)) end def event(line) @parser.create_when(remove_tag_from(:when ,line)) end end class ThenState < State def one_more_of_the_same(line) @parser.create_then(remove_tag_from(:and ,line)) end def outcome(line) @parser.create_then(remove_tag_from(:then ,line)) end end end end end end