require_relative '../dencli' class DenCli::Sub attr_reader :parent, :name, :description, :subs, :aliases, :defined_in def initialize parent, name, description, noshortaliases: nil, defined_in: nil #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}", {}, {} @noshortaliases, @defined_in = ! ! noshortaliases, defined_in || Kernel.caller end def _full_cmd( post) parent._full_cmd [@name]+post end def full_cmd() _full_cmd [] end def []( name) @aliases[name&.to_s] end def has?( name) @aliases.has_key? name&.to_s end def usage output: nil output ||= '' _usage output output end def _usage output output << full_cmd.join( ' ') if @aliases.has_key? nil output << " [ ...]" else output << " [...]" 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? output << "#{full_cmd.join ' '}: #{description}\n\n" self.class._help_commands output, @subs 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 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 <).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 output end end def goto *a return self if a.empty? n, *a = *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 end def call *a n, *a = *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, 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 @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 @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 [*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 end obj 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.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 def complete *pre, str if pre.empty? @subs.keys.grep /\A#{Regexp.escape str}/ elsif sub = @subs[pre[0]] sub.complete *pre[1..-1], str else $stdout.print "\a" end end def inspect "#<%s:0x%x %s @name=%p @description=%p @subs={%s} @aliases={%s} @parent=<%s:0x%x %s>>" % [ self.class.name, self.object_id, self.full_cmd, @name, @description, @subs.keys.join(', '), @aliases.keys.join(', '), @parent.class.name, @parent.class.object_id, @parent.full_cmd ] end end