All pastes #924488 Raw Edit

Unnamed

public text v1 · immutable
·published 2008-03-01 12:05 UTC
rendered paste body
# ----------------------------------------------------------------------------
# Copyright (c) 2007 Humanized, Inc. All rights reserved.
# ----------------------------------------------------------------------------
#
#   SampleEnsoExtension.py
#
#   Python Version - 2.4
#
# ----------------------------------------------------------------------------

"""
    This sample script sets up an Enso Extension for use with
    the Enso Developer Prototype Beta Product.
"""

# ----------------------------------------------------------------------------
# Imports
# ----------------------------------------------------------------------------

from SimpleXMLRPCServer import SimpleXMLRPCServer
import xmlrpclib
import socket
import time
import threading


# ----------------------------------------------------------------------------
# Constants
# ----------------------------------------------------------------------------

# The full URL for the XML-RPC endpoint of the Enso Developer
# Prototype, which we need to connect to in order to access Enso
# services.  For the time being, this is hard-coded, but in the future
# we may use some sort of name service to look it up.
XMLRPC_ENDPOINT_URL = "http://127.0.0.1:11374"

# The TCP port that our Enso Extension's XML-RPC endpoint will be
# located on.
EXTENSION_ENDPOINT_PORT = 8620

# The IP address that our Enso Extension's XML-RPC endpoint be located
# on.
EXTENSION_ENDPOINT_ADDRESS = "127.0.0.1"

# The full URL for our Enso Extension's XML-RPC endpoint.
EXTENSION_ENDPOINT_URL = "http://%s:%d" % ( EXTENSION_ENDPOINT_ADDRESS,
                                            EXTENSION_ENDPOINT_PORT )


# ----------------------------------------------------------------------------
# Server Thread
# ----------------------------------------------------------------------------

class ServerThread(threading.Thread):
    """
    Simple thread that encapsulates the running of the server that
    hosts our Enso Extension's XML-RPC endpoint.
    """

    def __init__( self, address ):
        threading.Thread.__init__( self )
        self._rpcServer = None
        self._stop = False
        self._address = address

    def run( self ):
        self._rpcServer = SimpleXMLRPCServer( self._address )

        # We want to set the timeout so that we can CTRL-C out of the
        # server on Windows machines.  Windows won't let keyboard
        # interrupts kick a process out of a socket system call, so we
        # have to listen for incoming connections in 1-second
        # "chunks".
        self._rpcServer.socket.settimeout( 1.0 )

        self._rpcServer.register_instance( EnsoExtensionMethods() )
        while not self._stop:
            self._rpcServer.handle_request()

    def stop( self ):
        self._stop = True

class EnsoExtensionMethods( object ):
    """
    Methods for our Enso Extension's XML-RPC endpoint, which implement
    the Enso Extension API.  Every public method here (i.e., every
    method that doesn't start with an underscore character) is a
    required method that must be implemented by all Enso Extensions,
    which Enso will call into when an Extension's services are
    required.
    """

    def __init__( self ):
        self.enso = xmlrpclib.ServerProxy( XMLRPC_ENDPOINT_URL )

    def callCommand( self, commandName, postfix ):
        """
        Extension API method that is called whenever an Enso user has
        executed a command that our Extension implements.

        This method must complete as soon as possible, because it is
        currently executed synchronously by Enso.

        'commandName' is the full name of the command that is to be
        run.

        'postfix' is the postfix (i.e., argument) that the user has
        specified for the command.  For instance, in the 'order
        {food}' command, this will be a string representing the type
        of food the end-user wants to order.  If a postfix is not
        specified or not applicable to a command, this will be an
        empty string.
        """

        if commandName == "translate to swedish":
            self.__translateToSwedish()
        elif commandName == "order {food}":
            self.__orderFood( postfix )
        else:
            raise AssertionError( "Unknown command name: %s" % \
                                  commandName )

        # The Enso Developer Prototype doesn't look at this return
        # value, but we can't return None because it's not part of the
        # XML-RPC specification, so we'll return True instead.
        return True

    def __translateToSwedish( self ):
        """
        Implementation of the 'translate to swedish' command.
        """

        import urllib

        # URL of the Swedish translation web service.
        URL = "http://www.cs.utexas.edu/users/jbc/bork/bork.cgi?"

        # Call an Enso Developer Prototype API function to get the
        # user's current unicode selection.
        text = self.enso.getUnicodeSelection()

        if len( text ) == 0:
            # Use the Enso Developer Prototype API to display a
            # transparent primary message to the end-user.
            self.enso.displayMessage( "<p>No text selected!</p>" )
        else:
            params = { "input": text, "type": "chef" }
            params = urllib.urlencode( params )

            # Note that we're making a network connection here, which
            # could take a long time to complete.  Ideally, this
            # should be executed in a separate thread, and only if it
            # takes longer than about 250 ms--the maximum wait time
            # before end-users start clicking on things--should the
            # Enso Extension print a primary message telling the
            # end-user to "please wait".  Furthermore, network errors
            # should be caught and converted into primary messages
            # informing the end-user.  Actually implementing such
            # behavior is left as an exercise for the reader.
            translated = urllib.urlopen( URL, params ).read()
            translated = translated.decode( "iso-8859-1" )

            # Use the Enso Developer Prototype API to replace the
            # user's current unicode selection.  The second parameter
            # is the name of the command that is originating the
            # change, for display purposes only.  If the unicode
            # selection can't be changed (e.g., because it is static
            # text on a web page), then the text will automatically be
            # inserted into Enso's put buffer for insertion at a later
            # time by the user (via the 'put' command).
            self.enso.setUnicodeSelection( translated,
                                           "translate to swedish" )

    def __orderFood( self, foodName ):
        """
        Implementation of the 'order {food}' command.
        """

        # The 'caption' tag can be used to provide a caption for the
        # main text, displayed in a slightly smaller font and
        # different color from the main text.
        self.enso.displayMessage(
            u"<p>Ordering %s\u2026</p>"
            "<caption>One moment please.</caption>" % foodName
            )

        def foodOrderConfirmation():
            """
            Simple thread that fakes the ordering of food and displays
            a primary message to the user when finished.

            This shows that Enso Developer Prototype API calls can be
            made from anywhere--not just from the context of a command
            execution.
            """

            time.sleep( 3 )
            self.enso.displayMessage(
                "<p>%s ordered!</p>"
                "<caption>I didn't really order it, though.</caption>" % \
                foodName
                )

        thread = threading.Thread( target = foodOrderConfirmation )
        # Just in case we kill the Extension, we don't want to
        # have to wait until the fake food ordering is complete. :)
        thread.setDaemon( True )
        thread.start()


if __name__ == "__main__":
    # For some reason we need to do this or else we'll get annoying
    # exceptions along the lines of "the socket operation could not
    # complete without blocking."
    socket.setdefaulttimeout( 10.0 )

    print "Starting XML-RPC server."
    serverThread = ServerThread( (EXTENSION_ENDPOINT_ADDRESS,
                                  EXTENSION_ENDPOINT_PORT) )
    serverThread.start()

    try:
        enso = xmlrpclib.ServerProxy( XMLRPC_ENDPOINT_URL )
        # Register our commands with the Enso Developer Prototype.
        enso.registerCommand(
            EXTENSION_ENDPOINT_URL,
            # Name of the command
            "translate to swedish",
            # One-line description of the command, which will be shown
            # in the command quasimode.
            "Translates your current selection to Swedish.",
            # XHTML fragment containing help text for the command,
            # which will be shown if the user executes the 'help'
            # command on this command.
            "<p>This command translates the current" \
            "text selection to Swedish.  Try it on this " \
            "sentence.</p>",
            # The postfix type of the command; it can either be
            # 'none', in which case this is an ordinary command that
            # takes no special postfix argument, or 'bounded', in
            # which case the end-user can choose a postfix from a list
            # of possibilities.
            "none"
            )
        enso.registerCommand(
            EXTENSION_ENDPOINT_URL,
            "order {food}",
            "order some food.",
            "<p>Allows you to order food.</p>",
            "bounded"
            )
        # Set valid postfixes for the 'order {food}' bounded command.
        # This function can be called at any time, if and when the
        # list of valid postfixes changes.
        enso.setCommandValidPostfixes( EXTENSION_ENDPOINT_URL,
                                       "order {food}",
                                       ["pizza", "lingonberries"] )

        try:
            # Now just block for input while our server thread
            # listens for incoming connections from Enso.
            print "press enter to exit."
            raw_input()
        finally:
            # Before we leave, let Enso know that we're not offering
            # our commands anymore.
            enso.unregisterCommand( EXTENSION_ENDPOINT_URL,
                                    "translate to swedish" )
            enso.unregisterCommand( EXTENSION_ENDPOINT_URL,
                                    "order {food}" )
    finally:
        print "Shutting down XML-RPC server."
        serverThread.stop()