dencli/lib/dencli.rb

184 lines
4.0 KiB
Ruby
Raw Normal View History

2020-12-13 15:54:09 +01:00
class DenCli
class UsageError < ::RuntimeError
end
class UnknownCommand < UsageError
end
class <<self
# Helper Function for generate Regular Expressions of string,
# which matches all strings which has parts fron beginning of the given string.
# `n("abc")` would produce: `/(?:a|ab|abc)/`
# You can define a minimum length to match:
# `n("abcdef",4)` => `/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=<arity=%d>>" % [
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