6 changed files with 250 additions and 102 deletions
@ -0,0 +1,31 @@
|
||||
require_relative '../dencli' |
||||
|
||||
class DenCli::CMD |
||||
attr_reader :parent, :name, :desc, :exe, :completion |
||||
|
||||
def initialize parent, name, desc, exe |
||||
raise "Proc expected, instead of: #{exe.inspect}" unless Proc === exe |
||||
@parent, @name, @desc, @exe = parent, name, desc, exe |
||||
completion {|*a| [] } |
||||
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 complete( *pre, str) @completion.call *pre, str end |
||||
|
||||
def completion &exe |
||||
@completion = exe |
||||
self |
||||
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 |
@ -0,0 +1,133 @@
|
||||
require_relative '../dencli' |
||||
|
||||
class DenCli::Interactive |
||||
attr_reader :cl, :prompt, :cur, :histfile |
||||
|
||||
def initialize cl, prompt, histfile: nil |
||||
require 'readline' |
||||
@cl, self.prompt = cl, prompt |
||||
@cur = cl.subs |
||||
cur.instance_variable_set :@name, '' |
||||
|
||||
@histfile = |
||||
case histfile |
||||
when nil then nil |
||||
when Pathname then histfile |
||||
else Pathname.new histfile.to_s |
||||
end |
||||
read_history if @histfile |
||||
|
||||
Readline.vi_editing_mode rescue NotImplementedError |
||||
Readline.completion_append_character = " " |
||||
Readline.completion_proc = method :complete |
||||
|
||||
prepare_sub cl.subs |
||||
cl.cmd :exit, "exit", min: 2 do |
||||
exit 0 |
||||
end |
||||
cl.subs.aliases['?'] = cl.subs.subs['help'] |
||||
cl.subs.subs.delete 'cli' |
||||
|
||||
stty_save = %x`stty -g`.chomp |
||||
trap "INT" do |
||||
system "stty", stty_save |
||||
Readline.refresh_line |
||||
end |
||||
end |
||||
|
||||
def history_file file = nil |
||||
file = |
||||
case file |
||||
when Pathname then file |
||||
when nil then @histfile |
||||
else Pathname.new file.to_s |
||||
end |
||||
end |
||||
|
||||
def read_history file = nil |
||||
file = history_file file |
||||
return unless file and file.exist? |
||||
file.each_line do |line| |
||||
Readline::HISTORY.push line.chomp |
||||
end |
||||
end |
||||
|
||||
def write_history file = nil |
||||
file = history_file file |
||||
return unless file |
||||
file.open 'w+' do |f| |
||||
Readline::HISTORY.each do |line| |
||||
f.puts line |
||||
end |
||||
end |
||||
end |
||||
|
||||
def prompt= s |
||||
@prompt = s.to_s |
||||
end |
||||
|
||||
def complete s |
||||
ws = words Readline.line_buffer |
||||
@cur.complete *ws[0...-1], s |
||||
end |
||||
|
||||
def sub *args, cur: nil |
||||
args.inject( cur || @cur) do |r, a| |
||||
return nil unless r.is_a? DenCli::Sub |
||||
r[a] |
||||
end |
||||
end |
||||
private :sub |
||||
|
||||
def words line = nil |
||||
r = line.split " " |
||||
r.push '' if ' ' == line[-1] |
||||
r |
||||
end |
||||
private :words |
||||
|
||||
def prepare_sub c |
||||
c.subs.values.each do |n| |
||||
case n |
||||
when DenCli::Sub |
||||
n.cmd :exit, "<- #{n.parent.full_cmd.join ' '} - #{n.parent.desc[3..-1]}", min: 2 do |
||||
@cur = n.parent |
||||
end |
||||
n.cmd '', "", min: 2, aliases: [nil] do |
||||
@cur = n |
||||
end |
||||
n.subs.delete '' |
||||
n.aliases['?'] = n.subs['help'] |
||||
prepare_sub n |
||||
when DenCli::CMD |
||||
else raise "Unsupported sub-type: #{x}" |
||||
end |
||||
end |
||||
end |
||||
|
||||
def read |
||||
line = Readline.readline( "#{prompt}#{cur.full_cmd.join ' '}> ", true) |
||||
return nil if line.nil? |
||||
Readline::HISTORY.pop if /^\s*$/ =~ line |
||||
if 0 < Readline::HISTORY.length-2 and Readline::HISTORY[Readline::HISTORY.length-2] == line |
||||
Readline::HISTORY.pop |
||||
end |
||||
line.split " " |
||||
end |
||||
|
||||
def step |
||||
line = read |
||||
return nil if line.nil? |
||||
begin |
||||
cur.call *line |
||||
rescue ::UsageError |
||||
STDERR.puts "! #$!" |
||||
end |
||||
true |
||||
end |
||||
|
||||
def run |
||||
while step |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,74 @@
|
||||
require_relative '../dencli' |
||||
|
||||
class DenCli::Sub |
||||
attr_reader :parent, :name, :desc, :subs, :aliases |
||||
|
||||
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.join ' '}" |
||||
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.join ' '}" |
||||
end |
||||
end |
||||
|
||||
def _add name, min, obj, aliases |
||||
name = name.to_s unless name.nil? |
||||
@subs[name] = obj |
||||
DenCli.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, DenCli::Sub.new( self, name, desc), aliases |
||||
block_given? ? yield( r) : r |
||||
end |
||||
|
||||
def cmd name, desc, min: nil, aliases: nil, &exe |
||||
_add name, min, DenCli::CMD.new( self, name, desc, exe), 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 @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 |
Loading…
Reference in new issue