Beginning a shell class

git-svn-id: svn+ssh://localhost/home/henryk/svn/cyberflex-shell/trunk@31 f711b948-2313-0410-aaa9-d29f33439f0b
This commit is contained in:
henryk 2005-10-04 06:04:23 +00:00
parent d8dab69c26
commit 9f8f494c36
1 changed files with 176 additions and 0 deletions

176
shell.py Normal file
View File

@ -0,0 +1,176 @@
# -*- coding: iso-8859-1 -*-
import os, re, binascii, sys, exceptions, traceback, inspect
try:
import readline
except ImportError:
pass
class Shell:
"""This class handles a shell with all the usual goodies: history,
tab-completion, start-up script. The readline module will be used when
available to provide some of the functionality."""
def __init__(self, basename):
"""Creates a new shell object with the specified basename. The
basename will be used when constructing the history and start-up script
filenames: ~/.basename.history and ~/.basenamerc.
Note that the start-up script will not be called at instantiation time
but when starting the main-loop. This is so that you can register your
own commands first."""
if sys.modules.has_key("readline"):
histfile = os.path.join(os.environ["HOME"], ".%s.history" % basename)
try:
readline.read_history_file(histfile)
except IOError:
pass
import atexit
atexit.register(readline.write_history_file, histfile)
del histfile
readline.parse_and_bind("tab: complete")
## FIXME basenamerc
else:
print >>sys.stderr, "Warning: No readline module available. Most functionality will be missing."
self._commandsets = [] ## This contains command sets, it's a list of (object, dictionary) tuples
self.basename = basename
self.env = {}
self.register_commands(self)
def _make_cmdset(target, commands):
"""Convenience function for code shared between register_commands
and unregister_commands."""
if commands is None:
if isinstance(target, dict):
new_target = None
new_commands = target
elif hasattr(target, "COMMANDS"):
new_target = target
new_commands = target.COMMANDS
else:
raise TypeError, "target must be either an object with a COMMANDS attribute or a dictionary, not %s" % type(target)
else:
if isinstance(commands, dict):
new_target = target
new_commands = commands
else:
raise TypeError, "commands must be a dictionary, not %s" % type(commandset)
return (new_target, new_commands)
_make_cmdset = staticmethod(_make_cmdset)
def register_commands(self, target, commands=None):
"""Register an object to provide commands.
When commands is None or not given then target must either be
an object with a COMMANDS attribute or a dictionary mapping command
strings to functions. When commands is given then target can be any
object and commands must be a dictionary mapping command strings to
functions."""
new_commandset = self._make_cmdset(target, commands)
current_commands = self.get_command_mapping()
for (command,function) in new_commandset[1].items():
if not hasattr(function, "__doc__"):
print >>sys.stderr, "Warning: function %s does not have a docstring, bug author" % function
old_commandset = current_commands.get(command)
if old_commandset is not None:
print >>sys.stderr, "Warning: command '%s' already defined from %s, new definition from %s" % (
command, old_commandset[0] or "Anonymous list", new_target or "Anonymous list"
)
self._commandsets.append( new_commandset )
def unregister_commands(self, target, command=None):
"""Unregister an object to provide commands.
You should provide the same parameters as in the call to
register_commands()."""
old_commandset = self._make_cmdset(target, commands)
return self._commandsets.remove( old_commandset )
def get_command_mapping(self):
"""Returns a dictionary that maps commands to their commandsets."""
commands = {}
for cmdset in self._commandsets:
for (command, function) in cmdset[1].items():
commands[command] = cmdset
return commands
def has_command(self, name):
"""Returns whether this shell knows about a specified command."""
for cmdset in self._commandsets:
for (command, function) in cmdset[1].items():
if command == name:
return True
return False
def help(self, name):
"""Return a dictionary with help about command named name. The dictionary
keys are: name, formatted_parameters, description, long_description"""
for cmdset in self._commandsets:
for (command, function) in cmdset[1].items():
if command == name:
parts = function.__doc__.split("\n", 1)
if len(parts) != 2:
parts = [ e.strip() for e in (parts + ["",""])[:2] ]
(argnames, varargs, varkw, defaults) = \
inspect.getargspec(function)
if argnames[0] == "self": ## Any better way?
argnames = argnames[1:]
len_mandatory = len(argnames) - \
len(defaults or [])
argstring = " ".join(
[(i < len_mandatory and "%s" or "[%s]")
% argnames[i] for i in range(len(argnames))]
)
return { "name": command,
"formatted_parameters": argstring,
"description": parts[0],
"long_description": parts[1]
}
raise ValueError, "No such command '%s'" % name
def cmd_exit(self):
"Exit the shell."
sys.exit(0)
def cmd_set(self, name=None, value=None):
"Set a variable or print current settings."
SHORT_HELP_FORMATSTRING = "%(name)-20s %(description)s"
LONG_HELP_FORMATSTRING = "%(description)s\nSynopsis: %(name)s %(formatted_parameters)s\n%(long_description)s"
def cmd_help(self, command=None):
"Print help, either for all commands or for a specific one."
if command is None:
command_list = self.get_command_mapping().keys()
for command in command_list:
print self.SHORT_HELP_FORMATSTRING % self.help(command)
else:
if self.has_command(command):
print self.LONG_HELP_FORMATSTRING % self.help(command)
else:
print "No such command '%s'" % command
COMMANDS = {
"exit": cmd_exit,
"set": cmd_set,
"help": cmd_help
}
if __name__ == "__main__":
s = Shell("foobar")