Skip to content

Commit

Permalink
Removed optparser
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheeguerin committed Jun 9, 2015
1 parent 62099bc commit c8231cf
Show file tree
Hide file tree
Showing 16 changed files with 270 additions and 203 deletions.
10 changes: 8 additions & 2 deletions examples/simple.rb
Expand Up @@ -9,12 +9,18 @@ class SimpleCommand < Clin::Command
option :echo, 'Echo some text'
general_option Clin::HelpOptions

description 'Simple command that print stuff!'

def run
puts @params[:message]
puts @params[:echo]
end
end

# Run example:
# SimpleCommand.parse('display "My Message" --echo SOME').run
# SimpleCommand.parse('').run
# SimpleCommand.parse('display "My Message" -e SOME').run
begin
SimpleCommand.parse('').run
rescue Clin::HelpError => e
puts e
end
1 change: 0 additions & 1 deletion lib/clin.rb
@@ -1,6 +1,5 @@
require 'active_support'
require 'active_support/core_ext'
require 'optparse'
require 'readline'
require 'clin/version'

Expand Down
4 changes: 2 additions & 2 deletions lib/clin/argument.rb
Expand Up @@ -63,7 +63,7 @@ def parse(argv)
def ensure_fixed(args)
[*args].each do |arg|
next if arg == @name
fail Clin::FixedArgumentError, @name, arg
fail Clin::RequiredArgumentError, @name, arg
end
end

Expand All @@ -77,7 +77,7 @@ def handle_empty
if @variable
fail Clin::MissingArgumentError, @name
else
fail Clin::FixedArgumentError, @name
fail Clin::RequiredArgumentError, @name
end
end

Expand Down
27 changes: 9 additions & 18 deletions lib/clin/command_mixin/core.rb
Expand Up @@ -121,24 +121,6 @@ def priority
@_default_priority + @_priority
end

# Build the Option Parser object
# Used to parse the option
# Useful for regenerating the help as well.
def option_parser(out = {})
OptionParser.new do |opts|
opts.banner = banner
opts.separator ''
opts.separator 'Options:'
register_options(opts, out)
dispatch_doc(opts)
unless @description.blank?
opts.separator "\nDescription:"
opts.separator @description
end
opts.separator ''
end
end

def default_commands
subcommands.sort_by(&:priority).reverse
end
Expand All @@ -148,5 +130,14 @@ def default_commands
def subcommands
subclasses.reject(&:abstract?)
end

def help
out = ''
out << banner << "\n\n"
out << "Options: \n"
out << option_help
out << "Description:\n#{description}\n" unless description.blank?
out << "\n"
end
end
end
48 changes: 27 additions & 21 deletions lib/clin/command_mixin/options.rb
Expand Up @@ -22,22 +22,6 @@ module ClassMethods # :nodoc:
attr_accessor :options
attr_accessor :general_options

# Add an option
# @param args list of arguments.
# * First argument must be the name if no block is given.
# It will set automatically read the value into the hash with +name+ as key
# * The remaining arguments are OptionsParser#on arguments
# ```
# option :require, '-r', '--require [LIBRARY]', 'Require the library'
# option '-h', '--helper', 'Show the help' do
# puts opts
# exit
# end
# ```
def opt_option(*args, &block)
add_option Clin::Option.new(*args, &block)
end

# Add an option.
# Helper method that just create a new Clin::Option with the argument then call add_option
# ```
Expand Down Expand Up @@ -105,16 +89,17 @@ def remove_general_option(option_cls)

# To be called inside OptionParser block
# Extract the option in the command line using the OptionParser and map it to the out map.
# @param opts [OptionParser]
# @param out [Hash] Where the options shall be extracted
def register_options(opts, out)
# @return [Hash] Where the options shall be extracted
def option_defaults
out = {}
@options.each do |option|
option.register(opts, out)
option.load_default(out)
end

@general_options.each do |_cls, option|
option.class.register_options(opts, out)
out.merge! option.class.option_defaults
end
out
end

# Call #execute on each of the general options.
Expand All @@ -130,5 +115,26 @@ def execute_general_options(options)
gopts.execute(options)
end
end

def find(value)
find_by(name: value)
end

def find_by(hash)
key, value = hash.first
self.options.select { |x| x.send(key) == value }.first
end

def option_help
out = ''
options.each do |option|
out << " #{option.banner}\n"
end

general_options.each do |cls, _|
out << cls.option_help
end
"#{out}\n"
end
end
end
158 changes: 105 additions & 53 deletions lib/clin/command_parser.rb
Expand Up @@ -11,75 +11,121 @@ def initialize(command_cls, argv = ARGV, fallback_help: true)
argv = Shellwords.split(argv) if argv.is_a? String
@argv = argv
@fallback_help = fallback_help
@options = {}
@arguments = {}
@errors = []
@skipped_options = []
end

def params
out = @options.merge(@arguments)
out[:skipped_options] = @skipped_options if @command.skip_options?
out
end

def init_defaults
@options = @command.option_defaults
end

# Parse the command line.
def parse
argv = @argv.clone
error = nil
options = {}
begin
options.merge! parse_options(argv)
rescue Clin::OptionError => e
error = e
end
begin
options.merge! parse_arguments(argv)
rescue Clin::ArgumentError => e
raise e unless @fallback_help
error = e
end
init_defaults
parse_options(argv)
parse_arguments(argv)

return redispatch(options) if @command.redispatch?
obj = @command.new(options)
handle_error(error)
return redispatch(params) if @command.redispatch?
obj = @command.new(params)
validate!
obj
end

# Parse the options in the argv.
# @return [Array] the list of argv that are not options(positional arguments)
LONG_OPTION_REGEX = /\A(?<name>--[^=]*)(?:=(?<value>.*))?/m
SHORT_OPTION_REGEX = /\A(?<name>-.)(?<value>(=).*|.+)?/m

def parse_options(argv)
out = {}
parser = @command.option_parser(out)
skipped = skipped_options
argv.reject! { |x| skipped.include?(x) }
begin
parser.parse!(argv)
rescue OptionParser::InvalidOption => e
raise Clin::OptionError, e.to_s
arguments = []
while (arg = argv.shift)
case arg
when LONG_OPTION_REGEX
name = Regexp.last_match[:name]
value = Regexp.last_match[:value]
option = @command.find_by(long: name)
parse_option(option, name, value, argv, false)
when SHORT_OPTION_REGEX
name = Regexp.last_match[:name]
value = Regexp.last_match[:value]
option = @command.find_by(short: name)
parse_option(option, name, value, argv, true)
else
arguments << arg
end
end
out[:skipped_options] = skipped if @command.skip_options?
out
argv.replace(arguments)
@options
end

# Get the options that have been skipped by options_first!
def skipped_options
return [] unless @command.skip_options?
argv = @argv.dup
skipped = []
parser = @command.option_parser
loop do
begin
parser.parse!(argv)
break
rescue OptionParser::InvalidOption => e
skipped << e.to_s.sub(/invalid option:\s+/, '')
next if argv.empty? || argv.first.start_with?('-')
skipped << argv.shift
def parse_option(option, name, value, argv, short)
if option.nil?
handle_unknown_option(name, value, argv)
return
end
if option.flag?
if value.nil?
option.trigger(self, @options, true)
elsif not short
add_error Clin::OptionUnexpectedArgumentError.new(option, value)
else
option.trigger(self, @options, true)
# -abc multiple flag options
value.each_char do |s|
option = @command.find_by(short: "-#{s}")
if option && !option.flag?
message = "Cannot combine short options that expect argument: #{option}"
add_error Clin::OptionError.new(message, option)
return
end
parse_option(option, "-#{s}", nil, [], true)
end
end
return
end
if value.nil? && argv.any? && !argv.first.start_with?('-')
value = argv.shift
end
if value.nil? && !option.argument_optional?
add_error Clin::MissingOptionArgumentError.new(option)
end
value ||= true
option.trigger(self, @options, value)
end

skipped
def handle_unknown_option(name, value, argv)
unless @command.skip_options?
add_error Clin::UnknownOptionError.new(name)
return
end
if value.nil? && argv.any? && !argv.first.start_with?('-')
value = argv.shift
end
@skipped_options += [name, value]
end

def add_error(err)
@errors << err
end

# Parse the argument. The options must have been strip out first.
def parse_arguments(argv)
out = {}
@command.args.each do |arg|
value, argv = arg.parse(argv)
out[arg.name.to_sym] = value

@arguments[arg.name.to_sym] = value
end
out.delete_if { |_, v| v.nil? }
@arguments.delete_if { |_, v| v.nil? }
@arguments
rescue Clin::ArgumentError => e
add_error e
end

# Method called after the argument have been parsed and before creating the command
Expand All @@ -91,7 +137,7 @@ def redispatch(params)
begin
dispatcher.parse(redispatch_arguments(params))
rescue Clin::HelpError
raise Clin::HelpError, @command.option_parser
raise Clin::HelpError, @command
end
end

Expand All @@ -105,11 +151,17 @@ def redispatch_arguments(params)
args
end

# Guard that check if there was an error and fail HelpError if there was
# @raise [Clin::HelpError]
def handle_error(error)
return unless error
fail Clin::HelpError, @command.option_parser if @fallback_help
fail error
def errors
@errors
end

def valid?
@errors.empty?
end

def validate!
return if valid?
fail Clin::HelpError, @command if @fallback_help
fail @errors.sort_by { |e| e.class.severity }.last
end
end
1 change: 1 addition & 0 deletions lib/clin/common/help_options.rb
@@ -1,5 +1,6 @@
require 'clin'
require 'clin/general_option'

# Help option class
# Add the help option to you command
# ```
Expand Down

0 comments on commit c8231cf

Please sign in to comment.