diff --git a/bin/example.rb b/bin/example.rb index d1b8fbc..ab13bff 100755 --- a/bin/example.rb +++ b/bin/example.rb @@ -95,7 +95,62 @@ class Capture 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| + sub.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| $stderr.puts sub.help(*args) } + 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( :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| + sub2.cmd( :help, "", aliases: [nil, '-h', '--help'], &lambda {|*commands| sub2.help( *commands, output: $stderr) }) + sub2.cmd( :last, "The last example", &lambda { $stderr.puts "The last example" }) + + sub2.sub( :'sub-commands', "Endless Sub-Sub- ... with a special alias") do |sub3| + # h -> help + # he -> hehe + # 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 + +cli.cmd( :cli, "Interactive shell", min: 3, &lambda {|| + 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 @@ -202,50 +257,6 @@ cli.sub :tests, "Some tests", noshortaliases: true do |tcli| end.opt( :verbose, '-v', 'Prints additional information per test', default: false) 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| - sub.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| $stderr.puts sub.help(*args) } - 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( :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| - sub2.cmd( :help, "", aliases: [nil, '-h', '--help'], &lambda {|*commands| sub2.help( *commands, output: $stderr) }) - sub2.cmd( :last, "The last example", &lambda { $stderr.puts "The last example" }) - - sub2.sub( :'sub-commands', "Endless Sub-Sub- ...") do |sub3| - sub3.cmd( :help, "") {|*args| $stderr.puts sub3.help( sub3, *args) } - sub3.cmd( :hehe, "The real last example", min: 2) { $stderr.puts "Trust me!" } - end - end -end - begin cli.call *ARGV rescue DenCli::UsageError diff --git a/lib/dencli.rb b/lib/dencli.rb index 45a233e..26ee3ae 100755 --- a/lib/dencli.rb +++ b/lib/dencli.rb @@ -80,6 +80,9 @@ class DenCli post end + def []( k) @subs[k] end + def has?( k) @subs.has? k end + def sub *a, **o, &exe @subs.sub *a, **o, &exe end @@ -110,10 +113,6 @@ class DenCli Sub._help_commands output, subs.to_enum( :commands) end - def [] k - @subs[k] - end - def interactive *args, **opts Interactive.new self, *args, **opts end diff --git a/lib/dencli/interactive.rb b/lib/dencli/interactive.rb index be778c2..8a75314 100644 --- a/lib/dencli/interactive.rb +++ b/lib/dencli/interactive.rb @@ -17,12 +17,15 @@ class DenCli::Interactive end read_history if @histfile - Readline.vi_editing_mode rescue NotImplementedError + begin + Readline.vi_editing_mode + rescue NotImplementedError + end Readline.completion_append_character = " " Readline.completion_proc = method :complete prepare_sub cl.subs - cl.cmd :exit, "exit", min: 2 do + cl.cmd :exit, "exit", min: cl.has?(:ex) ? cl.has?(:exi) ? 4 : 3 : 2 do exit 0 end cl.subs.aliases['?'] = cl.subs.subs['help'] @@ -90,14 +93,16 @@ class DenCli::Interactive c.subs.values.each do |n| case n when DenCli::Sub - n.cmd :exit, "<- #{n.parent.full_cmd.join ' '} - #{n.parent.description[3..-1]}", min: 2 do - @cur = n.parent - end + n.cmd :exit, + "<- #{n.parent.full_cmd.join ' '} - #{n.parent.description[3..-1]}", + min: n.has?(:ex) ? n.has?( :exi) ? 4 : 3 : 2, + &lambda {|| @cur = n.parent } + n.aliases.delete nil + n.subs.delete nil n.cmd '', "", min: 2, aliases: [nil] do @cur = n end - n.subs.delete '' - n.aliases['?'] = n.subs['help'] + n.aliases['?'] = n[:help] if n.has? :help and not n.has? '?' prepare_sub n when DenCli::CMD else raise "Unsupported sub-type: #{x}" diff --git a/lib/dencli/sub.rb b/lib/dencli/sub.rb index 19f9357..d8c34e6 100644 --- a/lib/dencli/sub.rb +++ b/lib/dencli/sub.rb @@ -13,7 +13,9 @@ class DenCli::Sub def _full_cmd( post) parent._full_cmd [@name]+post end def full_cmd() _full_cmd [] end - def []( k) @aliases[k] end + def []( name) @aliases[name&.to_s] end + def has?( name) @aliases.has_key? name&.to_s end + def usage output: nil output ||= '' @@ -40,8 +42,8 @@ class DenCli::Sub if n.nil? output << "#{full_cmd.join ' '}: #{description}\n\n" self.class._help_commands output, @subs - elsif @aliases.has_key? n - @aliases[n]._help output, *a + elsif has? n + self[n]._help output, *a else raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}" end @@ -96,8 +98,8 @@ class DenCli::Sub def goto *a return self if a.empty? n, *a = *a - if @aliases.has_key? n - @aliases[n].goto *a + if has? n + self[n].goto *a else raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}" end @@ -105,19 +107,19 @@ class DenCli::Sub def call *a n, *a = *a - if @aliases.has_key? n - @aliases[n].call *a + if has? n + self[n].call *a 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 _add name, min, obj, aliases - #DenCli::assert_type self, __method__, :name, name, Symbol, NilClass + #DenCli::assert_type self, __method__, :name, name, Symbol, String #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 unless name.nil? + name = name.to_s @subs[name] = obj if @noshortaliases 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 @@ -130,6 +132,7 @@ class DenCli::Sub end if aliases [*aliases].each do |a| + 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 @@ -138,11 +141,49 @@ class DenCli::Sub end private :_add + # Define a new sub-menu: + # + # 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, min, DenCli::Sub.new( self, name, description, noshortaliases: noshortaliases, defined_in: defined_in || Kernel.caller.first), aliases + 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 end + # Define a new command: + # + # 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