[rspec-users] ./script/story command

Bryan Helmkamp lists at brynary.com
Sat Dec 29 11:47:10 EST 2007


Below I've pasted a ./script/story command I've been using for about a
week. It has three modes of operation:

1. ./script/story with no arguments will run all *.story files in the story path
2. ./script/story with a path or glob will run the specified stories
3. If input is passed in via STDIN, it runs that text as a story. This
opens up story running via a simple interface (Unix pipes) for
integration with other tools.

This deals with plain text stories only, and does NOT require an
associated .rb runner file for them. Without Ruby runner code for the
stories, the problem becomes figuring out what steps to use with a
given plain text story file. The solution I put together is quite
simple.

I declare groups of step matchers in a separate directory, taking care
to match the step group name to the file name. When a story file is
run, if any steps match a part of the path, they are included. For
example, my story in
RAILS_ROOT/stories/scenarios/schedule_formatting/weekly.story will
attempt to use the step groups "schedule_formatting" and "weekly" if
they exist.

For cases where that convention is not flexible enough, I added
support for including a comment line in the .story file with
instructions for which steps it should be run with. I think about this
kind of like a she-bang line. So the first line of a .story file could
be:

# steps: navigations, more_steps

And that will override the default behavior.

I'm posting this because I hope it might be of immediate use to others
using plain text stories right now, and also because I would like to
consider possibilities for getting this functionality into RSpec core.

WDYT?

-- 
Bryan Helmkamp
http://brynary.com -- My blog


-----------------------------------------------

#!/usr/bin/env ruby

class StoryCommand
  ROOT_PATH = File.expand_path(File.dirname(__FILE__) + "/..")

  STORIES_PATH        = "#{ROOT_PATH}/stories/scenarios"
  STEP_MATCHERS_PATH  = "#{ROOT_PATH}/stories/steps"
  HELPER_PATH         = "#{ROOT_PATH}/stories/helper"

  def self.run
    self.new.run
  end

  def run
    if ARGV.empty? && first_char = using_stdin?
      setup_and_run_story((first_char + STDIN.read).split("\n"))
    elsif ARGV.empty?
      run_story_files(all_story_files)
    else
      run_story_files(ARGV)
    end
  end

  def all_story_files
    Dir["#{STORIES_PATH}/**/*.story"].uniq
  end

  def using_stdin?
    char = nil
    begin
      char = STDIN.read_nonblock(1)
    rescue Errno::EAGAIN
      return false
    end
    return char
  end

  def clean_story_paths(paths)
    paths.map! { |path| File.expand_path(path) }
    paths.map! { |path| path.gsub(/\.story$/, "") }
    paths.map! { |path| path.gsub(/#{STORIES_PATH}\//, "") }
  end

  def run_story_files(stories)
    clean_story_paths(stories).each do |story|
      setup_and_run_story(File.readlines("#{STORIES_PATH}/#{story}.story"),
story)
    end
  end

  def setup_and_run_story(lines, story_name = nil)
    require HELPER_PATH

    steps = steps_for_story(lines, story_name)
    steps.reject! { |step| !File.exist?("#{STEP_MATCHERS_PATH}/#{step}.rb") }
    steps.each    { |step| require "#{STEP_MATCHERS_PATH}/#{step}" }

    run_story(lines, steps)
  end

  def steps_for_story(lines, story_name)
    if lines.first =~ /^# steps: /
      lines.first.gsub(/^# steps: /, "").split(",").map(&:strip)
    else
      story_name.to_s.split("/")
    end
  end

  def run_story(lines, steps)
    tempfile = Tempfile.new("story")
    lines.each do |line|
      tempfile.puts line
    end
    tempfile.close

    with_steps_for(*steps.map(&:to_sym)) do
      run tempfile.path, :type => RailsStory
    end
  end

end

StoryCommand.run


More information about the rspec-users mailing list