opts-mething redesign. completion improved. example: args-command for demonstrate arguments and options.
This commit is contained in:
parent
5b5b620601
commit
d3930c00bc
|
@ -1,25 +1,47 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require 'pathname'
|
||||||
|
$:.unshift Pathname.new(__FILE__).dirname.dirname.join('lib').to_s
|
||||||
require 'dencli'
|
require 'dencli'
|
||||||
|
|
||||||
cli = DenCli.new 'example', "This is an example for generate a DenCli-API"
|
cli = DenCli.new 'example', "This is an example for generate a DenCli-API"
|
||||||
cli.cmd( :example, "I have an example command") { STDERR.puts "This is an example" }
|
cli.cmd( :example, "I have an example command") { STDERR.puts "This is an example" }
|
||||||
cli.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| STDERR.puts cli.help(*args) }
|
cli.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| STDERR.puts cli.help(*args) }
|
||||||
|
|
||||||
|
cli.cmd( :args, "Expects and prints given arguments", &lambda {|a, b, c:, d:, e:|
|
||||||
|
p a: a, b: b, c: c, d: d, e: e
|
||||||
|
}).
|
||||||
|
opt( :c, '-c=ForC', "Option c").
|
||||||
|
opt( :d, '-d=ForD', "Option d", default: "something").
|
||||||
|
opt( :e, '-e', "Toggle e", default: false)
|
||||||
|
|
||||||
cli.sub( :more, "Sub-Commands are also possible with a new cli") do |sub|
|
cli.sub( :more, "Sub-Commands are also possible with a new cli") do |sub|
|
||||||
|
sub.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| STDERR.puts sub.help(*args) }
|
||||||
sub.cmd( :help, "") {|*args| STDERR.puts cli.help( 'more', *args) }
|
sub.cmd( :help, "") {|*args| STDERR.puts cli.help( 'more', *args) }
|
||||||
sub.cmd( :example, "Here is an example, too") { STDERR.puts "This is an other example" }
|
sub.cmd( :example, "Here is an example, too") { STDERR.puts "This is an other example" }
|
||||||
sub.cmd( :foo, "BAR") { STDERR.puts "FOO bar"}
|
sub.cmd( :foo, "BAR") { STDERR.puts "FOO bar"}
|
||||||
|
|
||||||
|
sub.cmd( :args, "Expects and prints given arguments", &lambda {|a, b=1, c:, d: 5, e:|
|
||||||
|
p a: a, b: b, c: c, d: d, e: e
|
||||||
|
}).
|
||||||
|
opt( :c, '-c=ForC', "Option c").
|
||||||
|
opt( :d, '-d=ForD', "Option d (implicit default)").
|
||||||
|
opt( :e, '-e', "Toggle e")
|
||||||
|
|
||||||
sub.sub( :deeper, "You want to have Sub-Sub-Commands?") do |sub2|
|
sub.sub( :deeper, "You want to have Sub-Sub-Commands?") do |sub2|
|
||||||
sub2.cmd( :help, "") {|*args| STDERR.puts cli.help( 'more', 'deeper', *args) }
|
sub2.cmd( :help, "", aliases: [nil, '-h', '--help'], &lambda {|*args| STDERR.puts sub2.help(*args) })
|
||||||
sub2.cmd( :last, "The last example") { STDERR.puts "The last example" }
|
sub2.cmd( :last, "The last example", &lambda { STDERR.puts "The last example" })
|
||||||
|
|
||||||
sub2.sub( :'sub-commands', "Endless Sub-Sub- ...") do |sub3|
|
sub2.sub( :'sub-commands', "Endless Sub-Sub- ...") do |sub3|
|
||||||
sub2.cmd( :help, "") {|*args| STDERR.puts cli.help( 'more', 'deeper', 'sub-commands', *args) }
|
sub3.cmd( :help, "") {|*args| STDERR.puts sub3.help( sub3, *args) }
|
||||||
sub3.cmd( :hehe, "The real last example", min: 2) { STDERR.puts "Trust me!" }
|
sub3.cmd( :hehe, "The real last example", min: 2) { STDERR.puts "Trust me!" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
cli.call *ARGV
|
cli.call *ARGV
|
||||||
|
rescue DenCli::UsageError
|
||||||
|
STDERR.puts $!
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
require_relative '../dencli'
|
require_relative '../dencli'
|
||||||
|
|
||||||
|
|
||||||
class DenCli::CMD
|
class DenCli::CMD
|
||||||
attr_reader :parent, :name, :description, :exe, :completion, :options
|
attr_reader :parent, :name, :description, :exe, :completion, :options
|
||||||
|
|
||||||
def initialize parent, name, description, exe
|
def initialize parent, name, description, exe
|
||||||
raise "Proc expected, instead of: #{exe.inspect}" unless Proc === exe
|
raise "Proc expected, instead of: #{exe.inspect}" unless Proc === exe
|
||||||
@parent, @name, @description, @exe = parent, name, description, lambda( &exe)
|
@parent, @name, @description, @exe = parent, name, description, lambda( &exe)
|
||||||
@options = []
|
@options = {}
|
||||||
completion {|*a| [] }
|
completion {|*a| [] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -14,22 +15,32 @@ class DenCli::CMD
|
||||||
def full_cmd() _full_cmd [] end
|
def full_cmd() _full_cmd [] end
|
||||||
|
|
||||||
def parameters() @exe.parameters end
|
def parameters() @exe.parameters end
|
||||||
def required() @exe.parameters.select{|e|:req == e[0]}.map{|e|e[1]} end
|
def arguments_required() @exe.parameters.select {|e| :req == e[0] }.map {|e| e[1] } end
|
||||||
def additional() @exe.parameters.select{|e|:opt == e[0]}.map{|e|e[1]} end
|
alias required arguments_required
|
||||||
|
def arguments_additional() @exe.parameters.select {|e| :opt == e[0] }.map {|e| e[1] } end
|
||||||
|
alias additional arguments_additional
|
||||||
|
def arguments() @exe.parameters.select {|e| :req == e[0] or :opt == e[0] }.map {|e| e[1] } end
|
||||||
|
def options_required() @exe.parameters.select {|e| :keyreq == e[0] }.map {|e| e[1] } end
|
||||||
|
def options_additional() @exe.parameters.select {|e| :key == e[0] }.map {|e| e[1] } end
|
||||||
|
|
||||||
|
def help() "Usage: #{usage}\n#{description}\n#{options_help}" end
|
||||||
|
def complete( *pre, str) @completion.call *pre, str end
|
||||||
|
|
||||||
def call( *as)
|
def call( *as)
|
||||||
if @options.empty?
|
|
||||||
@exe.call *as
|
|
||||||
else
|
|
||||||
os = {}
|
os = {}
|
||||||
|
unless @options.empty?
|
||||||
|
# options like --abc | -x will be provided in os
|
||||||
options = OptionParser.new
|
options = OptionParser.new
|
||||||
options.banner = "#{full_cmd.join ' '}"
|
options.banner = "#{full_cmd.join ' '}"
|
||||||
@options.each do |(aname, aas, aos, aexe)|
|
# see also @options-array
|
||||||
os[aname] = aos[aname] if aos.has_key? :default
|
@options.each {|_, opt| opt.on options, os }
|
||||||
options.on( *aas) {|val| os[aname] = aexe[val] }
|
|
||||||
end
|
|
||||||
as = options.parse! as
|
as = options.parse! as
|
||||||
|
end
|
||||||
if @exe.lambda?
|
if @exe.lambda?
|
||||||
|
# The difference between a lambda and a Proc is, that Proc has anytime arity=-1.
|
||||||
|
# There will be no check if all arguments are given or some were missing or more than expected.
|
||||||
|
# lambda checks these arguments and has a arity.
|
||||||
|
# We will check it to provide useful errors.
|
||||||
pars = required
|
pars = required
|
||||||
if as.length < pars.length
|
if as.length < pars.length
|
||||||
raise DenCli::UsageError, "Missing parameter(s): #{pars[as.length..-1].join " "}"
|
raise DenCli::UsageError, "Missing parameter(s): #{pars[as.length..-1].join " "}"
|
||||||
|
@ -40,33 +51,127 @@ class DenCli::CMD
|
||||||
raise DenCli::UsageError, "Unused parameter(s): #{as[-pars.length..-1].shelljoin}"
|
raise DenCli::UsageError, "Unused parameter(s): #{as[-pars.length..-1].shelljoin}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
kr = @options.select {|_, o| o.required? and not os.has_key? o.name }
|
||||||
|
unless kr.empty?
|
||||||
|
raise DenCli::UsageError, "Missing argument(s): #{kr.map {|o| o.as.first }.join ', '}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@exe.call *as, **os
|
@exe.call *as, **os
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def usage
|
def usage
|
||||||
"#{parent.full_cmd.join ' '} #{name} "+
|
args =
|
||||||
@options.map{|(_,(o,_,_,_),_)|"[#{o}] "}.join( '')+
|
@options.map do |_, o|
|
||||||
|
s = "#{o.short}#{o.val ? ?= : ''}#{o.val}"; o.required? ? "#{s} " : "[#{s}] "
|
||||||
|
end
|
||||||
|
"#{full_cmd.join ' '} #{args.join ''}"+
|
||||||
(@exe.lambda? ? (
|
(@exe.lambda? ? (
|
||||||
required.join( " ")+
|
required.map{|s|"<#{s}>"}.join( " ")+
|
||||||
(additional.empty? ? "" : " [#{additional.join " "}]")
|
(additional.empty? ? "" : " [#{additional.map{|s|"<#{s}>"}.join " "}]")
|
||||||
) : '...')
|
) : '...')
|
||||||
end
|
end
|
||||||
|
|
||||||
def help
|
def options_help
|
||||||
"#{usage}\n#{description}"
|
sc, lc, dc = 0, 0, 0
|
||||||
|
@options.each do |_, o|
|
||||||
|
s = o.short&.length || 0
|
||||||
|
l = o.long&.length || 0
|
||||||
|
v = o.val&.length || 0
|
||||||
|
d = o.desc&.to_s&.length || 0
|
||||||
|
d += 3 + o.default.to_s.length if o.default?
|
||||||
|
if 0 == l
|
||||||
|
x = s + (0==v ? 0 : 1+v)
|
||||||
|
sc = x if sc < x
|
||||||
|
else
|
||||||
|
sc = s if sc < s
|
||||||
|
x = l + (0==v ? 0 : 1+v)
|
||||||
|
lc = x if lc < x
|
||||||
|
end
|
||||||
|
dc = d if dc < d
|
||||||
|
end
|
||||||
|
format = " %-#{sc}s%s %-#{lc}s %s"
|
||||||
|
@options.map {|_, o|
|
||||||
|
s, l, v, y = o.short, o.long, o.val, ','
|
||||||
|
if l.nil?
|
||||||
|
s += "=#{v}" if v
|
||||||
|
y = ' '
|
||||||
|
elsif s.nil?
|
||||||
|
l += "=#{v}" if v
|
||||||
|
y = ' '
|
||||||
|
end
|
||||||
|
d = o.desc || ''
|
||||||
|
d += " (#{o.default})" if o.default?
|
||||||
|
format % [ s, y, l, d ]
|
||||||
|
}.join "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
def complete( *pre, str) @completion.call *pre, str end
|
|
||||||
|
|
||||||
def completion &exe
|
def completion &exe
|
||||||
@completion = exe
|
@completion = exe
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def opt name, *as, **os, &exe
|
class Opt
|
||||||
@options.push [name.to_s.to_sym, as, os, exe || lambda{|val|val} ]
|
attr_reader :name, :long, :short, :val, :desc, :os, :conv, :req
|
||||||
|
def required?() @req end
|
||||||
|
def default?() NilClass != @default end
|
||||||
|
def default() NilClass == @default ? nil : @default end
|
||||||
|
|
||||||
|
def initialize cmd, name, opt, *alts, desc, default: NilClass, **os, &conv
|
||||||
|
long = short = val = nil
|
||||||
|
case opt
|
||||||
|
when /\A(--[^=-]+)=(.+)\z/
|
||||||
|
long, val = $1, $2
|
||||||
|
when /\A(--[^=-]+)\z/
|
||||||
|
long, val = $1, nil
|
||||||
|
when /\A(-[^=-]+)=(.+)\z/
|
||||||
|
short, val = $1, $2
|
||||||
|
when /\A(-[^=-]+)\z/
|
||||||
|
short, val = $1, nil
|
||||||
|
else raise ArgumentError, "Unexpected format for option: #{opt.inspect}"
|
||||||
|
end
|
||||||
|
alts.each do |alt|
|
||||||
|
case alt
|
||||||
|
when /\A(--[^=-]+)=(.+)\z/
|
||||||
|
long, val = $1, val || $2
|
||||||
|
when /\A(--[^=-]+)\z/
|
||||||
|
long, val = $1, nil
|
||||||
|
when /\A(-[^=-]+)=(.+)\z/
|
||||||
|
short, val = $1, val || $2
|
||||||
|
when /\A(-[^=-]+)\z/
|
||||||
|
short, val = $1, nil
|
||||||
|
else raise ArgumentError, "Unexpected format for option: #{alt.inspect}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@name, @short, @long, @val, @desc, @default, @os, @conv =
|
||||||
|
name.to_s.to_sym, short, long, val, desc, default, os, conv || lambda{|v|v}
|
||||||
|
@req =
|
||||||
|
if NilClass != default
|
||||||
|
false
|
||||||
|
elsif cmd.exe.lambda?
|
||||||
|
! cmd.exe.parameters.select {|e| [:keyreq, @name] == e[0..1] }.empty?
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def on parser, store
|
||||||
|
parser.on "#{@short}#{@val ? ?= : ''}#{@val}", "#{@long}#{@val ? ?= : ''}#{@val}", **@os do |val|
|
||||||
|
store[@name] = @conv[val]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"#<%s:0x%016x %s %s %s %s (%p) %p os=%p conv=%s>" % [
|
||||||
|
self.class.name, object_id, @req ? "<#{@name}>" : "[#{@name}]",
|
||||||
|
@short, @long, @val, @default, @desc, @os,
|
||||||
|
@exe ? "<#{@exe.lambda? ? :lambda: :proc} ##{@exe.arity}>" : "nil"
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def opt name, opt, *alts, desc, **os, &conv
|
||||||
|
r = Opt.new( self, name, opt, *alts, desc, **os, &conv)
|
||||||
|
@options[r.name] = r
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
class DenCli
|
class DenCli
|
||||||
VERSION = '0.3.2'
|
VERSION = '0.4.0'
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue