class Respec::App

Constants

RSPEC_OPTIONS_THAT_REQUIRE_AN_ARGUMENT

Attributes

default_failures_path[RW]
failures_path[RW]
generated_args[R]
raw_args[R]

Public Class Methods

new(*args) click to toggle source
# File lib/respec/app.rb, line 5
def initialize(*args)
  if (i = args.index('--'))
    @args = args.slice!(0...i)
    @raw_args = args[1..-1]
  else
    @args = args
    @raw_args = []
  end
  @failures_path = self.class.default_failures_path
  @update_failures = true
  @error = nil
  process_args
end

Public Instance Methods

command() click to toggle source
# File lib/respec/app.rb, line 26
def command
  @command ||= program_args + generated_args + raw_args + formatter_args
end
error() click to toggle source
# File lib/respec/app.rb, line 21
def error
  command
  @error
end
formatter_args() click to toggle source
# File lib/respec/app.rb, line 42
def formatter_args
  if @update_failures
    [File.expand_path('formatter.rb', File.dirname(__FILE__))]
  else
    []
  end
end
help() click to toggle source
# File lib/respec/app.rb, line 59
    def help
      <<-EOS.gsub(/^ *\|/, '')
        |USAGE: respec RESPEC-ARGS ... [ -- RSPEC-ARGS ... ]
        |
        |Run rspec, recording failed examples for easy rerunning later.
        |
        |RESPEC-ARGS may consist of:
        |
        |  d              Run all spec files changed since the last git commit
        |  f              Rerun all failed examples
        |  <N>            Rerun only the N-th failure
        |  <file name>    Run all specs in this file
        |  <file name:N>  Run specs at line N in this file
        |  <other>        Run only examples matching this pattern
        |  -<anything>    Passed directly to rspec.
        |  --help         This!  (Also 'help'.)
        |
        |Any arguments following a '--' argument are passed directly to rspec.
        |
        |More info: http://github.com/oggy/respec
      EOS
    end
help_only?() click to toggle source
# File lib/respec/app.rb, line 55
def help_only?
  @help_only
end
program_args() click to toggle source
# File lib/respec/app.rb, line 30
def program_args
  if File.exist?('bin/rspec')
    ['bin/rspec']
  elsif File.exist?(ENV['BUNDLE_GEMFILE'] || 'Gemfile')
    ['bundle', 'exec', 'rspec']
  else
    ['rspec']
  end
end

Private Instance Methods

changed_paths(paths) click to toggle source
# File lib/respec/app.rb, line 167
def changed_paths(paths)
  changes = `git status --short --untracked-files=all #{paths.shelljoin}`
  changes.lines.map do |line|
    path = line[3..-1].chomp
    path if File.exist?(path) && path =~ /\.rb\z/i
  end.compact.uniq
end
failures() click to toggle source
# File lib/respec/app.rb, line 175
def failures
  @failures ||=
    if File.exist?(failures_path)
      File.read(failures_path).split(/\n/)
    else
      []
    end
end
process_args() click to toggle source
# File lib/respec/app.rb, line 84
def process_args
  args = []
  files = []
  pass_next_arg = false
  using_filters = false
  changed_only = false
  @args.each do |arg|
    if pass_next_arg
      args << arg
      pass_next_arg = false
    elsif rspec_option_that_requires_an_argument?(arg)
      args << arg
      pass_next_arg = true
    elsif File.exist?(arg.sub(/:\d+\z/, ''))
      files << arg
    elsif arg =~ /\A(--)?help\z/
      @help_only = true
    elsif arg =~ /\A-/
      args << arg
    elsif arg =~ /\AFAILURES=(.*)\z/
      self.failures_path = $1
    elsif arg == 'd'
      changed_only = true
    elsif arg == 'f'
      # failures_path could still be overridden -- delay evaluation of this.
      args << lambda do
        if File.exist?(failures_path)
          if failures.empty?
            STDERR.puts "No specs failed!"
            []
          else
            failures.flat_map { |f| ['-e', f] }
          end
        else
          warn "no fail file - ignoring 'f' argument"
          []
        end
      end
      using_filters = true
    elsif arg =~ /\A\d+\z/
      i = Integer(arg)
      if (failure = failures[i - 1])
        args << '-e' << failure
        @update_failures = false
        using_filters = true
      else
        @error = "invalid failure: #{i} for (1..#{failures.size})"
      end
    else
      args << '-e' << arg.gsub(/[$]/, '\\\\\\0')
    end
  end

  expanded = []
  args.each do |arg|
    if arg.respond_to?(:call)
      expanded.concat(arg.call)
    else
      expanded << arg
    end
  end

  # If rerunning failures, chop off explicit line numbers, as they are
  # additive, and filters are subtractive.
  if using_filters
    files.map! { |f| f.sub(/:\d+\z/, '') }
  end

  # Since we append our formatter as a file to run, rspec won't fall back to
  # using 'spec' by default. Add it explicitly here.
  files << 'spec' if files.empty?

  # Filter files only to those changed if 'd' is present.
  if changed_only
    files = changed_paths(files)
  end

  # If we selected individual failures to rerun, don't give the files to
  # rspec, as those files will be run in their entirety.
  @generated_args = expanded
  @generated_args.concat(files)
end
rspec_option_that_requires_an_argument?(arg) click to toggle source
# File lib/respec/app.rb, line 184
def rspec_option_that_requires_an_argument?(arg)
  RSPEC_OPTIONS_THAT_REQUIRE_AN_ARGUMENT.include?(arg)
end