class Cmd

A simple framework for writing line-oriented command interpreters, based heavily on Python's cmd.py.

These are often useful for test harnesses, administrative tools, and prototypes that will later be wrapped in a more sophisticated interface.

A Cmd instance or subclass instance is a line-oriented interpreter framework. There is no good reason to instantiate Cmd itself; rather, it's useful as a superclass of an interpreter class you define yourself in order to inherit Cmd's methods and encapsulate action methods.

Attributes

hide_undocumented_commands[RW]

Flag that sets whether undocumented commands are listed in the help

current_command[W]

The current command

stdin[W]

STDIN stream used

stdout[W]

STDOUT stream used

Public Class Methods

new() click to toggle source
# File lib/cmd.rb, line 147
def initialize
  @stdin, @stdout = STDIN, STDOUT
  @stop = false
  setup
end
run(intro = nil) click to toggle source
# File lib/cmd.rb, line 131
def run(intro = nil)
  new.cmdloop(intro)
end

Public Instance Methods

cmdloop(intro = nil) click to toggle source

Starts up the command loop

# File lib/cmd.rb, line 154
def cmdloop(intro = nil)
  preloop
  write intro if intro
  begin
    set_completion_proc(:complete)
    begin
      execute_command
    # Catch ^C
    rescue Interrupt
      user_interrupt
    # I don't know why ZeroDivisionError isn't caught below...
    rescue ZeroDivisionError
      handle_all_remaining_exceptions(ZeroDivisionError)
    rescue => exception
      handle_all_remaining_exceptions(exception)
    end
  end until @stop
  postloop
end
Also aliased as: run
do_exit() click to toggle source
# File lib/cmd.rb, line 194
def do_exit; stoploop end
do_help(command = nil) click to toggle source
# File lib/cmd.rb, line 177
def do_help(command = nil)
  if command
    command = translate_shortcut(command)
    docs.include?(command) ? print_help(command) : no_help(command)
  else
    documented_commands.each {|cmd| print_help cmd}
    print_undocumented_commands if undocumented_commands?
  end
end
no_help(command) click to toggle source

Called when the command has no associated documentation, this could potentially mean that the command is non existant

# File lib/cmd.rb, line 189
def no_help(command)
  write "No help for command '#{command}'"
end
run(intro = nil)
Alias for: cmdloop
turn_off_readline() click to toggle source

Turns off readline even if it is supported

# File lib/cmd.rb, line 197
def turn_off_readline
  @readline_supported = false
  self
end

Protected Instance Methods

arguments_missing() click to toggle source

XXX Not implementd yet. Called when a do_ method that takes arguments doesn't get any

# File lib/cmd.rb, line 293
def arguments_missing
  write 'Invalid arguments'
  do_help(current_command) if docs.include?(current_command)
end
command(cmd) click to toggle source

The method name that corresponds to the passed in command.

# File lib/cmd.rb, line 317
def command(cmd)
  "do_#{cmd}".intern
end
command_abbreviations() click to toggle source

Returns lookup table of unambiguous identifiers for commands.

# File lib/cmd.rb, line 375
def command_abbreviations
  return @command_abbreviations if @command_abbreviations
  @command_abbreviations = Abbrev::abbrev(command_list)
end
command_list() click to toggle source

Lists of commands (i.e. do_* methods minus the 'do_' part).

# File lib/cmd.rb, line 364
def command_list
  collect_do - subcommand_list
end
command_lookup_table() click to toggle source

Definitive list of shortcuts and abbreviations of a command.

# File lib/cmd.rb, line 369
def command_lookup_table 
  return @command_lookup_table if @command_lookup_table
  @command_lookup_table = command_abbreviations.merge(shortcut_table)
end
command_missing(command, args) click to toggle source

Called when the line entered at the prompt does not map to any of the defined commands. By default it reports that there is no such command.

# File lib/cmd.rb, line 550
def command_missing(command, args)
  write "No such command '#{command}'"
end
command_shortcuts(cmd) click to toggle source

Returns the set of registered shortcuts for a command, or nil if none.

# File lib/cmd.rb, line 518
def command_shortcuts(cmd)
  shortcuts[cmd]  
end
complete(command) click to toggle source

The default completor. Looks up all do_* methods.

# File lib/cmd.rb, line 354
def complete(command)
  commands = completion_grep(command_list, command)
  if commands.size == 1
    cmd = commands.first
    set_completion_proc(complete_method(cmd)) if collect_complete.include?(cmd)
  end
  commands
end
complete_help(command) click to toggle source

Completor for the help command.

# File lib/cmd.rb, line 429
def complete_help(command)
  completion_grep(documented_commands, command)
end
complete_method(cmd) click to toggle source

The method name that corresponds to the complete command for the pass in command.

# File lib/cmd.rb, line 323
def complete_method(cmd)
  "complete_#{cmd}".intern
end
completion_grep(collection, pattern) click to toggle source
# File lib/cmd.rb, line 433
def completion_grep(collection, pattern)
  collection.grep(/^#{Regexp.escape(pattern)}/)
end
current_command() click to toggle source

The current command.

# File lib/cmd.rb, line 282
def current_command
  translate_shortcut @current_command
end
custom_exception_handler(exception) click to toggle source

Returns the customized handler for the exception

# File lib/cmd.rb, line 252
def custom_exception_handler(exception)
  # grych@tg.pl FIX, was: exception.to_s which return exception message in ruby >=(?) 1.9.3
  custom_exception_handlers[exception.class.to_s]
end
default_prompt() click to toggle source
# File lib/cmd.rb, line 554
def default_prompt
  "#{self.class.name}> "
end
display_prompt(prompt, with_history = true) click to toggle source

Displays the prompt.

# File lib/cmd.rb, line 271
def display_prompt(prompt, with_history = true)
  line = if readline_supported?
    Readline::readline(prompt, with_history)
  else
    print prompt
    @stdin.gets
  end
  line.respond_to?(:strip) ? line.strip : line
end
display_shortcuts(cmd) click to toggle source
# File lib/cmd.rb, line 312
def display_shortcuts(cmd)
  "(aliases: #{shortcuts[cmd].join(', ')})"
end
documented_commands() click to toggle source

List of commands which are documented.

# File lib/cmd.rb, line 397
def documented_commands
  docs.keys.sort
end
empty_line() click to toggle source

Called when an empty line is entered in response to the prompt.

# File lib/cmd.rb, line 347
def empty_line
end
exception_is_handled?(exception) click to toggle source

Determines if the given exception has a custome handler.

# File lib/cmd.rb, line 236
def exception_is_handled?(exception)
  custom_exception_handler(exception)
end
execute_command() click to toggle source
# File lib/cmd.rb, line 204
def execute_command
  unless ARGV.empty?
    stoploop
    execute_line(ARGV * ' ')
  else
    execute_line(display_prompt(prompt, true))
  end
end
execute_line(command) click to toggle source
# File lib/cmd.rb, line 221
def execute_line(command)
  postcmd(run_command(precmd(command)))
end
find_subcommand_in_args(subcommands, args) click to toggle source

Extracts a subcommand if there is one from the command line submitted. I guess this is a hack.

# File lib/cmd.rb, line 502
def find_subcommand_in_args(subcommands, args)
  (subcommands & (1..args.size).to_a.map {|num_elems| args.first(num_elems).join('_')}).max
end
handle_all_remaining_exceptions(exception) click to toggle source
# File lib/cmd.rb, line 213
def handle_all_remaining_exceptions(exception)
  if exception_is_handled?(exception) 
    run_custom_exception_handling(exception) 
  else
    handle_exception(exception)
  end
end
handle_exception(exception) click to toggle source

Exceptions in the cmdloop are caught and passed to handle_exception. Custom exception classes must inherit from StandardError to be passed to handle_exception.

# File lib/cmd.rb, line 266
def handle_exception(exception)
  raise exception
end
has_shortcuts?(cmd) click to toggle source

Indicates if the passed in command has any registerd shortcuts.

# File lib/cmd.rb, line 513
def has_shortcuts?(cmd)
  command_shortcuts(cmd)
end
has_subcommands?(command) click to toggle source

Indicates whether a given command has any subcommands.

# File lib/cmd.rb, line 392
def has_subcommands?(command)
  !subcommands(command).empty?
end
interrupt() click to toggle source

A bit of a hack I'm afraid. Since subclasses will be potentially overriding user_interrupt we want to ensure that it returns true so that it can be called with 'and return'

# File lib/cmd.rb, line 301
def interrupt
  user_interrupt or true
end
parse_line(line) click to toggle source

Receives the line as it was passed from the prompt (barring modification in precmd) and splits it into a command section and an args section. The args are by default set to nil if they are boolean false or empty then joined with spaces. The tokenize method can be used to further alter the args.

# File lib/cmd.rb, line 485
def parse_line(line)
  # line will be nil if ctr-D was pressed
  user_interrupt and return if line.nil? 

  cmd, *args = line.split
  args = args.empty? ? nil : args * ' '
  if args and has_subcommands?(cmd)
    if cmd = find_subcommand_in_args(subcommands(cmd), line.split)
      # XXX Completion proc should be passed array of subcommands somewhere
      args = line.split.join('_').match(/^#{cmd}/).post_match.gsub('_', ' ').strip
      args = nil if args.empty?
    end
  end
  [cmd, args]
end
postcmd(line) click to toggle source

Receives the returned value of the called command.

# File lib/cmd.rb, line 342
def postcmd(line)
  line
end
postloop() click to toggle source

Call back executed at the end of the cmdloop.

# File lib/cmd.rb, line 332
def postloop
end
precmd(line) click to toggle source

Receives line submitted at prompt and passes it along to the command being called.

# File lib/cmd.rb, line 337
def precmd(line)
  line
end
preloop() click to toggle source

Call back executed at the start of the cmdloop.

# File lib/cmd.rb, line 328
def preloop
end
print(*strings) click to toggle source

Writes out a message without newlines appended.

print_help(cmd) click to toggle source

Displays the help for the passed in command.

print_undocumented_commands() click to toggle source
puts(*strings)
Alias for: write
readline_supported?() click to toggle source

Indicates whether readline support is enabled

# File lib/cmd.rb, line 230
def readline_supported?
  @readline_supported = READLINE_SUPPORTED if @readline_supported.nil?
  @readline_supported
end
run_command(line) click to toggle source

Takes care of collecting the current command and its arguments if any and dispatching the appropriate command.

# File lib/cmd.rb, line 462
def run_command(line)
  cmd, args = parse_line(line)
  sanitize_readline_history(line) if line
  unless cmd then empty_line; return end

  cmd = translate_shortcut(cmd)
  self.current_command = cmd
  set_completion_proc(complete_method(cmd)) if collect_complete.include?(complete_method(cmd))
  cmd_method = command(cmd)
  if self.respond_to?(cmd_method) 
    # Perhaps just catch exceptions here (related to arity) and call a
    # method that reports a generic error like 'invalid arguments'
    self.method(cmd_method).arity.zero? ? self.send(cmd_method) : self.send(cmd_method, tokenize_args(args)) 
  else                              
    command_missing(current_command, tokenize_args(args))
  end
end
run_custom_exception_handling(exception) click to toggle source

Runs the customized exception handler for the given exception.

# File lib/cmd.rb, line 241
def run_custom_exception_handling(exception)
  case handler = custom_exception_handler(exception)
  when String
      write handler 
  when Symbol
      # grych@tg.pl FIX: added exception as an argument
      self.send(handler, exception)
  end
end
sanitize_readline_history(line) click to toggle source

Cleans up the readline history buffer by performing tasks such as removing empty lines and piggy-backed duplicates. Only executed if running with readline support.

# File lib/cmd.rb, line 530
def sanitize_readline_history(line)
  return unless readline_supported? 
  # Strip out empty lines
  Readline::HISTORY.pop if line.match(/^\s*$/)
  # Remove duplicates
  Readline::HISTORY.pop if Readline::HISTORY[-2] == line rescue IndexError
end
set_completion_proc(cmd) click to toggle source

Readline completion uses a procedure that takes the current readline buffer and returns an array of possible matches against the current buffer. This method sets the current procedure to use. Commands can specify customized completion procs by defining a method following the naming convetion complet_{command_name}.

# File lib/cmd.rb, line 543
def set_completion_proc(cmd)
  return unless readline_supported?
  Readline.completion_proc = self.method(cmd)
end
setup() click to toggle source

Called at object creation. This can be treated like 'initialize' for sub classes.

# File lib/cmd.rb, line 260
def setup
end
stoploop() click to toggle source
# File lib/cmd.rb, line 225
def stoploop
  @stop = true
end
subcommand_list() click to toggle source

List of all subcommands.

# File lib/cmd.rb, line 381
def subcommand_list
  with_underscore, without_underscore = collect_do.partition {|command| command.include?('_')}
  with_underscore.find_all {|do_method| without_underscore.include?(do_method[/^[^_]+/])}
end
subcommands(command) click to toggle source

Lists all subcommands of a given command.

# File lib/cmd.rb, line 387
def subcommands(command)
  completion_grep(subcommand_list, translate_shortcut(command).to_s + '_')
end
tokenize_args(args) click to toggle source

Called on command arguments as they are passed into the command.

# File lib/cmd.rb, line 523
def tokenize_args(args)
  args
end
translate_shortcut(cmd) click to toggle source

Looks up command shortcuts (e.g. '?' is a shortcut for 'help'). Short cuts can be added by using the shortcut class method.

# File lib/cmd.rb, line 508
def translate_shortcut(cmd)
  command_lookup_table[cmd] || cmd
end
undocumented_commands() click to toggle source

Returns list of undocumented commands.

# File lib/cmd.rb, line 419
def undocumented_commands
  command_list - documented_commands
end
undocumented_commands?() click to toggle source

Indicates if any commands are undocumeted.

# File lib/cmd.rb, line 424
def undocumented_commands?
  !undocumented_commands.empty?
end
undocumented_commands_hidden?() click to toggle source

Indicates whether undocummented commands will be listed by the help command (they are listed by default).

# File lib/cmd.rb, line 403
def undocumented_commands_hidden?
  self.class.hide_undocumented_commands  
end
user_interrupt() click to toggle source

Called when the user hits ctrl-C or ctrl-D. Terminates execution by default.

# File lib/cmd.rb, line 287
def user_interrupt
  write 'Terminating' # XXX get rid of this
  stoploop
end
write(*strings) click to toggle source

Writes out a message with newline.

# File lib/cmd.rb, line 438
def write(*strings)
  # We want newlines at the end of every line, so don't join with "\n"
  strings.each do |string|
    @stdout.write string
    @stdout.write "\n"
  end
end
Also aliased as: puts