<commit_before>pc.extend(pc.asset, function () {
    /**
    * @name pc.asset.AssetRegistry
    * @class Container for all assets that are available to this application
    * @constructor Create an instance of an AssetRegistry.
    * Note: PlayCanvas scripts are provided with an AssetRegistry instance as 'context.assets'.
    * @param {pc.resources.ResourceLoader} loader The ResourceLoader used to to load the asset files.
    * @param {String} prefix The prefix added to file urls before the loader tries to fetch them
    */
    var AssetRegistry = function (loader, prefix) {
        if (!loader) {
            throw new Error("Must provide a ResourceLoader instance for AssetRegistry");
        }

        this.loader = loader;
        this._prefix = prefix || "";

        this._cache = {}; // main asset cache, keyed by id
        this._names = {}; // index for looking up assets by name
        this._urls = {}; // index for looking up assets by url

        this._groups = {};
    };

    AssetRegistry.prototype = {
        /**
        * @private
        * @function
        * @name pc.asset.AssetRegistry#addGroup
        * @description Take a set of asset data (usually from the content file (data.js) or the API)
        * and create pc.asset.Asset instance for each entry. Group these Assets by name so they can be
        * loaded in a batch later.
        * @param {String} name The name of the group to store Assets under
        * @param {Object} toc The data source for getting the asset data. Should be an object keyed on asset resource ids.
        */
        addGroup: function (name, toc) {
            if (!this._groups[name]) {
                this._groups[name] = [];
            }

            for (var id in toc.assets) {
                var asset = this.getAssetById(id);

                if (!asset) {
                    // Create assets for every entry in TOC and add to AssetRegistry
                    var assetData = toc.assets[id];
                    asset = this.createAndAddAsset(id, assetData);
                } else {
                    // Update asset data
                    pc.extend(asset, toc.assets[id]);
                }

                this._groups[name].push(asset.id);
            }
        },

        /**
        * @private
        */
        createAndAddAsset: function (id, assetData) {
            var asset = new pc.asset.Asset(assetData.name, assetData.type, assetData.file, assetData.data, this._prefix);
            asset.id = id;

            this.addAsset(asset);

            if (asset.file) {
                this.loader.registerHash(asset.file.hash, asset.getFileUrl());
            }

            return asset;
        },

        /**
        * @private
        * @deprecated
        * @function
        * @name pc.asset.AssetRegistry#all
        * @description Return a list of all assets in the registry
        * @returns [pc.asset.Asset] List of all assets in the registry
        */
        all: function () {
            return Object.keys(this._cache).map(function (id) {
                return this.getAssetById(id);
            }, this);
        },

        /**
        * @function
        * @name pc.asset.AssetRegisty#list
        * @description Generate a list of all assets in a group (or all assets if no group name is supplied)
        * @param {String} groupName The name of the group to get the asset list from
        * @returns [pc.asset.Asset] List of all assets in the group
        */
        list: function (groupName) {
            if (groupName) {
                // return assets from the group
                if (this._groups[groupName]) {
                    return this._groups[groupName].map(function (id) {
                        return this.getAssetById(id);
                    }, this);
                }
            } else {
                // Return all assets
                return Object.keys(this._cache).map(function (id) {
                    return this.getAssetById(id);
                }, this);
            }
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#addAsset
        * @description Add a new asset to the registry
        * @param {pc.asset.Asset} asset The asset to add to the registry
        */
        addAsset: function (asset) {
            this._cache[asset.id] = asset;
            if (!this._names[asset.name]) {
                this._names[asset.name] = [];
            }
            this._names[asset.name].push(asset.id);
            if (asset.file) {
                this._urls[asset.getFileUrl()] = asset.id;
                asset.on('change', this._onAssetChanged, this);
            }
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#removeAsset
        * @description Removes an asset from the registry
        * @param {pc.asset.Asset} asset The asset to remove
        */
        removeAsset: function (asset) {
            delete this._cache[asset.id];
            delete this._names[asset.name];
            if (asset.file) {
                asset.off('change', this._onAssetChanged, this);
                delete this._urls[asset.file.url];
            }
        },

        /**
        * @private
        * @function
        * @name pc.asset.AssetRegistry#_onAssetChanged
        * @description Takes care of reloading an asset file if it has changed
        */
        _onAssetChanged: function (asset, attribute, newValue, oldValue) {
            var url;
            if (attribute === 'file') {
                if (oldValue) {
                    // if the file hash hasn't changed don't do anything
                    if (newValue && newValue.hash === oldValue.hash) {
                        return;
                    }

                    // get old asset url
                    url = asset.prefix ? pc.path.join(asset.prefix, oldValue.url) : oldValue.url;

                    // remove old url connections
                    delete this._urls[url];
                    this.loader.unregisterHash(url);
                    this.loader.removeFromCache(url);
                }

                if (newValue) {
                    // get new asset url
                    url = asset.getFileUrl();

                    // register new url
                    this._urls[url] = asset.id;
                    this.loader.registerHash(newValue.hash, url);

                    // reload asset resource
                    var oldResource = asset.resource;
                    this.load([asset]).then(function (resources) {
                        asset.fire('change', asset, 'resource', resources[0], oldResource);
                    });
                }
            }
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#findAll
        * @description Return all Assets with the specified name and type found in the registry
        * @param {String} name The name of the Assets to find
        * @param {String} [type] The type of the Assets to find
        * @returns {[pc.asset.Asset]} A list of all Assets found
        * @example
        * var assets = context.assets.findAll("myTextureAsset", pc.asset.ASSET_TEXTURE);
        * console.log("Found " + assets.length + " assets called " + name);
        */
        findAll: function (name, type) {
            var self = this;
            var ids = this._names[name];
            var assets;
            if (ids) {
                assets = ids.map(function (id) {
                    return self._cache[id];
                });

                if (type) {
                    return assets.filter(function (asset) {
                        return (asset.type === type);
                    });
                } else {
                    return assets;
                }
            } else {
                return [];
            }
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#find
        * @description Return the first Asset with the specified name and type found in the registry
        * @param {String} name The name of the Asset to find
        * @param {String} [type] The type of the Asset to find
        * @returns {pc.asset.Asset} A single Asset or null if no Asset is found
        * @example
        * var asset = context.assets.find("myTextureAsset", pc.asset.ASSET_TEXTURE);
        */
        find: function (name, type) {
            var asset = this.findAll(name, type);
            return asset ? asset[0] : null;
        },

        getAssetByResourceId: function (id) {
            console.warn("WARNING: getAssetByResourceId: Function is deprecated. Use getAssetById() instead.");
            return this._cache[id];
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#getAssetById
        * @description Return the {@link pc.asset.Asset} object in the AssetRegistry with the id provided
        * @param {Number} id The id of the Asset to return
        * @returns {pc.asset.Asset} The Asset or null if no Asset is found.
        */
        getAssetById: function (id) {
            return this._cache[id];
        },

        getAssetByUrl: function (url) {
            var id = this._urls[url];
            return this._cache[id];
        },

        /**
        * @private
        */
        getAssetByName: function (name) {
            console.warn("WARNING: getAssetByName: Function is deprecated. Use find() or findAll() instead.");
            return this.find(name);
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#load
        * @description Load the resources for a set of assets and return a promise the resources that they load.
        * If the asset type doesn't have file (e.g. Material Asset) then a resource is not returned (the resource list is shorter)
        * NOTE: Usually you won't have to call load() directly as Assets will be loaded as part of the Pack loading process. This is only
        * required if you are loading assets manually without using the PlayCanvas tools.
        * @param {[pc.fw.Asset]} assets The list of assets to load
        * @param {[Object]} [results] List of results for the resources to be stored in. This is usually not required
        * @param {Object} [options] Options to pass on to the loader
        * @returns {Promise} A Promise to the resources
        * @example
        * var asset = new pc.asset.Asset("My Texture", "texture", {
        *   url: "/example/mytexture.jpg"
        * });
        */
        load: function (assets, results, options) {
            if (assets && pc.type(assets) !== 'array') {
                assets = [assets];
            }

            if (options === undefined) {
                // shift arguments
                options = results;
                results = [];
            }

            var requests = [];
            assets.forEach(function (asset, index) {
                var existing = this.getAssetById(asset.id);
                if (!existing) {
                    // If the asset isn't in the registry then add it.
                    this.addAsset(asset);
                }

                switch(asset.type) {
                    case pc.asset.ASSET_MODEL:
                        requests.push(this._createModelRequest(asset));
                        break;
                    case pc.asset.ASSET_TEXTURE:
                        requests.push(this._createTextureRequest(asset, results[index]));
                        break;
                    case pc.asset.ASSET_CUBEMAP:
                        requests.push(this._createCubemapRequest(asset, results[index]));
                        break;
                    case pc.asset.ASSET_MATERIAL:
                        requests.push(this._createMaterialRequest(asset));
                        break;
                    default: {
                        requests.push(this._createAssetRequest(asset));
                        break;
                    }
                }
            }, this);

            // request all assets, then attach loaded resources onto asset
            return this.loader.request(requests.filter(function (r) { return r !== null; }), options).then(function (resources) {
                var promise = new pc.promise.Promise(function (resolve, reject) {
                    var index = 0;
                    requests.forEach(function (r, i) {
                        if (r) {
                            assets[i].resource = resources[index++];
                        }
                    });
                    resolve(resources);
                });
                return promise;
            }, function (error) {
                // Ensure exceptions while loading are thrown and not swallowed by promises
                setTimeout(function () {
                    throw error;
                }, 0)
            });
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#loadFromUrl
        * @description Load the resources for an asset by its URL and return a promise that will be resolved
        * when the asset is loaded.
        * @param  {String} url  The URL of the asset
        * @param  {String} type The type of the asset
        * @return {Promise} A Promise to the resources
        * @example
        * var url = "../assets/statue/Statue_1.json";
        *    application.context.assets.loadFromUrl(url, "model").then(function (results) {
        *    var model = results.resource;
        *    var asset = results.asset;
        *
        *    entity = new pc.fw.Entity();
        *     application.context.systems.model.addComponent(entity, {
        *        type: "asset",
        *        asset: asset
        *    });
        *    application.context.root.addChild(entity);
        * });
        */
        loadFromUrl: function (url, type) {
            if (!type) {
                throw Error("type required")
            }

            if (type === "model") {
                return this._loadModel(url);
            }

            var dir = pc.path.getDirectory(url);
            var basename = pc.path.getBasename(url);
            var name = basename.replace(".json", "");

            var asset = new pc.asset.Asset(name, type, {
                url: url
            });

            var promise = new pc.promise.Promise(function (resolve, reject) {
                this.load(asset).then(function (resource) {
                    resolve({
                        resource: resource,
                        asset: asset
                    });
                });
            }.bind(this));

            return promise;
        },

        _loadModel: function (url) {
            var self = this;

            var dir = pc.path.getDirectory(url);
            var basename = pc.path.getBasename(url);

            var name = basename.replace(".json", "");
            var mappingUrl = pc.path.join(dir, basename.replace(".json", ".mapping.json"));

            // Create Model Asset
            var modelAsset = new pc.asset.Asset(name, "model", {
                url: url
            });

            // Create Mapping Asset
            var mappingAsset = new pc.asset.Asset(name + ".mapping", "json", {
                url: mappingUrl
            });

            var promise = new pc.promise.Promise(function (resolve, reject) {
                // Load Model and Mapping
                self.load([modelAsset, mappingAsset]).then(function (resources) {
                    var model = resources[0];
                    var mapping = resources[1];

                    modelAsset.data = mapping; // Update model asset with mapping data

                    var materialAssets = [];
                    mapping.mapping.forEach(function (map) {
                        materialAssets.push(new pc.asset.Asset(pc.path.getBasename(map.path), "material", {
                            url: pc.path.join(dir, map.path)
                        }));
                    });

                    if (materialAssets.length) {
                        var promise = self.load(materialAssets)

                        promise.then(function (materials) {
                            for(var i = 0, n = model.meshInstances.length; i < n; i++) {
                                model.meshInstances[i].material = materials[i];
                            }
                            resolve({
                                resource: model,
                                asset: modelAsset
                            });
                        });

                        return promise;
                    } else {
                        resolve({
                            resource: model,
                            asset: modelAsset
                        });
                    }

                });
            });

            return promise;
        },

        _createAssetRequest: function (asset, result) {
            var url = asset.getFileUrl();
            if (url) {
                return this.loader.createFileRequest(url, asset.type);
            } else {
                return null;
            }

        },

        _createModelRequest: function (asset) {
            var url = asset.getFileUrl();
            var mapping = (asset.data && asset.data.mapping) ? asset.data.mapping: [];

            return new pc.resources.ModelRequest(url, mapping);
        },

        _createTextureRequest: function (asset, texture) {
            return new pc.resources.TextureRequest(asset.getFileUrl(), null, texture);
        },

        _createCubemapRequest: function (asset, texture) {
            var url = asset.getFileUrl();
            if (url) {
                return new pc.resources.CubemapRequest(url, null, texture);
            } else {
                return new pc.resources.CubemapRequest("asset://" + asset.id, null, texture);
            }
        },

        _createMaterialRequest: function (asset) {
            var url = asset.getFileUrl();
            if (url) {
                return new pc.resources.MaterialRequest(url);
            } else {
                return new pc.resources.MaterialRequest("asset://" + asset.id);
            }

        }
    };

    return {
        AssetRegistry: AssetRegistry
    };
}())
<commit_msg>First remove from cache and then unregister hash<commit_after>pc.extend(pc.asset, function () {
    /**
    * @name pc.asset.AssetRegistry
    * @class Container for all assets that are available to this application
    * @constructor Create an instance of an AssetRegistry.
    * Note: PlayCanvas scripts are provided with an AssetRegistry instance as 'context.assets'.
    * @param {pc.resources.ResourceLoader} loader The ResourceLoader used to to load the asset files.
    * @param {String} prefix The prefix added to file urls before the loader tries to fetch them
    */
    var AssetRegistry = function (loader, prefix) {
        if (!loader) {
            throw new Error("Must provide a ResourceLoader instance for AssetRegistry");
        }

        this.loader = loader;
        this._prefix = prefix || "";

        this._cache = {}; // main asset cache, keyed by id
        this._names = {}; // index for looking up assets by name
        this._urls = {}; // index for looking up assets by url

        this._groups = {};
    };

    AssetRegistry.prototype = {
        /**
        * @private
        * @function
        * @name pc.asset.AssetRegistry#addGroup
        * @description Take a set of asset data (usually from the content file (data.js) or the API)
        * and create pc.asset.Asset instance for each entry. Group these Assets by name so they can be
        * loaded in a batch later.
        * @param {String} name The name of the group to store Assets under
        * @param {Object} toc The data source for getting the asset data. Should be an object keyed on asset resource ids.
        */
        addGroup: function (name, toc) {
            if (!this._groups[name]) {
                this._groups[name] = [];
            }

            for (var id in toc.assets) {
                var asset = this.getAssetById(id);

                if (!asset) {
                    // Create assets for every entry in TOC and add to AssetRegistry
                    var assetData = toc.assets[id];
                    asset = this.createAndAddAsset(id, assetData);
                } else {
                    // Update asset data
                    pc.extend(asset, toc.assets[id]);
                }

                this._groups[name].push(asset.id);
            }
        },

        /**
        * @private
        */
        createAndAddAsset: function (id, assetData) {
            var asset = new pc.asset.Asset(assetData.name, assetData.type, assetData.file, assetData.data, this._prefix);
            asset.id = id;

            this.addAsset(asset);

            if (asset.file) {
                this.loader.registerHash(asset.file.hash, asset.getFileUrl());
            }

            return asset;
        },

        /**
        * @private
        * @deprecated
        * @function
        * @name pc.asset.AssetRegistry#all
        * @description Return a list of all assets in the registry
        * @returns [pc.asset.Asset] List of all assets in the registry
        */
        all: function () {
            return Object.keys(this._cache).map(function (id) {
                return this.getAssetById(id);
            }, this);
        },

        /**
        * @function
        * @name pc.asset.AssetRegisty#list
        * @description Generate a list of all assets in a group (or all assets if no group name is supplied)
        * @param {String} groupName The name of the group to get the asset list from
        * @returns [pc.asset.Asset] List of all assets in the group
        */
        list: function (groupName) {
            if (groupName) {
                // return assets from the group
                if (this._groups[groupName]) {
                    return this._groups[groupName].map(function (id) {
                        return this.getAssetById(id);
                    }, this);
                }
            } else {
                // Return all assets
                return Object.keys(this._cache).map(function (id) {
                    return this.getAssetById(id);
                }, this);
            }
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#addAsset
        * @description Add a new asset to the registry
        * @param {pc.asset.Asset} asset The asset to add to the registry
        */
        addAsset: function (asset) {
            this._cache[asset.id] = asset;
            if (!this._names[asset.name]) {
                this._names[asset.name] = [];
            }
            this._names[asset.name].push(asset.id);
            if (asset.file) {
                this._urls[asset.getFileUrl()] = asset.id;
                asset.on('change', this._onAssetChanged, this);
            }
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#removeAsset
        * @description Removes an asset from the registry
        * @param {pc.asset.Asset} asset The asset to remove
        */
        removeAsset: function (asset) {
            delete this._cache[asset.id];
            delete this._names[asset.name];
            if (asset.file) {
                asset.off('change', this._onAssetChanged, this);
                delete this._urls[asset.file.url];
            }
        },

        /**
        * @private
        * @function
        * @name pc.asset.AssetRegistry#_onAssetChanged
        * @description Takes care of reloading an asset file if it has changed
        */
        _onAssetChanged: function (asset, attribute, newValue, oldValue) {
            var url;
            if (attribute === 'file') {
                if (oldValue) {
                    // if the file hash hasn't changed don't do anything
                    if (newValue && newValue.hash === oldValue.hash) {
                        return;
                    }

                    // get old asset url
                    url = asset.prefix ? pc.path.join(asset.prefix, oldValue.url) : oldValue.url;

                    // remove old url connections
                    delete this._urls[url];
                    this.loader.removeFromCache(url);
                    this.loader.unregisterHash(url);
                }

                if (newValue) {
                    // get new asset url
                    url = asset.getFileUrl();

                    // register new url
                    this._urls[url] = asset.id;
                    this.loader.registerHash(newValue.hash, url);

                    // reload asset resource
                    var oldResource = asset.resource;
                    this.load([asset]).then(function (resources) {
                        asset.fire('change', asset, 'resource', resources[0], oldResource);
                    });
                }
            }
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#findAll
        * @description Return all Assets with the specified name and type found in the registry
        * @param {String} name The name of the Assets to find
        * @param {String} [type] The type of the Assets to find
        * @returns {[pc.asset.Asset]} A list of all Assets found
        * @example
        * var assets = context.assets.findAll("myTextureAsset", pc.asset.ASSET_TEXTURE);
        * console.log("Found " + assets.length + " assets called " + name);
        */
        findAll: function (name, type) {
            var self = this;
            var ids = this._names[name];
            var assets;
            if (ids) {
                assets = ids.map(function (id) {
                    return self._cache[id];
                });

                if (type) {
                    return assets.filter(function (asset) {
                        return (asset.type === type);
                    });
                } else {
                    return assets;
                }
            } else {
                return [];
            }
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#find
        * @description Return the first Asset with the specified name and type found in the registry
        * @param {String} name The name of the Asset to find
        * @param {String} [type] The type of the Asset to find
        * @returns {pc.asset.Asset} A single Asset or null if no Asset is found
        * @example
        * var asset = context.assets.find("myTextureAsset", pc.asset.ASSET_TEXTURE);
        */
        find: function (name, type) {
            var asset = this.findAll(name, type);
            return asset ? asset[0] : null;
        },

        getAssetByResourceId: function (id) {
            console.warn("WARNING: getAssetByResourceId: Function is deprecated. Use getAssetById() instead.");
            return this._cache[id];
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#getAssetById
        * @description Return the {@link pc.asset.Asset} object in the AssetRegistry with the id provided
        * @param {Number} id The id of the Asset to return
        * @returns {pc.asset.Asset} The Asset or null if no Asset is found.
        */
        getAssetById: function (id) {
            return this._cache[id];
        },

        getAssetByUrl: function (url) {
            var id = this._urls[url];
            return this._cache[id];
        },

        /**
        * @private
        */
        getAssetByName: function (name) {
            console.warn("WARNING: getAssetByName: Function is deprecated. Use find() or findAll() instead.");
            return this.find(name);
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#load
        * @description Load the resources for a set of assets and return a promise the resources that they load.
        * If the asset type doesn't have file (e.g. Material Asset) then a resource is not returned (the resource list is shorter)
        * NOTE: Usually you won't have to call load() directly as Assets will be loaded as part of the Pack loading process. This is only
        * required if you are loading assets manually without using the PlayCanvas tools.
        * @param {[pc.fw.Asset]} assets The list of assets to load
        * @param {[Object]} [results] List of results for the resources to be stored in. This is usually not required
        * @param {Object} [options] Options to pass on to the loader
        * @returns {Promise} A Promise to the resources
        * @example
        * var asset = new pc.asset.Asset("My Texture", "texture", {
        *   url: "/example/mytexture.jpg"
        * });
        */
        load: function (assets, results, options) {
            if (assets && pc.type(assets) !== 'array') {
                assets = [assets];
            }

            if (options === undefined) {
                // shift arguments
                options = results;
                results = [];
            }

            var requests = [];
            assets.forEach(function (asset, index) {
                var existing = this.getAssetById(asset.id);
                if (!existing) {
                    // If the asset isn't in the registry then add it.
                    this.addAsset(asset);
                }

                switch(asset.type) {
                    case pc.asset.ASSET_MODEL:
                        requests.push(this._createModelRequest(asset));
                        break;
                    case pc.asset.ASSET_TEXTURE:
                        requests.push(this._createTextureRequest(asset, results[index]));
                        break;
                    case pc.asset.ASSET_CUBEMAP:
                        requests.push(this._createCubemapRequest(asset, results[index]));
                        break;
                    case pc.asset.ASSET_MATERIAL:
                        requests.push(this._createMaterialRequest(asset));
                        break;
                    default: {
                        requests.push(this._createAssetRequest(asset));
                        break;
                    }
                }
            }, this);

            // request all assets, then attach loaded resources onto asset
            return this.loader.request(requests.filter(function (r) { return r !== null; }), options).then(function (resources) {
                var promise = new pc.promise.Promise(function (resolve, reject) {
                    var index = 0;
                    requests.forEach(function (r, i) {
                        if (r) {
                            assets[i].resource = resources[index++];
                        }
                    });
                    resolve(resources);
                });
                return promise;
            }, function (error) {
                // Ensure exceptions while loading are thrown and not swallowed by promises
                setTimeout(function () {
                    throw error;
                }, 0)
            });
        },

        /**
        * @function
        * @name pc.asset.AssetRegistry#loadFromUrl
        * @description Load the resources for an asset by its URL and return a promise that will be resolved
        * when the asset is loaded.
        * @param  {String} url  The URL of the asset
        * @param  {String} type The type of the asset
        * @return {Promise} A Promise to the resources
        * @example
        * var url = "../assets/statue/Statue_1.json";
        *    application.context.assets.loadFromUrl(url, "model").then(function (results) {
        *    var model = results.resource;
        *    var asset = results.asset;
        *
        *    entity = new pc.fw.Entity();
        *     application.context.systems.model.addComponent(entity, {
        *        type: "asset",
        *        asset: asset
        *    });
        *    application.context.root.addChild(entity);
        * });
        */
        loadFromUrl: function (url, type) {
            if (!type) {
                throw Error("type required")
            }

            if (type === "model") {
                return this._loadModel(url);
            }

            var dir = pc.path.getDirectory(url);
            var basename = pc.path.getBasename(url);
            var name = basename.replace(".json", "");

            var asset = new pc.asset.Asset(name, type, {
                url: url
            });

            var promise = new pc.promise.Promise(function (resolve, reject) {
                this.load(asset).then(function (resource) {
                    resolve({
                        resource: resource,
                        asset: asset
                    });
                });
            }.bind(this));

            return promise;
        },

        _loadModel: function (url) {
            var self = this;

            var dir = pc.path.getDirectory(url);
            var basename = pc.path.getBasename(url);

            var name = basename.replace(".json", "");
            var mappingUrl = pc.path.join(dir, basename.replace(".json", ".mapping.json"));

            // Create Model Asset
            var modelAsset = new pc.asset.Asset(name, "model", {
                url: url
            });

            // Create Mapping Asset
            var mappingAsset = new pc.asset.Asset(name + ".mapping", "json", {
                url: mappingUrl
            });

            var promise = new pc.promise.Promise(function (resolve, reject) {
                // Load Model and Mapping
                self.load([modelAsset, mappingAsset]).then(function (resources) {
                    var model = resources[0];
                    var mapping = resources[1];

                    modelAsset.data = mapping; // Update model asset with mapping data

                    var materialAssets = [];
                    mapping.mapping.forEach(function (map) {
                        materialAssets.push(new pc.asset.Asset(pc.path.getBasename(map.path), "material", {
                            url: pc.path.join(dir, map.path)
                        }));
                    });

                    if (materialAssets.length) {
                        var promise = self.load(materialAssets)

                        promise.then(function (materials) {
                            for(var i = 0, n = model.meshInstances.length; i < n; i++) {
                                model.meshInstances[i].material = materials[i];
                            }
                            resolve({
                                resource: model,
                                asset: modelAsset
                            });
                        });

                        return promise;
                    } else {
                        resolve({
                            resource: model,
                            asset: modelAsset
                        });
                    }

                });
            });

            return promise;
        },

        _createAssetRequest: function (asset, result) {
            var url = asset.getFileUrl();
            if (url) {
                return this.loader.createFileRequest(url, asset.type);
            } else {
                return null;
            }

        },

        _createModelRequest: function (asset) {
            var url = asset.getFileUrl();
            var mapping = (asset.data && asset.data.mapping) ? asset.data.mapping: [];

            return new pc.resources.ModelRequest(url, mapping);
        },

        _createTextureRequest: function (asset, texture) {
            return new pc.resources.TextureRequest(asset.getFileUrl(), null, texture);
        },

        _createCubemapRequest: function (asset, texture) {
            var url = asset.getFileUrl();
            if (url) {
                return new pc.resources.CubemapRequest(url, null, texture);
            } else {
                return new pc.resources.CubemapRequest("asset://" + asset.id, null, texture);
            }
        },

        _createMaterialRequest: function (asset) {
            var url = asset.getFileUrl();
            if (url) {
                return new pc.resources.MaterialRequest(url);
            } else {
                return new pc.resources.MaterialRequest("asset://" + asset.id);
            }

        }
    };

    return {
        AssetRegistry: AssetRegistry
    };
}())
<|endoftext|>


<commit_before>import {render} from 'react-dom';
import {compose} from 'react-komposer';
import {browserHistory} from 'react-router';

import component from '../components/course';
import { Server, Notification } from '../../core/helpers/';
import actions from '../actions/course';

const coursesService = Server.service('/courses');

const containsId = (array, value) => {
	return array.filter(entry => entry._id == value).length > 0;
};

const composer = (props, onData) => {

	let currentUser = Server.get("user");

	coursesService.find({query: {
		_id: props.params.id,
		$populate: ['ltiToolIds', 'userIds', 'teacherIds']
	}}).then(res => {

		let course = res.data[0];

		if (!containsId(course.userIds, currentUser._id) && !containsId(course.teacherIds, currentUser._id)) {
			onData(new Error('You don\'t have the permission to see this page.'));
			return;
		}

		let componentData = {
			actions,
			course: course
		};

		onData(null, componentData);
	}).catch(err => {
		console.log(err);
		Notification.showError("Kurs wurde nicht gefunden");
		//browserHistory.push("/courses/")
	});
};

export default compose(composer)(component);
<commit_msg>remove unnecessary stuff<commit_after>import {render} from 'react-dom';
import {compose} from 'react-komposer';
import {browserHistory} from 'react-router';

import component from '../components/course';
import { Server, Notification } from '../../core/helpers/';
import actions from '../actions/course';

const coursesService = Server.service('/courses');

const containsId = (array, value) => {
	return array.filter(entry => entry._id == value).length > 0;
};

const composer = (props, onData) => {

	let currentUser = Server.get("user");

	coursesService.find({query: {
		_id: props.params.id,
		$populate: ['ltiToolIds', 'userIds', 'teacherIds']
	}}).then(res => {

		let course = res.data[0];

		if (!containsId(course.userIds, currentUser._id) && !containsId(course.teacherIds, currentUser._id)) {
			onData(new Error('You don\'t have the permission to see this page.'));
			return;
		}

		let componentData = {
			actions,
			course: course
		};

		onData(null, componentData);
	}).catch(err => {
		Notification.showError("Kurs wurde nicht gefunden");
		browserHistory.push("/courses/")
	});
};

export default compose(composer)(component);
<|endoftext|>


<commit_before><commit_msg>fix(DuplicateIds): detectDuplicateIds flag didn't work correctly<commit_after><|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import $ from 'jquery';

import { compose } from 'react-apollo';

import getMuiTheme from 'material-ui/styles/getMuiTheme';
import RaisedButton from 'material-ui/RaisedButton';
import { Link } from 'react-router-dom';

// lib
import Utils from '../../../../lib/utils';
import muiTheme from '../../../../lib/muiTheme';

// graphql
import settingsQuery from '../../../settings/graphql/queries/list';

// components:
import BackgroundImageHolder from '../../../shared/components/BackgroundImageHolder/BackgroundImageHolder';
import CommentersList from '../../../commenters/components/CommenterList/CommentersList';
import KeywordsList from '../../../keywords/components/KeywordsList/KeywordsList';
import LoadingHome from '../../../../components/loading/LoadingHome';

// layouts:
import CommentaryContainer from '../../../comments/containers/CommentaryContainer';

class Home extends Component {
	constructor(props) {
		super(props);
		this.scrollToIntro = this.scrollToIntro.bind(this);
		this.state = {};
	}
	getChildContext() {
		return { muiTheme: getMuiTheme(muiTheme) };
	}

	scrollToIntro(e) {
		$('html, body').animate({ scrollTop: $('#intro').offset().top - 100 }, 300);

		e.preventDefault();
	}
	componentWillReceiveProps(nextProps) {
		const tenantId = sessionStorage.getItem('tenantId');

		this.setState({
			settings: nextProps.settingsQuery.loading ? {} : nextProps.settingsQuery.settings.find(x => x.tenantId === tenantId),
			ready: !nextProps.settingsQuery.loading
		});
	}
	render() {
		let imageUrl = `${window.location.origin}/images/hector.jpg`;
		let introImage = '/images/ajax_achilles_3.jpg';
		let introImageCaption = '';
		const { settings, ready } = this.state;

		if (!settings) {
			return <LoadingHome />;
		}

		if (
			settings.homepageCover
			&& settings.homepageCover.length
		) {
			imageUrl = settings.homepageCover;
		}

		if (
			settings.homepageIntroductionImage
			&& settings.homepageIntroductionImage.length
		) {
			introImage = settings.homepageIntroductionImage;
		}

		if (
			settings.homepageIntroductionImageCaption
			&& settings.homepageIntroductionImageCaption.length
		) {
			introImageCaption = Utils.getHtmlFromContext(Utils.getEditorState(settings.homepageIntroductionImageCaption).getCurrentContent());
		}

		Utils.setTitle(`Home | ${settings ? settings.title : ''}`);
		Utils.setDescription((settings ? settings.subtitle : ''));
		Utils.setMetaImage(imageUrl);

		return (

						<BackgroundImageHolder
							imgSrc={imageUrl}
						/>

{settings.name}

											{settings.subtitle}

									<RaisedButton
										href="#intro"
										className="cover-link learn-more"
										label="Learn More"
										onClick={this.scrollToIntro}
									/>
									<Link to="/commentary">
										<RaisedButton
											className="cover-link go-to-commentary"
											label="Go to Commentary"
										/>
									</Link>

								Scroll down for an overview of the project.

									{settings.homepageIntroductionTitle}

									{ready ? settings.introBlocks.map((block, i) => (

												{block.title}

												{block.text}

											{block.linkURL && block.linkText ?

													<Link to={block.linkURL}>
														<RaisedButton
															className="intro-block-link cover-link dark"
															label={block.linkText}
														/>
													</Link>

											: ''}

									)) : ''}

							{/* */}

						{/* */}

						<BackgroundImageHolder
							imgSrc="/images/school-athens.jpg"
						/>

Commentators

							<CommentersList
								featureOnHomepage
								defaultAvatarUrl="/images/default_user.jpg"
								limit={3}
							/>
							<Link to="/commenters">
								<RaisedButton
									className="cover-link light show-more "
									label="Other Commentators"
								/>
							</Link>

							The charts below visualize data about the number of comments per book or hymn,
							but they are also an interface into the commentary itself.
							The darker the shade of the bar, the more comments there are,
							but try clicking on the shaded elements and see what happens.

							{/* <WorksList /> */}

Words

							<KeywordsList type="word" title="Words" limit={5} />
							<Link to="/words">
								<RaisedButton
									className="cover-link show-more primary "
									label="More Words"
								/>
							</Link>

Ideas

							<KeywordsList type="idea" title="Ideas" limit={5} />
							<Link to="/ideas">
								<RaisedButton
									className="cover-link show-more primary "
									label="More Ideas"
								/>
							</Link>

					{/*

Get Started

							{ready ?
								<CommentaryContainer
									isOnHomeView
									filters={[]}
									skip={0}
									limit={10}
								/> : ''
							}

								<Link to="/commentary">
									<RaisedButton
										className="cover-link light show-more "
										label="Read More"
									/>
								</Link>

					*/}

		);
	}
}

Home.propTypes = {
	settingsQuery: PropTypes.object,
	isTest: PropTypes.bool,
};

Home.childContextTypes = {
	muiTheme: PropTypes.object.isRequired,
};

export default compose(settingsQuery)(Home);
<commit_msg>remove whole visualizations section on the homepage until fully implemented<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import $ from 'jquery';

import { compose } from 'react-apollo';

import getMuiTheme from 'material-ui/styles/getMuiTheme';
import RaisedButton from 'material-ui/RaisedButton';
import { Link } from 'react-router-dom';

// lib
import Utils from '../../../../lib/utils';
import muiTheme from '../../../../lib/muiTheme';

// graphql
import settingsQuery from '../../../settings/graphql/queries/list';

// components:
import BackgroundImageHolder from '../../../shared/components/BackgroundImageHolder/BackgroundImageHolder';
import CommentersList from '../../../commenters/components/CommenterList/CommentersList';
import KeywordsList from '../../../keywords/components/KeywordsList/KeywordsList';
import LoadingHome from '../../../../components/loading/LoadingHome';

// layouts:
import CommentaryContainer from '../../../comments/containers/CommentaryContainer';

class Home extends Component {
	constructor(props) {
		super(props);
		this.scrollToIntro = this.scrollToIntro.bind(this);
		this.state = {};
	}
	getChildContext() {
		return { muiTheme: getMuiTheme(muiTheme) };
	}

	scrollToIntro(e) {
		$('html, body').animate({ scrollTop: $('#intro').offset().top - 100 }, 300);

		e.preventDefault();
	}
	componentWillReceiveProps(nextProps) {
		const tenantId = sessionStorage.getItem('tenantId');

		this.setState({
			settings: nextProps.settingsQuery.loading ? {} : nextProps.settingsQuery.settings.find(x => x.tenantId === tenantId),
			ready: !nextProps.settingsQuery.loading
		});
	}
	render() {
		let imageUrl = `${window.location.origin}/images/hector.jpg`;
		let introImage = '/images/ajax_achilles_3.jpg';
		let introImageCaption = '';
		const { settings, ready } = this.state;

		if (!settings) {
			return <LoadingHome />;
		}

		if (
			settings.homepageCover
			&& settings.homepageCover.length
		) {
			imageUrl = settings.homepageCover;
		}

		if (
			settings.homepageIntroductionImage
			&& settings.homepageIntroductionImage.length
		) {
			introImage = settings.homepageIntroductionImage;
		}

		if (
			settings.homepageIntroductionImageCaption
			&& settings.homepageIntroductionImageCaption.length
		) {
			introImageCaption = Utils.getHtmlFromContext(Utils.getEditorState(settings.homepageIntroductionImageCaption).getCurrentContent());
		}

		Utils.setTitle(`Home | ${settings ? settings.title : ''}`);
		Utils.setDescription((settings ? settings.subtitle : ''));
		Utils.setMetaImage(imageUrl);

		return (

						<BackgroundImageHolder
							imgSrc={imageUrl}
						/>

{settings.name}

											{settings.subtitle}

									<RaisedButton
										href="#intro"
										className="cover-link learn-more"
										label="Learn More"
										onClick={this.scrollToIntro}
									/>
									<Link to="/commentary">
										<RaisedButton
											className="cover-link go-to-commentary"
											label="Go to Commentary"
										/>
									</Link>

								Scroll down for an overview of the project.

									{settings.homepageIntroductionTitle}

									{ready ? settings.introBlocks.map((block, i) => (

												{block.title}

												{block.text}

											{block.linkURL && block.linkText ?

													<Link to={block.linkURL}>
														<RaisedButton
															className="intro-block-link cover-link dark"
															label={block.linkText}
														/>
													</Link>

											: ''}

									)) : ''}

							{/* */}

						{/* */}

						<BackgroundImageHolder
							imgSrc="/images/school-athens.jpg"
						/>

Commentators

							<CommentersList
								featureOnHomepage
								defaultAvatarUrl="/images/default_user.jpg"
								limit={3}
							/>
							<Link to="/commenters">
								<RaisedButton
									className="cover-link light show-more "
									label="Other Commentators"
								/>
							</Link>

					{/*

							The charts below visualize data about the number of comments per book or hymn,
							but they are also an interface into the commentary itself.
							The darker the shade of the bar, the more comments there are,
							but try clicking on the shaded elements and see what happens.

							<WorksList />

					*/}

Words

							<KeywordsList type="word" title="Words" limit={5} />
							<Link to="/words">
								<RaisedButton
									className="cover-link show-more primary "
									label="More Words"
								/>
							</Link>

Ideas

							<KeywordsList type="idea" title="Ideas" limit={5} />
							<Link to="/ideas">
								<RaisedButton
									className="cover-link show-more primary "
									label="More Ideas"
								/>
							</Link>

					{/*

Get Started

							{ready ?
								<CommentaryContainer
									isOnHomeView
									filters={[]}
									skip={0}
									limit={10}
								/> : ''
							}

								<Link to="/commentary">
									<RaisedButton
										className="cover-link light show-more "
										label="Read More"
									/>
								</Link>

					*/}

		);
	}
}

Home.propTypes = {
	settingsQuery: PropTypes.object,
	isTest: PropTypes.bool,
};

Home.childContextTypes = {
	muiTheme: PropTypes.object.isRequired,
};

export default compose(settingsQuery)(Home);
<|endoftext|>


<commit_before>/*** AUTO-GENERATED BY GULP ***/

angular
  .module('lnPatterns')
  .controller('lnController', ['$scope', function($scope) {
    //TODO
  }])

;<commit_msg>Delete ngControllers.js<commit_after><|endoftext|>


<commit_before>
// @require core/cash.js

fn.empty = function () {

  const ele = this[0];

  if ( ele ) {

    while ( ele.firstChild ) {
      ele.removeChild( ele.firstChild );
    }

  }

  return this;

};
<commit_msg>`$.fn.empty`: updated code style<commit_after>
// @require core/cash.js

fn.empty = function () {

  const ele = this[0];

  if ( ele ) {

    while ( ele.firstChild ) {

      ele.removeChild ( ele.firstChild );

    }

  }

  return this;

};
<|endoftext|>


<commit_before>/* jslint node: true */

/**
 *
 * sclang - boots a supercollider language interpreter process
 *  and enables stdin/out and system level event responders
 *
 *  SuperCollider comes with an executable called sclang
 *  which can be communicated with via stdin/stdout
 *  or via OSC.
 *
 * methods:
 *   boot  - boot an sclang process
 *   write - write to stdin of the sclang process
 *   quit
 *
 */

var
  _ = require('underscore'),
  events = require('events'),
  spawn = require('child_process').spawn,
  path = require('path'),
  Logger = require('./logger'),
  Q = require('q'),
  uuid = require('node-uuid'),
  join = require('path').join;

/**
 *
 * Local sclang - a language interpreter running on the same machine
 *
 * @constructor
 * @param {dict} options - sclang command line options
 */
function SCLang(options) {
  this.options = options;
  this.process = null;
  this.calls = {};
  this.state = null;
  this.log = new Logger(this.options.debug, this.options.echo);
  this.log.dbug(this.options);
}

SCLang.super_ = events.EventEmitter;
SCLang.prototype = Object.create(events.EventEmitter.prototype, {
  constructor: {
    value: SCLang,
    enumerable: false
  }
});

/**
 * build args for sclang
 *
 *   -d <path>                      Set runtime directory
 *   -D                             Enter daemon mode (no input)
 *   -g <memory-growth>[km]         Set heap growth (default 256k)
 *   -h                             Display this message and exit
 *   -l <path>                      Set library configuration file
 *   -m <memory-space>[km]          Set initial heap size (default 2m)
 *   -r                             Call Main.run on startup
 *   -s                             Call Main.stop on shutdown
 *   -u <network-port-number>       Set UDP listening port (default 57120)
 *   -i <ide-name>                  Specify IDE name (for enabling IDE-specific class code, default "none")
 *   -a                             Standalone mode
 */
SCLang.prototype.args = function() {
  var o = [];
  if (this.options.executeFile) {
    o.push(this.options.executeFile);
  }
  o.push('-u', this.options.langPort);
  return o;
};

/**
 * boot
 *
 * start sclang and establish a pipe connection
 * to receive stdout and stderr
 */
SCLang.prototype.boot = function() {
  var
    deferred = Q.defer(),
    execPath = this.options.sclang,
    self = this;
  this.log.dbug('path:' + execPath);
  this.setState('booting');
  this.process = spawn(execPath, this.args(),
    {
      cwd: path.dirname(execPath)
    });

  function resolver(state) {
    if (state === 'ready') {
      deferred.resolve();
      self.removeListener('state', resolver);
    } else if (state === 'compileError') {
      deferred.reject();
      self.removeListener('state', resolver);
    }
  }
  // temporary listener till booted
  this.addListener('state', resolver);

  // long term listeners
  this.installListeners();
  return deferred.promise;
};

/**
  * installListeners
  */
SCLang.prototype.installListeners = function() {

  var self = this,
    states = {
      booting: [
        {
          re: /^compiling class library/m,
          fn: function() {
            self.setState('compiling');
            // and collect potential compile errors
          }
        }
      ],
      compiling: [
        {
          re: /^compile done/m,
          fn: function() {
            self.setState('compiled');
            // and collect potential startup errors
          }
        },
        {
          re: /^Library has not been compiled successfully/m,
          fn: function() {
            self.setState('compileError');
          }
        }
      ],
      compileError: [

      ],
      compiled: [
        {
          re: /^Welcome to SuperCollider/m,
          fn: function() {
            self.setState('ready');
          }
        }
      ],
      ready: [
        {
          // callback
          re: /^SUPERCOLLIDERJS\:([0-9a-f\-]+)\:([A-Za-z]+)\:(.+)$/m,
          fn: function(match) {
            var
              guid = match[1],
              type = match[2],
              json = match[3],
              obj = JSON.parse(json);
            if (guid in self.calls) {
              if (type === 'Result') {
                self.calls[guid].resolve(obj);
              } else {
                self.calls[guid].reject({type: type, error: obj});
              }
              delete self.calls[guid];
              return true;
            }
          }
        }
      ]
    };

  this.process.stdout.on('data', function(data) {
    var text = '' + data,
        passthru = true;
    states[self.state].forEach(function(stf) {
      var match = stf.re.exec(text);
      if (match) {
        // do not post if handler returns true
        if (stf.fn(match) === true) {
          passthru = false;
        }
      }
    });
    if (passthru) {
      self.log.stdout(text);
      self.emit('stdout', text);
    }
  });

  this.process.on('error', function(err) {
    self.log.err('ERROR:' + err, 'error');
    self.emit('stderr', err);
  });
  this.process.on('close', function(code, signal) {
    self.log.dbug('close ' + code + signal);
    self.emit('exit', code);
    self.setState(null);
  });
  this.process.on('exit', function(code, signal) {
    self.log.dbug('exit ' + code + signal);
    self.emit('exit', code);
    self.setState(null);
  });
  this.process.on('disconnect', function() {
    self.log.dbug('disconnect');
    self.emit('exit');
    self.setState(null);
  });

  this.process.stderr.on('data', function(data) {
    self.log.stderr('' + data);
    self.emit('stderr', '' + data);
  });

  if (this.options.stdin) {
    // global top level nodejs process
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', function(chunk) {
      if (chunk) {
        self.write(chunk, null, true);
      }
    });
  }
};

/**
 * write
 *
 * send a raw string to sclang to be interpreted
 * callback is called after write is complete
 */
SCLang.prototype.write = function(chunk, callback, noEcho) {
  if (!noEcho) {
    this.log.stdin(chunk);
  }
  this.process.stdin.write(chunk, 'UTF-8');
  // escape character means execute the currently accumulated command line as SC code
  this.process.stdin.write('\x0c', null, callback);
};

/**
  * initInterpreter
  *
  * make sclang execute the interpret.scd file
  * to load the functions used by interpret
  */
SCLang.prototype.initInterpreter = function(callback) {
  var path = join(__dirname, '../sc/interpret.scd');
  this.write('thisProcess.interpreter.executeFile("' + path + '")',
    callback, true);
};

/**
 * interpret
 *
 * evaluates code in sclang
 * and returns a promise
 *
 * resolves promise with result or rejects with error as JSON
 *
 * @param {string} code
 *        source code to evaluate
 * @param {string} nowExecutingPath
          set thisProcess.nowExecutingPath
 *        for use in a REPL to evaluate text in a file
 *        and let sclang know what file it is executing.
 * @param {Boolean} asString
 *        return result .asString for post window
 *        rather than as a full JSON object
 * @returns {Promise}
 */
SCLang.prototype.interpret = function(code, nowExecutingPath, asString) {
  var deferred = Q.defer(),
      escaped = code.replace(/[\n\r]/g, '__NL__').replace(/\"/g, '\\"'),
      scstring = '"' + escaped + '"',
      guid = uuid.v1(),
      guidString = '"' + guid + '"',
      nep = nowExecutingPath ? '"' + nowExecutingPath + '"' : 'nil',
      rras = asString ? 'true' : 'false',
      args = [guidString, scstring, nep, rras].join(','),
      scode;

  scode = 'Library.at(\\supercolliderjs, \\interpret).value(' + args +
    ');"SUPERCOLLIDERJS.interpreted".postln;';

  // register listener
  this.calls[guid] = deferred;
  this.write(scode, null, true);
  return deferred.promise;
};

/**
  * setState
  * set state to one of null, 'booting' 'compiling' 'compileError' 'compiled' 'ready'
  * and emit
  */
SCLang.prototype.setState = function(state) {
  if (state !== this.state) {
    this.state = state;
    this.emit('state', state);
    this.log.dbug('state: ' + state);
  }
};

/**
 * quit
 *
 * stop the sclang process
 */
SCLang.prototype.quit = function() {
  var deferred = Q.defer();
  if (this.process) {
    this.process.once('exit', function() {
      deferred.resolve();
    });
    // should do shutdown first
    this.process.kill('SIGINT');
    this.process = null;
  } else {
    deferred.resolve();
  }
  this.setState(null);
  return deferred.promise;
};

module.exports = SCLang;
<commit_msg>initInterpreter returns a promise, and emits an event when completed<commit_after>/* jslint node: true */

/**
 *
 * sclang - boots a supercollider language interpreter process
 *  and enables stdin/out and system level event responders
 *
 *  SuperCollider comes with an executable called sclang
 *  which can be communicated with via stdin/stdout
 *  or via OSC.
 *
 * methods:
 *   boot  - boot an sclang process
 *   write - write to stdin of the sclang process
 *   quit
 *
 */

var
  _ = require('underscore'),
  events = require('events'),
  spawn = require('child_process').spawn,
  path = require('path'),
  Logger = require('./logger'),
  Q = require('q'),
  uuid = require('node-uuid'),
  join = require('path').join;

/**
 *
 * Local sclang - a language interpreter running on the same machine
 *
 * @constructor
 * @param {dict} options - sclang command line options
 */
function SCLang(options) {
  this.options = options;
  this.process = null;
  this.calls = {};
  this.state = null;
  this.log = new Logger(this.options.debug, this.options.echo);
  this.log.dbug(this.options);
}

SCLang.super_ = events.EventEmitter;
SCLang.prototype = Object.create(events.EventEmitter.prototype, {
  constructor: {
    value: SCLang,
    enumerable: false
  }
});

/**
 * build args for sclang
 *
 *   -d <path>                      Set runtime directory
 *   -D                             Enter daemon mode (no input)
 *   -g <memory-growth>[km]         Set heap growth (default 256k)
 *   -h                             Display this message and exit
 *   -l <path>                      Set library configuration file
 *   -m <memory-space>[km]          Set initial heap size (default 2m)
 *   -r                             Call Main.run on startup
 *   -s                             Call Main.stop on shutdown
 *   -u <network-port-number>       Set UDP listening port (default 57120)
 *   -i <ide-name>                  Specify IDE name (for enabling IDE-specific class code, default "none")
 *   -a                             Standalone mode
 */
SCLang.prototype.args = function() {
  var o = [];
  if (this.options.executeFile) {
    o.push(this.options.executeFile);
  }
  o.push('-u', this.options.langPort);
  return o;
};

/**
 * boot
 *
 * start sclang and establish a pipe connection
 * to receive stdout and stderr
 */
SCLang.prototype.boot = function() {
  var
    deferred = Q.defer(),
    execPath = this.options.sclang,
    self = this;
  this.log.dbug('path:' + execPath);
  this.setState('booting');
  this.process = spawn(execPath, this.args(),
    {
      cwd: path.dirname(execPath)
    });

  function resolver(state) {
    if (state === 'ready') {
      deferred.resolve();
      self.removeListener('state', resolver);
    } else if (state === 'compileError') {
      deferred.reject();
      self.removeListener('state', resolver);
    }
  }
  // temporary listener till booted
  this.addListener('state', resolver);

  // long term listeners
  this.installListeners();
  return deferred.promise;
};

/**
  * installListeners
  */
SCLang.prototype.installListeners = function() {

  var self = this,
    states = {
      booting: [
        {
          re: /^compiling class library/m,
          fn: function() {
            self.setState('compiling');
            // and collect potential compile errors
          }
        }
      ],
      compiling: [
        {
          re: /^compile done/m,
          fn: function() {
            self.setState('compiled');
            // and collect potential startup errors
          }
        },
        {
          re: /^Library has not been compiled successfully/m,
          fn: function() {
            self.setState('compileError');
          }
        }
      ],
      compileError: [

      ],
      compiled: [
        {
          re: /^Welcome to SuperCollider/m,
          fn: function() {
            self.setState('ready');
          }
        }
      ],
      ready: [
        {
          // callback
          re: /^SUPERCOLLIDERJS\:([0-9a-f\-]+)\:([A-Za-z]+)\:(.+)$/m,
          fn: function(match) {
            var
              guid = match[1],
              type = match[2],
              json = match[3],
              obj = JSON.parse(json);
            if (guid in self.calls) {
              if (type === 'Result') {
                self.calls[guid].resolve(obj);
              } else {
                self.calls[guid].reject({type: type, error: obj});
              }
              delete self.calls[guid];
              return true;
            }
          }
        },
        {
          // interpreter.scd posts this at the end
          re: /^SUPERCOLLIDERJS-interpreter-loaded$/m,
          fn: function() {
            self.emit('interpreterLoaded');
            return true;
          }
        },
        {
          // interpret calls post this
          re: /^SUPERCOLLIDERJS.interpreted$/m,
          fn: function() {
            // supress output
            return true;
          }
        }
      ]
    };

  this.process.stdout.on('data', function(data) {
    var text = '' + data,
        passthru = true;
    states[self.state].forEach(function(stf) {
      var match = stf.re.exec(text);
      if (match) {
        // do not post if handler returns true
        if (stf.fn(match) === true) {
          passthru = false;
        }
      }
    });
    if (passthru) {
      self.log.stdout(text);
      self.emit('stdout', text);
    }
  });

  this.process.on('error', function(err) {
    self.log.err('ERROR:' + err, 'error');
    self.emit('stderr', err);
  });
  this.process.on('close', function(code, signal) {
    self.log.dbug('close ' + code + signal);
    self.emit('exit', code);
    self.setState(null);
  });
  this.process.on('exit', function(code, signal) {
    self.log.dbug('exit ' + code + signal);
    self.emit('exit', code);
    self.setState(null);
  });
  this.process.on('disconnect', function() {
    self.log.dbug('disconnect');
    self.emit('exit');
    self.setState(null);
  });

  this.process.stderr.on('data', function(data) {
    self.log.stderr('' + data);
    self.emit('stderr', '' + data);
  });

  if (this.options.stdin) {
    // global top level nodejs process
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', function(chunk) {
      if (chunk) {
        self.write(chunk, null, true);
      }
    });
  }
};

/**
 * write
 *
 * send a raw string to sclang to be interpreted
 * callback is called after write is complete
 */
SCLang.prototype.write = function(chunk, callback, noEcho) {
  if (!noEcho) {
    this.log.stdin(chunk);
  }
  this.process.stdin.write(chunk, 'UTF-8');
  // escape character means execute the currently accumulated command line as SC code
  this.process.stdin.write('\x0c', null, callback);
};

/**
  * initInterpreter
  *
  * make sclang execute the interpret.scd file
  * to load the functions used by interpret
  */
SCLang.prototype.initInterpreter = function() {
  var path = join(__dirname, '../sc/interpret.scd'),
      deferred = Q.defer(),
      self = this;
  this.once('interpreterLoaded', function() {
    deferred.resolve(self);
  });
  this.write('thisProcess.interpreter.executeFile("' + path + '")',
    null, true);
};

/**
 * interpret
 *
 * evaluates code in sclang
 * and returns a promise
 *
 * resolves promise with result or rejects with error as JSON
 *
 * @param {string} code
 *        source code to evaluate
 * @param {string} nowExecutingPath
          set thisProcess.nowExecutingPath
 *        for use in a REPL to evaluate text in a file
 *        and let sclang know what file it is executing.
 * @param {Boolean} asString
 *        return result .asString for post window
 *        rather than as a full JSON object
 * @returns {Promise}
 */
SCLang.prototype.interpret = function(code, nowExecutingPath, asString) {
  var deferred = Q.defer(),
      escaped = code.replace(/[\n\r]/g, '__NL__').replace(/\"/g, '\\"'),
      scstring = '"' + escaped + '"',
      guid = uuid.v1(),
      guidString = '"' + guid + '"',
      nep = nowExecutingPath ? '"' + nowExecutingPath + '"' : 'nil',
      rras = asString ? 'true' : 'false',
      args = [guidString, scstring, nep, rras].join(','),
      scode;

  scode = 'Library.at(\\supercolliderjs, \\interpret).value(' + args +
    ');"SUPERCOLLIDERJS.interpreted".postln;';

  // register listener
  this.calls[guid] = deferred;
  this.write(scode, null, true);
  return deferred.promise;
};

/**
  * setState
  * set state to one of null, 'booting' 'compiling' 'compileError' 'compiled' 'ready'
  * and emit
  */
SCLang.prototype.setState = function(state) {
  if (state !== this.state) {
    this.state = state;
    this.emit('state', state);
    this.log.dbug('state: ' + state);
  }
};

/**
 * quit
 *
 * stop the sclang process
 */
SCLang.prototype.quit = function() {
  var deferred = Q.defer();
  if (this.process) {
    this.process.once('exit', function() {
      deferred.resolve();
    });
    // should do shutdown first
    this.process.kill('SIGINT');
    this.process = null;
  } else {
    deferred.resolve();
  }
  this.setState(null);
  return deferred.promise;
};

module.exports = SCLang;
<|endoftext|>


<commit_before>(function (angular) {

  'use strict';

  angular.module('birdyard.utilities')

  .directive('menu', [
    '$rootScope',
    '$location',
    '$timeout',
    'authService',
    'firebaseService',
    '$mdToast',
    '$mdSidenav',
    'backdropService',

    function (
      $rootScope,
      $location,
      $timeout,
      authService,
      firebaseService,
      $mdToast,
      $mdSidenav,
      backdropService) {

    return {
      restrict: 'E',
      replace: true,
      template:
      '
' +

        '
Sign in with Twitter
' +

        '
' +
          '' +
          '' +
        '
' +

        '
' +
          '
View Your Profile
' +
          '
Notifications {{notificationCount}}
' +
          '
Make a New Room
' +
          '
Get Help
' +
          '
Legal Stuff
' +

          '
' +
            '<avatar class="birdyard-avatar"></avatar>' +
            '' +
              'SIGN OUT' +
            '' +
          '
' +
        '
' +
      '
',

      link: function (scope, element, attrs) {

        scope.showing = false;
        scope.ring =    false;

        // Private

        function clickToClose() {
          backdropService.set(true, 4, scope.toggleMenu);
        }

        // Event Listeners

        $rootScope.$on('notification', function ($event, count) {
          if (count) {
            // Ring the bell!!!!!!
            scope.ring = true;
            $timeout(function () {
              scope.ring = false;
            }, 5000);
          }
        });

        // Public

        scope.toggleMenu = function () {
          if ($rootScope.signedIn) {
            scope.showing = !scope.showing;
            clickToClose();
          }
        };

        scope.close = function () {
          if (scope.showing) {
            scope.showing = false;
            backdropService.reset();
          }
        }

        scope.goTo = function (loc) {
          if (scope.showing) {
            $location.path(loc);
          }
        };

        scope.toggleNotifications = function () {
          $mdSidenav('notifications-nav').toggle();
        };

        scope.signIn = function () {

          var $ref = firebaseService.getRef();

          $ref.authWithOAuthPopup('twitter', function(error, authData) {
            if (error) {
              console.error(error);
            } else {
              authService.signIn(authData).then(function () {
              angular.element(document.getElementById('text-input')).focus();
              }).catch(function (error) {
                console.error(error);
              });
            }
          });
        };

        scope.signOut = function () {

          if (!scope.showing) return;

          return authService.signOut().then(function () {
            return $mdToast.show(
              $mdToast.simple()
                .content('Signed out!')
                .theme('toast-default')
                .position('bottom right')
                .hideDelay(1000)
              );
          });
        };
      }
    }
  }]);

})(angular);<commit_msg>Bugfix: dingbell was getting pushed-out of its container<commit_after>(function (angular) {

  'use strict';

  angular.module('birdyard.utilities')

  .directive('menu', [
    '$rootScope',
    '$location',
    '$timeout',
    'authService',
    'firebaseService',
    '$mdToast',
    '$mdSidenav',
    'backdropService',

    function (
      $rootScope,
      $location,
      $timeout,
      authService,
      firebaseService,
      $mdToast,
      $mdSidenav,
      backdropService) {

    return {
      restrict: 'E',
      replace: true,
      template:
      '
' +

        '
Sign in with Twitter
' +

        '
' +
          '' +
          '' +
        '
' +

        '
' +
          '
View Your Profile
' +
          '
Notifications {{notificationCount}}
' +
          '
Make a New Room
' +
          '
Get Help
' +
          '
Legal Stuff
' +

          '
' +
            '<avatar class="birdyard-avatar"></avatar>' +
            '' +
              'SIGN OUT' +
            '' +
          '
' +
        '
' +
      '
',

      link: function (scope, element, attrs) {

        scope.showing = false;
        scope.ring =    false;

        // Private

        function clickToClose() {
          backdropService.set(true, 4, scope.toggleMenu);
        }

        // Event Listeners

        $rootScope.$on('notification', function ($event, count) {
          if (count && !scope.ring) {
            // Ring the bell!!!!!!
            scope.ring = true;
            $timeout(function () {
              scope.ring = false;
            }, 5000);
          }
        });

        // Public

        scope.toggleMenu = function () {
          if ($rootScope.signedIn) {
            scope.showing = !scope.showing;
            clickToClose();
          }
        };

        scope.close = function () {
          if (scope.showing) {
            scope.showing = false;
            backdropService.reset();
          }
        }

        scope.goTo = function (loc) {
          if (scope.showing) {
            $location.path(loc);
          }
        };

        scope.toggleNotifications = function () {
          $mdSidenav('notifications-nav').toggle();
        };

        scope.signIn = function () {

          var $ref = firebaseService.getRef();

          $ref.authWithOAuthPopup('twitter', function(error, authData) {
            if (error) {
              console.error(error);
            } else {
              authService.signIn(authData).then(function () {
              angular.element(document.getElementById('text-input')).focus();
              }).catch(function (error) {
                console.error(error);
              });
            }
          });
        };

        scope.signOut = function () {

          if (!scope.showing) return;

          return authService.signOut().then(function () {
            return $mdToast.show(
              $mdToast.simple()
                .content('Signed out!')
                .theme('toast-default')
                .position('bottom right')
                .hideDelay(1000)
              );
          });
        };
      }
    }
  }]);

})(angular);<|endoftext|>


<commit_before>/* jslint node: true */

/**
 *
 * sclang - boots a supercollider language interpreter process
 *  and enables stdin/out and system level event responders
 *
 *  SuperCollider comes with an executable called sclang
 *  which can be communicated with via stdin/stdout
 *  or via OSC.
 *
 * methods:
 *   boot  - boot an sclang process
 *   write - write to stdin of the sclang process
 *   quit
 *
 */

var
  _ = require('underscore'),
  events = require('events'),
  spawn = require('child_process').spawn,
  path = require('path'),
  Logger = require('./logger'),
  Q = require('q'),
  uuid = require('node-uuid'),
  join = require('path').join;

/**
 *
 * Local sclang - a language interpreter running on the same machine
 *
 * @constructor
 * @param {Object} options - sclang command line options
 */
function SCLang(options) {
  this.options = options;
  this.process = null;
  this.calls = {};
  this.state = null;
  this.log = new Logger(this.options.debug, this.options.echo);
  this.log.dbug(this.options);
}

SCLang.super_ = events.EventEmitter;
SCLang.prototype = Object.create(events.EventEmitter.prototype, {
  constructor: {
    value: SCLang,
    enumerable: false
  }
});

/**
 * build args for sclang
 *
 *   -d <path>                      Set runtime directory
 *   -D                             Enter daemon mode (no input)
 *   -g <memory-growth>[km]         Set heap growth (default 256k)
 *   -h                             Display this message and exit
 *   -l <path>                      Set library configuration file
 *   -m <memory-space>[km]          Set initial heap size (default 2m)
 *   -r                             Call Main.run on startup
 *   -s                             Call Main.stop on shutdown
 *   -u <network-port-number>       Set UDP listening port (default 57120)
 *   -i <ide-name>                  Specify IDE name (for enabling IDE-specific class code, default "none")
 *   -a                             Standalone mode
 */
SCLang.prototype.args = function() {
  var o = [];
  if (this.options.executeFile) {
    o.push(this.options.executeFile);
  }
  o.push('-u', this.options.langPort);
  return o;
};

/**
 * boot
 *
 * start sclang and establish a pipe connection
 * to receive stdout and stderr
 */
SCLang.prototype.boot = function() {
  var
    deferred = Q.defer(),
    execPath = this.options.sclang,
    self = this;
  this.log.dbug('path:' + execPath);
  this.setState('booting');
  this.process = spawn(execPath, this.args(),
    {
      cwd: path.dirname(execPath)
    });

  function resolver(state) {
    if (state === 'ready') {
      deferred.resolve();
      self.removeListener('state', resolver);
    } else if (state === 'compileError') {
      deferred.reject();
      self.removeListener('state', resolver);
    }
  }
  // temporary listener till booted
  this.addListener('state', resolver);

  // long term listeners
  this.installListeners();
  return deferred.promise;
};

/**
  * installListeners
  */
SCLang.prototype.installListeners = function() {

  var self = this,
    states = {
      booting: [
        {
          re: /^compiling class library/m,
          fn: function() {
            self.setState('compiling');
            // and collect potential compile errors
          }
        }
      ],
      compiling: [
        {
          re: /^compile done/m,
          fn: function() {
            self.setState('compiled');
            // and collect potential startup errors
          }
        },
        {
          re: /^Library has not been compiled successfully/m,
          fn: function() {
            self.setState('compileError');
          }
        }
      ],
      compileError: [

      ],
      compiled: [
        {
          re: /^Welcome to SuperCollider ([0-9a-zA-Z\.]+)\. /m,
          fn: function(match) {
            self.version = match[1];
            self.setState('ready');
          }
        }
      ],
      ready: [
        {
          // callback
          re: /^SUPERCOLLIDERJS\:([0-9a-f\-]+)\:([A-Za-z]+)\:(.+)$/m,
          fn: function(match, text) {
            var
              guid = match[1],
              type = match[2],
              json = match[3],
              obj = JSON.parse(json),
              stdout;
            if (guid in self.calls) {
              if (type === 'Result') {
                self.calls[guid].resolve(obj);
              } else {
                if (!obj) {
                  obj = {};
                }
                stdout = text.replace(/^SUPERCOLLIDERJS[\s\S]+/mg, '');
                obj.stdout = stdout;
                // TODO: parse errors from text
                self.calls[guid].reject({type: type, error: obj});
              }
              delete self.calls[guid];
              return true;
            }
          }
        },
        {
          // interpreter.scd posts this at the end
          re: /^SUPERCOLLIDERJS-interpreter-loaded$/m,
          fn: function() {
            self.emit('interpreterLoaded');
            return true;
          }
        }
        // {
        //   // interpret calls post this
        //   re: /^SUPERCOLLIDERJS.interpreted$/m,
        //   fn: function(match, text) {
        //     // chunk may contain errors
        //     // to capture
        //     // and reject this:
        //     // self.calls[guid].reject({type: type, error: obj});
        //
        //     // capture potential syntax errors
        //     // supress output
        //     // return true;
        //   }
        // }
      ]
    };

  this.process.stdout.on('data', function(data) {
    var text = '' + data,
        passthru = true;
    states[self.state].forEach(function(stf) {
      var match = stf.re.exec(text);
      if (match) {
        // do not post if handler returns true
        if (stf.fn(match, text) === true) {
          passthru = false;
        }
      }
    });
    if (passthru) {
      self.log.stdout(text);
      self.emit('stdout', text);
    }
  });

  this.process.on('error', function(err) {
    self.log.err('ERROR:' + err, 'error');
    self.emit('stderr', err);
  });
  this.process.on('close', function(code, signal) {
    self.log.dbug('close ' + code + signal);
    self.emit('exit', code);
    self.setState(null);
  });
  this.process.on('exit', function(code, signal) {
    self.log.dbug('exit ' + code + signal);
    self.emit('exit', code);
    self.setState(null);
  });
  this.process.on('disconnect', function() {
    self.log.dbug('disconnect');
    self.emit('exit');
    self.setState(null);
  });

  this.process.stderr.on('data', function(data) {
    self.log.stderr('' + data);
    self.emit('stderr', '' + data);
  });

  if (this.options.stdin) {
    // global top level nodejs process
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', function(chunk) {
      if (chunk) {
        self.write(chunk, null, true);
      }
    });
  }
};

/**
 * write
 *
 * send a raw string to sclang to be interpreted
 * callback is called after write is complete
 */
SCLang.prototype.write = function(chunk, callback, noEcho) {
  if (!noEcho) {
    this.log.stdin(chunk);
  }
  this.process.stdin.write(chunk, 'UTF-8');
  // escape character means execute the currently accumulated command line as SC code
  this.process.stdin.write('\x0c', null, callback);
};

/**
  * initInterpreter
  *
  * make sclang execute the interpret.scd file
  * to load the functions used by interpret
  */
SCLang.prototype.initInterpreter = function() {
  var path = join(__dirname, '../sc/interpret.scd'),
      deferred = Q.defer(),
      self = this;
  this.once('interpreterLoaded', function() {
    deferred.resolve(self);
  });
  this.write('thisProcess.interpreter.executeFile("' + path + '");\n',
    null, true);
  return deferred.promise;
};

/**
 * interpret
 *
 * evaluates code in sclang
 * and returns a promise
 *
 * resolves promise with result or rejects with error as JSON
 *
 * @param {String} code
 *        source code to evaluate
 * @param {String} nowExecutingPath
          set thisProcess.nowExecutingPath
 *        for use in a REPL to evaluate text in a file
 *        and let sclang know what file it is executing.
 * @param {Boolean} asString
 *        return result .asString for post window
 *        rather than as a full JSON object
 * @param {Boolean} postErrors
 *        call error.reportError on any errors
 *        which posts call stack, receiver, args, etc
 * @param {Boolean} getBacktrace
 *        return full backtrace
 * @returns {Promise}
 */
SCLang.prototype.interpret =
  function(code, nowExecutingPath, asString, postErrors, getBacktrace) {
    var deferred = Q.defer(),
        escaped = code.replace(/[\n\r]/g, '__NL__').replace(/\"/g, '\\"'),
        scstring = '"' + escaped + '"',
        guid = uuid.v1(),
        guidString = '"' + guid + '"',
        nep = nowExecutingPath ? '"' + nowExecutingPath + '"' : 'nil',
        rras = asString ? 'true' : 'false',
        pe = postErrors ? 'true' : 'false',
        bt = getBacktrace ? 'true' : 'false',
        args = [guidString, scstring, nep, rras, pe, bt].join(','),
        scode;

    scode = 'Library.at(\\supercolliderjs, \\interpret).value(' + args +
      ');"SUPERCOLLIDERJS.interpreted".postln;\n';

    // register listener
    this.calls[guid] = deferred;
    this.write(scode, null, true);
    return deferred.promise;
  };

/**
  * setState
  * set state to one of null, 'booting' 'compiling' 'compileError' 'compiled' 'ready'
  * and emit
  */
SCLang.prototype.setState = function(state) {
  if (state !== this.state) {
    this.state = state;
    this.emit('state', state);
    this.log.dbug('state: ' + state);
  }
};

/**
 * quit
 *
 * stop the sclang process
 */
SCLang.prototype.quit = function() {
  var deferred = Q.defer();
  if (this.process) {
    this.process.once('exit', function() {
      deferred.resolve();
    });
    // should do shutdown first
    this.process.kill('SIGINT');
    this.process = null;
  } else {
    deferred.resolve();
  }
  this.setState(null);
  return deferred.promise;
};

/**
  * alternate constructor
  * resolves options, boots and loads interpreter
  * @returns: a promise
  */
SCLang.boot = function(options) {
  var resolveOptions = require('./resolveOptions'),
      deferred = Q.defer();

  resolveOptions(null, options).then(function(options) {
    var sclang = new SCLang(options);

    sclang.boot()
      .then(function() {
        return sclang.initInterpreter();
      })
      .then(function() {
        deferred.resolve(sclang);
      })
      .catch(deferred.reject);

  });

  return deferred.promise;
};

module.exports = SCLang;
<commit_msg>sclang-interpret: parse syntax errors from STDOUT<commit_after>/* jslint node: true */

/**
 *
 * sclang - boots a supercollider language interpreter process
 *  and enables stdin/out and system level event responders
 *
 *  SuperCollider comes with an executable called sclang
 *  which can be communicated with via stdin/stdout
 *  or via OSC.
 *
 * methods:
 *   boot  - boot an sclang process
 *   write - write to stdin of the sclang process
 *   quit
 *
 */

var
  _ = require('underscore'),
  events = require('events'),
  spawn = require('child_process').spawn,
  path = require('path'),
  Logger = require('./logger'),
  Q = require('q'),
  uuid = require('node-uuid'),
  join = require('path').join;

/**
 *
 * Local sclang - a language interpreter running on the same machine
 *
 * @constructor
 * @param {Object} options - sclang command line options
 */
function SCLang(options) {
  this.options = options;
  this.process = null;
  this.calls = {};
  this.state = null;
  this.log = new Logger(this.options.debug, this.options.echo);
  this.log.dbug(this.options);
}

SCLang.super_ = events.EventEmitter;
SCLang.prototype = Object.create(events.EventEmitter.prototype, {
  constructor: {
    value: SCLang,
    enumerable: false
  }
});

/**
 * build args for sclang
 *
 *   -d <path>                      Set runtime directory
 *   -D                             Enter daemon mode (no input)
 *   -g <memory-growth>[km]         Set heap growth (default 256k)
 *   -h                             Display this message and exit
 *   -l <path>                      Set library configuration file
 *   -m <memory-space>[km]          Set initial heap size (default 2m)
 *   -r                             Call Main.run on startup
 *   -s                             Call Main.stop on shutdown
 *   -u <network-port-number>       Set UDP listening port (default 57120)
 *   -i <ide-name>                  Specify IDE name (for enabling IDE-specific class code, default "none")
 *   -a                             Standalone mode
 */
SCLang.prototype.args = function() {
  var o = [];
  if (this.options.executeFile) {
    o.push(this.options.executeFile);
  }
  o.push('-u', this.options.langPort);
  return o;
};

/**
 * boot
 *
 * start sclang and establish a pipe connection
 * to receive stdout and stderr
 */
SCLang.prototype.boot = function() {
  var
    deferred = Q.defer(),
    execPath = this.options.sclang,
    self = this;
  this.log.dbug('path:' + execPath);
  this.setState('booting');
  this.process = spawn(execPath, this.args(),
    {
      cwd: path.dirname(execPath)
    });

  function resolver(state) {
    if (state === 'ready') {
      deferred.resolve();
      self.removeListener('state', resolver);
    } else if (state === 'compileError') {
      deferred.reject();
      self.removeListener('state', resolver);
    }
  }
  // temporary listener till booted
  this.addListener('state', resolver);

  // long term listeners
  this.installListeners();
  return deferred.promise;
};

/**
  * installListeners
  */
SCLang.prototype.installListeners = function() {

  var self = this,
    states = {
      booting: [
        {
          re: /^compiling class library/m,
          fn: function() {
            self.setState('compiling');
            // and collect potential compile errors
          }
        }
      ],
      compiling: [
        {
          re: /^compile done/m,
          fn: function() {
            self.setState('compiled');
            // and collect potential startup errors
          }
        },
        {
          re: /^Library has not been compiled successfully/m,
          fn: function() {
            self.setState('compileError');
          }
        }
      ],
      compileError: [

      ],
      compiled: [
        {
          re: /^Welcome to SuperCollider ([0-9a-zA-Z\.]+)\. /m,
          fn: function(match) {
            self.version = match[1];
            self.setState('ready');
          }
        }
      ],
      ready: [
        {
          // callback
          re: /^SUPERCOLLIDERJS\:([0-9a-f\-]+)\:([A-Za-z]+)\:(.+)$/m,
          fn: function(match, text) {
            var
              guid = match[1],
              type = match[2],
              json = match[3],
              obj = JSON.parse(json),
              stdout;
            if (guid in self.calls) {
              if (type === 'Result') {
                self.calls[guid].resolve(obj);
              } else {
                if (!obj) {
                  obj = {};
                }
                stdout = text.replace(/^SUPERCOLLIDERJS[\s\S]+/mg, '');
                obj.stdout = stdout;
                if (type === 'SyntaxError') {
                  obj.syntaxErrors = self.parseSyntaxErrors(stdout);
                }
                self.calls[guid].reject({type: type, error: obj});
              }
              delete self.calls[guid];
              return true;
            }
          }
        },
        {
          // interpreter.scd posts this at the end
          re: /^SUPERCOLLIDERJS-interpreter-loaded$/m,
          fn: function() {
            self.emit('interpreterLoaded');
            return true;
          }
        }
        // {
        //   // interpret calls post this
        //   re: /^SUPERCOLLIDERJS.interpreted$/m,
        //   fn: function(match, text) {
        //     // chunk may contain errors
        //     // to capture
        //     // and reject this:
        //     // self.calls[guid].reject({type: type, error: obj});
        //
        //     // capture potential syntax errors
        //     // supress output
        //     // return true;
        //   }
        // }
      ]
    };

  this.process.stdout.on('data', function(data) {
    var text = '' + data,
        passthru = true;
    states[self.state].forEach(function(stf) {
      var match = stf.re.exec(text);
      if (match) {
        // do not post if handler returns true
        if (stf.fn(match, text) === true) {
          passthru = false;
        }
      }
    });
    if (passthru) {
      self.log.stdout(text);
      self.emit('stdout', text);
    }
  });

  this.process.on('error', function(err) {
    self.log.err('ERROR:' + err, 'error');
    self.emit('stderr', err);
  });
  this.process.on('close', function(code, signal) {
    self.log.dbug('close ' + code + signal);
    self.emit('exit', code);
    self.setState(null);
  });
  this.process.on('exit', function(code, signal) {
    self.log.dbug('exit ' + code + signal);
    self.emit('exit', code);
    self.setState(null);
  });
  this.process.on('disconnect', function() {
    self.log.dbug('disconnect');
    self.emit('exit');
    self.setState(null);
  });

  this.process.stderr.on('data', function(data) {
    self.log.stderr('' + data);
    self.emit('stderr', '' + data);
  });

  if (this.options.stdin) {
    // global top level nodejs process
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', function(chunk) {
      if (chunk) {
        self.write(chunk, null, true);
      }
    });
  }
};

/**
 * write
 *
 * send a raw string to sclang to be interpreted
 * callback is called after write is complete
 */
SCLang.prototype.write = function(chunk, callback, noEcho) {
  if (!noEcho) {
    this.log.stdin(chunk);
  }
  this.process.stdin.write(chunk, 'UTF-8');
  // escape character means execute the currently accumulated command line as SC code
  this.process.stdin.write('\x0c', null, callback);
};

/**
  * initInterpreter
  *
  * make sclang execute the interpret.scd file
  * to load the functions used by interpret
  */
SCLang.prototype.initInterpreter = function() {
  var path = join(__dirname, '../sc/interpret.scd'),
      deferred = Q.defer(),
      self = this;
  this.once('interpreterLoaded', function() {
    deferred.resolve(self);
  });
  this.write('thisProcess.interpreter.executeFile("' + path + '");\n',
    null, true);
  return deferred.promise;
};

/**
 * interpret
 *
 * evaluates code in sclang
 * and returns a promise
 *
 * resolves promise with result or rejects with error as JSON
 *
 * @param {String} code
 *        source code to evaluate
 * @param {String} nowExecutingPath
          set thisProcess.nowExecutingPath
 *        for use in a REPL to evaluate text in a file
 *        and let sclang know what file it is executing.
 * @param {Boolean} asString
 *        return result .asString for post window
 *        rather than as a full JSON object
 * @param {Boolean} postErrors
 *        call error.reportError on any errors
 *        which posts call stack, receiver, args, etc
 * @param {Boolean} getBacktrace
 *        return full backtrace
 * @returns {Promise}
 */
SCLang.prototype.interpret =
  function(code, nowExecutingPath, asString, postErrors, getBacktrace) {
    var deferred = Q.defer(),
        escaped = code.replace(/[\n\r]/g, '__NL__').replace(/\"/g, '\\"'),
        scstring = '"' + escaped + '"',
        guid = uuid.v1(),
        guidString = '"' + guid + '"',
        nep = nowExecutingPath ? '"' + nowExecutingPath + '"' : 'nil',
        rras = asString ? 'true' : 'false',
        pe = postErrors ? 'true' : 'false',
        bt = getBacktrace ? 'true' : 'false',
        args = [guidString, scstring, nep, rras, pe, bt].join(','),
        scode;

    scode = 'Library.at(\\supercolliderjs, \\interpret).value(' + args +
      ');"SUPERCOLLIDERJS.interpreted".postln;\n';

    // register listener
    this.calls[guid] = deferred;
    this.write(scode, null, true);
    return deferred.promise;
  };

/**
  * parse syntax error from STDOUT
  */
SCLang.prototype.parseSyntaxErrors = function(text) {
  var
      msgRe = /^ERROR: syntax error, (.+)$/m,
      fileRe = /in file '(.+)'/m,
      lineRe = /line ([0-9]+) char ([0-9]+):$/m;

  var msg = msgRe.exec(text),
      line = lineRe.exec(text),
      file = fileRe.exec(text),
      code = text.split('\n').slice(4, -3).join('\n').trim();
  return {
    msg: msg && msg[1],
    file: file && file[1],
    line: line && parseInt(line[1], 10),
    charPos: line && parseInt(line[2], 10),
    code: code
  };
};

/**
  * setState
  * set state to one of null, 'booting' 'compiling' 'compileError' 'compiled' 'ready'
  * and emit
  */
SCLang.prototype.setState = function(state) {
  if (state !== this.state) {
    this.state = state;
    this.emit('state', state);
    this.log.dbug('state: ' + state);
  }
};

/**
 * quit
 *
 * stop the sclang process
 */
SCLang.prototype.quit = function() {
  var deferred = Q.defer();
  if (this.process) {
    this.process.once('exit', function() {
      deferred.resolve();
    });
    // should do shutdown first
    this.process.kill('SIGINT');
    this.process = null;
  } else {
    deferred.resolve();
  }
  this.setState(null);
  return deferred.promise;
};

/**
  * alternate constructor
  * resolves options, boots and loads interpreter
  * @returns: a promise
  */
SCLang.boot = function(options) {
  var resolveOptions = require('./resolveOptions'),
      deferred = Q.defer();

  resolveOptions(null, options).then(function(options) {
    var sclang = new SCLang(options);

    sclang.boot()
      .then(function() {
        return sclang.initInterpreter();
      })
      .then(function() {
        deferred.resolve(sclang);
      })
      .catch(deferred.reject);

  });

  return deferred.promise;
};

module.exports = SCLang;
<|endoftext|>