From 9f8f494c36fd481fdba55e1ebdb894da80d6b12e Mon Sep 17 00:00:00 2001 From: henryk Date: Tue, 4 Oct 2005 06:04:23 +0000 Subject: [PATCH] Beginning a shell class git-svn-id: svn+ssh://localhost/home/henryk/svn/cyberflex-shell/trunk@31 f711b948-2313-0410-aaa9-d29f33439f0b --- shell.py | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 shell.py diff --git a/shell.py b/shell.py new file mode 100644 index 0000000..3d1538e --- /dev/null +++ b/shell.py @@ -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") + +