Use OptionParser (closes #7).

* Supports -h and --help options.
* Rescue OptionParser::ParseErrors and re-raise them as CLI::Errors.
* Moved CLIParser::EXIT_FAILURE, EXIT_SUCCESS and Error into CLI.
* Removed CLIParser.
This commit is contained in:
Postmodern 2013-05-14 18:25:16 -07:00
parent f730590d4d
commit b4108f17ea
4 changed files with 81 additions and 152 deletions

View file

@ -114,7 +114,6 @@ require 'mutant/runner'
require 'mutant/runner/config' require 'mutant/runner/config'
require 'mutant/runner/subject' require 'mutant/runner/subject'
require 'mutant/runner/mutation' require 'mutant/runner/mutation'
require 'mutant/cli_parser'
require 'mutant/cli' require 'mutant/cli'
require 'mutant/cli/classifier' require 'mutant/cli/classifier'
require 'mutant/cli/classifier/namespace' require 'mutant/cli/classifier/namespace'

View file

@ -1,9 +1,17 @@
require 'optparse'
module Mutant module Mutant
# Comandline parser # Comandline parser
class CLI < CLIParser class CLI
include Adamantium::Flat, Equalizer.new(:config) include Adamantium::Flat, Equalizer.new(:config)
# Error raised when CLI argv is inalid
Error = Class.new(RuntimeError)
EXIT_FAILURE = 1
EXIT_SUCCESS = 0
# Run cli with arguments # Run cli with arguments
# #
# @param [Array<String>] arguments # @param [Array<String>] arguments
@ -13,8 +21,8 @@ module Mutant
# #
# @api private # @api private
# #
def self.run(*arguments) def self.run(arguments)
config = new(*arguments).config config = new(arguments).config
runner = Runner::Config.run(config) runner = Runner::Config.run(config)
runner.success? ? EXIT_SUCCESS : EXIT_FAILURE runner.success? ? EXIT_SUCCESS : EXIT_FAILURE
rescue Error => exception rescue Error => exception
@ -22,15 +30,6 @@ module Mutant
EXIT_FAILURE EXIT_FAILURE
end end
OPTIONS = {
'--code' => [:add_filter, Mutation::Filter::Code ],
'--debug' => [:set_debug ],
'-d' => [:set_debug ],
'--rspec-unit' => [:set_strategy, Strategy::Rspec::Unit ],
'--rspec-full' => [:set_strategy, Strategy::Rspec::Full ],
'--rspec-dm2' => [:set_strategy, Strategy::Rspec::DM2 ]
}.freeze
# Initialize objecct # Initialize objecct
# #
# @param [Array<String>] # @param [Array<String>]
@ -39,9 +38,10 @@ module Mutant
# #
# @api private # @api private
# #
def initialize(arguments) def initialize(arguments=[])
@filters, @matchers = [], [] @filters, @matchers = [], []
super(arguments)
parse(arguments)
strategy strategy
matcher matcher
end end
@ -135,57 +135,18 @@ module Mutant
end end
memoize :matcher memoize :matcher
# Move processed argument by amount
#
# @param [Fixnum] amount
# the amount of arguments to be consumed
#
# @return [undefined]
#
# @api private
#
def consume(amount)
@index += amount
end
# Process matcher argument
#
# @return [undefined]
#
# @api private
#
def dispatch_matcher
argument = current_argument
consume(1)
matcher = Classifier.build(argument)
@matchers << matcher if matcher
end
# Process option argument
#
# @return [Undefined]
#
# @api private
#
def dispatch_option
argument = current_argument
arguments = *OPTIONS.fetch(argument) do
raise Error, "Unknown option: #{argument.inspect}"
end
send(*arguments)
end
# Add mutation filter # Add mutation filter
# #
# @param [Class<Mutant::Filter>] klass # @param [Class<Mutant::Filter>] klass
# #
# @param [String] filter
#
# @return [undefined] # @return [undefined]
# #
# @api private # @api private
# #
def add_filter(klass) def add_filter(klass,filter)
@filters << klass.new(current_option_value) @filters << klass.new(filter)
consume(2)
end end
# Set debug mode # Set debug mode
@ -195,7 +156,6 @@ module Mutant
# @return [undefined] # @return [undefined]
# #
def set_debug def set_debug
consume(1)
@debug = true @debug = true
end end
@ -208,8 +168,65 @@ module Mutant
# @return [undefined] # @return [undefined]
# #
def set_strategy(strategy) def set_strategy(strategy)
consume(1)
@strategy = strategy @strategy = strategy
end end
# Parses the command-line options.
#
# @param [Array<String>] arguments
# Command-line options and arguments to be parsed.
#
# @raise [Error]
# An error occurred while parsing the options.
#
# @api private
#
def parse(arguments)
opts = OptionParser.new do |opts|
opts.banner = 'usage: mutant STRATEGY [options] MATCHERS ...'
opts.separator ''
opts.separator 'Strategies:'
opts.on('--rspec-unit','executes all specs under ./spec/unit') do
set_strategy Strategy::Rspec::Unit
end
opts.on('--rspec-full','executes all specs under ./spec') do
set_strategy Strategy::Rspec::Full
end
opts.on('--rspec-dm2','executes spec/unit/namespace/class/method_spec.rb') do
set_strategy Strategy::Rspec::DM2
end
opts.separator ''
opts.separator 'Options:'
opts.on('--code FILTER','Adds a code filter') do |filter|
add_filter Mutation::Filter::Code, filter
end
opts.on('-d','--debug','Enable debugging output') do
set_debug
end
opts.on_tail('-h','--help','Show this message') do
puts opts
exit
end
end
matchers = begin
opts.parse!(arguments)
rescue OptionParser::ParseError => e
raise(Error,e.message,e.backtrace)
end
matchers.each do |pattern|
matcher = Classifier.build(pattern)
@matchers << matcher if matcher
end
end
end end
end end

View file

@ -1,87 +0,0 @@
module Mutant
# Base class for cli parsers
#
# I hate base classes for reusable functionallity.
# But could not come up with a nice composition/instantiation
# solution.
#
class CLIParser
# Error raised when CLI argv is inalid
Error = Class.new(RuntimeError)
EXIT_FAILURE = 1
EXIT_SUCCESS = 0
OPTION_PATTERN = %r(\A-(?:-)?[a-zA-Z0-9\-]+\z).freeze
# Initialize CLI
#
# @param [Array<String>] arguments
#
# @return [undefined]
#
# @api private
#
def initialize(arguments)
@arguments, @index = arguments, 0
while @index < @arguments.length
dispatch
end
end
private
# Return option for argument with index
#
# @param [Fixnum] index
#
# @return [String]
#
# @api private
#
def option(index)
@arguments.fetch(index+1)
end
# Return current argument
#
# @return [String]
#
# @api private
#
def current_argument
@arguments.fetch(@index)
end
# Return current option value
#
# @return [String]
#
# @raise [CLI::Error]
# raises error when option is missing
#
# @api private
#
def current_option_value
@arguments.fetch(@index+1)
rescue IndexError
raise Error, "#{current_argument.inspect} is missing an argument"
end
# Process current argument
#
# @return [undefined]
#
# @api private
#
def dispatch
if OPTION_PATTERN =~ current_argument
dispatch_option
else
dispatch_matcher
end
end
end
end

View file

@ -2,7 +2,7 @@ require 'spec_helper'
shared_examples_for 'an invalid cli run' do shared_examples_for 'an invalid cli run' do
it 'should raise error' do it 'should raise error' do
expect { subject }.to raise_error(Mutant::CLIParser::Error, expected_message) expect { subject }.to raise_error(Mutant::CLI::Error, expected_message)
end end
end end
@ -35,7 +35,7 @@ describe Mutant::CLI, '.new' do
context 'with unknown flag' do context 'with unknown flag' do
let(:arguments) { %w(--invalid) } let(:arguments) { %w(--invalid) }
let(:expected_message) { 'Unknown option: "--invalid"' } let(:expected_message) { 'invalid option: --invalid' }
it_should_behave_like 'an invalid cli run' it_should_behave_like 'an invalid cli run'
end end
@ -43,7 +43,7 @@ describe Mutant::CLI, '.new' do
context 'with unknown option' do context 'with unknown option' do
let(:arguments) { %w(--invalid Foo) } let(:arguments) { %w(--invalid Foo) }
let(:expected_message) { 'Unknown option: "--invalid"' } let(:expected_message) { 'invalid option: --invalid' }
it_should_behave_like 'an invalid cli run' it_should_behave_like 'an invalid cli run'
end end
@ -64,7 +64,7 @@ describe Mutant::CLI, '.new' do
context 'with code filter and missing argument' do context 'with code filter and missing argument' do
let(:arguments) { %w(--rspec-unit --code) } let(:arguments) { %w(--rspec-unit --code) }
let(:expected_message) { '"--code" is missing an argument' } let(:expected_message) { 'missing argument: --code' }
it_should_behave_like 'an invalid cli run' it_should_behave_like 'an invalid cli run'
end end