class DenCli class UsageError < ::RuntimeError end class UnknownCommand < UsageError end class < `/abcd(?:|e|ef)/` def n s, min = nil min ||= 1 /#{s.length <= min ? Regexp.quote(s) : "#{Regexp.quote s[0...min]}#{ s[min...-1]. reverse. each_char. reduce( "#{Regexp.quote s[-1]}?") {|f,n| "(?:#{Regexp.quote n}#{f})?" } }" }/ end # Wraps `n(s,min=)` in a full matching RegExp with ending `\0`: # `r("abc")` would produce: `/\A(?:a|ab|abc)\0\z/` # You can define a minimum length to match: # `r("abcdef",4)` => `/\aabcd(?:|e|ef)\0\z/` def r s, min = nil /\A#{n s, min}\0\z/ end # Generates a list of aliases for given `cmd`: # `g(:abc)` => `["a", "ab", "abc"]` # `g(:abcdef, 4)` => `["abcd", "abcde", "abcdef"]` def gen_aliases cmd, min = nil r = ((min||1)-1).upto cmd.length-1 if block_given? r.each {|i| yield cmd[0..i] } else r.map {|i| cmd[0..i] } end end alias g gen_aliases end class CMD attr_reader :parent, :name, :desc, :exe def initialize parent, name, desc, exe raise "Proc expected, instead of: #{exe.inspect}" unless Proc === exe @parent, @name, @desc, @exe = parent, name, desc, exe end def _full_cmd post parent._full_cmd [@name]+post end def full_cmd _full_cmd [] end def call *a @exe.call *a end def help "#{parent.full_cmd.join ' '} #{name}\n#{ desc}" end def inspect "#<%s:0x%x %s @name=%p @desc=%p @parent=<%s:0x%x %s> @exe=>" % [ self.class.name, self.object_id, self.full_cmd, @name, @desc, @parent.class.name, @parent.class.object_id, @parent.full_cmd, @exe.arity ] end end class Sub attr_reader :parent, :name, :desc, :subs def initialize parent, name, desc @parent, @name, @desc, @subs, @aliases = parent, name, desc, {}, {} end def _full_cmd post parent._full_cmd [@name]+post end def full_cmd _full_cmd [] end def [] k @aliases[k] end def help n = nil, *a if n.nil? r = "#{full_cmd.join ' '}: #{desc}\n\n" m = @subs.map {|k,_| k.length }.max @subs.each do |k, c| r += " % -#{m}s %s\n" % [k, c.desc] unless k.nil? end r elsif @aliases.has_key? n @aliases[n].help *a else raise UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.inspect}" end end def call *a n, *a = *a if @aliases.has_key? n @aliases[n].call *a else raise UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.inspect}" end end def _add name, min, obj, aliases name = name.to_s unless name.nil? @subs[name] = obj CL.gen_aliases( name, min) {|a| @aliases[a] = obj } if aliases [*aliases].each {|a| @aliases[a] = obj } end obj end private :_add def sub name, desc, min: nil, aliases: nil, &exe r = _add name, min, Sub.new( self, name, desc), aliases block_given? ? yield( r) : r end def cmd name, desc, min: nil, aliases: nil, &exe _add name, min, CMD.new( self, name, desc, exe), aliases end def inspect "#<%s:0x%x %s @name=%p @desc=%p @subs={%s} @aliases={%s} @parent=<%s:0x%x %s>>" % [ self.class.name, self.object_id, self.full_cmd, @name, @desc, @subs.keys.join(', '), @aliases.keys.join(', '), @parent.class.name, @parent.class.object_id, @parent.full_cmd ] end end def initialize progname, desc @subs = Sub.new self, progname, desc end def full_cmd [] end def _full_cmd post post end def sub *a, &exe @subs.sub *a, &exe end def cmd *a, &exe @subs.cmd *a, &exe end def call *a @subs.call *a end def help *args @subs.help *args end def [] k @subs[k] end end