2020-12-27 23:00:53 +01:00
|
|
|
require_relative '../dencli'
|
|
|
|
|
2021-11-30 15:23:05 +01:00
|
|
|
|
2020-12-27 23:00:53 +01:00
|
|
|
class DenCli::CMD
|
2021-01-03 16:28:12 +01:00
|
|
|
attr_reader :parent, :name, :description, :exe, :completion, :options
|
2020-12-27 23:00:53 +01:00
|
|
|
|
2021-01-03 13:42:00 +01:00
|
|
|
def initialize parent, name, description, exe
|
2020-12-27 23:00:53 +01:00
|
|
|
raise "Proc expected, instead of: #{exe.inspect}" unless Proc === exe
|
2021-01-03 16:28:12 +01:00
|
|
|
@parent, @name, @description, @exe = parent, name, description, lambda( &exe)
|
2021-12-08 00:08:47 +01:00
|
|
|
@parameters = @exe.parameters
|
|
|
|
@arguments_required = @exe.parameters.select {|e| :req == e[0] }.map {|e| e[1] }
|
|
|
|
@arguments_additional = @exe.parameters.select {|e| :opt == e[0] }.map {|e| e[1] }
|
|
|
|
@arguments = @exe.parameters.select {|e| :req == e[0] or :opt == e[0] }.map {|e| e[1] }
|
|
|
|
@options_required = @exe.parameters.select {|e| :keyreq == e[0] }.map {|e| e[1] }
|
|
|
|
@options_additional = @exe.parameters.select {|e| :key == e[0] }.map {|e| e[1] }
|
2021-11-30 15:23:05 +01:00
|
|
|
@options = {}
|
2020-12-27 23:00:53 +01:00
|
|
|
completion {|*a| [] }
|
|
|
|
end
|
|
|
|
|
|
|
|
def _full_cmd( post) parent._full_cmd [@name]+post end
|
|
|
|
def full_cmd() _full_cmd [] end
|
2021-01-03 16:28:12 +01:00
|
|
|
|
2021-12-08 00:08:47 +01:00
|
|
|
attr_reader :parameters,
|
|
|
|
:arguments_required, :arguments_additional, :arguments,
|
|
|
|
:options_required, :options_additional
|
2021-11-30 15:23:05 +01:00
|
|
|
alias required arguments_required
|
|
|
|
alias additional arguments_additional
|
|
|
|
|
2021-11-30 23:09:06 +01:00
|
|
|
def complete *pre, str
|
|
|
|
@completion.call *pre, str
|
|
|
|
end
|
2021-01-03 16:28:12 +01:00
|
|
|
|
2021-11-30 23:09:06 +01:00
|
|
|
def call *as
|
2021-11-30 15:23:05 +01:00
|
|
|
os = {}
|
|
|
|
unless @options.empty?
|
|
|
|
# options like --abc | -x will be provided in os
|
2021-01-03 13:42:00 +01:00
|
|
|
options = OptionParser.new
|
|
|
|
options.banner = "#{full_cmd.join ' '}"
|
2021-11-30 15:23:05 +01:00
|
|
|
# see also @options-array
|
|
|
|
@options.each {|_, opt| opt.on options, os }
|
2021-01-03 13:42:00 +01:00
|
|
|
as = options.parse! as
|
2021-11-30 15:23:05 +01:00
|
|
|
end
|
|
|
|
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
|
|
|
|
if as.length < pars.length
|
|
|
|
raise DenCli::UsageError, "Missing parameter(s): #{pars[as.length..-1].join " "}"
|
|
|
|
end
|
|
|
|
if parameters.select {|e| :rest == e[0] }.empty?
|
|
|
|
pars = pars + additional
|
|
|
|
if as.length > pars.length
|
|
|
|
raise DenCli::UsageError, "Unused parameter(s): #{as[-pars.length..-1].shelljoin}"
|
2021-01-03 16:28:12 +01:00
|
|
|
end
|
|
|
|
end
|
2021-11-30 15:23:05 +01:00
|
|
|
kr = @options.select {|_, o| o.required? and not os.has_key? o.name }
|
|
|
|
unless kr.empty?
|
2021-12-08 00:08:47 +01:00
|
|
|
raise DenCli::UsageError, "Missing argument(s): #{kr.map {|_, o| o.long || o.short }.join ', '}"
|
2021-11-30 15:23:05 +01:00
|
|
|
end
|
2021-01-03 13:42:00 +01:00
|
|
|
end
|
2021-11-30 15:23:05 +01:00
|
|
|
@exe.call *as, **os
|
2021-01-03 13:42:00 +01:00
|
|
|
end
|
2021-01-03 16:28:12 +01:00
|
|
|
|
2021-11-30 23:09:06 +01:00
|
|
|
def usage output: nil
|
|
|
|
output ||= ''
|
|
|
|
_usage output
|
|
|
|
output
|
|
|
|
end
|
|
|
|
|
|
|
|
def _usage output
|
|
|
|
output << full_cmd.join( ' ')
|
|
|
|
@options.each do |_, o|
|
2021-12-08 00:17:37 +01:00
|
|
|
s = "#{o.short||o.long}#{(!o.short && o.val) ? ?= : ''}#{o.val}"
|
2021-11-30 23:09:06 +01:00
|
|
|
output << (o.required? ? " #{s}" : " [#{s}]")
|
|
|
|
end
|
|
|
|
if @exe.lambda?
|
2021-12-08 23:52:05 +01:00
|
|
|
parameters.each do |(type, name)|
|
|
|
|
case type
|
|
|
|
when :req
|
|
|
|
output << " <#{name}>"
|
|
|
|
when :opt
|
|
|
|
output << " [<#{name}>]"
|
|
|
|
when :rest
|
|
|
|
output << " [<#{name}> ...]"
|
|
|
|
end
|
|
|
|
end
|
2021-11-30 23:09:06 +01:00
|
|
|
else
|
|
|
|
output << ' ...'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-12-07 23:48:28 +01:00
|
|
|
def commands *args
|
|
|
|
yield name, self
|
2021-01-03 16:28:12 +01:00
|
|
|
end
|
|
|
|
|
2021-11-30 23:09:06 +01:00
|
|
|
def goto *args
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def help output: nil
|
|
|
|
output ||= ''
|
|
|
|
_help output
|
|
|
|
output
|
|
|
|
end
|
|
|
|
|
|
|
|
def _help output
|
|
|
|
output << "Usage: #{usage}\n#{description}\n"
|
|
|
|
_help_options output
|
|
|
|
end
|
|
|
|
|
|
|
|
def help_options output: nil
|
|
|
|
output ||= ''
|
|
|
|
_help_options output
|
|
|
|
output
|
|
|
|
end
|
|
|
|
|
|
|
|
def _help_options output
|
2021-11-30 15:23:05 +01:00
|
|
|
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
|
2021-11-30 23:09:06 +01:00
|
|
|
format = " %-#{sc}s%s %-#{lc}s %s\n"
|
|
|
|
@options.map do |_, o|
|
2021-11-30 15:23:05 +01:00
|
|
|
s, l, v, y = o.short, o.long, o.val, ','
|
|
|
|
if l.nil?
|
2021-12-08 23:52:05 +01:00
|
|
|
s += "#{v}" if v
|
2021-11-30 15:23:05 +01:00
|
|
|
y = ' '
|
|
|
|
elsif s.nil?
|
|
|
|
l += "=#{v}" if v
|
|
|
|
y = ' '
|
|
|
|
end
|
|
|
|
d = o.desc || ''
|
|
|
|
d += " (#{o.default})" if o.default?
|
2021-11-30 23:09:06 +01:00
|
|
|
output << format % [ s, y, l, d ]
|
|
|
|
end
|
2021-01-03 16:28:12 +01:00
|
|
|
end
|
2020-12-27 23:00:53 +01:00
|
|
|
|
|
|
|
def completion &exe
|
|
|
|
@completion = exe
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2021-11-30 15:23:05 +01:00
|
|
|
class Opt
|
|
|
|
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
|
|
|
|
|
2021-11-30 23:09:06 +01:00
|
|
|
def parse_opt_string opt
|
2021-11-30 15:23:05 +01:00
|
|
|
case opt
|
2021-11-30 23:09:06 +01:00
|
|
|
when /\A(--\[no-\][^=]+)\z/
|
|
|
|
@long, @val = $1, nil
|
|
|
|
when /\A(--[^=]+)=(.+)\z/
|
|
|
|
@long, @val = $1, $2 || @val
|
|
|
|
when /\A(--[^=]+)\z/
|
|
|
|
@long, @val = $1, nil
|
2021-12-08 00:17:37 +01:00
|
|
|
when /\A(-[^=-])=?(.+)\z/
|
2021-11-30 23:09:06 +01:00
|
|
|
@short, @val = $1, $2 || @val
|
2021-12-08 00:17:37 +01:00
|
|
|
when /\A(-[^=-])\z/
|
2021-11-30 23:09:06 +01:00
|
|
|
@short, @val = $1, nil
|
|
|
|
else
|
|
|
|
raise ArgumentError, "Unexpected format for option: #{opt.inspect}"
|
2021-11-30 15:23:05 +01:00
|
|
|
end
|
2021-11-30 23:09:06 +01:00
|
|
|
end
|
|
|
|
private :parse_opt_string
|
|
|
|
|
|
|
|
def initialize cmd, name, opt, *alts, desc, default: NilClass, **os, &conv
|
|
|
|
@name, @desc, @default, @os, @conv, @val =
|
|
|
|
name.to_s.to_sym, desc, default, os, conv || lambda{|v|v}, nil
|
|
|
|
parse_opt_string opt
|
|
|
|
alts.each &method( :parse_opt_string)
|
2021-11-30 15:23:05 +01:00
|
|
|
@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
|
2021-11-30 23:09:06 +01:00
|
|
|
store[@name] = @default if default?
|
2021-11-30 15:23:05 +01:00
|
|
|
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
|
2021-01-03 13:42:00 +01:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2020-12-27 23:00:53 +01:00
|
|
|
def inspect
|
2021-01-03 13:42:00 +01:00
|
|
|
"#<%s:0x%x %s @name=%p @description=%p @parent=<%s:0x%x %s> @exe=<arity=%d>>" % [
|
2020-12-27 23:00:53 +01:00
|
|
|
self.class.name, self.object_id, self.full_cmd,
|
2021-01-03 13:42:00 +01:00
|
|
|
@name, @description, @parent.class.name, @parent.class.object_id, @parent.full_cmd,
|
2020-12-27 23:00:53 +01:00
|
|
|
@exe.arity
|
|
|
|
]
|
|
|
|
end
|
|
|
|
end
|