Can now script yourself.

Can now script yourself.

system.require('scriptingGuiUtil.em');

(function()
{
    var TIME_TO_WAIT_FOR_RESPONSE = 20;

    //actIDs to std.ScriptingGui.Actions
    var actionMap = {};

    var guiScriptActionId = std.ScriptingGui.Util.uniqueId();
    var scriptAction =
        new std.ScriptingGui.Action('script action','',guiScriptActionId);

    //map from visible id to visible object
    //which visibles are near me
    var nearbyVisMap   = {};
    //map from visible id to fileManagerElement
    //which visibles I have scripted before.
    var scriptedVisMap = {};

    var scripter    = null;
    var console     = null;
    var gui         = null;

    //basic constructor
    std.ScriptingGui.Controller = function(simulator,presScripter)
    {
        console = new std.ScriptingGui.Console(scriptedVisMap);

        scripter = presScripter;
        if (typeof(presScripter) === 'undefined')
            scripter = system.self;

        gui = new std.ScriptingGui(
            this,simulator,nearbyVisMap,
            scriptedVisMap,actionMap,console);

        instantiateProxHandlers();
        this.addVisible(scripter);
    };

    //************* ACTIONS ***********************//
    function existsInActMap(id)
    {
        return (id in actionMap);
    }

    std.ScriptingGui.Controller.prototype.getScriptActionId =
        function()
    {
        return guiScriptActionId;
    };

    std.ScriptingGui.Controller.prototype.execScriptAction =
        function(visId,text)
    {

        if (! this.scriptedVisExists(visId))
        {
            throw new Error('Error executing script action.  ' +
                            'Have no record of visId: ' + visId.toString());
        }

        scriptAction.edit(text);
        var vis = scriptedVisMap[visId].vis;
        return internalExecAction(this,scriptAction,vis);
    };

    std.ScriptingGui.Controller.prototype.addAction =
        function(name,text)
    {
        var actId =  std.ScriptingGui.Util.uniqueId();
        actionMap[actId] =
            new std.ScriptingGui.Action(name,text,actId);

        gui.redraw();
        return actId;
    };

    std.ScriptingGui.Controller.prototype.removeAction =
        function(id)
    {
        if (!existsInActMap(id))
        {
            throw new Error('Cannot remove action, because ' +
                            'have no record of it.');
        }

        delete actionMap[id];
    };

    std.ScriptingGui.Controller.prototype.editAction =
        function(id,newText)
    {
        if (!existsInActMap(id))
        {
            throw new Error('Cannot modify action, because ' +
                            'have no record of it.');
        }

        actionMap[id].edit(newText);
    };

    std.ScriptingGui.Controller.prototype.execAction =
        function(actId,visId)
    {
        if (!existsInActMap(actId))
        {
            throw new Error('Cannot execute action, because ' +
                            'have no record of it.');
        }

        var vis = getVisAndChangeToUsed(visId,this);

        if (vis === null)
        {
            throw new Error ('Cannot execute action on visible ' +
                             visId + ' have no record of.');
        }

        internalExecAction(this,action,vis);
    };

    function internalExecAction(controller,action,vis)
    {
        var visId = vis.toString();

        scripter # action.createScriptMsg() >> vis >>
            [std.core.bind(onActResp,undefined,controller,action),
             TIME_TO_WAIT_FOR_RESPONSE,
             std.core.bind(onNoActResp,undefined,controller,action,visId)];

        var consDescription = action.getConsoleDescription(visId);
        console.scriptSentEvent(consDescription);
    }

    /**
     If we've alread scripted this visible before (visId key exists in
     nearbyVisMap), then returns the visible object associated with
     it.

     If we have not already scripted this object before, then check if
     object is in nearbyVisMap.  If it is, then we create a new
     filemanagerelement object associated with visible in
     scriptedVisMap, and return visible associated with it.

     Otherwise, returns null.
     */
    function getVisAndChangeToUsed(visId,controller)
    {
        var returner = null;
        if (visId in scriptedVisMap)
            returner = scriptedVisMap[visId].vis;
        else if (visId in nearbyVisMap)
        {
            returner = nearbyVisMap[visId];
            controller.addVisible(returner);
        }

        return returner;
    }

    /**
     @param {object} msg -- response sent from other side.

     @param {visible} sender -- visible that we tried to run action on.

     @param {std.ScriptingGui.Controller} controller -- the scripting
     gui (can use it to access console object).  (FIXME: not really
     needed anymore.)

     @param {std.ScriptingGui.Action} action -- action that was
     requested of other side.

     */
    function onActResp(controller,action,msg,sender)
    {
        var resp=
            action.getConsoleResponseDescription(msg,sender.toString());
        //tell the console about the change
        console.scriptRespEvent(resp);
    }

    /**
     @param {std.ScriptingGui.Controller} controller -- the scripting
     gui (can use it to access the console object).

     @param {std.ScriptingGui.Action} action -- the action object that
     we tried to execute on the visible with id visId.

     @param {string} visId -- the id of the visible we tried to
     execute the action on.
     */
    function onNoActResp(controller,action,visId)
    {
        var resp =
            action.getConsoleNoResponseDescription(visId);

        //tell the console about the change
        console.scriptRespEvent(resp);
    }

    //************** VISIBLES ***********************//

    function instantiateProxHandlers()
    {
        system.self.onProxAdded(addToNearby,true);
        system.self.onProxRemoved(removeFromNearby);
        function addToNearby(newVis)
        {
            nearbyVisMap[newVis.toString()] = newVis;

            var consMsg = {
                    type: 'NEARBY_ADDED',
                    addedVis: newVis
                };
            console.nearbyEvent(consMsg);
        }
        function removeFromNearby(oldVis)
        {
            delete nearbyVisMap[oldVis.toString()];

            var consMsg = {
                    type: 'NEARBY_REMOVED',
                    removedVis: oldVis
                };
            console.nearbyEvent(consMsg);
        }
    }

    //************** FILES    ***********************//

    //returns true if visId is a key in scriptedVisMap (ie, have
    //already scripted a visible with id visId)
    std.ScriptingGui.Controller.prototype.scriptedVisExists =
        function(visId)
    {
        return (visId in scriptedVisMap);
    };

    std.ScriptingGui.Controller.prototype.addVisible =
        function(visToAdd)
    {
        if (this.scriptedVisExists(visToAdd))
        {
            throw new Error ('Error in FileManager.addVisible.  ' +
                             'Already had record for this visible.');
        }

        scriptedVisMap[visToAdd.toString()] =
            new std.FileManager.FileManagerElement(
                visToAdd,defaultErrorFunction,
                generateScriptingDirectory(visToAdd));

        var consMsg = {
                type: 'ADD_VISIBLE',
                visible: visToAdd
            };

        console.scriptedObjEvent(consMsg);
        gui.redraw();
    };

    std.ScriptingGui.Controller.prototype.removeVisible =
        function(visToRemove)
    {
        if (!this.scriptedVisExists(visToRemove))
        {
            throw new Error ('Error in FileManager.removeVisible.  ' +
                             'Do not have record for this visible.');
        }
        system.__debugPrint('\nNote: not removing previously-written files.\n');
        delete scriptedVisMap[visToRemove.toString()];

        var consMsg = {
                type: 'REMOVE_VISIBLE',
                visible: visToRemove
            };
        console.scriptedObjEvent(consMsg);
    };

    std.ScriptingGui.Controller.prototype.removeFile =
        function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.removeFile.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].removeFile(filename);
    };

    /**
     @param vis (can be string or visible)
     @param {String} filename (optional).  If undefined, will reread
     all files associated with this visible from disk.
     */
    std.ScriptingGui.Controller.prototype.rereadFile = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.reareadFile.  '+
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].rereadFile(filename);
    };

    std.ScriptingGui.Controller.prototype.rereadAllFiles =
        function(visId)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.reareadAllFiles.  '+
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].rereadAllFiles();
    };

    /**
     @returns {object: <string (visibleId):
              object: <string(filename):string(filename)>>}

     keyed by visible id, elements are maps of files that each remote
     visible has on it.

     @see ishmaelRedraw in scriptingGui.em
     */
    std.ScriptingGui.Controller.prototype.htmlFileMap =function()
    {
        var returner = {};

        for (var s in scriptedVisMap)
            returner[s] = scriptedVisMap[s].getAllFilenames();
        return returner;
    };

    //if text is undefined, means just use the version on disk.
    std.ScriptingGui.Controller.prototype.addFile = function(vis,filename,text)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.addFile.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].addFile(filename,text);
    };

    //checks to see if file exists.  if does, then use the text from
    //that file.  if does not, then creates it with empty string of text.
    std.ScriptingGui.Controller.prototype.addExistingFileIfCan =
        function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.addExistingFileIfCan.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].addExistingFileIfCan(filename);
    };

    std.ScriptingGui.Controller.prototype.checkFileExists = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.checkFileExists.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].checkFileExists(filename);
    };

    std.ScriptingGui.Controller.prototype.updateAll = function(vis)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.updateAll.  ' +
                             'Do not have record for this visible.');
        }

        var updateFileId = std.ScriptingGui.Util.uniqueId();
        var consMsg = scriptedVisMap[vis.toString()].updateAll(
            updateFileId,
            std.core.bind(onFileUpdateSuccess,undefined,this,updateFileId,vis.toString()),
            std.core.bind(onFileUpdateFailure,undefined,this,updateFileId,vis.toString()));

        console.fileEvent(consMsg);
    };

    std.ScriptingGui.Controller.prototype.updateFile = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.updateFilename.  ' +
                             'Do not have record for this visible.');
        }

        var updateFileId = std.ScriptingGui.Util.uniqueId();
        var consMsg = scriptedVisMap[vis.toString()].updateFile(
            filename,updateFileId,
            std.core.bind(onFileUpdateSuccess,undefined,this,updateFileId,vis.toString()),
            std.core.bind(onFileUpdateFailure,undefined,this,updateFileId,vis.toString()));

        console.fileEvent(consMsg);
    };

    function onFileUpdateSuccess(controller,updateId,visId,filename)
    {
        var consMsg = {
            type: 'FILE_UPDATE_SUCCESS',
            filename: filename,
            updateId: updateId,
            visId: visId
        };
        console.fileEvent(consMsg);
    }

    function onFileUpdateFailure(controller,updateId,visId,filename)
    {
        var consMsg = {
            type: 'FILE_UPDATE_FAIL',
            filename: filename,
            updateId: updateId,
            visId: visId
        };
        console.fileEvent(consMsg);
    }

    std.ScriptingGui.Controller.prototype.getFileText = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.getFileText.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].getFileText(filename);
    };

    std.ScriptingGui.Controller.prototype.getRemoteVersionText = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.getRemoteVersionText.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].getRemoteVersionText(filename);
    };

    function defaultErrorFunction(errorString)
    {
        system.__debugPrint('\n');
        system.__debugPrint(errorString);
        system.__debugPrint('\n');
    }

    /**
     Returns the directory that all scripts associated with remove
     visible, visible, will be stored.
     */
    function generateScriptingDirectory(visible)
    {
        return 'ishmael_scripting/' + visible.toString();
    }

    //************ Handles print messages ***************//
    handlePrint << {'request':'print':};

    function handlePrint(msg,sender)
    {
        //note, will eventually remove this if can print to self.
        if (sender.toString() == system.self.toString())
            return;

        if (console === null)
            return;

        var printMsg = {
            type: 'PRINT_MESSAGE',
            visId: sender.toString(),
            value: msg.print
        };
        console.printEvent(printMsg);
        gui.redraw();
    }

    //************ External interface ***************//
    std.ScriptingGui.Controller.prototype.script = function (visOrPres)
    {
        var visibler = getVisAndChangeToUsed(visOrPres.toString(),this);
        if (visibler === null)
        {
            throw new Error('Error scripting: no record of visible with id ' +
                            visOrPres.toString() + ' to script.');
        }

        gui.redraw(visOrPres.toString());
    };

})();

system.require('scriptingGuiUtil.em');

(function()
{
    var TIME_TO_WAIT_FOR_RESPONSE = 20;

    //actIDs to std.ScriptingGui.Actions
    var actionMap = {};

    var guiScriptActionId = std.ScriptingGui.Util.uniqueId();
    var scriptAction =
        new std.ScriptingGui.Action('script action','',guiScriptActionId);

    //map from visible id to visible object
    //which visibles are near me
    var nearbyVisMap   = {};
    //map from visible id to fileManagerElement
    //which visibles I have scripted before.
    var scriptedVisMap = {};

    var scripter    = null;
    var console     = null;
    var gui         = null;

    //basic constructor
    std.ScriptingGui.Controller = function(simulator,presScripter)
    {
        console = new std.ScriptingGui.Console(scriptedVisMap);

        scripter = presScripter;
        if (typeof(presScripter) === 'undefined')
            scripter = system.self;

        gui = new std.ScriptingGui(
            this,simulator,nearbyVisMap,
            scriptedVisMap,actionMap,console);

        instantiateProxHandlers();
    };

    //************* ACTIONS ***********************//
    function existsInActMap(id)
    {
        return (id in actionMap);
    }

    std.ScriptingGui.Controller.prototype.getScriptActionId =
        function()
    {
        return guiScriptActionId;
    };

    std.ScriptingGui.Controller.prototype.execScriptAction =
        function(visId,text)
    {

        if (! this.scriptedVisExists(visId))
        {
            throw new Error('Error executing script action.  ' +
                            'Have no record of visId: ' + visId.toString());
        }

        scriptAction.edit(text);
        var vis = scriptedVisMap[visId].vis;
        return internalExecAction(this,scriptAction,vis);
    };

    std.ScriptingGui.Controller.prototype.addAction =
        function(name,text)
    {
        var actId =  std.ScriptingGui.Util.uniqueId();
        actionMap[actId] =
            new std.ScriptingGui.Action(name,text,actId);

        gui.redraw();
        return actId;
    };

    std.ScriptingGui.Controller.prototype.removeAction =
        function(id)
    {
        if (!existsInActMap(id))
        {
            throw new Error('Cannot remove action, because ' +
                            'have no record of it.');
        }

        delete actionMap[id];
    };

    std.ScriptingGui.Controller.prototype.editAction =
        function(id,newText)
    {
        if (!existsInActMap(id))
        {
            throw new Error('Cannot modify action, because ' +
                            'have no record of it.');
        }

        actionMap[id].edit(newText);
    };

    std.ScriptingGui.Controller.prototype.execAction =
        function(actId,visId)
    {
        if (!existsInActMap(actId))
        {
            throw new Error('Cannot execute action, because ' +
                            'have no record of it.');
        }

        var vis = getVisAndChangeToUsed(visId,this);

        if (vis === null)
        {
            throw new Error ('Cannot execute action on visible ' +
                             visId + ' have no record of.');
        }

        internalExecAction(this,action,vis);
    };

    function internalExecAction(controller,action,vis)
    {
        var visId = vis.toString();

        scripter # action.createScriptMsg() >> vis >>
            [std.core.bind(onActResp,undefined,controller,action),
             TIME_TO_WAIT_FOR_RESPONSE,
             std.core.bind(onNoActResp,undefined,controller,action,visId)];

        var consDescription = action.getConsoleDescription(visId);
        console.scriptSentEvent(consDescription);
    }

    /**
     If we've alread scripted this visible before (visId key exists in
     nearbyVisMap), then returns the visible object associated with
     it.

     If we have not already scripted this object before, then check if
     object is in nearbyVisMap.  If it is, then we create a new
     filemanagerelement object associated with visible in
     scriptedVisMap, and return visible associated with it.

     Otherwise, returns null.
     */
    function getVisAndChangeToUsed(visId,controller)
    {
        var returner = null;
        if (visId in scriptedVisMap)
            returner = scriptedVisMap[visId].vis;
        else if (visId in nearbyVisMap)
        {
            returner = nearbyVisMap[visId];
            controller.addVisible(returner);
        }

        return returner;
    }

    /**
     @param {object} msg -- response sent from other side.

     @param {visible} sender -- visible that we tried to run action on.

     @param {std.ScriptingGui.Controller} controller -- the scripting
     gui (can use it to access console object).  (FIXME: not really
     needed anymore.)

     @param {std.ScriptingGui.Action} action -- action that was
     requested of other side.

     */
    function onActResp(controller,action,msg,sender)
    {
        var resp=
            action.getConsoleResponseDescription(msg,sender.toString());
        //tell the console about the change
        console.scriptRespEvent(resp);
    }

    /**
     @param {std.ScriptingGui.Controller} controller -- the scripting
     gui (can use it to access the console object).

     @param {std.ScriptingGui.Action} action -- the action object that
     we tried to execute on the visible with id visId.

     @param {string} visId -- the id of the visible we tried to
     execute the action on.
     */
    function onNoActResp(controller,action,visId)
    {
        var resp =
            action.getConsoleNoResponseDescription(visId);

        //tell the console about the change
        console.scriptRespEvent(resp);
    }

    //************** VISIBLES ***********************//

    function instantiateProxHandlers()
    {
        system.self.onProxAdded(addToNearby,true);
        system.self.onProxRemoved(removeFromNearby);
        function addToNearby(newVis)
        {
            nearbyVisMap[newVis.toString()] = newVis;

            var consMsg = {
                    type: 'NEARBY_ADDED',
                    addedVis: newVis
                };
            console.nearbyEvent(consMsg);
        }
        function removeFromNearby(oldVis)
        {
            delete nearbyVisMap[oldVis.toString()];

            var consMsg = {
                    type: 'NEARBY_REMOVED',
                    removedVis: oldVis
                };
            console.nearbyEvent(consMsg);
        }
    }

    //************** FILES    ***********************//

    //returns true if visId is a key in scriptedVisMap (ie, have
    //already scripted a visible with id visId)
    std.ScriptingGui.Controller.prototype.scriptedVisExists =
        function(visId)
    {
        return (visId in scriptedVisMap);
    };

    std.ScriptingGui.Controller.prototype.addVisible =
        function(visToAdd)
    {
        if (this.scriptedVisExists(visToAdd))
        {
            throw new Error ('Error in FileManager.addVisible.  ' +
                             'Already had record for this visible.');
        }

        scriptedVisMap[visToAdd.toString()] =
            new std.FileManager.FileManagerElement(
                visToAdd,defaultErrorFunction,
                generateScriptingDirectory(visToAdd));

        var consMsg = {
                type: 'ADD_VISIBLE',
                visible: visToAdd
            };

        console.scriptedObjEvent(consMsg);
        gui.redraw();
    };

    std.ScriptingGui.Controller.prototype.removeVisible =
        function(visToRemove)
    {
        if (!this.scriptedVisExists(visToRemove))
        {
            throw new Error ('Error in FileManager.removeVisible.  ' +
                             'Do not have record for this visible.');
        }
        system.__debugPrint('\nNote: not removing previously-written files.\n');
        delete scriptedVisMap[visToRemove.toString()];

        var consMsg = {
                type: 'REMOVE_VISIBLE',
                visible: visToRemove
            };
        console.scriptedObjEvent(consMsg);
    };

    std.ScriptingGui.Controller.prototype.removeFile =
        function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.removeFile.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].removeFile(filename);
    };

    /**
     @param vis (can be string or visible)
     @param {String} filename (optional).  If undefined, will reread
     all files associated with this visible from disk.
     */
    std.ScriptingGui.Controller.prototype.rereadFile = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.reareadFile.  '+
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].rereadFile(filename);
    };

    std.ScriptingGui.Controller.prototype.rereadAllFiles =
        function(visId)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.reareadAllFiles.  '+
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].rereadAllFiles();
    };

    /**
     @returns {object: <string (visibleId):
              object: <string(filename):string(filename)>>}

     keyed by visible id, elements are maps of files that each remote
     visible has on it.

     @see ishmaelRedraw in scriptingGui.em
     */
    std.ScriptingGui.Controller.prototype.htmlFileMap =function()
    {
        var returner = {};

        for (var s in scriptedVisMap)
            returner[s] = scriptedVisMap[s].getAllFilenames();
        return returner;
    };

    //if text is undefined, means just use the version on disk.
    std.ScriptingGui.Controller.prototype.addFile = function(vis,filename,text)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.addFile.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].addFile(filename,text);
    };

    //checks to see if file exists.  if does, then use the text from
    //that file.  if does not, then creates it with empty string of text.
    std.ScriptingGui.Controller.prototype.addExistingFileIfCan =
        function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.addExistingFileIfCan.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].addExistingFileIfCan(filename);
    };

    std.ScriptingGui.Controller.prototype.checkFileExists = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.checkFileExists.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].checkFileExists(filename);
    };

    std.ScriptingGui.Controller.prototype.updateAll = function(vis)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.updateAll.  ' +
                             'Do not have record for this visible.');
        }

        var updateFileId = std.ScriptingGui.Util.uniqueId();
        var consMsg = scriptedVisMap[vis.toString()].updateAll(
            updateFileId,
            std.core.bind(onFileUpdateSuccess,undefined,this,updateFileId,vis.toString()),
            std.core.bind(onFileUpdateFailure,undefined,this,updateFileId,vis.toString()));

        console.fileEvent(consMsg);
    };

    std.ScriptingGui.Controller.prototype.updateFile = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.updateFilename.  ' +
                             'Do not have record for this visible.');
        }

        var updateFileId = std.ScriptingGui.Util.uniqueId();
        var consMsg = scriptedVisMap[vis.toString()].updateFile(
            filename,updateFileId,
            std.core.bind(onFileUpdateSuccess,undefined,this,updateFileId,vis.toString()),
            std.core.bind(onFileUpdateFailure,undefined,this,updateFileId,vis.toString()));

        console.fileEvent(consMsg);
    };

    function onFileUpdateSuccess(controller,updateId,visId,filename)
    {
        var consMsg = {
            type: 'FILE_UPDATE_SUCCESS',
            filename: filename,
            updateId: updateId,
            visId: visId
        };
        console.fileEvent(consMsg);
    }

    function onFileUpdateFailure(controller,updateId,visId,filename)
    {
        var consMsg = {
            type: 'FILE_UPDATE_FAIL',
            filename: filename,
            updateId: updateId,
            visId: visId
        };
        console.fileEvent(consMsg);
    }

    std.ScriptingGui.Controller.prototype.getFileText = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.getFileText.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].getFileText(filename);
    };

    std.ScriptingGui.Controller.prototype.getRemoteVersionText = function(vis,filename)
    {
        if (!this.scriptedVisExists(vis))
        {
            throw new Error ('Error in FileManager.getRemoteVersionText.  ' +
                             'Do not have record for this visible.');
        }

        return scriptedVisMap[vis.toString()].getRemoteVersionText(filename);
    };

    function defaultErrorFunction(errorString)
    {
        system.__debugPrint('\n');
        system.__debugPrint(errorString);
        system.__debugPrint('\n');
    }

    /**
     Returns the directory that all scripts associated with remove
     visible, visible, will be stored.
     */
    function generateScriptingDirectory(visible)
    {
        return 'ishmael_scripting/' + visible.toString();
    }

    //************ Handles print messages ***************//
    handlePrint << {'request':'print':};

    function handlePrint(msg,sender)
    {
        //note, will eventually remove this if can print to self.
        if (sender.toString() == system.self.toString())
            return;

        if (console === null)
            return;

        var printMsg = {
            type: 'PRINT_MESSAGE',
            visId: sender.toString(),
            value: msg.print
        };
        console.printEvent(printMsg);
        gui.redraw();
    }

    //************ External interface ***************//
    std.ScriptingGui.Controller.prototype.script = function (visOrPres)
    {
        var visibler = getVisAndChangeToUsed(visOrPres.toString(),this);
        if (visibler === null)
        {
            throw new Error('Error scripting: no record of visible with id ' +
                            visOrPres.toString() + ' to script.');
        }

        gui.redraw(visOrPres.toString());
    };

})();


duplicate parameter template

duplicate parameter template


rebase fixup

rebase fixup

Signed-off-by: Mikael Arguedas <a16f295d1139a1392bb19262902c32cf3e999ce4@gmail.com>

@(TEMPLATE(
    'snippet/add_generated_comment.Dockerfile.em',
    user_name=user_name,
    tag_name=tag_name,
    source_template_name=template_name,
))@
@(TEMPLATE(
    'snippet/from_base_image.Dockerfile.em',
    template_packages=template_packages,
    os_name=os_name,
    os_code_name=os_code_name,
    arch=arch,
    base_image=base_image,
    maintainer_name=maintainer_name,
))@
@(TEMPLATE(
    'snippet/setup_tzdata.Dockerfile.em',
    os_name=os_name,
    os_code_name=os_code_name,
))@
@{
template_dependencies = [
    'dirmngr',
    'gnupg2',
    'lsb-release',
]
# add 'python3-pip' to 'template_dependencies' if pip dependencies are declared
if 'pip3_install' in locals():
    if isinstance(pip3_install, list) and pip3_install != []:
        template_dependencies.append('python3-pip')
}@
@(TEMPLATE(
    'snippet/install_upstream_package_list.Dockerfile.em',
    packages=template_dependencies,
    upstream_packages=upstream_packages if 'upstream_packages' in locals() else [],
))@
@
# setup keys
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 421C365BD9FF1F717815A3895523BAEEB01FA116

# setup sources.list
RUN echo "deb http://packages.ros.org/ros2/ubuntu `lsb_release -sc` main" > /etc/apt/sources.list.d/ros2-latest.list

# setup environment
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

@(TEMPLATE(
    'snippet/install_ros_bootstrap_tools.Dockerfile.em',
    ros_version=ros_version,
))@

@[if 'ros2_repo_packages' in locals()]@
@[  if ros2_repo_packages]@
# install packages from the ROS repositories
RUN apt-get update && apt-get install -y --no-install-recommends \
    @(' \\\n    '.join(sorted(ros2_repo_packages)))@  \
    && rm -rf /var/lib/apt/lists/*

@[  end if]@
@[end if]@
@
@[if 'pip3_install' in locals()]@
@[  if pip3_install]@
# install python packages
RUN pip3 install -U \
    @(' \\\n    '.join(pip3_install))@

@[  end if]@
@[end if]@

ENV ROS_DISTRO @ros2_distro

# bootstrap rosdep
@[if 'rosdep' in locals()]@
@[  if 'rosdistro_index_url' in rosdep]@
ENV ROSDISTRO_INDEX_URL @(rosdep['rosdistro_index_url'])
@[  end if]@
@[end if]@
RUN rosdep init \
    && rosdep update

# clone source
ENV ROS2_WS @(ws)
RUN mkdir -p $ROS2_WS/src
WORKDIR $ROS2_WS
@[if 'vcs' in locals()]@
@[  if vcs]@

@(TEMPLATE(
    'snippet/vcs_import.Dockerfile.em',
    vcs=vcs,
    ws='src',
))@

@[  end if]@
@[end if]@
@
@[if 'rosdep' in locals()]@
@[  if 'install' in rosdep]@
@(TEMPLATE(
    'snippet/install_rosdep_dependencies.Dockerfile.em',
    install_args=rosdep['install'],
))@
@[  end if]@
@[end if]@

@[if 'colcon_args' in locals()]@
@[  if colcon_args]@
# build source
RUN colcon \
    @(' \\\n    '.join(colcon_args))@

@[  end if]@
@[end if]@
# setup bashrc
RUN cp /etc/skel/.bashrc ~/

@[if 'entrypoint_name' in locals()]@
@[  if entrypoint_name]@
@{
entrypoint_file = entrypoint_name.split('/')[-1]
}@
# setup entrypoint
COPY ./@entrypoint_file /

ENTRYPOINT ["/@entrypoint_file"]
@[  end if]@
@[end if]@
@{
cmds = [
'bash',
]
}@
CMD ["@(' && '.join(cmds))"]


more one->many tests

more one->many tests

describe "rest", ->

  adapter = null

  beforeEach ->
    require('./_shared').setupRest.apply(this)
    adapter = @adapter

  context 'one->many', ->

    beforeEach ->
      class @Post extends Ep.Model
        title: Ep.attr('string')
      @App.Post = @Post

      class @Comment extends Ep.Model
        message: Ep.attr('string')
        post: Ep.belongsTo(@Post)
      @App.Comment = @Comment

      @Post.reopen
        comments: Ep.hasMany(@Comment)

      @container.register 'model:post', @Post, instantiate: false
      @container.register 'model:comment', @Comment, instantiate: false

    it 'loads lazily', ->
      @adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comment_ids: [2]}
      @adapter.r['GET:/comments/2'] = comments: {id: 2, message: 'first', post_id: 1}

      session = @adapter.newSession()

      ajaxCalls = @adapter.h
      session.load(@Post, 1).then (post) ->
        expect(ajaxCalls).to.eql(['GET:/posts/1'])
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(post.comments.length).to.eq(1)
        comment = post.comments.firstObject
        expect(comment.message).to.be.undefined

        post.comments.firstObject.then ->
          expect(ajaxCalls).to.eql(['GET:/posts/1', 'GET:/comments/2'])
          expect(comment.message).to.eq('first')
          expect(comment.post.isEqual(post)).to.be.true

    it 'saves', ->
      @adapter.r['POST:/posts'] = -> posts: {client_id: post.clientId, id: 1, title: 'topological sort', comment_ids: []}
      @adapter.r['POST:/comments'] = -> comments: {client_id: comment.clientId, id: 2, message: 'seems good', post_id: 1}

      session = @adapter.newSession()

      post = session.create('post')
      post.title = 'topological sort'

      comment = session.create('comment')
      comment.message = 'seems good'
      comment.post = post

      expect(post.comments.firstObject).to.eq(comment)

      ajaxCalls = @adapter.h
      session.flush().then ->
        expect(post.id).to.not.be.null
        expect(post.title).to.eq('topological sort')
        expect(comment.id).to.not.be.null
        expect(comment.message).to.eq('seems good')
        expect(comment.post).to.eq(post)
        expect(post.comments.firstObject).to.eq(comment)
        expect(ajaxCalls).to.eql(['POST:/posts', 'POST:/comments'])

    it 'saves child', ->
      @adapter.r['POST:/comments'] = -> comments: {client_id: comment.clientId, id: 2, message: 'new child', post_id: 1}

      session = @adapter.newSession()

      post = @Post.create(id: "1", title: 'parent');
      @adapter.loaded(post)

      comment = null

      session.load(@Post, 1).then (post) ->
        comment = session.create('comment', message: 'new child')
        comment.post = post
        expect(post.comments.toArray()).to.eql([comment])
        session.flush().then ->
          expect(post.comments.toArray()).to.eql([comment])
          expect(comment.message).to.eq('new child')
          expect(adapter.h).to.eql(['POST:/comments'])

    it 'updates parent, updates child, and saves sibling', ->
      @adapter.r['PUT:/posts/1'] = -> post: {id: 1, title: 'polychild', comment_ids: [2]}
      @adapter.r['PUT:/comments/2'] = -> comments: {id: 2, title: 'original sibling', post_id: 1}
      @adapter.r['POST:/comments'] = -> comments: {client_id: sibling.clientId, id: 3, message: 'sibling', post_id: 1}

      session = @adapter.newSession()

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      @adapter.loaded(post)

      comment = null
      sibling = null

      session.load(@Post, 1).then (post) ->
        comment = post.comments.firstObject
        sibling = session.create('comment', message: 'sibling')
        sibling.post = post
        comment.message = 'original sibling'
        post.title = 'polychild'
        expect(post.comments.toArray()).to.eql([comment, sibling])
        session.flush().then ->
          expect(post.comments.toArray()).to.eql([comment, sibling])
          expect(adapter.h).to.eql(['PUT:/posts/1', 'PUT:/comments/2', 'POST:/comments'])

    it 'updates with unloaded child', ->
      @adapter.r['GET:/posts/1'] = -> posts: {id: 1, title: 'mvcc ftw', comment_ids: [2]}
      @adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated', comment_ids: [2]}
      session = @adapter.newSession()
      session.load('post', 1).then (post) ->
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])
        post.title = 'updated'
        session.flush().then ->
          expect(post.title).to.eq('updated')
          expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])

    it 'deletes child', ->
      @adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comment_ids: [2]}
      @adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      @adapter.loaded(post)

      session = @adapter.newSession()

      ajaxCalls = @adapter.h
      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        session.flush().then ->
          expect(ajaxCalls).to.eql(['DELETE:/comments/2'])
          expect(post.comments.length).to.eq(0)

    it 'deletes child and updates parent', ->
      @adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'childless', comment_ids: [2]}
      @adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      @adapter.loaded(post)

      session = @adapter.newSession()

      ajaxCalls = @adapter.h
      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        post.title = 'childless'
        session.flush().then ->
          expect(ajaxCalls).to.eql(['PUT:/posts/1', 'DELETE:/comments/2'])
          expect(post.comments.length).to.eq(0)
          expect(post.title).to.eq('childless')

    it 'deletes parent and child', ->
      @adapter.r['DELETE:/posts/1'] = {}
      @adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      @adapter.loaded(post)

      session = @adapter.newSession()

      ajaxCalls = @adapter.h
      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        session.deleteModel(post)
        session.flush().then ->
          expect(ajaxCalls).to.eql(['DELETE:/posts/1', 'DELETE:/comments/2'])
          expect(post.isDeleted).to.be.true
          expect(comment.isDeleted).to.be.true

    context 'embedded', ->

      beforeEach ->
        @RestAdapter.map @Post,
          comments: { embedded: 'always' }
        # Re-instantiate since mappings are reified
        @adapter = @container.lookup('adapter:main')
        adapter = @adapter

      it 'loads', ->
        @adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post_id: 1, message: 'first'}]}

        session = @adapter.newSession()

        ajaxCalls = @adapter.h
        session.load(@Post, 1).then (post) ->
          expect(ajaxCalls).to.eql(['GET:/posts/1'])
          expect(post.id).to.eq("1")
          expect(post.title).to.eq('mvcc ftw')
          expect(post.comments.length).to.eq(1)
          comment = post.comments.firstObject
          expect(comment.message).to.eq 'first'
          expect(comment.post.isEqual(post)).to.be.true

      it 'updates child', ->
        @adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post_id: 1, message: 'first'}]}
        @adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post_id: 1, message: 'first again'}]}

        session = @adapter.newSession()

        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          comment = post.comments.firstObject
          comment.message = 'first again'
          session.flush().then ->
            expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])
            expect(post.comments.firstObject).to.eq(comment)
            expect(comment.message).to.eq('first again')

      it 'adds child', ->
        @adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: []}
        @adapter.r['PUT:/posts/1'] =  -> posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, client_id: comment.clientId, post_id: 1, message: 'reborn'}]}
        session = @adapter.newSession()

        Comment = @Comment
        comment = null
        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          expect(post.comments.length).to.eq(0)
          comment = session.create('comment', message: 'reborn')
          comment.post = post
          session.flush().then ->
            expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])
            expect(comment.message).to.eq('reborn')
            expect(post.comments.firstObject).to.eq(comment)

      it 'deletes child', ->
        @adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: []}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child'))
        @adapter.loaded(post)

        session = @adapter.newSession()

        ajaxCalls = @adapter.h
        session.load('post', 1).then (post) ->
          comment = post.comments.firstObject
          session.deleteModel(comment)
          expect(post.comments.length).to.eq(0)
          session.flush().then ->
            expect(ajaxCalls).to.eql(['PUT:/posts/1'])
            expect(post.comments.length).to.eq(0)

      it 'deletes parent and child', ->
        @adapter.r['DELETE:/posts/1'] = {}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child'))
        @adapter.loaded(post)

        session = @adapter.newSession()

        # TODO: once we have support for side deletions beef up this test
        ajaxCalls = @adapter.h
        session.load('post', 1).then (post) ->
          session.deleteModel(post)
          session.flush().then ->
            expect(ajaxCalls).to.eql(['DELETE:/posts/1'])
            expect(post.isDeleted).to.be.true

describe "rest", ->

  adapter = null

  beforeEach ->
    require('./_shared').setupRest.apply(this)
    adapter = @adapter

  context 'one->many', ->

    beforeEach ->
      class @Post extends Ep.Model
        title: Ep.attr('string')
      @App.Post = @Post

      class @Comment extends Ep.Model
        message: Ep.attr('string')
        post: Ep.belongsTo(@Post)
      @App.Comment = @Comment

      @Post.reopen
        comments: Ep.hasMany(@Comment)

      @container.register 'model:post', @Post, instantiate: false
      @container.register 'model:comment', @Comment, instantiate: false

    it 'loads lazily', ->
      @adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comment_ids: [2]}
      @adapter.r['GET:/comments/2'] = comments: {id: 2, message: 'first', post_id: 1}

      session = @adapter.newSession()

      ajaxCalls = @adapter.h
      session.load(@Post, 1).then (post) ->
        expect(ajaxCalls).to.eql(['GET:/posts/1'])
        expect(post.id).to.eq("1")
        expect(post.title).to.eq('mvcc ftw')
        expect(post.comments.length).to.eq(1)
        comment = post.comments.firstObject
        expect(comment.message).to.be.undefined

        post.comments.firstObject.then ->
          expect(ajaxCalls).to.eql(['GET:/posts/1', 'GET:/comments/2'])
          expect(comment.message).to.eq('first')
          expect(comment.post.isEqual(post)).to.be.true

    it 'saves', ->
      @adapter.r['POST:/posts'] = -> posts: {client_id: post.clientId, id: 1, title: 'topological sort', comment_ids: []}
      @adapter.r['POST:/comments'] = -> comments: {client_id: comment.clientId, id: 2, message: 'seems good', post_id: 1}

      session = @adapter.newSession()

      post = session.create('post')
      post.title = 'topological sort'

      comment = session.create('comment')
      comment.message = 'seems good'
      comment.post = post

      expect(post.comments.firstObject).to.eq(comment)

      ajaxCalls = @adapter.h
      session.flush().then ->
        expect(post.id).to.not.be.null
        expect(post.title).to.eq('topological sort')
        expect(comment.id).to.not.be.null
        expect(comment.message).to.eq('seems good')
        expect(comment.post).to.eq(post)
        expect(post.comments.firstObject).to.eq(comment)
        expect(ajaxCalls).to.eql(['POST:/posts', 'POST:/comments'])

    # TODO: saves child
    # TODO: saves and updates sibling

    it 'updates with unloaded child', ->
      @adapter.r['GET:/posts/1'] = -> posts: {id: 1, title: 'mvcc ftw', comment_ids: [2]}
      @adapter.r['PUT:/posts/1'] = -> posts: {id: 1, title: 'updated', comment_ids: [2]}
      session = @adapter.newSession()
      session.load('post', 1).then (post) ->
        expect(post.title).to.eq('mvcc ftw')
        expect(adapter.h).to.eql(['GET:/posts/1'])
        post.title = 'updated'
        session.flush().then ->
          expect(post.title).to.eq('updated')
          expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])

    it 'deletes child', ->
      @adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comment_ids: [2]}
      @adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      @adapter.loaded(post)

      session = @adapter.newSession()

      ajaxCalls = @adapter.h
      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        session.flush().then ->
          expect(ajaxCalls).to.eql(['DELETE:/comments/2'])
          expect(post.comments.length).to.eq(0)

    it 'deletes child and updates parent', ->
      @adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'childless', comment_ids: [2]}
      @adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      @adapter.loaded(post)

      session = @adapter.newSession()

      ajaxCalls = @adapter.h
      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        post.title = 'childless'
        session.flush().then ->
          expect(ajaxCalls).to.eql(['PUT:/posts/1', 'DELETE:/comments/2'])
          expect(post.comments.length).to.eq(0)
          expect(post.title).to.eq('childless')

    it 'deletes parent and child', ->
      @adapter.r['DELETE:/posts/1'] = {}
      @adapter.r['DELETE:/comments/2'] = {}

      post = @Post.create(id: "1", title: 'parent');
      post.comments.addObject(@Comment.create(id: "2", message: 'child'))
      @adapter.loaded(post)

      session = @adapter.newSession()

      ajaxCalls = @adapter.h
      session.load('post', 1).then (post) ->
        comment = post.comments.firstObject
        session.deleteModel(comment)
        expect(post.comments.length).to.eq(0)
        session.deleteModel(post)
        session.flush().then ->
          expect(ajaxCalls).to.eql(['DELETE:/posts/1', 'DELETE:/comments/2'])
          expect(post.isDeleted).to.be.true
          expect(comment.isDeleted).to.be.true

    context 'embedded', ->

      beforeEach ->
        @RestAdapter.map @Post,
          comments: { embedded: 'always' }
        # Re-instantiate since mappings are reified
        @adapter = @container.lookup('adapter:main')
        adapter = @adapter

      it 'loads', ->
        @adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post_id: 1, message: 'first'}]}

        session = @adapter.newSession()

        ajaxCalls = @adapter.h
        session.load(@Post, 1).then (post) ->
          expect(ajaxCalls).to.eql(['GET:/posts/1'])
          expect(post.id).to.eq("1")
          expect(post.title).to.eq('mvcc ftw')
          expect(post.comments.length).to.eq(1)
          comment = post.comments.firstObject
          expect(comment.message).to.eq 'first'
          expect(comment.post.isEqual(post)).to.be.true

      it 'updates child', ->
        @adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post_id: 1, message: 'first'}]}
        @adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, post_id: 1, message: 'first again'}]}

        session = @adapter.newSession()

        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          comment = post.comments.firstObject
          comment.message = 'first again'
          session.flush().then ->
            expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])
            expect(post.comments.firstObject).to.eq(comment)
            expect(comment.message).to.eq('first again')

      it 'adds child', ->
        @adapter.r['GET:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: []}
        @adapter.r['PUT:/posts/1'] =  -> posts: {id: 1, title: 'mvcc ftw', comments: [{id: 2, client_id: comment.clientId, post_id: 1, message: 'reborn'}]}
        session = @adapter.newSession()

        Comment = @Comment
        comment = null
        session.load(@Post, 1).then (post) ->
          expect(adapter.h).to.eql(['GET:/posts/1'])
          expect(post.comments.length).to.eq(0)
          comment = session.create('comment', message: 'reborn')
          comment.post = post
          session.flush().then ->
            expect(adapter.h).to.eql(['GET:/posts/1', 'PUT:/posts/1'])
            expect(comment.message).to.eq('reborn')
            expect(post.comments.firstObject).to.eq(comment)

      it 'deletes child', ->
        @adapter.r['PUT:/posts/1'] = posts: {id: 1, title: 'mvcc ftw', comments: []}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child'))
        @adapter.loaded(post)

        session = @adapter.newSession()

        ajaxCalls = @adapter.h
        session.load('post', 1).then (post) ->
          comment = post.comments.firstObject
          session.deleteModel(comment)
          expect(post.comments.length).to.eq(0)
          session.flush().then ->
            expect(ajaxCalls).to.eql(['PUT:/posts/1'])
            expect(post.comments.length).to.eq(0)

      it 'deletes parent and child', ->
        @adapter.r['DELETE:/posts/1'] = {}

        post = @Post.create(id: "1", title: 'parent');
        post.comments.addObject(@Comment.create(id: "2", message: 'child'))
        @adapter.loaded(post)

        session = @adapter.newSession()

        # TODO: once we have support for side deletions beef up this test
        ajaxCalls = @adapter.h
        session.load('post', 1).then (post) ->
          session.deleteModel(post)
          session.flush().then ->
            expect(ajaxCalls).to.eql(['DELETE:/posts/1'])
            expect(post.isDeleted).to.be.true


Add back dependency file generation.

Add back dependency file generation.

This slows translation significantly, but is needed if changes to headers
should be picked up automatically.

@###############################################################################
@#
@# Copyright (c) 2016, G.A. vd. Hoorn
@#
@# Licensed under the Apache License, Version 2.0 (the "License");
@# you may not use this file except in compliance with the License.
@# You may obtain a copy of the License at
@#
@#     http://www.apache.org/licenses/LICENSE-2.0
@#
@# Unless required by applicable law or agreed to in writing, software
@# distributed under the License is distributed on an "AS IS" BASIS,
@# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@# See the License for the specific language governing permissions and
@# limitations under the License.
@#
@###############################################################################
@#
@# rossum ninja build script EmPy template
@#
@#
@# Author: G.A. vd. Hoorn
@#
@###############################################################################
################################################################################
#
# This file was auto-generated by rossum v@(rossum_version) at @(tstamp).
#
# Package directories searched at configuration time:
#
@[for pkg_dir in ws.sources]@
#   @pkg_dir.path
@[end for]@
#
# Packages build by this build file:
#
@[for pkg in ws.pkgs]@
#   - @pkg.manifest.name
@[end for]@
#
# Do not modify this file. Rather, regenerate it using rossum.
#
################################################################################

### build setup ################################################################

build_dir = @(ws.build.path)
cc_ktrans = @(ktransw.path)
plain_ktrans = @(ktrans.path)

### build rules ################################################################

# .kl -> .pc
#
# this rule always places the Karel support directory corresponding to the
# runtime version on the include path, as that is a globally needed path.
rule ktrans_pc
  command = $cc_ktrans $
               -q $
               -MM -MP -MT $out -MF $out.d $
               --ktrans=$plain_ktrans $
               $lib_includes $
               /I@(ktrans.support.path) $
               $in $
               /ver @(ktrans.support.version_string) $
               /config @(ws.robot_ini.path)
  depfile = $out.d
  deps = gcc

### build statements ###########################################################

@[for pkg in ws.pkgs]@
@# don't generate rules for packages that don't have any objects declared
@[if len(pkg.objects) > 0]@
### @(pkg.manifest.name) ###################

@(pkg.manifest.name)_dir = @(pkg.location)
@(pkg.manifest.name)_deps = @(str.join(' ', [d.manifest.name for d in pkg.dependencies]))
@(pkg.manifest.name)_include_flags = @(str.join(' ', ['/I' + d for d in pkg.include_dirs]))

@[for (src, obj) in pkg.objects]@
build $build_dir\@(obj): ktrans_pc $@(pkg.manifest.name)_dir\@(src)
  lib_includes = $@(pkg.manifest.name)_include_flags
  description = @(pkg.manifest.name) :: @(src)

@[end for]@

@# TODO: add tests

@# pkg in ws.pkgs
@[end if]@
@[end for]@

@###############################################################################
@#
@# Copyright (c) 2016, G.A. vd. Hoorn
@#
@# Licensed under the Apache License, Version 2.0 (the "License");
@# you may not use this file except in compliance with the License.
@# You may obtain a copy of the License at
@#
@#     http://www.apache.org/licenses/LICENSE-2.0
@#
@# Unless required by applicable law or agreed to in writing, software
@# distributed under the License is distributed on an "AS IS" BASIS,
@# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@# See the License for the specific language governing permissions and
@# limitations under the License.
@#
@###############################################################################
@#
@# rossum ninja build script EmPy template
@#
@#
@# Author: G.A. vd. Hoorn
@#
@###############################################################################
################################################################################
#
# This file was auto-generated by rossum v@(rossum_version) at @(tstamp).
#
# Package directories searched at configuration time:
#
@[for pkg_dir in ws.sources]@
#   @pkg_dir.path
@[end for]@
#
# Packages build by this build file:
#
@[for pkg in ws.pkgs]@
#   - @pkg.manifest.name
@[end for]@
#
# Do not modify this file. Rather, regenerate it using rossum.
#
################################################################################

### build setup ################################################################

build_dir = @(ws.build.path)
cc_ktrans = @(ktransw.path)
plain_ktrans = @(ktrans.path)

### build rules ################################################################

# .kl -> .pc
#
# this rule always places the Karel support directory corresponding to the
# runtime version on the include path, as that is a globally needed path.
rule ktrans_pc
  command = $cc_ktrans $
               -q $
               --ktrans=$plain_ktrans $
               $lib_includes $
               /I@(ktrans.support.path) $
               $in $
               /ver @(ktrans.support.version_string) $
               /config @(ws.robot_ini.path)

### build statements ###########################################################

@[for pkg in ws.pkgs]@
@# don't generate rules for packages that don't have any objects declared
@[if len(pkg.objects) > 0]@
### @(pkg.manifest.name) ###################

@(pkg.manifest.name)_dir = @(pkg.location)
@(pkg.manifest.name)_deps = @(str.join(' ', [d.manifest.name for d in pkg.dependencies]))
@(pkg.manifest.name)_include_flags = @(str.join(' ', ['/I' + d for d in pkg.include_dirs]))

@[for (src, obj) in pkg.objects]@
build $build_dir\@(obj): ktrans_pc $@(pkg.manifest.name)_dir\@(src)
  lib_includes = $@(pkg.manifest.name)_include_flags
  description = @(pkg.manifest.name) :: @(src)

@[end for]@

@# TODO: add tests

@# pkg in ws.pkgs
@[end if]@
@[end for]@


Can use gui to remove actions.

Can use gui to remove actions.

system.require('std/core/simpleInput.em');

(function()
 {

     /**
      @param {std.ScriptingGui.Controller}

      @param {object: <String(visibleId):visible object>} nearbyVisMap
      -- all the visible objects that satisfy scripter's proximity
      query.

      @param {object: <String(visibleId):
      std.FileManager.FileManagerElement> scriptedVisMap -- A record
      of all visibles that we have scripted at some point.

      @param {object: <int:std.ScriptingGui.Action> actionMap -- Which
      actions are selectable by scripter.

      @param {std.ScriptingGui.Console} console -- has several
      scripting events.
      */
     std.ScriptingGui =
         function(controller,nearbyVisMap,scriptedVisMap,actionMap,console)
     {
         if (typeof(simulator) == 'undefined')
         {
             throw new Error('Cannot initialize scripting gui without ' +
                             'simulator graphics.');
         }

         this.controller       = controller;
         this.nearbyVisMap     = nearbyVisMap;
         this.scriptedVisMap   = scriptedVisMap;
         this.actionMap        = actionMap;
         this.console          = console;

         this.guiMod = simulator._simulator.addGUITextModule(
             guiName(),
             getGuiText(),
             std.core.bind(guiInitFunc,undefined,this));
         this.hasInited = false;
     };

     system.require('scriptingGuiUtil.em');

     std.ScriptingGui.prototype.redraw = function()
     {
         if (!this.hasInited)
             return;

         //trigger redraw call
         this.guiMod.call(
             'ishmaelRedraw',toHtmlNearbyMap(this),toHtmlScriptedMap(this),
             toHtmlActionMap(this));

     };

     /**
      @param {String} visId -- Id of visible.
      */
     //called by html to add the visible with id
     std.ScriptingGui.prototype.hAddVisible = function(visId)
     {
         //already can script the visible.
         if (visId in this.scriptedVisMap)
         {
             this.console.guiEvent();
             this.redraw();
             return;
         }

         if (!(visId in this.nearbyVisMap))
         {
             //visible is no longer available.
             this.console.guiEvent();
             this.redraw();
             return;
         }

         //redraw called from inside of addVisible.
         this.controller.addVisible(this.nearbyVisMap[visId]);
     };

     std.ScriptingGui.prototype.hRemoveVisible = function(visId)
     {
         //FIXME: should finish this call
         throw new Error('FIXME: must finish hRemoveVisible call ' +
                         'in scriptingGui');
     };

     /**
      @param {String?} actId -- Should be parsedInt to get an index
      into actionMap
      @param {String} newText -- What action with actId should set as
      its text.
      */
     std.ScriptingGui.prototype.hSaveAction = function(actId,newText)
     {
         this.controller.editAction(parseInt(actId),newText);
     };

     /**
      @param {String?} actId -- Should be parsedInt to get an index
      into actionMap
      @param {String} newText -- What action with actId should set as
      its text.
      @param {String} visId   -- Id of visible to execute action on.
      */
     std.ScriptingGui.prototype.hSaveAndExecuteAction =
         function(actId,newText, visId)
     {
         this.hSaveAction(actId,newText);
         this.controller.execAction(actId,visId);
     };

     /**
      Prompt user for the name of the new action using
      std.core.SimpleInput.
      */
     std.ScriptingGui.prototype.hNewAction =
         function()
     {
         var newInput = std.core.SimpleInput(
             std.core.SimpleInput.ENTER_TEXT,
             'Enter new action\'s name',
             std.core.bind(addActionInputCB,undefined,this));
     };

     /**
      @param {std.ScriptingGui} scriptingGui
      @param {String} userResp_actionName - The name of the new action
      that the user wants.
      */
     function addActionInputCB(scriptingGui,userResp_actionName)
     {
         //new action won't have any text in it.
         scriptingGui.controller.addAction(userResp_actionName,'');
         scriptingGui.redraw();
     }

     /**
      @param {String?} actId -- Should be parsedInt to get an index
      into actionMap
      */
     std.ScriptingGui.prototype.hRemoveAction =
         function(actId)
     {
         this.controller.removeAction(parseInt(actId));
         this.redraw();
     };

     /**
      @param {std.ScriptingGui}
      */
     function guiInitFunc(scriptingGui)
     {
         scriptingGui.hasInited = true;

         //when a user clicks on any of the nearby visibles to script
         //them, want that object to move into scripted objects.
         scriptingGui.guiMod.bind(
             'addVisible',
             std.core.bind(scriptingGui.hAddVisible,scriptingGui));

         //when a user clicks to remove any of the scripted visibles
         //from scripted map, then destroy it.
         scriptingGui.guiMod.bind(
             'removeVisible',
             std.core.bind(scriptingGui.hRemoveVisible,scriptingGui));

         //when a user updates a particular action, and clicks to save
         //the new action text.
         scriptingGui.guiMod.bind(
             'saveAction',
             std.core.bind(scriptingGui.hSaveAction,scriptingGui));

         //saves and executes action to visible.
         scriptingGui.guiMod.bind(
             'saveAndExecuteAction',
             std.core.bind(scriptingGui.hSaveAndExecuteAction,scriptingGui));

         scriptingGui.guiMod.bind(
             'newAction',
             std.core.bind(scriptingGui.hNewAction,scriptingGui));

         scriptingGui.guiMod.bind(
             'removeAction',
             std.core.bind(scriptingGui.hRemoveAction,scriptingGui));

         scriptingGui.redraw();
     }

     /**
      @returns {object: <string (visibleId): string (visibleId)>}
      scriptingGui -- Takes the list of nearby objects and turns it
      into an object that (for now) maps visibleIds to visibleIds.

      @see ishmaelRedraw
      */
     function toHtmlNearbyMap(scriptingGui)
     {
         var returner = { };
         for (var s in scriptingGui.nearbyVisMap)
             returner[s] = s;
         return returner;
     }

     /**
      @returns {object: <string (visibleId): string (visibleId)>}
      scriptingGui -- Takes the list of nearby objects and turns it
      into an object that (for now) maps visibleIds to visibleIds.

      @see ishmaelRedraw
      */
     function toHtmlScriptedMap(scriptingGui)
     {
         var returner = { };
         for (var s in scriptingGui.scriptedVisMap)
             returner[s] = s;
         return returner;
     }

     function toHtmlActionMap(scriptingGui)
     {
         return scriptingGui.actionMap;
     }

     function guiName()
     {
         return 'ishmaelEditor';
     }

     function getGuiText()
     {

         var returner = "sirikata.ui('" + guiName() + "',";
         returner += 'function(){ ';

         returner += @

         function ishmaelWindowId()
         {
             return 'ishmael__windowID_';
         }

         //the div that surrounds all the nearby objects.
         function nearbyListId()
         {
             return 'ishmael__nearbyListID__';
         }

         //the div that surrounds all the scripted objects.
         function scriptedListId()
         {
             return 'ishmael__scriptedListID__';
         }

         function actionListId()
         {
             return 'ishmael__actionListID__';
         }

         function actionTareaId()
         {
             return 'ishmael__actionEditor__';
         }

         function saveActionButtonId()
         {
             return 'ishmael__saveActionButton__';
         }

         function execActionButtonId()
         {
             return 'ishmael__execActionButton__';
         }

         function newActionButtonId()
         {
             return 'ishmael__newActionButton__';
         }

         function removeActionButtonId()
         {
             return 'ishmael__removeActionButton__';
         }

         /**
          \param {String} nearbyObj (id of visible that we are
          communicating with).
          gives the div for each nearby object.
          */
         function generateNearbyDivId(nearbyObj)
         {
             var visIdDivable = divizeVisibleId(nearbyObj);
             return 'ishmael__nearbyDivID___' + visIdDivable;
         }

         /**
          \param {String} scriptedObj (id of visible that we are
          communicating with).
          gives the div for each scripted object.
          */
         function generateScriptedDivId(scriptedObj)
         {
             var visIdDivable = divizeVisibleId(scriptedObj);
             return 'ishmael__scriptedDivID___' + visIdDivable;
         }

         function divizeVisibleId(visId)
         {
             return visId.replace(':','');
         }

         $('
'   +

           'Scripted presences
' +
           '<select id="'     + scriptedListId() + '" size=5>' +
           '</select>
'   +

           'Nearby presences
'      +
           '<select id="'     + nearbyListId() + '" size=5>'   +
           '</select>
'   +

           '<select id="'     + actionListId() + '" size=5>'   +
           '</select>'        +

           '<textarea id="'   + actionTareaId()         + '">' +
           '</textarea>'      +

           '<button id="'     + saveActionButtonId()    + '">' +
           'save action'      +
           '</button>'        +

           '<button id="'     + execActionButtonId()    + '">' +
           'exec&save action' +
           '</button>'        +

           '<button id="'     + newActionButtonId()     + '">' +
           'new action'       +
           '</button>'        +

           '<button id="'     + removeActionButtonId()  + '">' +
           'remove action'    +
           '</button>'        +

           '
' //end div at top.
          ).attr({id:ishmaelWindowId(),title:'ishmael'}).appendTo('body');

         //The id of the visible that the scripter has selected to
         //program.
         var currentlySelectedVisible = undefined;

         //int id of the currently selected action.
         var currentlySelectedAction = undefined;
         //map from int to std.ScriptingGui.Action objects.  It gets
         //updated whenever we received an ishmaelRedraw call.  Note
         //that the redraw call will not write over the text of
         //currentlySelectedAction.  This ensures that scripter
         //edits will not be lost while they are being written if some
         //other action triggers a call to ishmaelRedraw (for
         //instance, a new nearbyObject gets added).
         var allActions = undefined;

         $('#' + scriptedListId()).change(
             function()
             {
                 //loads the visible id
                 var val = $('#' + scriptedListId()).val();
                 currentlySelectedVisible = val;

                 //for debugging
                 sirikata.log(
                     'error','Selected new vis to script: ' + val.toString());

                 //if we change which visible we're scripting from the
                 //scripting selection menu, we want that change to be
                 //reflected in the nearby selection list.
                 updateNearbySelection(val.toString());
             });

         //set a listener for action list.  whenever select an option,
         //should communicate that to emerson gui, so that can keep
         //track of selected action.
         $('#' + actionListId()).change(
             function()
             {
                 var val =
                     $('#' + actionListId()).val();
                 changeActionText(parseInt(val));
                 sirikata.log('error', 'Selected action: '  + val.toString());
             });

         $('#' + nearbyListId()).change(
             function()
             {
                 var val = $('#'+nearbyListId()).val();
                 currentlySelectedVisible = val;
                 sirikata.event('addVisible',val);
             });

         //when hit save, sends the action text through to controller
         //to save it.
         $('#' + saveActionButtonId()).click(
             function()
             {
                 //no action is selected
                 if ((typeof(currentlySelectedAction) == 'undefined') ||
                     (currentlySelectedAction === null))
                     return;

                 var toSaveText = $('#' + actionTareaId()).val();

                 //saving action does not force a redraw.  must
                 //preserve new text in allActions on our end. (takes
                 //care of failure case where save an action, then
                 //click on another action, then return to current
                 //action).
                 allActions[currentlySelectedAction].text = toSaveText;

                 sirikata.event(
                     'saveAction',currentlySelectedAction,toSaveText);
             });

         //saves and executes current action
         $('#' + execActionButtonId()).click(
             function()
             {
                 if ((typeof(currentlySelectedAction)  == 'undefined') ||
                     (typeof(currentlySelectedVisible) == 'undefined'))
                 {
                     sirikata.log(
                         'error','Cannot execute action.  ' +
                             'No vis or action selected.');
                     return;
                 }

                 //see comments in click handler for saveActionButton.
                 var toSaveText = $('#' + actionTareaId()).val();
                 allActions[currentlySelectedAction].text = toSaveText;
                 sirikata.event(
                     'saveAndExecuteAction',currentlySelectedAction,
                     toSaveText,currentlySelectedVisible);
             });

         //user asks for new action: we pass event down to
         //scriptingGui, which creates a simpleInput asking for new
         //action name.
         $('#' + newActionButtonId()).click(
             function()
             {
                 sirikata.event('newAction');
             });

         $('#' + removeActionButtonId()).click(
             function()
             {
                 if (typeof(currentlySelectedAction) == 'undefined')
                     return;

                 sirikata.event('removeAction',currentlySelectedAction);
                 currentlySelectedAction = undefined;
             });

         var inputWindow = new sirikata.ui.window(
             '#' + ishmaelWindowId(),
                 {
	             autoOpen: true,
	             height: 'auto',
	             width: 600,
                     height: 600,
                     position: 'right'
                 }
             );
         inputWindow.show();

         /**
          changes selection in updateNearby to the one associated with
          visibleId (if it's available).  If it's not, then we just
          unselect.

          Right now, we take a performance hit, because the selection
          change that we do here will trigger the .change handler
          associated with nearbyList.  Should still maintain semantics
          though.
          */
         function updateNearbySelection(visibleId)
         {
             var nearbyOption = $('#' + generateNearbyDivId(visibleId));
             if (nearbyOption.size())
             {
                 //means that the element did exist.  now want it to
                 //appear selected in nearbyList.
                 var nearbyVal = nearbyOption.val();
                 $('#' + nearbyListId()).val(nearbyVal);
             }
             else
             {
                 var errMsg =
                     'In updatenearbyselection attempting '+
                     'to set selector to none.';

                 sirikatal.log('error',errMsg);
                 $('#' + nearbyListId()).val(null);
             }
         }

         /**
          \param {int or undefined} idActSelected.  If an int, then
          looks through record of allActions, and sets action text
          area to that.  If undefined, then clears action text area.
          */
         function changeActionText(idActSelected)
         {
             currentlySelectedAction = idActSelected;
             var textToSetTo = '';

             if (typeof(idActSelected) !='undefined')
             {
                 if (! idActSelected in allActions)
                 {
                     sirikata.log('error','action ids out of ' +
                                  'sync with actions in scripting gui.');
                     return;
                 }
                 textToSetTo = allActions[idActSelected].text;
             }

             //actually update textarea with correct text
             $('#' + actionTareaId()).val(textToSetTo);
         }

         /**
          \param {object: <int:std.ScriptingGui.Action>} actionMap

          FIXEM: currentlySelectedAction is no longer passed. Instead,
          maintain it in html/js

          \param {int or undefined} currentlySelectedAction -- null if
          do not have a currently selected action.  the id of the
          action that is currently selected otherwise.  this id can be
          used to index into actionMap
          */
         ishmaelRedraw = function(
             nearbyObjs,scriptedObjs,actionMap)
         {
             redrawNearby(nearbyObjs);
             redrawScriptedObjs(scriptedObjs);
             redrawActionList(actionMap);
         };

         /**
          \param {object: <string (visibleId): string (visibleId)>}
          nearbyObjs -- all objects that are in vicinity.
          */
         function redrawNearby(nearbyObjs)
         {
             var newHtml = '';
             for (var s in nearbyObjs)
             {
                 if (s===currentlySelectedVisible)
                     newHtml += '<option selected ';
                 else
                     newHtml += '<option ';

                 newHtml += 'value="' + s + '" ';
                 newHtml += 'id="' + generateNearbyDivId(s) + '">';
                 newHtml += s;
                 newHtml += '</option>';
             }
             $('#' + nearbyListId()).html(newHtml);
         }

         /**
          \param {object: <string (visibleId): string(visibleId)>}
          scriptedObjs -- all objects that we have a scripting
          relationship with.
          */
         function redrawScriptedObjs(scriptedObjs)
         {
             var newHtml = '';
             for (var s in scriptedObjs)
             {
                 if (s === currentlySelectedVisible)
                     newHtml += '<option selected ';
                 else
                     newHtml += '<option ';

                 newHtml += 'value="' +s +  '">';
                 newHtml += s;
                 newHtml += '</option>';
             }
             $('#' + scriptedListId()).html(newHtml);
         }

         function redrawActionList(actionMap)
         {
             var prevCurAct = null;
             if (typeof(currentlySelectedAction) != 'undefined')
             {
                 prevCurAct = allActions[currentlySelectedAction];
                 //update with text that had entered in tarea.
                 prevCurAct.text = $('#' + actionTareaId()).val();
             }

             allActions = actionMap;
             //preserves edits that scripter was potentially making
             //when other action triggered redraw (eg. a new nearby
             //object).
             if (prevCurAct !== null)
                 allActions[prevCurAct.id] = prevCurAct;

             var newHtml = '';
             for (var s in actionMap)
             {
                 if (parseInt(s) === parseInt(currentlySelectedAction))
                     newHtml += '<option selected ';
                 else
                     newHtml += '<option ';

                 newHtml += 'value="' + s + '">';
                 newHtml += actionMap[s].name ;
                 newHtml += '</option>';
             }

             $('#' + actionListId()).html(newHtml);
         }

         @;

         returner += '});';
         return returner;
     }
 })();

//do all further imports for file and gui control.
system.require('controller.em');
system.require('action.em');
system.require('console.em');
system.require('fileManagerElement.em');

system.require('std/core/simpleInput.em');

(function()
 {

     /**
      @param {std.ScriptingGui.Controller}

      @param {object: <String(visibleId):visible object>} nearbyVisMap
      -- all the visible objects that satisfy scripter's proximity
      query.

      @param {object: <String(visibleId):
      std.FileManager.FileManagerElement> scriptedVisMap -- A record
      of all visibles that we have scripted at some point.

      @param {object: <int:std.ScriptingGui.Action> actionMap -- Which
      actions are selectable by scripter.

      @param {std.ScriptingGui.Console} console -- has several
      scripting events.
      */
     std.ScriptingGui =
         function(controller,nearbyVisMap,scriptedVisMap,actionMap,console)
     {
         if (typeof(simulator) == 'undefined')
         {
             throw new Error('Cannot initialize scripting gui without ' +
                             'simulator graphics.');
         }

         this.controller       = controller;
         this.nearbyVisMap     = nearbyVisMap;
         this.scriptedVisMap   = scriptedVisMap;
         this.actionMap        = actionMap;
         this.console          = console;

         this.guiMod = simulator._simulator.addGUITextModule(
             guiName(),
             getGuiText(),
             std.core.bind(guiInitFunc,undefined,this));
         this.hasInited = false;
     };

     system.require('scriptingGuiUtil.em');

     std.ScriptingGui.prototype.redraw = function()
     {
         if (!this.hasInited)
             return;

         //trigger redraw call
         this.guiMod.call(
             'ishmaelRedraw',toHtmlNearbyMap(this),toHtmlScriptedMap(this),
             toHtmlActionMap(this));

     };

     /**
      @param {String} visId -- Id of visible.
      */
     //called by html to add the visible with id
     std.ScriptingGui.prototype.hAddVisible = function(visId)
     {
         //already can script the visible.
         if (visId in this.scriptedVisMap)
         {
             this.console.guiEvent();
             this.redraw();
             return;
         }

         if (!(visId in this.nearbyVisMap))
         {
             //visible is no longer available.
             this.console.guiEvent();
             this.redraw();
             return;
         }

         //redraw called from inside of addVisible.
         this.controller.addVisible(this.nearbyVisMap[visId]);
     };

     std.ScriptingGui.prototype.hRemoveVisible = function(visId)
     {
         //FIXME: should finish this call
         throw new Error('FIXME: must finish hRemoveVisible call ' +
                         'in scriptingGui');
     };

     /**
      @param {String?} actId -- Should be parsedInt to get an index
      into actionMap
      @param {String} newText -- What action with actId should set as
      its text.
      */
     std.ScriptingGui.prototype.hSaveAction = function(actId,newText)
     {
         this.controller.editAction(parseInt(actId),newText);
     };

     /**
      @param {String?} actId -- Should be parsedInt to get an index
      into actionMap
      @param {String} newText -- What action with actId should set as
      its text.
      @param {String} visId   -- Id of visible to execute action on.
      */
     std.ScriptingGui.prototype.hSaveAndExecuteAction =
         function(actId,newText, visId)
     {
         this.hSaveAction(actId,newText);
         this.controller.execAction(actId,visId);
     };

     /**
      Prompt user for the name of the new action using
      std.core.SimpleInput.
      */
     std.ScriptingGui.prototype.hNewAction =
         function()
     {
         var newInput = std.core.SimpleInput(
             std.core.SimpleInput.ENTER_TEXT,
             'Enter new action\'s name',
             std.core.bind(addActionInputCB,undefined,this));
     };

     /**
      @param {std.ScriptingGui} scriptingGui
      @param {String} userResp_actionName - The name of the new action
      that the user wants.
      */
     function addActionInputCB(scriptingGui,userResp_actionName)
     {
         //new action won't have any text in it.
         scriptingGui.controller.addAction(userResp_actionName,'');
         scriptingGui.redraw();
     }

     /**
      @param {std.ScriptingGui}
      */
     function guiInitFunc(scriptingGui)
     {
         scriptingGui.hasInited = true;

         //when a user clicks on any of the nearby visibles to script
         //them, want that object to move into scripted objects.
         scriptingGui.guiMod.bind(
             'addVisible',
             std.core.bind(scriptingGui.hAddVisible,scriptingGui));

         //when a user clicks to remove any of the scripted visibles
         //from scripted map, then destroy it.
         scriptingGui.guiMod.bind(
             'removeVisible',
             std.core.bind(scriptingGui.hRemoveVisible,scriptingGui));

         //when a user updates a particular action, and clicks to save
         //the new action text.
         scriptingGui.guiMod.bind(
             'saveAction',
             std.core.bind(scriptingGui.hSaveAction,scriptingGui));

         //saves and executes action to visible.
         scriptingGui.guiMod.bind(
             'saveAndExecuteAction',
             std.core.bind(scriptingGui.hSaveAndExecuteAction,scriptingGui));

         scriptingGui.guiMod.bind(
             'newAction',
             std.core.bind(scriptingGui.hNewAction,scriptingGui));

         scriptingGui.redraw();
     }

     /**
      @returns {object: <string (visibleId): string (visibleId)>}
      scriptingGui -- Takes the list of nearby objects and turns it
      into an object that (for now) maps visibleIds to visibleIds.

      @see ishmaelRedraw
      */
     function toHtmlNearbyMap(scriptingGui)
     {
         var returner = { };
         for (var s in scriptingGui.nearbyVisMap)
             returner[s] = s;
         return returner;
     }

     /**
      @returns {object: <string (visibleId): string (visibleId)>}
      scriptingGui -- Takes the list of nearby objects and turns it
      into an object that (for now) maps visibleIds to visibleIds.

      @see ishmaelRedraw
      */
     function toHtmlScriptedMap(scriptingGui)
     {
         var returner = { };
         for (var s in scriptingGui.scriptedVisMap)
             returner[s] = s;
         return returner;
     }

     function toHtmlActionMap(scriptingGui)
     {
         return scriptingGui.actionMap;
     }

     function guiName()
     {
         return 'ishmaelEditor';
     }

     function getGuiText()
     {

         var returner = "sirikata.ui('" + guiName() + "',";
         returner += 'function(){ ';

         returner += @

         function ishmaelWindowId()
         {
             return 'ishmael__windowID_';
         }

         //the div that surrounds all the nearby objects.
         function nearbyListId()
         {
             return 'ishmael__nearbyListID__';
         }

         //the div that surrounds all the scripted objects.
         function scriptedListId()
         {
             return 'ishmael__scriptedListID__';
         }

         function actionListId()
         {
             return 'ishmael__actionListID__';
         }

         function actionTareaId()
         {
             return 'ishmael__actionEditor__';
         }

         function saveActionButtonId()
         {
             return 'ishmael__saveActionButton__';
         }

         function execActionButtonId()
         {
             return 'ishmael__execActionButton__';
         }

         function newActionButtonId()
         {
             return 'ishmael__newActionButton__';
         }

         /**
          \param {String} nearbyObj (id of visible that we are
          communicating with).
          gives the div for each nearby object.
          */
         function generateNearbyDivId(nearbyObj)
         {
             var visIdDivable = divizeVisibleId(nearbyObj);
             return 'ishmael__nearbyDivID___' + visIdDivable;
         }

         /**
          \param {String} scriptedObj (id of visible that we are
          communicating with).
          gives the div for each scripted object.
          */
         function generateScriptedDivId(scriptedObj)
         {
             var visIdDivable = divizeVisibleId(scriptedObj);
             return 'ishmael__scriptedDivID___' + visIdDivable;
         }

         function divizeVisibleId(visId)
         {
             return visId.replace(':','');
         }

         $('
'   +

           'Scripted presences
' +
           '<select id="'+ scriptedListId() + '"  size=5>' +
           '</select>
'  +

           'Nearby presences
'      +
           '<select id="'+ nearbyListId() + '"  size=5>' +
           '</select>
'  +

           '<select id="' + actionListId() + '" size=5>'   +
           '</select>' +

           '<textarea id="'+ actionTareaId() + '"  >' +
           '</textarea>' +

           '<button id="' + saveActionButtonId() + '">' +
           'save action' +
           '</button>' +

           '<button id="' + execActionButtonId() + '">' +
           'exec&save action' +
           '</button>' +

           '<button id="' + newActionButtonId() + '">' +
           'new action'   +
           '</button>'    +

           '
' //end div at top.
          ).attr({id:ishmaelWindowId(),title:'ishmael'}).appendTo('body');

         //The id of the visible that the scripter has selected to
         //program.
         var currentlySelectedVisible = undefined;

         //int id of the currently selected action.
         var currentlySelectedAction = undefined;
         //map from int to std.ScriptingGui.Action objects.  It gets
         //updated whenever we received an ishmaelRedraw call.  Note
         //that the redraw call will not write over the text of
         //currentlySelectedAction.  This ensures that scripter
         //edits will not be lost while they are being written if some
         //other action triggers a call to ishmaelRedraw (for
         //instance, a new nearbyObject gets added).
         var allActions = undefined;

         $('#' + scriptedListId()).change(
             function()
             {
                 //loads the visible id
                 var val = $('#' + scriptedListId()).val();
                 currentlySelectedVisible = val;

                 //for debugging
                 sirikata.log(
                     'error','Selected new vis to script: ' + val.toString());

                 //if we change which visible we're scripting from the
                 //scripting selection menu, we want that change to be
                 //reflected in the nearby selection list.
                 updateNearbySelection(val.toString());
             });

         //set a listener for action list.  whenever select an option,
         //should communicate that to emerson gui, so that can keep
         //track of selected action.
         $('#' + actionListId()).change(
             function()
             {
                 var val =
                     $('#' + actionListId()).val();
                 changeActionText(parseInt(val));
                 sirikata.log('error', 'Selected action: '  + val.toString());
             });

         $('#' + nearbyListId()).change(
             function()
             {
                 var val = $('#'+nearbyListId()).val();
                 currentlySelectedVisible = val;
                 sirikata.event('addVisible',val);
             });

         //when hit save, sends the action text through to controller
         //to save it.
         $('#' + saveActionButtonId()).click(
             function()
             {
                 //no action is selected
                 if ((typeof(currentlySelectedAction) == 'undefined') ||
                     (currentlySelectedAction === null))
                     return;

                 var toSaveText = $('#' + actionTareaId()).val();

                 //saving action does not force a redraw.  must
                 //preserve new text in allActions on our end. (takes
                 //care of failure case where save an action, then
                 //click on another action, then return to current
                 //action).
                 allActions[currentlySelectedAction].text = toSaveText;

                 sirikata.event(
                     'saveAction',currentlySelectedAction,toSaveText);
             });

         //saves and executes current action
         $('#' + execActionButtonId()).click(
             function()
             {
                 if ((typeof(currentlySelectedAction)  == 'undefined') ||
                     (typeof(currentlySelectedVisible) == 'undefined'))
                 {
                     sirikata.log(
                         'error','Cannot execute action.  ' +
                             'No vis or action selected.');
                     return;
                 }

                 //see comments in click handler for saveActionButton.
                 var toSaveText = $('#' + actionTareaId()).val();
                 allActions[currentlySelectedAction].text = toSaveText;
                 sirikata.event(
                     'saveAndExecuteAction',currentlySelectedAction,
                     toSaveText,currentlySelectedVisible);
             });

         //user asks for new action: we pass event down to
         //scriptingGui, which creates a simpleInput asking for new
         //action name.
         $('#' + newActionButtonId()).click(
             function()
             {
                 sirikata.event('newAction');
             });

         var inputWindow = new sirikata.ui.window(
             '#' + ishmaelWindowId(),
                 {
	             autoOpen: true,
	             height: 'auto',
	             width: 600,
                     height: 600,
                     position: 'right'
                 }
             );
         inputWindow.show();

         /**
          changes selection in updateNearby to the one associated with
          visibleId (if it's available).  If it's not, then we just
          unselect.

          Right now, we take a performance hit, because the selection
          change that we do here will trigger the .change handler
          associated with nearbyList.  Should still maintain semantics
          though.
          */
         function updateNearbySelection(visibleId)
         {
             var nearbyOption = $('#' + generateNearbyDivId(visibleId));
             if (nearbyOption.size())
             {
                 //means that the element did exist.  now want it to
                 //appear selected in nearbyList.
                 var nearbyVal = nearbyOption.val();
                 $('#' + nearbyListId()).val(nearbyVal);
             }
             else
             {
                 var errMsg =
                     'In updatenearbyselection attempting '+
                     'to set selector to none.';

                 sirikatal.log('error',errMsg);
                 $('#' + nearbyListId()).val(null);
             }
         }

         /**
          \param {int or undefined} idActSelected.  If an int, then
          looks through record of allActions, and sets action text
          area to that.  If undefined, then clears action text area.
          */
         function changeActionText(idActSelected)
         {
             currentlySelectedAction = idActSelected;
             var textToSetTo = '';

             if (typeof(idActSelected) !='undefined')
             {
                 if (! idActSelected in allActions)
                 {
                     sirikata.log('error','action ids out of ' +
                                  'sync with actions in scripting gui.');
                     return;
                 }
                 textToSetTo = allActions[idActSelected].text;
             }

             //actually update textarea with correct text
             $('#' + actionTareaId()).val(textToSetTo);
         }

         /**
          \param {object: <int:std.ScriptingGui.Action>} actionMap

          FIXEM: currentlySelectedAction is no longer passed. Instead,
          maintain it in html/js

          \param {int or undefined} currentlySelectedAction -- null if
          do not have a currently selected action.  the id of the
          action that is currently selected otherwise.  this id can be
          used to index into actionMap
          */
         ishmaelRedraw = function(
             nearbyObjs,scriptedObjs,actionMap)
         {
             redrawNearby(nearbyObjs);
             redrawScriptedObjs(scriptedObjs);
             redrawActionList(actionMap);
         };

         /**
          \param {object: <string (visibleId): string (visibleId)>}
          nearbyObjs -- all objects that are in vicinity.
          */
         function redrawNearby(nearbyObjs)
         {
             var newHtml = '';
             for (var s in nearbyObjs)
             {
                 if (s===currentlySelectedVisible)
                     newHtml += '<option selected ';
                 else
                     newHtml += '<option ';

                 newHtml += 'value="' + s + '" ';
                 newHtml += 'id="' + generateNearbyDivId(s) + '">';
                 newHtml += s;
                 newHtml += '</option>';
             }
             $('#' + nearbyListId()).html(newHtml);
         }

         /**
          \param {object: <string (visibleId): string(visibleId)>}
          scriptedObjs -- all objects that we have a scripting
          relationship with.
          */
         function redrawScriptedObjs(scriptedObjs)
         {
             var newHtml = '';
             for (var s in scriptedObjs)
             {
                 if (s === currentlySelectedVisible)
                     newHtml += '<option selected ';
                 else
                     newHtml += '<option ';

                 newHtml += 'value="' +s +  '">';
                 newHtml += s;
                 newHtml += '</option>';
             }
             $('#' + scriptedListId()).html(newHtml);
         }

         function redrawActionList(actionMap)
         {
             var prevCurAct = null;
             if (typeof(currentlySelectedAction) != 'undefined')
             {
                 prevCurAct = allActions[currentlySelectedAction];
                 //update with text that had entered in tarea.
                 prevCurAct.text = $('#' + actionTareaId()).val();
             }

             allActions = actionMap;
             //preserves edits that scripter was potentially making
             //when other action triggered redraw (eg. a new nearby
             //object).
             if (prevCurAct !== null)
                 allActions[prevCurAct.id] = prevCurAct;

             var newHtml = '';
             for (var s in actionMap)
             {
                 if (parseInt(s) === parseInt(currentlySelectedAction))
                     newHtml += '<option selected ';
                 else
                     newHtml += '<option ';

                 newHtml += 'value="' + s + '">';
                 newHtml += actionMap[s].name ;
                 newHtml += '</option>';
             }

             $('#' + actionListId()).html(newHtml);
         }

         @;

         returner += '});';
         return returner;
     }
 })();

//do all further imports for file and gui control.
system.require('controller.em');
system.require('action.em');
system.require('console.em');
system.require('fileManagerElement.em');