view enso/ui/commands/manager.py @ 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
children
line wrap: on
line source

# 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