Compare commits
No commits in common. "master" and "v0.3.0" have entirely different histories.
266
bin/example.rb
266
bin/example.rb
|
@ -1,265 +1,25 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
require 'pathname'
|
require 'dencli'
|
||||||
require 'shellwords'
|
|
||||||
require 'stringio'
|
|
||||||
require 'ipaddr'
|
|
||||||
require_relative '../lib/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" }
|
||||||
class Capture
|
cli.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| STDERR.puts cli.help(*args) }
|
||||||
def initialize cli, verbose:
|
|
||||||
@cli, @counter, @verbose = cli, 0, verbose
|
|
||||||
end
|
|
||||||
|
|
||||||
def capture
|
|
||||||
@args = NilClass
|
|
||||||
@counter += 1
|
|
||||||
stdout, stderr = $stdout, $stderr
|
|
||||||
$stdout = $stderr = StringIO.new
|
|
||||||
begin
|
|
||||||
yield stdout, stderr
|
|
||||||
ensure
|
|
||||||
$stderr, $stdout = stderr, stdout
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def args= args
|
|
||||||
@args = args
|
|
||||||
end
|
|
||||||
|
|
||||||
def logstart command
|
|
||||||
STDERR.printf "[% 4d] \e[1;35m? \e[0m %s tests %s\r", @counter, $0.shellescape, command.shelljoin
|
|
||||||
end
|
|
||||||
|
|
||||||
def logok info, command
|
|
||||||
STDERR.printf "[% 4d] \e[1;32mok\e[0m %s | %s tests %s\e[J\n", @counter, info, $0.shellescape, command.shelljoin
|
|
||||||
end
|
|
||||||
|
|
||||||
def logfail command
|
|
||||||
STDERR.printf "[% 4d] \e[1;31mer\e[0m %s tests %s\e[J\n", @counter, $0.shellescape, command.shelljoin
|
|
||||||
end
|
|
||||||
|
|
||||||
def logexception prefix, exception
|
|
||||||
loginfo "#{prefix} (#{exception.class.name}) #{exception}"
|
|
||||||
exception.backtrace[0...-Kernel.caller.length].each {|l| loginfo " #{l}" }
|
|
||||||
end
|
|
||||||
|
|
||||||
def loginfo text
|
|
||||||
STDERR.printf " %s\n", text
|
|
||||||
end
|
|
||||||
|
|
||||||
def should_ok expect, *command
|
|
||||||
logstart command
|
|
||||||
$capture.capture { @cli.call 'tests', *command }
|
|
||||||
if expect === @args
|
|
||||||
logok @args, command
|
|
||||||
else
|
|
||||||
logfail command
|
|
||||||
loginfo "expected args: #{expect.inspect}"
|
|
||||||
loginfo "given args: #{@args.inspect}"
|
|
||||||
STDERR.puts
|
|
||||||
end
|
|
||||||
rescue SystemExit
|
|
||||||
if 0 == $!.status
|
|
||||||
logok @args, command
|
|
||||||
else
|
|
||||||
logfail command
|
|
||||||
end
|
|
||||||
rescue Object
|
|
||||||
logfail command
|
|
||||||
logexception "unexpected raise:", $!
|
|
||||||
STDERR.puts
|
|
||||||
end
|
|
||||||
|
|
||||||
def should_fail exception, message, *command
|
|
||||||
logstart command
|
|
||||||
$capture.capture { @cli.call 'tests', *command }
|
|
||||||
logfail command
|
|
||||||
rescue exception
|
|
||||||
if message === $!.message
|
|
||||||
logok exception, command
|
|
||||||
if @verbose
|
|
||||||
logexception "raised:", $!
|
|
||||||
STDERR.puts
|
|
||||||
end
|
|
||||||
else
|
|
||||||
logexception "unexpected message:", $!
|
|
||||||
STDERR.puts
|
|
||||||
end
|
|
||||||
rescue Object
|
|
||||||
logfail command
|
|
||||||
logexception "unexpected raised:", $!
|
|
||||||
STDERR.puts
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
cli.cmd( :args, "Expects and prints given arguments",
|
|
||||||
&lambda {|a, b, c:, d:, e:, f:, g:|
|
|
||||||
p a: a, b: b, c: c, d: d, e: e
|
|
||||||
}).
|
|
||||||
opt( :c, '-c=ForC', "Option c").
|
|
||||||
opt( :d, '-dForD', "Option d", default: "something").
|
|
||||||
opt( :e, '-e', "Toggle e", default: false).
|
|
||||||
opt( :f, '--[no-]f', "Toggle f", default: false).
|
|
||||||
opt( :g, '--long-option=sth', "Long option, no short option", default: "nothing").
|
|
||||||
opt( :h, '-hsth', "No long option, only short option", default: "nothing")
|
|
||||||
|
|
||||||
cli.cmd( :example, "I have an example command") { $stderr.puts "This is an example" }
|
|
||||||
cli.cmd( :help, "An example for help", aliases: [nil, '-h', '--help'], &lambda {|*commands, full:|
|
|
||||||
if full
|
|
||||||
cli.help_full *commands, output: $stderr
|
|
||||||
else
|
|
||||||
cli.help *commands, output: $stderr
|
|
||||||
end
|
|
||||||
}).
|
|
||||||
opt( :full, '-f', '--[no-]full', "Print all commands and sub-commands.", 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( :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, "", aliases: [nil, '-h', '--help'], &lambda {|*commands| sub2.help( *commands, output: $stderr) })
|
sub2.cmd( :help, "") {|*args| STDERR.puts cli.help( 'more', 'deeper', *args) }
|
||||||
sub2.cmd( :last, "The last example", &lambda { $stderr.puts "The last example" })
|
sub2.cmd( :last, "The last example") { STDERR.puts "The last example" }
|
||||||
|
|
||||||
sub2.sub( :'sub-commands', "Endless Sub-Sub- ... with a special alias") do |sub3|
|
sub2.sub( :'sub-commands', "Endless Sub-Sub- ...") do |sub3|
|
||||||
# h -> help
|
sub2.cmd( :help, "") {|*args| STDERR.puts cli.help( 'more', 'deeper', 'sub-commands', *args) }
|
||||||
# he -> hehe
|
sub3.cmd( :hehe, "The real last example", min: 2) { STDERR.puts "Trust me!" }
|
||||||
# hel -> help
|
|
||||||
# help -> help
|
|
||||||
# heh -> hehe
|
|
||||||
# hehe -> hehe
|
|
||||||
sub3.cmd( :help, "", min: 3, aliases: [nil, :h]) {|*args| $stderr.puts sub3.help( *args) }
|
|
||||||
sub3.cmd( :hehe, "The real last example", min: 2) { $stderr.puts "Trust me!" }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
cli.cmd( :cli, "Interactive shell", min: 3, &lambda {||
|
cli.call *ARGV
|
||||||
cli.interactive( File.basename($0, '.rb')).run
|
|
||||||
})
|
|
||||||
|
|
||||||
cli.sub :tests, "Some tests", noshortaliases: true do |tcli|
|
|
||||||
tcli.cmd( :help, "", min: 4) {|*args| $stderr.puts tcli.help( *args) }
|
|
||||||
OptionParser.accept IPAddr do |arg|
|
|
||||||
begin
|
|
||||||
IPAddr.new arg
|
|
||||||
rescue IPAddr::InvalidAddressError
|
|
||||||
raise OptionParser::InvalidArgument, "#{$!.message}: #{arg}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
tcli.cmd( :'-', "No arguments no options expected", &lambda {|| $capture.args = [] })
|
|
||||||
tcli.cmd( :'arg', "", &lambda {|one| $capture.args = [one] })
|
|
||||||
tcli.cmd( :'oar', "", &lambda {|one=nil| $capture.args = [one] })
|
|
||||||
tcli.cmd( :'arg-arg', "", &lambda {|one, two| $capture.args = [one, two] })
|
|
||||||
tcli.cmd( :'oar-oar', "", &lambda {|one=nil, two=nil| $capture.args = [one, two] })
|
|
||||||
tcli.cmd( :'arg-oar', "expected", &lambda {|one, two=nil| $capture.args = [one, two] })
|
|
||||||
tcli.cmd( :'oar-arg', "expected", &lambda {|one=nil, two| $capture.args = [one, two] })
|
|
||||||
tcli.cmd( :'bool', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '-a', '')
|
|
||||||
tcli.cmd( :'optbool', '', &lambda {|a: nil| $capture.args = [a] }).opt(:a, '-a', '')
|
|
||||||
tcli.cmd( :'defbool', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '-a', '', default: 'default')
|
|
||||||
tcli.cmd( :'str', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '-a=STR', '')
|
|
||||||
tcli.cmd( :'lstr', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '--astr=STR', '')
|
|
||||||
tcli.cmd( :'bstr', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '-a', '--astr=STR', '')
|
|
||||||
tcli.cmd( :'ipaddr', '', &lambda {|a:| $capture&.args = [a] }).opt(:a, '-a', '--addr=ADDR', IPAddr, '')
|
|
||||||
|
|
||||||
tcli.cmd( :run, "Run all tests") do |verbose:|
|
|
||||||
$capture = Capture.new cli, verbose: verbose
|
|
||||||
|
|
||||||
$capture.should_fail DenCli::UnknownCommand, //, 'unknown-command'
|
|
||||||
|
|
||||||
$capture.should_ok [], '-'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, '-', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok %w[first], 'arg', 'first'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'arg'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'arg', 'first', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok %w[first], 'oar', 'first'
|
|
||||||
$capture.should_ok [nil], 'oar'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'oar', 'first', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok %w[first two], 'oar-oar', 'first', 'two'
|
|
||||||
$capture.should_ok ['first', nil], 'oar-oar', 'first'
|
|
||||||
$capture.should_ok [nil,nil], 'oar-oar'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'oar-oar', 'first', 'two', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok %w[first two], 'arg-oar', 'first', 'two'
|
|
||||||
$capture.should_ok ['first', nil], 'arg-oar', 'first'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'arg-oar'
|
|
||||||
|
|
||||||
$capture.should_ok [nil, 'first'], 'oar-arg', 'first'
|
|
||||||
$capture.should_ok ['first', 'second'], 'oar-arg', 'first', 'second'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'oar-arg'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'oar-arg', 'first', 'two', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok [true], 'bool', '-a'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'bool'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'bool', '-a', 'unexpected'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'bool', '-b'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'bool', '--unexpected'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'bool', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok [true], 'optbool', '-a'
|
|
||||||
$capture.should_ok [nil], 'optbool'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'optbool', '-a', 'unexpected'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'optbool', '-b'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'optbool', '--unexpected'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'optbool', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok [true], 'defbool', '-a'
|
|
||||||
$capture.should_ok ['default'], 'defbool'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'defbool', '-a', 'unexpected'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'defbool', '-b'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'defbool', '--unexpected'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'defbool', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok %w[first], 'str', '-a', 'first'
|
|
||||||
$capture.should_ok %w[first], 'str', '-afirst'
|
|
||||||
$capture.should_fail OptionParser::MissingArgument, //, 'str', '-a'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'str'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'str', '-b'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'str', '--unexpected'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'str', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok %w[first], 'lstr', '--astr', 'first'
|
|
||||||
$capture.should_ok %w[first], 'lstr', '--astr=first'
|
|
||||||
$capture.should_fail OptionParser::MissingArgument, //, 'lstr', '--astr'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'lstr'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'lstr', '-b'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'lstr', '--unexpected'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'lstr', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok %w[first], 'bstr', '-a', 'first'
|
|
||||||
$capture.should_ok %w[first], 'bstr', '-afirst'
|
|
||||||
$capture.should_ok %w[first], 'bstr', '--astr', 'first'
|
|
||||||
$capture.should_ok %w[first], 'bstr', '--astr=first'
|
|
||||||
$capture.should_fail OptionParser::MissingArgument, //, 'bstr', '--astr'
|
|
||||||
$capture.should_fail OptionParser::MissingArgument, //, 'bstr', '-a'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'bstr'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'bstr', '-b'
|
|
||||||
$capture.should_fail OptionParser::InvalidOption, //, 'bstr', '--unexpected'
|
|
||||||
$capture.should_fail DenCli::UsageError, //, 'bstr', 'unexpected'
|
|
||||||
|
|
||||||
$capture.should_ok [IPAddr.new('1.2.3.4')], 'ipaddr', '-a', '1.2.3.4'
|
|
||||||
$capture.should_fail OptionParser::InvalidArgument, /invalid address/, 'ipaddr', '-a', '1.2.3.400'
|
|
||||||
end.opt( :verbose, '-v', 'Prints additional information per test', default: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
cli.call *ARGV
|
|
||||||
rescue DenCli::UsageError
|
|
||||||
$stderr.puts $!
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
require 'optparse'
|
require 'optparse'
|
||||||
|
|
||||||
|
|
||||||
class DenCli
|
class DenCli
|
||||||
def self.assert_type klass, method, argument_name, object, *types
|
|
||||||
unless types.any? {|type| object.is_a? type }
|
|
||||||
raise ArgumentError, "#{klass.name}.#{method} expects #{types.map( &:to_s).join '|' } for #{argument_name}, not: #{object.inspect}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UsageError < ::RuntimeError
|
class UsageError < ::RuntimeError
|
||||||
end
|
end
|
||||||
class UnknownCommand < UsageError
|
class UnknownCommand < UsageError
|
||||||
|
@ -51,16 +44,11 @@ class DenCli
|
||||||
# `g(:abc)` => `["a", "ab", "abc"]`
|
# `g(:abc)` => `["a", "ab", "abc"]`
|
||||||
# `g(:abcdef, 4)` => `["abcd", "abcde", "abcdef"]`
|
# `g(:abcdef, 4)` => `["abcd", "abcde", "abcdef"]`
|
||||||
def gen_aliases cmd, min = nil
|
def gen_aliases cmd, min = nil
|
||||||
case min
|
r = ((min||1)-1).upto cmd.length-1
|
||||||
when false then min = cmd.length
|
|
||||||
when nil then min = 1
|
|
||||||
end
|
|
||||||
r = ([min, 1].max - 1).upto cmd.length-2
|
|
||||||
if block_given?
|
if block_given?
|
||||||
r.each {|i| yield cmd[0..i] }
|
r.each {|i| yield cmd[0..i] }
|
||||||
yield cmd
|
|
||||||
else
|
else
|
||||||
r.map {|i| cmd[0..i] } + [cmd]
|
r.map {|i| cmd[0..i] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
alias g gen_aliases
|
alias g gen_aliases
|
||||||
|
@ -80,37 +68,24 @@ class DenCli
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
|
||||||
def []( k) @subs[k] end
|
def sub *a, &exe
|
||||||
def has?( k) @subs.has? k end
|
@subs.sub *a, &exe
|
||||||
|
|
||||||
def sub *a, **o, &exe
|
|
||||||
@subs.sub *a, **o, &exe
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cmd *a, **o, &exe
|
def cmd *a, &exe
|
||||||
@subs.cmd *a, **o, &exe
|
@subs.cmd *a, &exe
|
||||||
end
|
end
|
||||||
|
|
||||||
def call *a
|
def call *a
|
||||||
@subs.call *a
|
@subs.call *a
|
||||||
end
|
end
|
||||||
|
|
||||||
def usage *args, **opts
|
def help *args
|
||||||
@subs.usage *args, **opts
|
@subs.help *args
|
||||||
end
|
end
|
||||||
|
|
||||||
def help *args, **opts
|
def [] k
|
||||||
@subs.help *args, **opts
|
@subs[k]
|
||||||
end
|
|
||||||
|
|
||||||
def help_full *args, output: nil
|
|
||||||
output ||= $stdout
|
|
||||||
x = @subs.goto *args
|
|
||||||
_help_full output, x
|
|
||||||
end
|
|
||||||
|
|
||||||
def _help_full output, subs
|
|
||||||
Sub._help_commands output, subs.to_enum( :commands)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def interactive *args, **opts
|
def interactive *args, **opts
|
||||||
|
|
|
@ -1,232 +1,50 @@
|
||||||
require_relative '../dencli'
|
require_relative '../dencli'
|
||||||
|
|
||||||
|
|
||||||
class DenCli::CMD
|
class DenCli::CMD
|
||||||
attr_reader :parent, :name, :description, :exe, :completion, :options, :defined_in
|
attr_reader :parent, :name, :description, :exe, :completion, :arguments
|
||||||
|
|
||||||
def initialize parent, name, description, exe, defined_in
|
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, @defined_in = parent, name, description, lambda( &exe), defined_in
|
@parent, @name, @description, @exe = parent, name, description, exe
|
||||||
@parameters = @exe.parameters
|
@arguments = []
|
||||||
@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] }
|
|
||||||
@options = {}
|
|
||||||
completion {|*a| [] }
|
completion {|*a| [] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def _full_cmd( post) parent._full_cmd [@name]+post end
|
def _full_cmd( post) parent._full_cmd [@name]+post end
|
||||||
def full_cmd() _full_cmd [] end
|
def full_cmd() _full_cmd [] end
|
||||||
|
def call( *as)
|
||||||
attr_reader :parameters,
|
if @arguments.empty?
|
||||||
:arguments_required, :arguments_additional, :arguments,
|
|
||||||
:options_required, :options_additional
|
|
||||||
alias required arguments_required
|
|
||||||
alias additional arguments_additional
|
|
||||||
|
|
||||||
def complete *pre, str
|
|
||||||
@completion.call *pre, str
|
|
||||||
end
|
|
||||||
|
|
||||||
def call *as
|
|
||||||
os = {}
|
|
||||||
unless @options.empty?
|
|
||||||
# options like --abc | -x will be provided in os
|
|
||||||
options = OptionParser.new
|
|
||||||
options.banner = "#{full_cmd.join ' '}"
|
|
||||||
# see also @options-array
|
|
||||||
@options.each {|_, opt| opt.on options, os }
|
|
||||||
as = options.parse! as
|
|
||||||
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}"
|
|
||||||
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.long || o.short }.join ', '}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if os.empty?
|
|
||||||
@exe.call *as
|
@exe.call *as
|
||||||
else
|
else
|
||||||
|
os = {}
|
||||||
|
options = OptionParser.new
|
||||||
|
options.banner = "#{full_cmd.join ' '}"
|
||||||
|
@arguments.each do |(aname, aas, aos, aexe)|
|
||||||
|
os[aname] = aos[aname] if aos.has_key? :default
|
||||||
|
options.on( *aas) {|val| os[aname] = aexe[val] }
|
||||||
|
end
|
||||||
|
as = options.parse! as
|
||||||
@exe.call *as, **os
|
@exe.call *as, **os
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
def help() "#{parent.full_cmd.join ' '} #{name}\n#{description}" end
|
||||||
|
|
||||||
def usage output: nil
|
def complete( *pre, str) @completion.call *pre, str end
|
||||||
output ||= ''
|
|
||||||
_usage output
|
|
||||||
output
|
|
||||||
end
|
|
||||||
|
|
||||||
def _usage output
|
|
||||||
output << full_cmd.join( ' ')
|
|
||||||
@options.each do |_, o|
|
|
||||||
s = "#{o.short||o.long}#{(!o.short && o.val) ? ?= : ''}#{o.val}"
|
|
||||||
output << (o.required? ? " #{s}" : " [#{s}]")
|
|
||||||
end
|
|
||||||
if @exe.lambda?
|
|
||||||
parameters.each do |(type, name)|
|
|
||||||
case type
|
|
||||||
when :req
|
|
||||||
output << " <#{name}>"
|
|
||||||
when :opt
|
|
||||||
output << " [<#{name}>]"
|
|
||||||
when :rest
|
|
||||||
output << " [<#{name}> ...]"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
output << ' [...]'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def commands *args
|
|
||||||
yield name, self
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
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\n"
|
|
||||||
@options.map do |_, 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?
|
|
||||||
output << format % [ s, y, l, d ]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def completion &exe
|
def completion &exe
|
||||||
@completion = exe
|
@completion = exe
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
class Opt
|
def arg name, *as, **os, &exe
|
||||||
attr_reader :name, :long, :short, :type, :val, :desc, :conv, :req
|
@arguments.push [name.to_s.to_sym, as, os, exe || lambda{|*a|a} ]
|
||||||
def required?() @req end
|
|
||||||
def default?() NilClass != @default end
|
|
||||||
def default() NilClass == @default ? nil : @default end
|
|
||||||
|
|
||||||
def parse_opt_string opt
|
|
||||||
case opt
|
|
||||||
when /\A(--\[no-\][^= ]+)\z/
|
|
||||||
@long, @val = $1, nil
|
|
||||||
when /\A(--[^= ]+)[= ](.+)\z/
|
|
||||||
@long, @val = $1, $2 || @val
|
|
||||||
when /\A(--[^= ]+)\z/
|
|
||||||
@long, @val = $1, nil
|
|
||||||
when /\A(-[^= -])[= ]?(.+)\z/
|
|
||||||
@short, @val = $1, $2 || @val
|
|
||||||
when /\A(-[^= -])\z/
|
|
||||||
@short, @val = $1, nil
|
|
||||||
else
|
|
||||||
raise ArgumentError, "Unexpected format for option: #{opt.inspect}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private :parse_opt_string
|
|
||||||
|
|
||||||
def initialize cmd, name, opt, *args, desc, default: NilClass, &conv
|
|
||||||
@name, @desc, @default, @conv, @val, @type =
|
|
||||||
name.to_s.to_sym, desc, default, conv || lambda{|v|v}, nil, nil
|
|
||||||
parse_opt_string opt
|
|
||||||
@type = args.pop if OptionParser.top.atype.has_key? args.last
|
|
||||||
args.each &method( :parse_opt_string)
|
|
||||||
@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
|
|
||||||
store[@name] = @default if default?
|
|
||||||
short = "#{@short}#{@val ? ?= : ''}#{@val}"
|
|
||||||
long = "#{@long}#{@val ? ?= : ''}#{@val}"
|
|
||||||
parser.on short, long, *[@type].compact do |val|
|
|
||||||
store[@name] = @conv[val]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<%s:0x%x %s %s %s %s (%p) %p conv=%s>" % [
|
|
||||||
self.class.name, object_id, @req ? "<#{@name}>" : "[#{@name}]",
|
|
||||||
@short, @long, @val, @default, @desc, @type ? " type=#{type}" : '',
|
|
||||||
@exe ? "<#{@exe.lambda? ? :lambda: :proc} ##{@exe.arity}>" : "nil"
|
|
||||||
]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def opt name, opt, *args, desc, default: NilClass, &conv
|
|
||||||
r = Opt.new( self, name, opt, *args, desc, default: default, &conv)
|
|
||||||
@options[r.name] = r
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
"#<%s:0x%x %s @name=%p @description=%p @options=%p @parent=<%s:0x%x %s> @exe=<arity=%d>>" % [
|
"#<%s:0x%x %s @name=%p @description=%p @parent=<%s:0x%x %s> @exe=<arity=%d>>" % [
|
||||||
self.class.name, self.object_id, self.full_cmd,
|
self.class.name, self.object_id, self.full_cmd,
|
||||||
@name, @description, @options.values, @parent.class.name, @parent.class.object_id, @parent.full_cmd,
|
@name, @description, @parent.class.name, @parent.class.object_id, @parent.full_cmd,
|
||||||
@exe.arity
|
@exe.arity
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,15 +17,12 @@ class DenCli::Interactive
|
||||||
end
|
end
|
||||||
read_history if @histfile
|
read_history if @histfile
|
||||||
|
|
||||||
begin
|
Readline.vi_editing_mode rescue NotImplementedError
|
||||||
Readline.vi_editing_mode
|
|
||||||
rescue NotImplementedError
|
|
||||||
end
|
|
||||||
Readline.completion_append_character = " "
|
Readline.completion_append_character = " "
|
||||||
Readline.completion_proc = method :complete
|
Readline.completion_proc = method :complete
|
||||||
|
|
||||||
prepare_sub cl.subs
|
prepare_sub cl.subs
|
||||||
cl.cmd :exit, "exit", min: cl.has?(:ex) ? cl.has?(:exi) ? 4 : 3 : 2 do
|
cl.cmd :exit, "exit", min: 2 do
|
||||||
exit 0
|
exit 0
|
||||||
end
|
end
|
||||||
cl.subs.aliases['?'] = cl.subs.subs['help']
|
cl.subs.aliases['?'] = cl.subs.subs['help']
|
||||||
|
@ -93,16 +90,14 @@ class DenCli::Interactive
|
||||||
c.subs.values.each do |n|
|
c.subs.values.each do |n|
|
||||||
case n
|
case n
|
||||||
when DenCli::Sub
|
when DenCli::Sub
|
||||||
n.cmd :exit,
|
n.cmd :exit, "<- #{n.parent.full_cmd.join ' '} - #{n.parent.description[3..-1]}", min: 2 do
|
||||||
"<- #{n.parent.full_cmd.join ' '} - #{n.parent.description[3..-1]}",
|
@cur = n.parent
|
||||||
min: n.has?(:ex) ? n.has?( :exi) ? 4 : 3 : 2,
|
end
|
||||||
&lambda {|| @cur = n.parent }
|
|
||||||
n.aliases.delete nil
|
|
||||||
n.subs.delete nil
|
|
||||||
n.cmd '', "", min: 2, aliases: [nil] do
|
n.cmd '', "", min: 2, aliases: [nil] do
|
||||||
@cur = n
|
@cur = n
|
||||||
end
|
end
|
||||||
n.aliases['?'] = n[:help] if n.has? :help and not n.has? '?'
|
n.subs.delete ''
|
||||||
|
n.aliases['?'] = n.subs['help']
|
||||||
prepare_sub n
|
prepare_sub n
|
||||||
when DenCli::CMD
|
when DenCli::CMD
|
||||||
else raise "Unsupported sub-type: #{x}"
|
else raise "Unsupported sub-type: #{x}"
|
||||||
|
@ -126,7 +121,7 @@ class DenCli::Interactive
|
||||||
begin
|
begin
|
||||||
cur.call *line
|
cur.call *line
|
||||||
rescue ::DenCli::UsageError
|
rescue ::DenCli::UsageError
|
||||||
$stderr.puts "! #$!"
|
STDERR.puts "! #$!"
|
||||||
end
|
end
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,105 +1,26 @@
|
||||||
require_relative '../dencli'
|
require_relative '../dencli'
|
||||||
|
|
||||||
class DenCli::Sub
|
class DenCli::Sub
|
||||||
attr_reader :parent, :name, :description, :subs, :aliases, :defined_in
|
attr_reader :parent, :name, :description, :subs, :aliases
|
||||||
|
|
||||||
def initialize parent, name, description, noshortaliases: nil, defined_in: nil
|
def initialize parent, name, description
|
||||||
#DenCli::assert_type self, __method__, :name, name, Symbol
|
|
||||||
#DenCli::assert_type self, __method__, :parent, parent, DenCli, DenCli::Sub
|
|
||||||
#DenCli::assert_type self, __method__, :description, description, String
|
|
||||||
@parent, @name, @description, @subs, @aliases = parent, name, "-> #{description}", {}, {}
|
@parent, @name, @description, @subs, @aliases = parent, name, "-> #{description}", {}, {}
|
||||||
@noshortaliases, @defined_in = ! ! noshortaliases, defined_in || Kernel.caller
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def _full_cmd( post) parent._full_cmd [@name]+post end
|
def _full_cmd( post) parent._full_cmd [@name]+post end
|
||||||
def full_cmd() _full_cmd [] end
|
def full_cmd() _full_cmd [] end
|
||||||
def []( name) @aliases[name&.to_s] end
|
def []( k) @aliases[k] end
|
||||||
def has?( name) @aliases.has_key? name&.to_s end
|
|
||||||
|
|
||||||
|
def help n = nil, *a
|
||||||
def usage output: nil
|
|
||||||
output ||= ''
|
|
||||||
_usage output
|
|
||||||
output
|
|
||||||
end
|
|
||||||
|
|
||||||
def _usage output
|
|
||||||
output << full_cmd.join( ' ')
|
|
||||||
if @aliases.has_key? nil
|
|
||||||
output << " [<command> ...]"
|
|
||||||
else
|
|
||||||
output << " <command> [...]"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def help n = nil, *a, output: nil
|
|
||||||
output ||= ''
|
|
||||||
_help output, n, *a
|
|
||||||
output
|
|
||||||
end
|
|
||||||
|
|
||||||
def _help output, n = nil, *a
|
|
||||||
if n.nil?
|
if n.nil?
|
||||||
output << "#{full_cmd.join ' '}: #{description}\n\n"
|
r = "#{full_cmd.join ' '}: #{description}\n\n"
|
||||||
self.class._help_commands output, @subs
|
m = @subs.map {|k,_| k.length }.max
|
||||||
elsif has? n
|
@subs.each do |k, c|
|
||||||
self[n]._help output, *a
|
r += " % -#{m}s %s\n" % [k, c.description] unless k.nil?
|
||||||
else
|
|
||||||
raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def commands &exe
|
|
||||||
yield name, self
|
|
||||||
@subs.each {|k, c| c.commands &exe }
|
|
||||||
end
|
|
||||||
|
|
||||||
def help_commands output: nil
|
|
||||||
output ||= ''
|
|
||||||
self.class._help_commands output, subs.map {|_,c| c}
|
|
||||||
output
|
|
||||||
end
|
|
||||||
|
|
||||||
class <<self
|
|
||||||
def _help_commands output, subs
|
|
||||||
m = subs.map {|_name,c| x = c.usage.length; 25 < x ? 0 : x }.max
|
|
||||||
subs.each do |_name, c|
|
|
||||||
if 25 < c.usage.length
|
|
||||||
output << "% -#{m}s\n#{' ' * m} " % [c.usage]
|
|
||||||
else
|
|
||||||
output << "% -#{m}s " % [c.usage]
|
|
||||||
end
|
|
||||||
n = m+2
|
|
||||||
prefix = nil
|
|
||||||
c.description.split( /\n/).each do |l|
|
|
||||||
c = 0
|
|
||||||
l.split( %r< >).each do |w|
|
|
||||||
if prefix
|
|
||||||
output << prefix
|
|
||||||
prefix = nil
|
|
||||||
end
|
|
||||||
wl = w.length
|
|
||||||
if 75 < c+wl
|
|
||||||
output << "\n#{' ' * n}#{w}"
|
|
||||||
c = n+2+wl
|
|
||||||
else
|
|
||||||
output << " #{w}"
|
|
||||||
c += 1 + wl
|
|
||||||
end
|
|
||||||
end
|
|
||||||
prefix = "\n#{' ' * n}"
|
|
||||||
end
|
|
||||||
output << "\n"
|
|
||||||
end
|
end
|
||||||
output
|
r
|
||||||
end
|
elsif @aliases.has_key? n
|
||||||
end
|
@aliases[n].help *a
|
||||||
|
|
||||||
def goto *a
|
|
||||||
return self if a.empty?
|
|
||||||
n, *a = *a
|
|
||||||
if has? n
|
|
||||||
self[n].goto *a
|
|
||||||
else
|
else
|
||||||
raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
|
raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
|
||||||
end
|
end
|
||||||
|
@ -107,85 +28,31 @@ class DenCli::Sub
|
||||||
|
|
||||||
def call *a
|
def call *a
|
||||||
n, *a = *a
|
n, *a = *a
|
||||||
if has? n
|
if @aliases.has_key? n
|
||||||
self[n].call *a
|
@aliases[n].call *a
|
||||||
else
|
else
|
||||||
raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
|
raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def _add name, min, obj, aliases
|
def _add name, min, obj, aliases
|
||||||
#DenCli::assert_type self, __method__, :name, name, Symbol, String
|
name = name.to_s unless name.nil?
|
||||||
#DenCli::assert_type self, __method__, :min, min, Integer, NilClass
|
|
||||||
#DenCli::assert_type self, __method__, :obj, obj, DenCli::Sub, DenCli::CMD
|
|
||||||
#DenCli::assert_type self, __method__, :aliases, aliases, Array, NilClass
|
|
||||||
name = name.to_s
|
|
||||||
@subs[name] = obj
|
@subs[name] = obj
|
||||||
if @noshortaliases
|
DenCli.gen_aliases( name, min) {|a| @aliases[a] = obj }
|
||||||
warn "Command/Alias for #{obj.full_cmd} defined in #{obj.defined_in} already exists: #{full_cmd.join ' '} #{name}. Used by #{@aliases[name].full_cmd} defined in #{@aliases[name].defined_in}" if @aliases.has_key? name
|
|
||||||
@aliases[name] = obj
|
|
||||||
else
|
|
||||||
DenCli.gen_aliases name, min do |a|
|
|
||||||
warn "Command/Alias for #{obj.full_cmd} defined in #{obj.defined_in} already exists: #{full_cmd.join ' '} #{a}. Used by #{@aliases[a].full_cmd} defined in #{@aliases[a].defined_in}" if @aliases.has_key? a
|
|
||||||
@aliases[a] ||= obj
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if aliases
|
if aliases
|
||||||
[*aliases].each do |a|
|
[*aliases].each {|a| @aliases[a] = obj }
|
||||||
a = a&.to_s
|
|
||||||
raise ArgumentError, "Alias for #{obj.full_cmd} defined in #{obj.defined_in} already exists: #{full_cmd.join ' '} #{a}. Used by #{@aliases[a].full_cmd} defined in #{@aliases[a].defined_in}" if @aliases.has_key? a
|
|
||||||
@aliases[a] = obj
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
obj
|
obj
|
||||||
end
|
end
|
||||||
private :_add
|
private :_add
|
||||||
|
|
||||||
# Define a new sub-menu:
|
def sub name, description, min: nil, aliases: nil, &exe
|
||||||
#
|
r = _add name, min, DenCli::Sub.new( self, name, description), aliases
|
||||||
# DenCli.new {|c|
|
|
||||||
# c.sub( 'sub-command') {|s|
|
|
||||||
# s.cmd( :hello, 'Greetings', &lambda {|| puts 'hello world' })
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# # ./prog sub-command hello
|
|
||||||
#
|
|
||||||
# name should be a string/symbol. It will be converted to string.
|
|
||||||
# If provided, aliases must be a list of different aliases. It will be converted to string.
|
|
||||||
def sub name, description, min: nil, aliases: nil, noshortaliases: nil, defined_in: nil, &exe
|
|
||||||
r = _add name.to_s, min, DenCli::Sub.new( self, name, description, noshortaliases: noshortaliases, defined_in: defined_in || Kernel.caller.first), aliases
|
|
||||||
block_given? ? yield( r) : r
|
block_given? ? yield( r) : r
|
||||||
end
|
end
|
||||||
|
|
||||||
# Define a new command:
|
def cmd name, description, min: nil, aliases: nil, &exe
|
||||||
#
|
_add name, min, DenCli::CMD.new( self, name, description, exe), aliases
|
||||||
# DenCli.new {|c|
|
|
||||||
# c.cmd( :hello, 'Greetings', &lambda {|| puts 'hello world' })
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# # ./prog hello
|
|
||||||
# hello world
|
|
||||||
#
|
|
||||||
# name should be a string/symbol. It will be converted to string.
|
|
||||||
# If provided, aliases must be a list of different aliases. Except of nil, any alias will be converted to string.
|
|
||||||
# nil is an alias for a default command for sub-commands, but interactive shells.
|
|
||||||
#
|
|
||||||
# DenCli.new {|c|
|
|
||||||
# c.sub( :greetings, 'Hello, Welcome, ...') do |s|
|
|
||||||
# s.cmd( :hello, 'A simple Hello', aliases: %w[hello-world hello_world], &lambda {|| puts 'Hello World' })
|
|
||||||
# s.cmd( :welcome, 'More gracefull', aliases: [nil, 'welcome-world', :hello_world], &lambda {|| puts 'Welcome World' })
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
#
|
|
||||||
# # ./prog greetings
|
|
||||||
# Welcome World
|
|
||||||
# # ./prog greetings welcome
|
|
||||||
# Welcome World
|
|
||||||
# # ./prog greetings hello
|
|
||||||
# Hello World
|
|
||||||
def cmd name, description, min: nil, aliases: nil, defined_in: nil, &exe
|
|
||||||
_add name, min, DenCli::CMD.new( self, name, description, exe, defined_in || Kernel.caller.first), aliases
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def complete *pre, str
|
def complete *pre, str
|
||||||
|
@ -194,7 +61,7 @@ class DenCli::Sub
|
||||||
elsif sub = @subs[pre[0]]
|
elsif sub = @subs[pre[0]]
|
||||||
sub.complete *pre[1..-1], str
|
sub.complete *pre[1..-1], str
|
||||||
else
|
else
|
||||||
$stdout.print "\a"
|
STDOUT.print "\a"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
class DenCli
|
class DenCli
|
||||||
VERSION = '0.5.5'
|
VERSION = '0.3.0'
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue