Skip to content

Instantly share code, notes, and snippets.

@ylqjk
Last active January 3, 2016 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ylqjk/8474345 to your computer and use it in GitHub Desktop.
Save ylqjk/8474345 to your computer and use it in GitHub Desktop.
LazyCLI.rb
require './lazy_cli.rb'
class Example
include LazyCLI
default_cmd 'descript default'
cmd :foo, 'descript foo'
def foo
puts 'ok, foo'
end
cmd :bar, [Integer, String], 'descript piyo'
def bar(num, str = nil, *arr)
puts 'ok, bar'
puts "num = #{num}"
puts "str = #{str}" if str
puts "arr = #{arr}" if arr
end
cmd :baz, 'descript baz',
'a/abc' => 'abc option',
'd/def:' => 'def option',
'g/' => 'g option',
'jkl:' => [Integer, 'jkl option']
def baz(val)
puts "val = #{val}"
puts "options = #{options.inspect}"
end
end
Example.run
module LazyCLI
class LazyOptionParser
require 'forwardable'
require 'optparse'
extend Forwardable
attr_reader :size
def_delegators :@opt, :accept, :banner, :banner=, :default_argv,
:default_argv=, :environment, :getopts, :help, :to_s, :load,
:program_name, :program_name=, :reject, :release, :release=,
:separator, :summarize, :summary_indent, :summary_indent=,
:summary_width, :summary_width=, :to_a, :ver, :version,
:version=
def initialize(*args, &block)
@opt = OptionParser.new(*args, &block)
self.summary_indent = ' ' * 3
self.summary_width = summary_indent.size
@size = 0
end
def empty?
@size.zero?
end
%w(on on_head on_tail).each do |name|
define_method(name) do |option, *args, &block|
@size += 1
return @opt.send(name, option, *args, &block) if option.to_s[0] == '-'
m = option.to_s.match(/^(?:([a-z])\/)?([\w_]+)?(\:([\w_\[\]]+)?)?$/i)
raise ArgumentError, "invalid option format: option = #{option}" unless m && (m[1] || m[2])
short_option = m[1]
long_option = m[2]
variable_name = m[4] || 'value' if m[3]
variable_name_with_space = " #{variable_name}" if variable_name
key = (long_option || short_option).gsub('-', '_').to_sym
unless args.last.kind_of?(String)
last_arg = args.last
case last_arg
when Array then args << "#{m[4] || key} is #{last_arg.map(&:to_s).map(&:inspect).join(' or ')}"
end
end
args.unshift("--#{long_option}#{variable_name_with_space}") if long_option
args.unshift("-#{short_option}#{variable_name_with_space}") if short_option
new_summary_width = summary_indent.size
new_summary_width += ['--'.sirze, "-#{short_option}".size].max
new_summary_width += "--#{long_option}".size if long_option
new_summary_width += variable_name.size if variable_name
new_summary_width += ', '.size
self.summary_width = new_summary_width if summary_width < new_summary_width
@opt.send(name, *args) do |value|
@results[key] = value
end
end
end
%w(order order! parse parse! permute permute!).each do |name|
define_method(name) do |*args, &block|
@results = {}
@opt.send(name, *args, &block)
@results
end
end
end
def self.included(base)
class << base
attr_accessor :program_name, :default_command, :commands
def run(*args)
run_(*args)
rescue ArgumentError, OptionParser::ParseError => e
unless e.to_s.empty?
puts e
puts
end
puts opt
exit -1
end
def run_(args = ARGV.dup)
self.program_name ||= File.basename($0, '.*')
self.commands ||= {}
opt = create_optparser(default_command)
command_names = commands.keys
if command_names.empty?
opt.banner = "Usage: #{program_name}"
opt.banner << ' [options]' unless opt.empty?
else
opt.banner = "Usage: #{program_name}"
opt.banner << ' [global_options]' unless opt.empty?
opt.banner << ' <command> [options]'
opt.separator('')
opt.separator('Available subcommands are:')
command_list_format = "#{opt.summary_indent}%-#{command_names.map(&:size).max+opt.summary_indent.size}s%s"
command_names.each do |name|
next if commands[name][:description].nil?
opt.separator(sprintf(command_list_format, name, commands[name][:description]))
end
end
ins = new(opt.order!(args))
return if command_names.empty?
command_name = args.shift
raise ArgumentError, '' if command_name.nil? || command_name.empty?
unless commands.include?(command_name) && (ins.method(command_name) rescue false)
raise ArgumentError, "#{program_name}: '#{command_name}' is not a #{program_name} command."
end
command = commands[command_name]
opt = create_optparser(command)
opt.banner = "Usage: #{program_name} #{command_name}"
opt.banner << ' [options]' unless opt.empty?
ins.method(command_name).parameters.each do |p|
arg_name = p.last.to_s
case p.first
when :req then opt.banner << " <#{arg_name}>"
when :opt then opt.banner << " [#{arg_name}]"
when :rest then opt.banner << " [#{arg_name}...]"
end
end
ins.options = opt.parse!(args)
command[:types].each_with_index do |type, i|
break if args.size <= i
args[i] = parse_value(args[i], type)
end
ins.send(command_name, *args)
end
private
def default_cmd(*args)
config = { description: nil, options: {} }
config[:description] = args.shift if args.first.kind_of?(String)
config[:options] = args.shift if args.first.kind_of?(Hash)
raise ArgumentError, "invalid rest arguments: rest = #{args}" if args.any?
self.default_command = config
end
def cmd(name, *args)
config = { types: [], description: nil, options: {} }
config[:types] = args.shift if args.first.kind_of?(Array)
config[:description] = args.shift if args.first.kind_of?(String)
config[:options] = args.shift if args.first.kind_of?(Hash)
raise ArgumentError, "invalid rest arguments: rest = #{args}" if args.any?
self.commands ||= {}
self.commands[name.to_s] = config
end
def create_optparser(command)
command ||= {}
opt = LazyCLI::LazyOptionParser.new
if command[:description] && !command[:description].empty?
opt.separator('')
opt.separator(command[:description])
end
if command[:options]
command[:options].each do |key, val|
opt.on(key.to_s, *(val.kind_of?(Array) ? val : [val]))
end
end
opt
end
def parse_value(value, type)
if type.kind_of?(Regexp)
return value if type =~ value
elsif type == String
return value unless value.empty?
elsif type == Integer
return Integer(value)
elsif type == Float
return Float(value)
elsif type == Numeric
return Integer(value) rescue Float(value)
elsif type == TrueClass || type == FalseClass
case value
when 'yes', 'true', '+' then return true
when 'no', 'false', '-' then return false
end
elsif type == Array
return value.split(',')
end
raise ArgumentError, "invalid argument: #{value.inspect}"
end
end
end
attr_accessor :global_options, :options
def initialize(global_options)
self.global_options = global_options || {}
self.options = {}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment