module Maxitest::Testrbl

def all_test_files_in(folder)

def all_test_files_in(folder)
  Dir[File.join(folder, "{**/,}*_{test,spec}.rb")].uniq
end

def block_start_from_line(line)

def block_start_from_line(line)
  if line =~ /^(\s*).* do( \|.*\|)?$/
    [$1, nil]
  end
end

def changed_files

def changed_files
  changed_files = sh("git status -s").split("\n").map { |l| l.strip.split(/\s+/, 2)[1] }
  if changed_files.empty?
    # user wants to test last commit and not current diff
    changed_files = sh("git show --name-only").split("\n\n").last.split("\n")
  end
  # we only want test files that were added or changed (not deleted)
  changed_files.select { |f| f =~ /_(test|spec)\.rb$/ && File.exist?(f) }
end

def filter_duplicate_final(patterns)

only keep 1 pattern that stops matching via $
def filter_duplicate_final(patterns)
  found_final = 0
  patterns.reject { |p| p.end_with?("$") and (found_final += 1) > 1 }
end

def line_pattern_option(file, line)

overwritten by maxitest to just return line
def line_pattern_option(file, line)
  [file, "-n", "/#{pattern_from_file(File.readlines(file), line)}/"]
end

def localize(file)

fix 1.9 not being able to load local files
def localize(file)
  file =~ /^[-a-z\d_]/ ? "./#{file}" : file
end

def partition_argv(argv)

def partition_argv(argv)
  next_is_option = false
  argv.partition do |arg|
    if next_is_option
      next_is_option = false
    else
      if arg =~ /^-.$/ or  arg =~ /^--/ # single letter option followed by argument like -I test or long options like --verbose
        next_is_option = true if OPTION_WITH_ARGUMENT.include?(arg)
        false
      elsif arg =~ /^-/ # multi letter option like -Itest
        false
      else
        true
      end
    end
  end
end

def partition_options(options)

def partition_options(options)
  next_is_before = false
  options.partition do |option|
    if next_is_before
      next_is_before = false
      true
    else
      if option =~ /^-(r|I)/
        next_is_before = (option.size == 2)
        true
      else
        false
      end
    end
  end
end

def pattern_from_file(lines, line)

usable via external tools like zeus
def pattern_from_file(lines, line)
  possible_lines = lines[0..(line.to_i-1)].reverse
  found = possible_lines.map { |line| test_pattern_from_line(line) || block_start_from_line(line) }.compact
  # pattern and the groups it is nested under (like describe - describe - it)
  last_spaces = " " * 100
  patterns = found.select do |spaces, name|
    last_spaces = spaces if spaces.size < last_spaces.size
  end.map(&:last).compact
  return filter_duplicate_final(patterns).reverse.join(".*") if found.size > 0
  raise "no test found before line #{line}"
end

def ruby

def ruby
  if File.file?("Gemfile")
    ["ruby", "-rbundler/setup"] # faster then bundle exec ruby
  else
    ["ruby"]
  end
end

def run(command)

def run(command)
  puts command.join(" ")
  STDOUT.flush # if exec fails horribly we at least see some output
  Kernel.exec *command
end

def run_from_cli(argv)

def run_from_cli(argv)
  files, options = partition_argv(argv)
  files.concat(changed_files) if options.delete("--changed")
  files = ["test"] if files.empty?
  files = files.map { |f| localize(f) }
  load_options, options = partition_options(options)
  if files.size == 1 and files.first =~ /^(\S+):(\d+)$/
    file = $1
    line = $2
    run(ruby + load_options + line_pattern_option(file, line) + options)
  else
    if files.size == 1 and File.file?(files.first)
      run(ruby + load_options + files + options)
    elsif options.none? { |arg| arg =~ /^-n/ }
      seed = if seed = options.index("--seed")
        ["--"] + options.slice!(seed, 2)
      else
        []
      end
      files = files.map { |f| File.directory?(f) ? all_test_files_in(f) : f }.flatten
      run(ruby + load_options + files.map { |f| "-r#{f}" } + options + ["-e", ""] + seed)
    else # pass though
      # no bundle exec: projects with mini and unit-test do not run well via bundle exec testrb
      run ["testrb"] + argv
    end
  end
end

def sh(command)

def sh(command)
  result = `#{command}`
  raise "Failed: #{command} -> #{result}" unless $?.success?
  result
end

def test_pattern_from_line(line)

def test_pattern_from_line(line)
  PATTERNS.each do |r|
    next unless line =~ r
    whitespace, method, test_name = $1, $2, $4
    return [whitespace, test_pattern_from_match(method, test_name)]
  end
  nil
end

def test_pattern_from_match(method, test_name)

def test_pattern_from_match(method, test_name)
  regex = Regexp.escape(test_name).gsub("\\ "," ").gsub(INTERPOLATION, ".*")
  regex = if method == "test"
    # test "xxx -_ yyy"
    # test-unit:     "test: xxx -_ yyy"
    # activesupport: "test_xxx_-__yyy"
    "^test(: |_)#{regex.gsub(" ", ".")}$"
  elsif method == "describe" || (method == "context" && !via_shoulda?)
    "#{regex}(::)?"
  elsif method == "should" && via_shoulda?
    optional_test_name = "(?:\(.*\))?"
    "#{method} #{regex}\. #{optional_test_name}$"
  elsif ["it", "should"].include?(method) # minitest aliases for shoulda
    "#test_\\d+_#{regex}$"
  else
    regex
  end
  regex.gsub("'", ".")
end

def via_shoulda?

def via_shoulda?
  return @via_shoulda if defined?(@via_shoulda)
  @via_shoulda = !File.exist?("Gemfile.lock") || File.read("Gemfile.lock").include?(" shoulda-context ")
end