Mercurial > enso_core
changeset 25:8f4663bc7486
Added implementation of enso.ui.commands.manager.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Sat, 23 Feb 2008 08:40:35 -0600 |
parents | 1964b0e2c912 |
children | 63b5f5f313b8 |
files | enso/ui/commands/manager.py |
diffstat | 1 files changed, 313 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/enso/ui/commands/manager.py Sat Feb 23 08:40:35 2008 -0600 @@ -0,0 +1,313 @@ +# Copyright (c) 2008, Humanized, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of Enso nor the names of its contributors may +# be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Humanized, Inc. ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Humanized, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# ---------------------------------------------------------------------------- +# +# enso.ui.commands.manager +# +# ---------------------------------------------------------------------------- + +""" + The CommandManager singleton. +""" + +# ---------------------------------------------------------------------------- +# Imports +# ---------------------------------------------------------------------------- + +import logging + +from enso.ui.commands.interfaces import CommandExpression, CommandObject +from enso.ui.commands.interfaces import AbstractCommandFactory +from enso.ui.commands.factories import GenericPrefixFactory + + +# ---------------------------------------------------------------------------- +# The Command Manager +# ---------------------------------------------------------------------------- + +class _TheCommandManager: + """ + Provides an interface to register and retrieve all commands. + + Allows client code to register new command implementations, find + suggestions, and retrieve command objects. + """ + + CMD_KEY = CommandExpression( "{all named commands}" ) + + def __init__( self ): + """ + Initializes the command manager. + """ + + self.__cmdObjReg = CommandObjectRegistry() + self.__cmdFactoryDict = { + self.CMD_KEY : self.__cmdObjReg, + } + + + def registerCommand( self, cmdName, cmdObj ): + """ + Called to register a new command with the command manager. + """ + + try: + cmdExpr = CommandExpression( cmdName ) + except AssertionError, why: + logging.error( "Could not register %s : %s " + % ( cmdName, why ) ) + raise + + if cmdExpr.hasArgument(): + # The command expression has an argument; it is a command + # with an argument. + assert isinstance( cmdObj, AbstractCommandFactory ) + assert not self.__cmdFactoryDict.has_key( cmdExpr ) + self.__cmdFactoryDict[ cmdExpr ] = cmdObj + else: + # The command expression has no argument; it is a + # simple command with an exact name. + assert isinstance( cmdObj, CommandObject ), \ + "Could not register %s" % cmdName + self.__cmdObjReg.addCommandObj( cmdObj, cmdExpr ) + + def unregisterCommand( self, cmdName ): + cmdFound = False + for cmdExpr in self.__cmdFactoryDict.keys(): + if str(cmdExpr) == cmdName: + del self.__cmdFactoryDict[cmdExpr] + cmdFound = True + break + + if not cmdFound: + self.__cmdObjReg.removeCommandObj( cmdName ) + cmdFound = True + if not cmdFound: + raise RuntimeError( "Command '%s' does not exist." % cmdName ) + + def getCommandExpression( self, commandName ): + """ + Returns the unique command expression that is assosciated with + commandName. For example, if commandName is 'open emacs', and + the command expression was 'open {file}', then a command expression + object for 'open {file}' will be returned. + """ + + commands = [] + + for expr in self.__cmdFactoryDict.iterkeys(): + if expr.matches( commandName ): + # This expression matches commandName; try to fetch a + # command object from the corresponding factory. + cmd = self.__cmdFactoryDict[expr].getCommandObj( commandName ) + if expr == self.CMD_KEY and cmd != None: + commands.append( ( commandName, commandName ) ) + elif cmd != None: + # The factory returned a non-nil command object. + # Make sure that nothing else has matched this + # commandName. + commands.append( (expr.getPrefix(), expr) ) + + if len(commands) == 0: + return None + else: + # If there are several matching commands, return only + # the alphabetically first. + commands.sort( lambda a,b : cmp( a[0], b[0] ) ) + return commands[0][1] + + + def getCommand( self, commandName ): + """ + Returns the unique command with commandName, based on the + registered CommandObjects and the registered CommandFactories. + + If no command matches, returns None explicitly. + """ + + commands = [] + + for expr in self.__cmdFactoryDict.iterkeys(): + if expr.matches( commandName ): + # This expression matches commandName; try to fetch a + # command object from the corresponding factory. + cmd = self.__cmdFactoryDict[expr].getCommandObj( commandName ) + if cmd != None: + # The factory returned a non-nil command object. + commands.append( ( expr, cmd ) ) + + if len( commands ) == 0: + return None + else: + # If there are several matching commands, return only + # the alphabetically first. + prefixes = [ (e.getPrefix(),c) for (e,c) in commands ] + prefixes.sort( lambda a,b : cmp( a[0], b[0] ) ) + return prefixes[0][1] + + + def autoComplete( self, userText ): + """ + Returns the best match it can find to userText, or None. + """ + + completions = [] + + # Check each of the command factories for a match. + for expr in self.__cmdFactoryDict.iterkeys(): + if expr.matches( userText ): + cmdFact = self.__cmdFactoryDict[expr] + completion = cmdFact.autoComplete( userText ) + if completion != None: + completions.append( completion ) + + if len( completions ) == 0: + return None + else: + completions.sort( lambda a,b : cmp( a.toText(), b.toText() ) ) + return completions[0] + + + def retrieveSuggestions( self, userText ): + """ + Returns an unsorted list of suggestions. + """ + + suggestions = [] + # Extend the suggestions using each of the command factories + for expr in self.__cmdFactoryDict.iterkeys(): + if expr.matches( userText ): + factory = self.__cmdFactoryDict[expr] + suggestions += factory.retrieveSuggestions( userText ) + + return suggestions + + + def getCommands( self ): + """ + Returns a dictionary of command expression strings and their + associated implementations (command objects or factories). + """ + + # Get a dictionary form of the command object registry: + cmdDict = self.__cmdObjReg.getDict() + + # Extend the dictionary to cover the command factories. + for expr in self.__cmdFactoryDict.keys(): + if expr == self.CMD_KEY: + # This is the command object registry; pass. + pass + else: + # Cast the expression as a string. + cmdDict[ str(expr) ] = self.__cmdFactoryDict[expr] + + return cmdDict + + +# ---------------------------------------------------------------------------- +# A CommandObject Registry +# ---------------------------------------------------------------------------- + +class CommandAlreadyRegisteredError( Exception ): + """ + Error raised when someone tries to register two commands under + the same name with the registry. + """ + + pass + + +class CommandObjectRegistry( GenericPrefixFactory ): + """ + Class for efficiently storing and searching a large number of + commands (where each command is an object with a corresponding + command name). + """ + + PREFIX = "" + + def __init__( self ): + """ + Initialize the command registry. + """ + + GenericPrefixFactory.__init__( self ) + + self.__cmdObjDict = {} + self.__dictTouched = False + + def update( self ): + pass + + def getDict( self ): + return self.__cmdObjDict + + def addCommandObj( self, command, cmdExpr ): + """ + Adds command to the registry under the name str(cmdExpr). + """ + + assert isinstance( cmdExpr, CommandExpression ) + assert not cmdExpr.hasArgument() + + cmdName = str(cmdExpr) + if self.__cmdObjDict.has_key( cmdName ): + raise CommandAlreadyRegisteredError() + + self.__cmdObjDict[ cmdName ] = command + self.__dictTouched = True + + self._addPostfix( cmdName ) + + + def removeCommandObj( self, cmdExpr ): + cmdFound = False + if self.__cmdObjDict.has_key( cmdExpr ): + del self.__cmdObjDict[cmdExpr] + cmdFound = True + if cmdFound: + self.__dictTouched = True + self._removePostfix( cmdExpr ) + else: + raise RuntimeError( "Command object '%s' not found." % cmdExpr ) + + + + def getCommandObj( self, cmdNameString ): + """ + Returns the object corresponding to cmdNameString. + + NOTE: This will raise a KeyError if cmdNameString is not a + valid command name. + """ + + try: + return self.__cmdObjDict[ cmdNameString ] + except KeyError: + return None