Mercurial > enso_core
changeset 26:63b5f5f313b8
Added enso.ui.commands.interfaces.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Sat, 23 Feb 2008 08:49:42 -0600 |
parents | 8f4663bc7486 |
children | 943e412a8bd9 |
files | enso/ui/commands/interfaces.py |
diffstat | 1 files changed, 330 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/enso/ui/commands/interfaces.py Sat Feb 23 08:49:42 2008 -0600 @@ -0,0 +1,330 @@ +# 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 + +