Last active
January 3, 2016 14:09
-
-
Save ylqjk/8474345 to your computer and use it in GitHub Desktop.
LazyCLI.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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