view enso/ui/commands/interfaces.py @ 26:63b5f5f313b8

Added enso.ui.commands.interfaces.
author Atul Varma <varmaa@toolness.com>
date Sat, 23 Feb 2008 08:49:42 -0600
parents
children 2d7464119dd0
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.interfaces
#
# ----------------------------------------------------------------------------

"""
    This module defines classes that can be sub-classed to implement
    commands.

    == Background ==============================================
    
    In Enso, we are ultimately attempting to transform the characters
    typed by the user (the "user text") into an action (a "command").

    In general, there are two ways of doing this.  The first is an
    absolute mapping between a unique string of characters (the
    "command name") and a distinct action (the "command").
    Technically, every single command can be implemented in this way;
    but from both the user's perspective and the developer's
    perspective, it is unwieldy to think of "fontsize 12" as a
    seperate command from "fontsize 8".

    So enters the second method of mapping user text to commands:
    commands with arguments.  For a command with arguments, the
    command name is split into its "prefix" (the part that is fixed)
    and an "argument" (that part that changes).

    So where does that leave us?  With a complex situation that cannot
    be simplified.

    == Module Description =======================================

    This module lays out four class interfaces: _CommandImpl,
    CommandObject, AbstractCommandFactory, and CommandExpression.

    The _CommandImpl is an abstract class that any command
    implementation should inherit from, i.e., a base class common to
    both CommandObject and AbstractCommandFactory.

    The CommandObject defines the interface for objects that execute a
    single command.  This object encapsulates the actual code
    associated with exactly one unique string of characters typed by
    the user.  It has methods for describing and executing the action
    corresponding to exactly one possible user text.

    The AbstractCommandFactory defines a generic interface for objects
    that implement commands with arguments, and therefore can respond
    to a range of possible user text strings.  As its name implies,
    the ultimate goal of a CommandFactory is to produce an actual
    command, in this case, a CommandObject.  The CommandFactory has
    methods for determining whether or not it can produce a command
    corresponding to some user text, as well as for producing a
    CommandObject when required.

    Finally, the CommandExpression encapsulates the form that a
    command name can take. For many commands, this will simply be the
    unique string of characters that form the command's name.  But for
    commands that can take arguments, a CommandExpression can take a
    more complicated form.

    Whereever commands are implemented, there should be an exact
    mapping between CommandExpression and _CommandImpl objects.
"""

# ----------------------------------------------------------------------------
# Command Objects
# ----------------------------------------------------------------------------

class _CommandImpl( object ):
    """
    A "command implementation" is either a CommandObject (for commands
    that take no arguments) or a CommandFactory (for commands that
    take arguments).

    This class is a placeholder ancestor class that holds
    functionality (e.g., product association) common to both kinds of
    implementations.
    """

    def __init__( self ):
        """
        Initializes the command implementation object.
        """
        
        self.__products = []
        self.__description = None
        self.__helpText = None

    # LONGTERM TODO: Consider using Python's property() function
    # for providing access to these data members.
    
    def addProduct( self, productId ):
        self.__products.append( productId )

    def getProducts( self ):
        return self.__products
    
    def setDescription( self, descr ):
        self.__description = descr

    def getDescription( self ):
        return self.__description


    def setHelp( self, helpText ):
        self.__helpText = helpText

    def getHelp( self ):
        return self.__helpText


class CommandObject( _CommandImpl ):
    """
    An object with a run() method which implements the action of a
    command.  It also has various getters and setters for certain
    properties of commands.
    """
    
    def __init__( self ):
        """
        Initializes the command object.
        """
        
        _CommandImpl.__init__( self )
        
        self.__name = None


    def getName( self ):
        return self.__name

    def setName( self, name ):
        self.__name = name


    def run( self ):
        """
        Abstract Method: Should execute the command.

        NOTE: This method should execute shortly, and return
        immediately. If the command implementation needs to launch a
        thread or process in order to accomplish this, then so be it.

        NOTE: Commands should only raise exceptions when they are
        actually broken.  Any "user errors", like malformed selections
        or invalid applications, should cause the command to do
        something graceful (e.g., showing a primary message), and
        should not raise an exception.
        """

        raise NotImplementedError()

class AbstractCommandFactory( _CommandImpl ):
    """
    A "CommandFactory" is an object which can take some text, and
    return the best matched CommandObject from among some collection
    that it understands.  It is intended to implement commands with
    arguments (i.e., sets of closely related commands with a common
    prefix in their name).

    To allow for implementation optimizations, this class has methods
    for fetching Suggestions for a given chunk of user text.  That
    way, subclasses can determine the most appropriate autocompletions
    and suggestions.  For example, a command factory implementing the
    "FONT SIZE {number}" set of commands might never provide
    autocompletions, because it may be determined that the user should
    always enter an exact number.
    """

    def getCommandList( self ):
        """
        Returns a list of all available command names (a list of strings).
        """

        raise NotImplementedError()


    def retrieveSuggestions( self, userText ):
        """
        Returns a list containing the VERY LATEST suggestions (in the
        form of Suggestion objects) available that match the userText
        string.  Can (and often will) have side effects on the object's
        internal data structure.
        """

        raise NotImplementedError()

        
    def autoComplete( self, userText ):
        """
        If this factory can produce a match to userText, then returns
        an AutoCompletion object.  Otherwise, returns None.
        """

        raise NotImplementedError()


    def getCommandObj( self, commandName ):
        """
        Should return a CommandObject matching commandName, or else
        None.
        """

        raise NotImplementedError()


# ----------------------------------------------------------------------------
# Command Expression Class
# ----------------------------------------------------------------------------

class CommandExpression:
    """
    A "CommandExpression" is an object that encapsulates the basic form
    of the name or names of a given command implementation.

    Command expressions, in their string form, are either strings
    that are exactly one command name, or are strings containing
    curly brackets {} to indicate that they take a parameter, e.g.:
      "minimize"
      "upper case"
      "open {object}"
      "goto {window name}"
      "font size {number}"
    There must be at most one curly-bracket parameter, and it must come
    at the end of the string.
    """

    def __init__( self, stringExpression ):
        """
        Instantiates the expression object.
        """

        expr, prefix, arg = self.__computeExpression( stringExpression )
        self.__string = expr
        self.__prefix = prefix
        self.__arg = arg


    def __str__( self ):
        return self.getString()
    
    def getString( self ):
        return self.__string

    def getPrefix( self ):
        return self.__prefix

    def getArg( self ):
        return self.__arg

    def hasArgument( self ):
        return len(self.__arg) > 0

    def __computeExpression( self, expr ):
        """
        Calculates and stores all approriate information for
        expr.
        """
        
        bracket1 = expr.find( "{" )
        bracket2 = expr.find( "}" )

        why = "Malformed command expression: %s" % expr
        # The end bracket must be at the end, or not found at all.
        assert ( bracket2 == len(expr)-1 ) or \
               ( bracket2 == -1 ), why
        # Either both brackets are found, or neither are found.
        assert ( bracket1 == -1 or not bracket2 == -1 ), why

        string = expr
        # The argument is everything after the first bracket, up to
        # but not including the second bracket:
        if bracket1 > -1:
            arg = expr[bracket1+1:bracket2]
            prefix = expr[:bracket1]
        else:
            arg = ""
            prefix = expr

        return string, prefix, arg


    def matches( self, userText ):
        """
        Determines whether userText matches this command expression;
        note that this does not necessarily guarantee that userText
        maps to an existing command.

        Returns True if:
          (1) self.__prefix starts with userText, or
          (2) userText starts with self.__prefix.
        Returns False otherwise.
        """

        if len(userText)<len(self.__prefix):
            return self.__prefix.startswith( userText )
        else:
            return userText.startswith( self.__prefix )
        return True