<commit_before>var fs = require('fs'),
    path = require('path'),
    glob = require('glob'),
    util = require('util'),
    https = require('https'),
    growl = require('growl'),
    isBinaryFile = require('isbinaryfile'),
    ShopifyApi = require('shopify-api');

module.exports = function(grunt) {
    var shopify = {};
    shopify._api = false;

    /*
     * Get the Shopify API instance.
     *
     * @return {ShopifyApi}
     */
    shopify._getApi = function() {
        if (!shopify._api) {
            var config = grunt.config('shopify');
            var opts = {
                auth: config.options.api_key + ':' + config.options.password,
                host: config.options.url,
                port: config.options.port,
                timeout: config.options.timeout
            };

            shopify._api = new ShopifyApi(opts);
        }

        return shopify._api;
    };

    /*
     * Return the base api host with the basic auth. Does not require the
     * protocol.
     *
     * @return {string}
     */
    shopify.getHost = function() {
        var config = grunt.config('shopify');

        return config.options.url;
    };

    /*
     * Return the authentication header for basic auth
     *
     * @return {string}
     */
    shopify.getAuth = function() {
        var config = grunt.config('shopify');

        return config.options.api_key + ":" + config.options.password;
    };

    /*
     * Return the theme id
     * @return {string}
     */
    shopify.getTheme = function() {
        var config = grunt.config('shopify');
        var theme_id = ('theme' in config.options ? config.options.theme : false);
        return (theme_id ? theme_id : false);
    };

    /*
     * Return remote path, including the theme id if present in Gruntfile
     * @return {string}
     */
    shopify.remotePath = function() {
        return (shopify.getTheme() ? '/admin/themes/' + shopify.getTheme() : '/admin');
    };

    /**
     * Helper for reporting Http response success and error messages to the
     * user. To notify the user without a response (i.e for an info note) simply
     * don't pass a response notify("hello");
     *
     * @param {response}|{string}
     * @param {string}
     */
    shopify.notify = function(res, msg) {
        var config = grunt.config('shopify');

        if (typeof res !== "string") {
            if (res.statusCode >= 400) {
                msg = "Error "+ msg +" (Status Code: "+ res.statusCode + ")";

                if (!config.options.disable_growl_notifications) {
                    growl(msg, { title: 'Grunt Shopify'});
                }

                if (!config.options.disable_grunt_log) {
                    grunt.log.error('[grunt-shopify] - ' + msg);
                }
            } else {
                msg = "Success "+ msg +" (Status Code: "+ res.statusCode + ")";

                if (!config.options.disable_growl_notifications) {
                    growl(msg, { title: 'Grunt Shopify'});
                }

                if (!config.options.disable_grunt_log) {
                    grunt.log.ok('[grunt-shopify] - ' + msg);
                }
            }
        } else {
            if (!config.options.disable_growl_notifications) {
               growl(res, { title: 'Grunt Shopify'});
            }

            if (!config.options.disable_grunt_log) {
                grunt.log.ok('[grunt-shopify] - ' + res);
            }
        }
    };

    /*
     * Convert a file path on the local file system to an asset path in shopify
     * as you may run grunt at a higher directory locally.
     *
     * The original path to a file may be something like shop/assets/site.css
     * whereas we require assets/site.css in the API. To customize the base
     * set shopify.options.base config option.
     *
     * @param {string}
     * @return {string}
     *
     * @todo
     */
    shopify.getAssetKey = function(path) {
        var c = grunt.config('shopify');

        if (c.options.base) {
            return path.substring(path.indexOf(c.options.base) + c.options.base.length);
        }

        return path;
    };

    /*
     * Remove a given file path from shopify.
     *
     * File should be the relative path on the local filesystem. See
     * getAssetKey for the conversion to remote asset location
     *
     * @param {string} file
     * @param {function} async completion callback
     */
    shopify.remove = function(file, done) {
        shopify.notify("Deleting " + file);
            file = file.replace("\\","/");

        var path = shopify.getAssetKey(file).replace("\\","/");

        var options = {
            host: shopify.getHost(),
            auth: shopify.getAuth(),
            path: shopify.remotePath() + '/assets.json?asset[key]=' + path,
            method: 'DELETE',
            headers: {
                'Content-Length': 0
            }
        };

        var req = https.request(options, function(res) {
            res.setEncoding('utf8');

            var body = '';

            res.on('data', function(chunk) {
              body += chunk;
            });

            res.on('end', function () {
              if (res.statusCode >= 400 ) {
                shopify.notify(res, "delete failed with response " + body);
              } else {
                shopify.notify(res, "deleted file " + path + " from shopify");
              }

              shopify.notify(res, "deleting file");
              return done(true);
            });

        });

        req.on('error', function(e) {
            shopify.notify('Problem with DELETE request: ' + e.message);

            return done(false);
        });

        req.end();

        return true;
    };

    /*
     * Upload a given file path to Shopify
     *
     * Assets need to be in a suitable directory.
     *      - Liquid templates => "templates/"
     *      - Liquid layouts => "layout/"
     *      - Liquid snippets => "snippets/"
     *      - Theme settings => "config/"
     *      - General assets => "assets/"
     *
     * Some requests may fail if those folders are ignored
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.upload = function(filepath, done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();
        var key = shopify._makeAssetKey(filepath);

        var isBinary = isBinaryFile(filepath);
        var contents = grunt.file.read(filepath, { encoding: isBinary ? null : 'utf8' });
        var props = {
            asset: {
                key: key
            }
        };

        if (isBinary) {
            props.asset.attachment = contents.toString('base64');
        } else {
            props.asset.value = contents.toString();
        }

        function onUpdate(err) {
            if (err) {
                done(err);
                return;
            }

            shopify.notify('File "' + key + '" uploaded.');
            done();
        }

        if (themeId) {
            api.asset.update(themeId, props, onUpdate);
        } else {
            api.assetLegacy.update(props, onUpdate);
        }
    };

    shopify.deploy = function(done) {
        var c = grunt.config('shopify');
        var paths = [];
        ['assets','config','layout','snippets','templates'].forEach(function(folder) {
          paths = paths.concat(glob.sync(path.join(c.options.base || '', folder, '*.*')));
        });
        function next(i) {
            if (i < paths.length) {
                shopify.upload(paths[i].replace("\\","/"), function(success) {
                    if (!success) {
                        return done(false);
                    }
                    next(i+1);
                });
            } else {
                done(true);
            }
        }
        next(0);
    };

    shopify.getOneAsset = function(key, done) {
        var remote_path = shopify.remotePath() + '/assets.json?asset[key]=' + key;

        // Add theme_id param to path if theme is specified
        if (shopify.getTheme()) {
          remote_path += '&theme_id=' + shopify.getTheme();
        }

        var options = {
            hostname: shopify.getHost(),
            auth: shopify.getAuth(),
            path: remote_path,
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        };

        var req = https.request(options, function(res) {
            res.setEncoding('utf8');

            var c = grunt.config('shopify');
            var body = '';
            var destination = path.join(c.options.base || '', key);

            res.on('data', function(chunk) {
                body += chunk;
            });

            res.on('end', function () {
                try {
                    var obj = JSON.parse(body);
                    if (obj.asset) {
                        var value, encoding;
                        if (typeof obj.asset.value !== 'undefined') {
                            value = obj.asset.value;
                            encoding = 'utf8';
                        } else if (typeof obj.asset.attachment !== 'undefined') {
                            value = new Buffer(obj.asset.attachment, 'base64');
                            encoding = null;
                        } else {
                            grunt.log.error('Parsed object is not complete: ' + util.inspect(obj));
                            return done(false);
                        }

                        if (grunt.option('no-write')) {
                            shopify.notify(util.format('dry run: Downloaded %s to %s', key, destination));
                            console.log(util.inspect(obj));
                            done(true);
                        } else {
                            fs.writeFile(destination, value, encoding, function(err) {
                                if (err) {
                                    grunt.log.error(util.format('Error saving asset %s to %s: %s', key, destination, err.message));
                                    done(false);
                                } else {
                                    shopify.notify(util.format('Downloaded %s to %s', key, destination));
                                    done(true);
                                }
                            });
                        }
                    } else {
                        grunt.log.error('Parsed object is not complete: ' + util.inspect(obj));
                        return done(false);
                    }
                } catch(e) {
                    grunt.log.error('Failed to parse JSON response');
                    done(false);
                }
            });
        });

        req.on('error', function(e) {
            shopify.notify('Problem with GET request: ' + e.message);
            return done(false);
        });
        req.end();
    };

    shopify.download = function(done) {
        var options = {
            hostname: shopify.getHost(),
            auth: shopify.getAuth(),
            path: shopify.remotePath() + '/assets.json',
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        };

        var req = https.request(options, function(res) {
            res.setEncoding('utf8');

            var body = '';
            var obj;

            function next(i) {
                if (i < obj.assets.length) {
                    shopify.getOneAsset(obj.assets[i].key, function(success) {
                        if (!success) {
                          return done(false);
                        }
                        next(i+1);
                    });
                } else {
                    shopify.notify('Theme sync complete.');
                    done(true);
                }
            }

            res.on('data', function(chunk) {
                body += chunk;
            });

            res.on('end', function () {
                try {
                    obj = JSON.parse(body);
                    if (obj.assets) {
                        next(0);
                    } else {
                        grunt.log.error('Failed to get shopify assets');
                        return done(false);
                    }
                } catch (e) {
                    grunt.log.error('Failed to parse JSON response');
                    done(false);
                }
            });
        });

        req.on('error', function(e) {
            shopify.notify('Problem with GET request: ' + e.message);
            return done(false);
        });

        req.end();
    };

    return shopify;
};<commit_msg>few helpers.<commit_after>var fs = require('fs'),
    path = require('path'),
    glob = require('glob'),
    util = require('util'),
    https = require('https'),
    growl = require('growl'),
    isBinaryFile = require('isbinaryfile'),
    ShopifyApi = require('shopify-api');

module.exports = function(grunt) {
    var shopify = {};
    shopify._api = false;

    /*
     * Get the Shopify API instance.
     *
     * @return {ShopifyApi}
     */
    shopify._getApi = function() {
        if (!shopify._api) {
            var config = grunt.config('shopify');
            var opts = {
                auth: config.options.api_key + ':' + config.options.password,
                host: config.options.url,
                port: config.options.port,
                timeout: config.options.timeout
            };

            shopify._api = new ShopifyApi(opts);
        }

        return shopify._api;
    };

    /*
     * Get the Theme ID.
     *
     * @return {integer}
     */
    shopify._getThemeId = function() {
        var config = grunt.config('shopify');
        return ('theme' in config.options) ? config.options.themeId : false;
    };

    /*
     * Convert a file path on the local file system to an asset path in shopify
     * as you may run grunt at a higher directory locally.
     *
     * The original path to a file may be something like shop/assets/site.css
     * whereas we require assets/site.css in the API. To customize the base
     * set shopify.options.base config option.
     *
     * @param {string}
     * @return {string}
     */
    shopify._makeAssetKey = function(path) {
        path = path.replace(/\\/g, '/');

        var c = grunt.config('shopify');

        if (c.options.base) {
            path = path.substring(path.indexOf(c.options.base) + c.options.base.length).replace(/\\/g, '/');
        }

        return path.replace(/^\/+/, '');
    };

    /*
     * Return the base api host with the basic auth. Does not require the
     * protocol.
     *
     * @return {string}
     */
    shopify.getHost = function() {
        var config = grunt.config('shopify');

        return config.options.url;
    };

    /*
     * Return the authentication header for basic auth
     *
     * @return {string}
     */
    shopify.getAuth = function() {
        var config = grunt.config('shopify');

        return config.options.api_key + ":" + config.options.password;
    };

    /*
     * Return the theme id
     * @return {string}
     */
    shopify.getTheme = function() {
        var config = grunt.config('shopify');
        var theme_id = ('theme' in config.options ? config.options.theme : false);
        return (theme_id ? theme_id : false);
    };

    /*
     * Return remote path, including the theme id if present in Gruntfile
     * @return {string}
     */
    shopify.remotePath = function() {
        return (shopify.getTheme() ? '/admin/themes/' + shopify.getTheme() : '/admin');
    };

    /**
     * Helper for reporting Http response success and error messages to the
     * user. To notify the user without a response (i.e for an info note) simply
     * don't pass a response notify("hello");
     *
     * @param {response}|{string}
     * @param {string}
     */
    shopify.notify = function(res, msg) {
        var config = grunt.config('shopify');

        if (typeof res !== "string") {
            if (res.statusCode >= 400) {
                msg = "Error "+ msg +" (Status Code: "+ res.statusCode + ")";

                if (!config.options.disable_growl_notifications) {
                    growl(msg, { title: 'Grunt Shopify'});
                }

                if (!config.options.disable_grunt_log) {
                    grunt.log.error('[grunt-shopify] - ' + msg);
                }
            } else {
                msg = "Success "+ msg +" (Status Code: "+ res.statusCode + ")";

                if (!config.options.disable_growl_notifications) {
                    growl(msg, { title: 'Grunt Shopify'});
                }

                if (!config.options.disable_grunt_log) {
                    grunt.log.ok('[grunt-shopify] - ' + msg);
                }
            }
        } else {
            if (!config.options.disable_growl_notifications) {
               growl(res, { title: 'Grunt Shopify'});
            }

            if (!config.options.disable_grunt_log) {
                grunt.log.ok('[grunt-shopify] - ' + res);
            }
        }
    };

    /*
     * Convert a file path on the local file system to an asset path in shopify
     * as you may run grunt at a higher directory locally.
     *
     * The original path to a file may be something like shop/assets/site.css
     * whereas we require assets/site.css in the API. To customize the base
     * set shopify.options.base config option.
     *
     * @param {string}
     * @return {string}
     *
     * @todo
     */
    shopify.getAssetKey = function(path) {
        var c = grunt.config('shopify');

        if (c.options.base) {
            return path.substring(path.indexOf(c.options.base) + c.options.base.length);
        }

        return path;
    };

    /*
     * Remove a given file path from shopify.
     *
     * File should be the relative path on the local filesystem. See
     * getAssetKey for the conversion to remote asset location
     *
     * @param {string} file
     * @param {function} async completion callback
     */
    shopify.remove = function(file, done) {
        shopify.notify("Deleting " + file);
            file = file.replace("\\","/");

        var path = shopify.getAssetKey(file).replace("\\","/");

        var options = {
            host: shopify.getHost(),
            auth: shopify.getAuth(),
            path: shopify.remotePath() + '/assets.json?asset[key]=' + path,
            method: 'DELETE',
            headers: {
                'Content-Length': 0
            }
        };

        var req = https.request(options, function(res) {
            res.setEncoding('utf8');

            var body = '';

            res.on('data', function(chunk) {
              body += chunk;
            });

            res.on('end', function () {
              if (res.statusCode >= 400 ) {
                shopify.notify(res, "delete failed with response " + body);
              } else {
                shopify.notify(res, "deleted file " + path + " from shopify");
              }

              shopify.notify(res, "deleting file");
              return done(true);
            });

        });

        req.on('error', function(e) {
            shopify.notify('Problem with DELETE request: ' + e.message);

            return done(false);
        });

        req.end();

        return true;
    };

    /*
     * Upload a given file path to Shopify
     *
     * Assets need to be in a suitable directory.
     *      - Liquid templates => "templates/"
     *      - Liquid layouts => "layout/"
     *      - Liquid snippets => "snippets/"
     *      - Theme settings => "config/"
     *      - General assets => "assets/"
     *
     * Some requests may fail if those folders are ignored
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.upload = function(filepath, done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();
        var key = shopify._makeAssetKey(filepath);

        var isBinary = isBinaryFile(filepath);
        var contents = grunt.file.read(filepath, { encoding: isBinary ? null : 'utf8' });
        var props = {
            asset: {
                key: key
            }
        };

        if (isBinary) {
            props.asset.attachment = contents.toString('base64');
        } else {
            props.asset.value = contents.toString();
        }

        function onUpdate(err) {
            if (err) {
                done(err);
                return;
            }

            shopify.notify('File "' + key + '" uploaded.');
            done();
        }

        if (themeId) {
            api.asset.update(themeId, props, onUpdate);
        } else {
            api.assetLegacy.update(props, onUpdate);
        }
    };

    shopify.deploy = function(done) {
        var c = grunt.config('shopify');
        var paths = [];
        ['assets','config','layout','snippets','templates'].forEach(function(folder) {
          paths = paths.concat(glob.sync(path.join(c.options.base || '', folder, '*.*')));
        });
        function next(i) {
            if (i < paths.length) {
                shopify.upload(paths[i].replace("\\","/"), function(success) {
                    if (!success) {
                        return done(false);
                    }
                    next(i+1);
                });
            } else {
                done(true);
            }
        }
        next(0);
    };

    shopify.getOneAsset = function(key, done) {
        var remote_path = shopify.remotePath() + '/assets.json?asset[key]=' + key;

        // Add theme_id param to path if theme is specified
        if (shopify.getTheme()) {
          remote_path += '&theme_id=' + shopify.getTheme();
        }

        var options = {
            hostname: shopify.getHost(),
            auth: shopify.getAuth(),
            path: remote_path,
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        };

        var req = https.request(options, function(res) {
            res.setEncoding('utf8');

            var c = grunt.config('shopify');
            var body = '';
            var destination = path.join(c.options.base || '', key);

            res.on('data', function(chunk) {
                body += chunk;
            });

            res.on('end', function () {
                try {
                    var obj = JSON.parse(body);
                    if (obj.asset) {
                        var value, encoding;
                        if (typeof obj.asset.value !== 'undefined') {
                            value = obj.asset.value;
                            encoding = 'utf8';
                        } else if (typeof obj.asset.attachment !== 'undefined') {
                            value = new Buffer(obj.asset.attachment, 'base64');
                            encoding = null;
                        } else {
                            grunt.log.error('Parsed object is not complete: ' + util.inspect(obj));
                            return done(false);
                        }

                        if (grunt.option('no-write')) {
                            shopify.notify(util.format('dry run: Downloaded %s to %s', key, destination));
                            console.log(util.inspect(obj));
                            done(true);
                        } else {
                            fs.writeFile(destination, value, encoding, function(err) {
                                if (err) {
                                    grunt.log.error(util.format('Error saving asset %s to %s: %s', key, destination, err.message));
                                    done(false);
                                } else {
                                    shopify.notify(util.format('Downloaded %s to %s', key, destination));
                                    done(true);
                                }
                            });
                        }
                    } else {
                        grunt.log.error('Parsed object is not complete: ' + util.inspect(obj));
                        return done(false);
                    }
                } catch(e) {
                    grunt.log.error('Failed to parse JSON response');
                    done(false);
                }
            });
        });

        req.on('error', function(e) {
            shopify.notify('Problem with GET request: ' + e.message);
            return done(false);
        });
        req.end();
    };

    shopify.download = function(done) {
        var options = {
            hostname: shopify.getHost(),
            auth: shopify.getAuth(),
            path: shopify.remotePath() + '/assets.json',
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        };

        var req = https.request(options, function(res) {
            res.setEncoding('utf8');

            var body = '';
            var obj;

            function next(i) {
                if (i < obj.assets.length) {
                    shopify.getOneAsset(obj.assets[i].key, function(success) {
                        if (!success) {
                          return done(false);
                        }
                        next(i+1);
                    });
                } else {
                    shopify.notify('Theme sync complete.');
                    done(true);
                }
            }

            res.on('data', function(chunk) {
                body += chunk;
            });

            res.on('end', function () {
                try {
                    obj = JSON.parse(body);
                    if (obj.assets) {
                        next(0);
                    } else {
                        grunt.log.error('Failed to get shopify assets');
                        return done(false);
                    }
                } catch (e) {
                    grunt.log.error('Failed to parse JSON response');
                    done(false);
                }
            });
        });

        req.on('error', function(e) {
            shopify.notify('Problem with GET request: ' + e.message);
            return done(false);
        });

        req.end();
    };

    return shopify;
};<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var util = require('util');
var growl = require('growl');
var async = require('async');
var isBinaryFile = require('isbinaryfile');
var ShopifyApi = require('shopify-api');

module.exports = function(grunt) {
    var shopify = {};
    shopify._api = false;

    /*
     * Get the Shopify API instance.
     *
     * @return {ShopifyApi}
     */
    shopify._getApi = function() {
        if (!shopify._api) {
            var config = grunt.config('shopify');
            var opts = {
                auth: config.options.api_key + ':' + config.options.password,
                host: config.options.url,
                port: config.options.port,
                timeout: config.options.timeout
            };

            shopify._api = new ShopifyApi(opts);
        }

        return shopify._api;
    };

    /*
     * Get the Theme ID.
     *
     * @return {integer}
     */
    shopify._getThemeId = function() {
        var config = grunt.config('shopify');
        return ('theme' in config.options) ? config.options.themeId : false;
    };

    /*
     * Convert a file path on the local file system to an asset path in shopify
     * as you may run grunt at a higher directory locally.
     *
     * The original path to a file may be something like shop/assets/site.css
     * whereas we require assets/site.css in the API. To customize the base
     * set shopify.options.base config option.
     *
     * @param {string}
     * @return {string}
     */
    shopify._makeAssetKey = function(path) {
        path = path.replace(/\\/g, '/');

        var c = grunt.config('shopify');

        if (c.options.base) {
            path = path.substring(path.indexOf(c.options.base) + c.options.base.length).replace(/\\/g, '/');
        }

        return path.replace(/^\/+/, '');
    };

    /*
     * Save a Shopify asset to disk.
     *
     * @param {string} key
     * @param {Object} obj
     * @param {Function} done
     */
    shopify._saveAsset = function(key, obj, done) {
        var contents;

        var c = grunt.config('shopify');
        var destination = path.join(c.options.base || '', key);

        if (typeof obj.asset.value !== 'undefined') {
            contents = obj.asset.value;
        } else if (typeof obj.asset.attachment !== 'undefined') {
            contents = new Buffer(obj.asset.attachment, 'base64');
        } else {
            done(new Error('Parsed object is not complete'));
            return;
        }

        if (grunt.option('no-write')) {
            console.log(util.inspect(obj));
        } else {
            grunt.file.write(destination, contents);
            shopify.notify('File "' + key + '" saved to disk.');
        }

        done();
    };

    /*
     * Helper for reporting messages to the user.
     *
     * @param {string} msg
     */
    shopify.notify = function(msg) {
        var config = grunt.config('shopify');

        if (!config.options.disable_growl_notifications) {
            growl(msg, { title: 'Grunt Shopify'});
        }

        if (!config.options.disable_grunt_log) {
            grunt.log.ok('[grunt-shopify] - ' + msg);
        }
    };

    /*
     * Remove a given file path from Shopify.
     *
     * File should be the relative path on the local filesystem.
     *
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.remove = function(filepath, done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();
        var key = shopify._makeAssetKey(filepath);

        function onDestroy(err) {
            if (!err) {
                shopify.notify('File "' + key + '" removed.');
            }

            done(err);
        }

        if (themeId) {
            api.asset.destroy(themeId, key, onDestroy);
        } else {
            api.assetLegacy.destroy(key, onDestroy);
        }
    };

    /*
     * Upload a given file path to Shopify
     *
     * Assets need to be in a suitable directory.
     *      - Liquid templates => "templates/"
     *      - Liquid layouts => "layout/"
     *      - Liquid snippets => "snippets/"
     *      - Theme settings => "config/"
     *      - General assets => "assets/"
     *
     * Some requests may fail if those folders are ignored
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.upload = function(filepath, done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();
        var key = shopify._makeAssetKey(filepath);

        var isBinary = isBinaryFile(filepath);
        var contents = grunt.file.read(filepath, { encoding: isBinary ? null : 'utf8' });
        var props = {
            asset: {
                key: key
            }
        };

        if (isBinary) {
            props.asset.attachment = contents.toString('base64');
        } else {
            props.asset.value = contents.toString();
        }

        function onUpdate(err) {
            if (!err) {
                shopify.notify('File "' + key + '" uploaded.');
            }

            done(err);
        }

        if (themeId) {
            api.asset.update(themeId, props, onUpdate);
        } else {
            api.assetLegacy.update(props, onUpdate);
        }
    };

    /*
     * Deploy an entire theme to Shopify.
     *
     * @param {Function} done
     */
    shopify.deploy = function(done) {
        var c = grunt.config('shopify');

        var base = c.options.base || '';
        var filepaths = grunt.file.expand({ cwd: base }, [
            'assets/*.*',
            'config/*.*',
            'layout/*.*',
            'snippets/*.*',
            'templates/*.*'
        ]);

        async.eachSeries(filepaths, function(filepath, next) {
            shopify.upload(path.join(base, filepath), next);
        }, function(err) {
          if (!err) {
              shopify.notify('Theme deploy complete.');
          }

          done(err);
        });
    };

    /*
     * Download an asset from Shopify.
     *
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.download = function(filepath, done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();
        var key = shopify._makeAssetKey(filepath);

        function onRetrieve(err, obj) {
            if (err) {
                done(err);
                return;
            }

            if (!obj.asset) {
                done(new Error('Failed to get asset data'));
                return;
            }

            shopify._saveAsset(key, obj, done);
        }

        if (themeId) {
            api.asset.retrieve(themeId, key, onRetrieve);
        } else {
            api.assetLegacy.retrieve(key, onRetrieve);
        }
    };

    /*
     * Download an entire theme from Shopify.
     *
     * @param {Function} done
     */
    shopify.downloadTheme = function(done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();

        function onRetrieve(err, obj) {
            if (err) {
                done(err);
                return;
            }

            if (!obj.assets) {
                done(new Error('Failed to get theme assets list'));
                return;
            }

            async.eachSeries(obj.assets, function(asset, next) {
                shopify.download(asset.key, next);
            }, function(err) {
                if (!err) {
                    shopify.notify('Theme download complete.');
                }

                done(err);
            });
        }

        if (themeId) {
            api.asset.list(themeId, onRetrieve);
        } else {
            api.assetLegacy.list(onRetrieve);
        }
    };

    return shopify;
};<commit_msg>use correct key for theme id.<commit_after>var fs = require('fs');
var path = require('path');
var util = require('util');
var growl = require('growl');
var async = require('async');
var isBinaryFile = require('isbinaryfile');
var ShopifyApi = require('shopify-api');

module.exports = function(grunt) {
    var shopify = {};
    shopify._api = false;

    /*
     * Get the Shopify API instance.
     *
     * @return {ShopifyApi}
     */
    shopify._getApi = function() {
        if (!shopify._api) {
            var config = grunt.config('shopify');
            var opts = {
                auth: config.options.api_key + ':' + config.options.password,
                host: config.options.url,
                port: config.options.port,
                timeout: config.options.timeout
            };

            shopify._api = new ShopifyApi(opts);
        }

        return shopify._api;
    };

    /*
     * Get the Theme ID.
     *
     * @return {integer}
     */
    shopify._getThemeId = function() {
        var config = grunt.config('shopify');
        return ('theme' in config.options) ? config.options.theme : false;
    };

    /*
     * Convert a file path on the local file system to an asset path in shopify
     * as you may run grunt at a higher directory locally.
     *
     * The original path to a file may be something like shop/assets/site.css
     * whereas we require assets/site.css in the API. To customize the base
     * set shopify.options.base config option.
     *
     * @param {string}
     * @return {string}
     */
    shopify._makeAssetKey = function(path) {
        path = path.replace(/\\/g, '/');

        var c = grunt.config('shopify');

        if (c.options.base) {
            path = path.substring(path.indexOf(c.options.base) + c.options.base.length).replace(/\\/g, '/');
        }

        return path.replace(/^\/+/, '');
    };

    /*
     * Save a Shopify asset to disk.
     *
     * @param {string} key
     * @param {Object} obj
     * @param {Function} done
     */
    shopify._saveAsset = function(key, obj, done) {
        var contents;

        var c = grunt.config('shopify');
        var destination = path.join(c.options.base || '', key);

        if (typeof obj.asset.value !== 'undefined') {
            contents = obj.asset.value;
        } else if (typeof obj.asset.attachment !== 'undefined') {
            contents = new Buffer(obj.asset.attachment, 'base64');
        } else {
            done(new Error('Parsed object is not complete'));
            return;
        }

        if (grunt.option('no-write')) {
            console.log(util.inspect(obj));
        } else {
            grunt.file.write(destination, contents);
            shopify.notify('File "' + key + '" saved to disk.');
        }

        done();
    };

    /*
     * Helper for reporting messages to the user.
     *
     * @param {string} msg
     */
    shopify.notify = function(msg) {
        var config = grunt.config('shopify');

        if (!config.options.disable_growl_notifications) {
            growl(msg, { title: 'Grunt Shopify'});
        }

        if (!config.options.disable_grunt_log) {
            grunt.log.ok('[grunt-shopify] - ' + msg);
        }
    };

    /*
     * Remove a given file path from Shopify.
     *
     * File should be the relative path on the local filesystem.
     *
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.remove = function(filepath, done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();
        var key = shopify._makeAssetKey(filepath);

        function onDestroy(err) {
            if (!err) {
                shopify.notify('File "' + key + '" removed.');
            }

            done(err);
        }

        if (themeId) {
            api.asset.destroy(themeId, key, onDestroy);
        } else {
            api.assetLegacy.destroy(key, onDestroy);
        }
    };

    /*
     * Upload a given file path to Shopify
     *
     * Assets need to be in a suitable directory.
     *      - Liquid templates => "templates/"
     *      - Liquid layouts => "layout/"
     *      - Liquid snippets => "snippets/"
     *      - Theme settings => "config/"
     *      - General assets => "assets/"
     *
     * Some requests may fail if those folders are ignored
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.upload = function(filepath, done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();
        var key = shopify._makeAssetKey(filepath);

        var isBinary = isBinaryFile(filepath);
        var contents = grunt.file.read(filepath, { encoding: isBinary ? null : 'utf8' });
        var props = {
            asset: {
                key: key
            }
        };

        if (isBinary) {
            props.asset.attachment = contents.toString('base64');
        } else {
            props.asset.value = contents.toString();
        }

        function onUpdate(err) {
            if (!err) {
                shopify.notify('File "' + key + '" uploaded.');
            }

            done(err);
        }

        if (themeId) {
            api.asset.update(themeId, props, onUpdate);
        } else {
            api.assetLegacy.update(props, onUpdate);
        }
    };

    /*
     * Deploy an entire theme to Shopify.
     *
     * @param {Function} done
     */
    shopify.deploy = function(done) {
        var c = grunt.config('shopify');

        var base = c.options.base || '';
        var filepaths = grunt.file.expand({ cwd: base }, [
            'assets/*.*',
            'config/*.*',
            'layout/*.*',
            'snippets/*.*',
            'templates/*.*'
        ]);

        async.eachSeries(filepaths, function(filepath, next) {
            shopify.upload(path.join(base, filepath), next);
        }, function(err) {
          if (!err) {
              shopify.notify('Theme deploy complete.');
          }

          done(err);
        });
    };

    /*
     * Download an asset from Shopify.
     *
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.download = function(filepath, done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();
        var key = shopify._makeAssetKey(filepath);

        function onRetrieve(err, obj) {
            if (err) {
                done(err);
                return;
            }

            if (!obj.asset) {
                done(new Error('Failed to get asset data'));
                return;
            }

            shopify._saveAsset(key, obj, done);
        }

        if (themeId) {
            api.asset.retrieve(themeId, key, onRetrieve);
        } else {
            api.assetLegacy.retrieve(key, onRetrieve);
        }
    };

    /*
     * Download an entire theme from Shopify.
     *
     * @param {Function} done
     */
    shopify.downloadTheme = function(done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId();

        function onRetrieve(err, obj) {
            if (err) {
                done(err);
                return;
            }

            if (!obj.assets) {
                done(new Error('Failed to get theme assets list'));
                return;
            }

            async.eachSeries(obj.assets, function(asset, next) {
                shopify.download(asset.key, next);
            }, function(err) {
                if (!err) {
                    shopify.notify('Theme download complete.');
                }

                done(err);
            });
        }

        if (themeId) {
            api.asset.list(themeId, onRetrieve);
        } else {
            api.assetLegacy.list(onRetrieve);
        }
    };

    return shopify;
};<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict'

var Metalsmith = require('metalsmith')
var concat = require('..') // require('metalsmith-concat')

Metalsmith(__dirname)
  .use(concat({ files: '**/*.css', output: 'main.css' }))
  .build(function (err) { if (err) { throw err } })
<commit_msg>style: err -> error<commit_after>#!/usr/bin/env node
'use strict'

var Metalsmith = require('metalsmith')
var concat = require('..') // require('metalsmith-concat')

Metalsmith(__dirname)
  .use(concat({ files: '**/*.css', output: 'main.css' }))
  .build(function (error) { if (error) { throw error } })
<|endoftext|>


<commit_before>import {
  certificates,
  sign,
} from '../src/index';

document.addEventListener('DOMContentLoaded', () => {
  setTimeout(getCertificates, 1500);
});

const getCertificates = () =>
  certificates().then(certificates => injectListOfCertificates(certificates));

const injectListOfCertificates = (certificates) => {
  const parentElement = document.querySelector('.certificates__list');
  buildListOfCertificates(certificates)
    .map(node => parentElement.appendChild(node));
};

const buildListOfCertificates = certificates =>
  certificates.reduce((result, certificate) =>
    result.concat(buildCertificateItem(certificate)), []);

const buildCertificateItem = (certificate) => {
  const div = document.createElement('div');

  div.className = 'certificates__list__item';
  div.innerHTML = certificate.subjectName.CN;
  div.onclick = () =>
    sign(certificate.thumbprint, getDataFromInput()).then((signature) => {
      document.querySelector('.result').innerHTML = signature;
    });

  return div;
};

const getDataFromInput = () => {
  const { value } = document.querySelector('.field');
  return convertToBase64(value) || alert('You need to write any text!');
};

const convertToBase64 = value => window.btoa(value);
<commit_msg>Example page was updated<commit_after>import {
  certificates,
  sign,
} from '../src/index';

document.addEventListener('DOMContentLoaded', () => {
  setTimeout(getCertificates, 1500);
});

const getCertificates = () =>
  certificates().then(certificates => injectListOfCertificates(certificates));

const injectListOfCertificates = (certificates) => {
  const parentElement = document.querySelector('.certificates__list');
  buildListOfCertificates(certificates)
    .map(node => parentElement.appendChild(node));
};

const buildListOfCertificates = certificates =>
  certificates.reduce((result, certificate) =>
    result.concat(buildCertificateItem(certificate)), []);

const buildCertificateItem = (certificate) => {
  const div = document.createElement('div');

  div.className = 'certificates__list__item';
  div.innerHTML = `${certificate.subjectName.CN} Алгоритм: ${certificate.algorithm}`;
  div.onclick = () =>
    sign(certificate.thumbprint, getDataFromInput()).then((signature) => {
      document.querySelector('.result').innerHTML = signature;
    });

  return div;
};

const getDataFromInput = () => {
  const { value } = document.querySelector('.field');
  return convertToBase64(value) || alert('You need to write any text!');
};

const convertToBase64 = value => window.btoa(value);
<|endoftext|>


<commit_before>var SDK	 		= require('../lib/index').SDK;
var conf 		= require('./config/config.json');
var privateKey 	= require('fs').readFileSync('./config/private_sept.key');
var privateKey2 = require('fs').readFileSync('./config/private_october.key');
var app 		= new SDK(conf["SDK"]);

var tokenContent = {
	account: "000002",
	appId: "fee8a312946c4a593046ed36d2",
	scopes: "profilehub::getVisitor,profilehub::getVisitors,profilehub::getVisitorsFields,profilehub::getRelease,abc,profilehub::getToto"
};

var tokenContent2 = {
	account: "666666",
	appId: "fee8a312946c4a593046ed36d2",
	scopes: "profilehub::*"
};

var tokenContent4 = {
	account: "000002",
	appId: "fee8a312946c4a593046ed36d2",
	scopes: "profilehub::get*,profilehub::getVisitor*,profile*::get*"
};

var tokenContent5 = {
	account: "000002",
	appId: "fee8a312946c4a593046ed36d2",
	scopes: "profilehub::post*"
};

var tokenContent6 = {
	account: "000002",
	appId: "fee8a312946c4a593046ed36d2",
	scopes: "profilehub::*"
};

// var accessToken = app.signToken(tokenContent2, privateKey);
// console.log(accessToken);
// console.log("0000002");
// var accessToken6 = app.signToken(tokenContent6, privateKey);
// console.log(accessToken6);
// console.log(app.verifyScopes(tokenContent4.scopes, 'profilehub::getVisitors')); //true
// // console.log(app.verifyScopes(tokenContent2.scopes, 'profilehub::getVisitor')); //
// console.log(app.verifyScopes(tokenContent.scopes, 'profilehub::getRelease')); //true
// // console.log(app.verifyScopes(tokenContent6.scopes, 'profilehub::getToto')); // true
// console.log(app.verifyScopes(tokenContent5.scopes, 'profilehub::getTchou1991')); // false
// console.log(app.verifyScopes(tokenContent5.scopes, 'profilehub::plop')); // true
// console.log(app.verifyScopes(tokenContent5.scopes, 'profilehub::postRelease')); // true

// //good token
var accessToken = app.signToken(tokenContent, privateKey);
// //bad account
// var accessToken3 = app.signToken(tokenContent2, privateKey);
// //one scope
// var accessToken4 = app.signToken(tokenContent4, privateKey);
// //bad scope
// var accessToken5 = app.signToken(tokenContent5, privateKey);
// //bad token
// var accessToken2 = app.signToken(tokenContent, privateKey);
// //super scope
var accessToken6 = app.signToken(tokenContent6, privateKey2);

// console.log("Good Token:");
// console.log(accessToken);
// console.log("Good Token but wrong account:");
// console.log(accessToken3);
// console.log("Good Token - getCounters :")
// console.log(accessToken2);
// console.log("Good token - one scope :")
// console.log(accessToken4);
// console.log("Good token - get* :")
// console.log(accessToken5);
// console.log("Good token - Super scope :")
// console.log(accessToken6);

var testPromise = app.verifyToken(accessToken);
var testPromise2 = app.verifyToken(accessToken6);

testPromise.then( function (tokenContent) {
	console.log(tokenContent);
}).catch(function (err) {
	console.log(err);;
});

testPromise2.then( function (tokenContent) {
	console.log(tokenContent);
}).catch(function (err) {
	console.log(err);;
});<commit_msg>[SDK] Review example/index.js<commit_after>var SDK	 		= require('../lib/index').SDK;
var conf 		= require('./config/config.json');
var privateKey 	= require('fs').readFileSync('./config/private_sept.key');
var privateKey2 = require('fs').readFileSync('./config/private_october.key');
var app 		= new SDK(conf["SDK"]);

var tokenContent = {
	account: "000002",
	appId: "fee8a312946c4a593046ed36d2",
	scopes: "profilehub::getVisitor,profilehub::getVisitors,profilehub::getVisitorsFields,profilehub::getRelease,abc,profilehub::getToto"
};

// good token
var accessToken = app.signToken(tokenContent, privateKey);
// bad token
var accessToken2 = app.signToken(tokenContent6, privateKey2);

var testPromise = app.verifyToken(accessToken);
var testPromise2 = app.verifyToken(accessToken2);

testPromise.then( function (tokenContent) {
	console.log(tokenContent);
}).catch(function (err) {
	console.log(err);;
});

testPromise2.then( function (tokenContent) {
	console.log(tokenContent);
}).catch(function (err) {
	console.log(err);;
});<|endoftext|>


<commit_before>var Tithe = require("../lib");
Tithe.insert({
    value: 100,
    tithe: true
});
<commit_msg>Fixed the example<commit_after>var Tithe = require("../lib");
Tithe.insert({
    val: 100,
    tithe: true
});
<|endoftext|>


<commit_before>let collectionEl = $("

")
$("body").append(collectionEl)
let model = new Lanurite.Model({"name": "Robot"})
let collection = new Lanurite.Collection([{"name": "Yura"}, {"name": "Dima"}, {name: "Mama"}, {"name": "Papa"}, model]);
collection.each((model)=>{
    model.on("change",()=>{
        model.get("el").text(model.get("name"))
    })

    model.on("destroy", () =>{
        model.get("el").remove()
    })

})
function initCollection() {
    collectionEl.html("")
    collection.each((model)=>{
        let modelEl = $("

")
        modelEl.text(model.get("name"))
        model.set("el", modelEl)
        collectionEl.append(modelEl)

    })
}

collection.on("reset", initCollection)

initCollection()<commit_msg>Update test2.js<commit_after>let collectionEl = $("

")
$("body").append(collectionEl)
let model = new Lanurite.Model({"name": "Robot"})
let collection = new Lanurite.Collection([{"name": "Person1"}, {"name": "Person2"}, {name: "Person3"}, {"name": "Person4"}, model]);
collection.each((model)=>{
    model.on("change",()=>{
        model.get("el").text(model.get("name"))
    })

    model.on("destroy", () =>{
        model.get("el").remove()
    })

})
function initCollection() {
    collectionEl.html("")
    collection.each((model)=>{
        let modelEl = $("

")
        modelEl.text(model.get("name"))
        model.set("el", modelEl)
        collectionEl.append(modelEl)

    })
}

collection.on("reset", initCollection)

initCollection()
<|endoftext|>


<commit_before>// Dependencies:
import { useMochaHook } from '@phenomnomnominal/protractor-use-mocha-hook';
import { getConfig } from '@tractor/config-loader';
import { TractorError } from '@tractor/error-handler';
import { exists } from 'fs';
import marge from 'mochawesome-report-generator';
import { merge } from 'mochawesome-merge';
import * as optimist from 'optimist';
import path from 'path';
import rimraf from 'rimraf';
import { promisify } from 'util';
import { browserInfo } from './browser-info';
import { debug } from './debug';
import { setTags } from './tags';
import { world } from './world';

// Constants:
const DEFAULT_SERIAL_REPORTER = 'mochawesome';
const DEFAULT_PARALLEL_REPORTER = 'spec';
const DEFAULT_MULTIPLE_REPORTER = 'mocha-multi-reporters';
const DEFAULT_MOCHAWESOME_OPTIONS = {
    autoOpen: false,
    html: false,
    inline: true,
    json: true,
    overwrite: false
};
const rimrafAsync = promisify(rimraf);
const existsAsync = promisify(exists);

export function plugin (protractorConfig) {
    if (protractorConfig.framework) {
        throw new TractorError('Cannot configure `mocha`. `framework` is already set in protractor config.');
    }

    let tractorConfig = getConfig();

    let specGlob = path.join(process.cwd(), tractorConfig.mochaSpecs.directory, '**/*.e2e-spec.js');

    protractorConfig.framework = 'mocha';
    protractorConfig.specs = protractorConfig.specs || [specGlob];

    protractorConfig.mochaOpts = protractorConfig.mochaOpts || {};
    let { mochaOpts } = protractorConfig;
    const reportDirOptions = { reportDir: tractorConfig.mochaSpecs.reportsDirectory };

    if (!mochaOpts.reporter) {
        const { capabilities, multiCapabilities } = protractorConfig;
        const isSharded = capabilities && capabilities.shardTestFiles && capabilities.maxInstances > 1;
        const isMultiSharded = multiCapabilities && multiCapabilities.some(capability => capability.shardTestFiles && capabilities.maxInstances > 1);
        mochaOpts.reporter = isSharded || isMultiSharded ? DEFAULT_PARALLEL_REPORTER : DEFAULT_SERIAL_REPORTER;
    }

    let autoOpen = true;
    if (mochaOpts.reporter === DEFAULT_SERIAL_REPORTER) {
        if (mochaOpts.reporterOptions) {
            autoOpen = !!mochaOpts.reporterOptions.autoOpen;
        }
        mochaOpts.reporterOptions = {
            ...DEFAULT_MOCHAWESOME_OPTIONS,
            ...reportDirOptions,
            ...mochaOpts.reporterOptions
        };
    }

    if (mochaOpts.reporter === DEFAULT_MULTIPLE_REPORTER && mochaOpts.reporterOptions && mochaOpts.reporterOptions.reporterEnabled) {
        if (mochaOpts.reporterOptions.reporterEnabled.includes('mochawesome')) {
            mochaOpts.reporterOptions.mochawesomeReporterOptions = {
                ...DEFAULT_MOCHAWESOME_OPTIONS,
                ...reportDirOptions,
                ...mochaOpts.reporterOptions.mochawesomeReporterOptions
            };
        }
    }

    let args = optimist.parse(process.argv.slice(2));
    let params = args && args.params || {};
    if (params && params.tag) {
        setTags(mochaOpts, params.tag);
    }
    if (params && params.spec) {
        protractorConfig.specs = [params.spec];
    }

    protractorConfig.plugins = protractorConfig.plugins || [];
    protractorConfig.plugins.push({
        inline: {
            onPrepare () {
                world();
            },
            postTest: function () {
                // use `function` over `=>` to let Mocha set `this`:
                useMochaHook('afterEach', async function () {
                    await browserInfo(this);
                    debug(this);
                });
            }
        }
    });

    const afterLaunch = protractorConfig.afterLaunch;
    protractorConfig.afterLaunch = async function (...args) {
        const reports = await existsAsync(reportDirOptions.reportDir);
        if (reports) {
            const report = await merge(reportDirOptions);
            await marge.create(report, { autoOpen, ...reportDirOptions });
            await rimrafAsync(path.resolve(reportDirOptions.reportDir, './*.json'));
        }
        if (afterLaunch) {
            afterLaunch.call(this, ...args);
        }
    };

    return protractorConfig;
}
<commit_msg>fix(mocha-specs :coffee:) - fixing the hook for browser info<commit_after>// Dependencies:
import { useMochaHook } from '@phenomnomnominal/protractor-use-mocha-hook';
import { getConfig } from '@tractor/config-loader';
import { TractorError } from '@tractor/error-handler';
import { exists } from 'fs';
import marge from 'mochawesome-report-generator';
import { merge } from 'mochawesome-merge';
import * as optimist from 'optimist';
import path from 'path';
import rimraf from 'rimraf';
import { promisify } from 'util';
import { browserInfo } from './browser-info';
import { debug } from './debug';
import { setTags } from './tags';
import { world } from './world';

// Constants:
const DEFAULT_SERIAL_REPORTER = 'mochawesome';
const DEFAULT_PARALLEL_REPORTER = 'spec';
const DEFAULT_MULTIPLE_REPORTER = 'mocha-multi-reporters';
const DEFAULT_MOCHAWESOME_OPTIONS = {
    autoOpen: false,
    html: false,
    inline: true,
    json: true,
    overwrite: false
};
const rimrafAsync = promisify(rimraf);
const existsAsync = promisify(exists);

export function plugin (protractorConfig) {
    if (protractorConfig.framework) {
        throw new TractorError('Cannot configure `mocha`. `framework` is already set in protractor config.');
    }

    let tractorConfig = getConfig();

    let specGlob = path.join(process.cwd(), tractorConfig.mochaSpecs.directory, '**/*.e2e-spec.js');

    protractorConfig.framework = 'mocha';
    protractorConfig.specs = protractorConfig.specs || [specGlob];

    protractorConfig.mochaOpts = protractorConfig.mochaOpts || {};
    let { mochaOpts } = protractorConfig;
    const reportDirOptions = { reportDir: tractorConfig.mochaSpecs.reportsDirectory };

    if (!mochaOpts.reporter) {
        const { capabilities, multiCapabilities } = protractorConfig;
        const isSharded = capabilities && capabilities.shardTestFiles && capabilities.maxInstances > 1;
        const isMultiSharded = multiCapabilities && multiCapabilities.some(capability => capability.shardTestFiles && capabilities.maxInstances > 1);
        mochaOpts.reporter = isSharded || isMultiSharded ? DEFAULT_PARALLEL_REPORTER : DEFAULT_SERIAL_REPORTER;
    }

    let autoOpen = true;
    if (mochaOpts.reporter === DEFAULT_SERIAL_REPORTER) {
        if (mochaOpts.reporterOptions) {
            autoOpen = !!mochaOpts.reporterOptions.autoOpen;
        }
        mochaOpts.reporterOptions = {
            ...DEFAULT_MOCHAWESOME_OPTIONS,
            ...reportDirOptions,
            ...mochaOpts.reporterOptions
        };
    }

    if (mochaOpts.reporter === DEFAULT_MULTIPLE_REPORTER && mochaOpts.reporterOptions && mochaOpts.reporterOptions.reporterEnabled) {
        if (mochaOpts.reporterOptions.reporterEnabled.includes('mochawesome')) {
            mochaOpts.reporterOptions.mochawesomeReporterOptions = {
                ...DEFAULT_MOCHAWESOME_OPTIONS,
                ...reportDirOptions,
                ...mochaOpts.reporterOptions.mochawesomeReporterOptions
            };
        }
    }

    let args = optimist.parse(process.argv.slice(2));
    let params = args && args.params || {};
    if (params && params.tag) {
        setTags(mochaOpts, params.tag);
    }
    if (params && params.spec) {
        protractorConfig.specs = [params.spec];
    }

    protractorConfig.plugins = protractorConfig.plugins || [];
    protractorConfig.plugins.push({
        inline: {
            onPrepare () {
                world();
            },
            setup () {
                // use `function` over `=>` to let Mocha set `this`:
                useMochaHook('afterEach', async function () {
                    await browserInfo(this);
                    debug(this);
                });
            }
        }
    });

    const afterLaunch = protractorConfig.afterLaunch;
    protractorConfig.afterLaunch = async function (...args) {
        const reports = await existsAsync(reportDirOptions.reportDir);
        if (reports) {
            const report = await merge(reportDirOptions);
            await marge.create(report, { autoOpen, ...reportDirOptions });
            await rimrafAsync(path.resolve(reportDirOptions.reportDir, './*.json'));
        }
        if (afterLaunch) {
            afterLaunch.call(this, ...args);
        }
    };

    return protractorConfig;
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

treeherder.factory('ThLogSliceModel', [
    '$http', '$q', '$timeout', 'thUrl',
    function($http, $q, $timeout, thUrl) {

    // ThLogSliceModel is the js counterpart of logslice

    var ThLogSliceModel = function(job_id, buffer_chunk_size, buffer_size) {
        this.job_id = job_id;
        this.chunk_size = buffer_chunk_size || 500;
        this.buffer_size = buffer_size || 10;
        this.buffer = {};
    };

    ThLogSliceModel.get_uri = function(){return thUrl.getProjectUrl("/logslice/");};

    ThLogSliceModel.prototype.find_in_buffer = function (options) {
        var ret = [], arr;

        for (var i = options.start_line; i < options.end_line; i += this.chunk_size) {
            arr = this.buffer[Math.floor(i/this.chunk_size)] || false;

            if (arr) {
                // update for LRU
                arr.used = Date.now();
                ret = ret.concat(arr.data);
            } else {
                return false;
            }
        }

        return ret;
    };

    ThLogSliceModel.prototype.insert_into_buffer = function (options, res) {
        for (var i = options.start_line, j = 0; i < options.end_line; i += this.chunk_size, j++) {
            this.buffer[Math.floor(i/this.chunk_size)] = {
                data: res.slice(j * this.chunk_size, (j+1) * this.chunk_size),
                used: Date.now()
            };
        }

        var size = this.buffer_size + 1;

        while (size > this.buffer_size) {
            size = 0;
            var indexLRU = 0, baseDate = Date.now();

            for (var i in this.buffer) {
                if (this.buffer.hasOwnProperty(i)) {
                    size++;
                    if (this.buffer[i].used < baseDate) {
                        baseDate = this.buffer[i].used;
                        indexLRU = i;
                    }
                }
            }

            if (size > this.buffer_size) {
                delete this.buffer[indexLRU];
            }
        }
    };

    ThLogSliceModel.prototype.load_more = function (bounds, element) {
        var deferred = $q.defer(), range, req, above, below;
        var self = this;

        if (!this.loading) {
            // move the line number either up or down depending which boundary was hit
            this.line_number = moveLineNumber(bounds);

            range = {
                start: this.line_number,
                end: this.line_number
            };

            if (bounds.top) {
                above = getChunkAbove(range);
            } else if (bounds.bottom) {
                below = getChunkBelow(range);
            } else {
                range = getChunksSurrounding(this.line_number);
            }

            // dont do the call if we already have all the lines
            if ( range.start === range.end ) return deferred.promise;

            this.loading = true;

            this.get_line_range({
                job_id: this.job_id,
                start_line: range.start,
                end_line: range.end
            }).then(function(data) {
                var slicedData, length;

                drawErrorLines(data);

                if (bounds.top) {
                    for (var i = data.length - 1; i >= 0; i--) {
                        // make sure we are inserting at the right place
                        if (self.lines[0].index != data[i].index + 1) continue;
                        self.lines.unshift(data[i]);
                    }

                    $timeout(function () {
                        if (above) removeChunkBelow();
                    }, 100);
                } else if (bounds.bottom) {
                    var sh = element.scrollHeight;
                    var lines = self.lines;

                    for (var i = 0; i < data.length; i++) {
                        // make sure we are inserting at the right place
                        if (lines[ lines.length - 1 ].index != data[i].index - 1) continue;
                        self.lines.push(data[i]);
                    }

                    $timeout(function () {
                        if (below) {
                            removeChunkAbove();
                            element.scrollTop -= element.scrollHeight - sh;
                        }
                    }, 100);
                } else {
                    self.lines = data;
                }

                self.loading = false;
                deferred.resolve();
            });
        } else {
            $scope.loading = false;
            deferred.reject();
        }

        return deferred.promise;
    };

    ThLogSliceModel.prototype.get_line_range = function(options, config) {
        config = config || {};
        var timeout = config.timeout || null;
        var found = this.find_in_buffer(options);
        var self = this;
        var deferred = $q.defer();

        if (found) {
            deferred.resolve(found);
            return deferred.promise;
        }

        return $http.get(ThLogSliceModel.get_uri(),{
            params: options,
            timeout: timeout
        }).then(function (res) {
            self.insert_into_buffer(options, res.data);

            return res.data;
        }, function (res) {
            return $q.reject("Log not found");
        });

    };

    return ThLogSliceModel;
}]);
<commit_msg>code cleanup and dead code removal<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

treeherder.factory('ThLogSliceModel', [
    '$http', '$q', '$timeout', 'thUrl',
    function($http, $q, $timeout, thUrl) {

    // ThLogSliceModel is the js counterpart of logslice

    var ThLogSliceModel = function(job_id, buffer_chunk_size, buffer_size) {
        this.job_id = job_id;
        this.chunk_size = buffer_chunk_size || 500;
        this.buffer_size = buffer_size || 10;
        this.buffer = {};
    };

    ThLogSliceModel.get_uri = function(){return thUrl.getProjectUrl("/logslice/");};

    ThLogSliceModel.prototype.find_in_buffer = function (options) {
        var ret = [], arr;

        for (var i = options.start_line; i < options.end_line; i += this.chunk_size) {
            arr = this.buffer[Math.floor(i/this.chunk_size)] || false;

            if (arr) {
                // update for LRU
                arr.used = Date.now();
                ret = ret.concat(arr.data);
            } else {
                return false;
            }
        }

        return ret;
    };

    ThLogSliceModel.prototype.insert_into_buffer = function (options, res) {
        for (var i = options.start_line, j = 0; i < options.end_line; i += this.chunk_size, j++) {
            this.buffer[Math.floor(i/this.chunk_size)] = {
                data: res.slice(j * this.chunk_size, (j+1) * this.chunk_size),
                used: Date.now()
            };
        }

        var size = this.buffer_size + 1;

        while (size > this.buffer_size) {
            size = 0;
            var indexLRU = 0, baseDate = Date.now();

            for (var k in this.buffer) {
                if (this.buffer.hasOwnProperty(k)) {
                    size++;
                    if (this.buffer[k].used < baseDate) {
                        baseDate = this.buffer[k].used;
                        indexLRU = k;
                    }
                }
            }

            if (size > this.buffer_size) {
                delete this.buffer[indexLRU];
            }
        }
    };

    ThLogSliceModel.prototype.get_line_range = function(options, config) {
        config = config || {};
        var timeout = config.timeout || null;
        var found = this.find_in_buffer(options);
        var self = this;
        var deferred = $q.defer();

        if (found) {
            deferred.resolve(found);
            return deferred.promise;
        }

        return $http.get(ThLogSliceModel.get_uri(),{
            params: options,
            timeout: timeout
        }).then(function (res) {
            self.insert_into_buffer(options, res.data);

            return res.data;
        }, function (res) {
            return $q.reject("Log not found");
        });

    };

    return ThLogSliceModel;
}]);
<|endoftext|>


<commit_before>;(function (undefined) {
    'use strict';

    if (typeof sigma === 'undefined') {
    	throw 'sigma is not declared';
    }

    if (!('Promise' in window)) {
    	throw 'Your browser must support Promise Object';
    }

    var driver = null,
        sig = null,
        version = null;

    // Declare neo4j package
    sigma.utils.pkg("sigma.neo4j");

    /*
     * Convert the Record object of neo4j bolt web driver in order to get
     * the integer value
     *
     * @param {Record} primitive.   The Record object returned by neo4j bolt web api.
     *
     */
    var convertPrimitive = function convertPrimitive (primitive) {
        var keys = Object.keys(primitive.properties);
        for (var i = 0; i < keys.length; i++) {
            var key = keys[i],
                property = primitive.properties[key];
            if (typeof property === 'object'
                    && 'high' in property && 'low' in property) {
                property = property.low;
            }
        }
        primitive.identity = primitive.identity.low;
        if ('start' in primitive && 'end' in primitive) {
            primitive.start = primitive.start.low;
            primitive.end = primitive.end.low;
        }
    };

    /**
     * This function parse a neo4j cypher query result, and transform it into
     * a sigma graph object. (Inspired by sigma.neo4j.cypher plugin)
     *
     * @param {Result} response.   The Result object returned by neo4j bolt web api.
     *
     * @return {graph} Sigma.js Graph object
     */
    var driverResultToSigmaGraph = function driverResultToSigmaGraph (response) {
        var graph = { nodes: [], edges: [] },
            nodesMap = {},
            edgesMap = {},
            key;

        response.records.forEach(function (record) {
            record._fields.forEach(function (primitive) {
                if (!primitive) {
                    return;
                }
                convertPrimitive(primitive);
                if (('start' in primitive
                        || 'end' in primitive
                        || 'type' in primitive)
                        && !sig.graph.edges(primitive.identity)) {
                    edgesMap[primitive.identity] = {
                        id : primitive.identity,
                        label : primitive.type,
                        source : primitive.start,
                        target : primitive.end,
                        color : '#7D7C8E',
                        neo4j_type : primitive.type,
                        neo4j_data : primitive.properties
                    };
                } else if (!sig.graph.nodes(primitive.identity)) {
                    nodesMap[primitive.identity] =  {
                        id : primitive.identity,
                        label : primitive.identity,
                        x : Math.random(),
                        y : Math.random(),
                        size : 1,
                        color : '#000000',
                        neo4j_labels : primitive.labels,
                        neo4j_data : primitive.properties
                    };
                }
            });
        });

        // construct sigma nodes
        for (key in nodesMap) {
            graph.nodes.push(nodesMap[key]);
        }
        // construct sigma nodes
        for (key in edgesMap) {
            graph.edges.push(edgesMap[key]);
        }
        return graph;
    }

    //pass draw the graph
    var onResolve = function onResolve (response, addToGraph) {
        var graph = driverResultToSigmaGraph(response);
        if (!addToGraph) {//if it is invoked by queryStream method doesn't clear the graph
           sig.graph.clear();
        }
        sig.graph.read(graph);
        return sig;
    };

    /**
     * This function connect to neo4j via bolt connection and create the sigmajs object
     *
     * @param {object} neo4jOpts.   Options to pass to neo4j; defaults:
     * {
     *   endpoint : 'bolt://localhost',
     *   auth : { scheme : 'basic', principal : '<neo4j_connection_username>', password : '<neo4j_connection_password>' },
     *   version : 'v1'
     * }
     * @param {object} sigOpts.     Options to pass to sigma.js, see sigma.js docs for details.
     *
     * @return {Driver} A neo4j Driver object
     */
    sigma.neo4j.connect = function connect (neo4jOpts, sigOpts) {
    	if (arguments.length !== 2) {
    		throw 'You must define both neo4j and sigma options';
    	}
        version = neo4jOpts.version || 'v1';
        var endpoint = neo4jOpts.endpoint || 'bolt://localhost';
        var auth = !neo4jOpts.auth.scheme ?
                neo4j[version].auth.basic(neo4jOpts.auth.principal, neo4jOpts.auth.password) : neo4jOpts.auth;
        sig = new sigma(sigOpts);
        sig.refresh();
        driver = neo4j[version].driver(endpoint, auth);
        return driver;
    };

    /**
     * This function is for quering neo4j and draw the graph once the result is completly retrived
     *
     * @param {string} statement.   Neo4j Statement
     * @param {object} params.      Params of statement template
     *
     * @return {Promise} A Promise object
     */
    sigma.neo4j.query = function query (statement, params) {
    	return new Promise(function (resolve, reject) {
    		var session = driver.session();
            session.run(statement, params)
    			.then(function (response) {
                    resolve(onResolve(response));
                    session.close();
                })
    			.catch(reject);
    	});
    };

    /**
     * This function is for quering neo4j and draw the graph progressively
     * as records arrive by neo4j stream api
     *
     * @param {string} statement.   Neo4j Statement
     * @param {object} params.      Params of statement template
     *
     * @return {Promise} A Promise object
     */
    sigma.neo4j.queryStream = function queryStream (statement, params) {
    	return new Promise(function (resolve, reject) {
            var session = driver.session();
            session.run(statement, params)
                .subscribe({
                    onNext : function onNext (record) {
                        //console.log('onNext', record._fields);
                        onResolve({ records : [record] }, true);
                    },
                    onCompleted : function onCompleted () {
                        // Completed!
                        session.close();
                        resolve(sig);
                    },
                    onError : function onError (error) {
                        console.log('onError', error);
                        reject(error);
                    }
                });
        })
    };

    /**
     * This function collect all the node labels of the graph.
     *
     * @return {array} An array of labels
     */
    sigma.neo4j.getLabels = function() {
        //sigma.neo4j.send(neo4j, '/db/data/labels', 'GET', null, callback);
        return new Promise(function (resolve, reject) {
            var session = driver.session();
            session.run('match (n) with distinct labels(n) as labels unwind labels as label return label')
                .then(function (response) {
                    resolve(response.records.map(function (elem) { return elem._fields[0]; }));
                    session.close();
                })
                .catch(reject);

        });
    };

    /**
     * This function collect all the relationship types of the graph.
     *
     * @return {array} An array of relationships type
     */
    sigma.neo4j.getTypes = function() {
        //sigma.neo4j.send(neo4j, '/db/data/relationship/types', 'GET', null, callback);
        return new Promise(function (resolve, reject) {
            var session = driver.session();
            session.run('match (n)-[r]-() return distinct type(r)')
                .then(function (response) {
                    resolve(response.records.map(function (elem) { return elem._fields[0]; }));
                    session.close();
                })
                .catch(reject);
        });
    };

}).call(this);

<commit_msg>Camelized fields<commit_after>;(function (undefined) {
    'use strict';

    if (typeof sigma === 'undefined') {
    	throw 'sigma is not declared';
    }

    if (!('Promise' in window)) {
    	throw 'Your browser must support Promise Object';
    }

    var driver = null,
        sig = null,
        version = null;

    // Declare neo4j package
    sigma.utils.pkg("sigma.neo4j");

    /*
     * Convert the Record object of neo4j bolt web driver in order to get
     * the integer value
     *
     * @param {Record} primitive.   The Record object returned by neo4j bolt web api.
     *
     */
    var convertPrimitive = function convertPrimitive (primitive) {
        var keys = Object.keys(primitive.properties);
        for (var i = 0; i < keys.length; i++) {
            var key = keys[i],
                property = primitive.properties[key];
            if (typeof property === 'object'
                    && 'high' in property && 'low' in property) {
                property = property.low;
            }
        }
        primitive.identity = primitive.identity.low;
        if ('start' in primitive && 'end' in primitive) {
            primitive.start = primitive.start.low;
            primitive.end = primitive.end.low;
        }
    };

    /**
     * This function parse a neo4j cypher query result, and transform it into
     * a sigma graph object. (Inspired by sigma.neo4j.cypher plugin)
     *
     * @param {Result} response.   The Result object returned by neo4j bolt web api.
     *
     * @return {graph} Sigma.js Graph object
     */
    var driverResultToSigmaGraph = function driverResultToSigmaGraph (response) {
        var graph = { nodes: [], edges: [] },
            nodesMap = {},
            edgesMap = {},
            key;

        response.records.forEach(function (record) {
            record._fields.forEach(function (primitive) {
                if (!primitive) {
                    return;
                }
                convertPrimitive(primitive);
                if (('start' in primitive
                        || 'end' in primitive
                        || 'type' in primitive)
                        && !sig.graph.edges(primitive.identity)) {
                    edgesMap[primitive.identity] = {
                        id : primitive.identity,
                        label : primitive.type,
                        source : primitive.start,
                        target : primitive.end,
                        color : '#7D7C8E',
                        neo4jType : primitive.type,
                        neo4jData : primitive.properties
                    };
                } else if (!sig.graph.nodes(primitive.identity)) {
                    nodesMap[primitive.identity] =  {
                        id : primitive.identity,
                        label : primitive.identity,
                        x : Math.random(),
                        y : Math.random(),
                        size : 1,
                        color : '#000000',
                        neo4jLabels : primitive.labels,
                        neo4jData : primitive.properties
                    };
                }
            });
        });

        // construct sigma nodes
        for (key in nodesMap) {
            graph.nodes.push(nodesMap[key]);
        }
        // construct sigma nodes
        for (key in edgesMap) {
            graph.edges.push(edgesMap[key]);
        }
        return graph;
    }

    //pass draw the graph
    var onResolve = function onResolve (response, addToGraph) {
        var graph = driverResultToSigmaGraph(response);
        if (!addToGraph) {//if it is invoked by queryStream method doesn't clear the graph
           sig.graph.clear();
        }
        sig.graph.read(graph);
        return sig;
    };

    /**
     * This function connect to neo4j via bolt connection and create the sigmajs object
     *
     * @param {object} neo4jOpts.   Options to pass to neo4j; defaults:
     * {
     *   endpoint : 'bolt://localhost',
     *   auth : { scheme : 'basic', principal : '<neo4j_connection_username>', password : '<neo4j_connection_password>' },
     *   version : 'v1'
     * }
     * @param {object} sigOpts.     Options to pass to sigma.js, see sigma.js docs for details.
     *
     * @return {Driver} A neo4j Driver object
     */
    sigma.neo4j.connect = function connect (neo4jOpts, sigOpts) {
    	if (arguments.length !== 2) {
    		throw 'You must define both neo4j and sigma options';
    	}
        version = neo4jOpts.version || 'v1';
        var endpoint = neo4jOpts.endpoint || 'bolt://localhost';
        var auth = !neo4jOpts.auth.scheme ?
                neo4j[version].auth.basic(neo4jOpts.auth.principal, neo4jOpts.auth.password) : neo4jOpts.auth;
        sig = new sigma(sigOpts);
        sig.refresh();
        driver = neo4j[version].driver(endpoint, auth);
        return driver;
    };

    /**
     * This function is for quering neo4j and draw the graph once the result is completly retrived
     *
     * @param {string} statement.   Neo4j Statement
     * @param {object} params.      Params of statement template
     *
     * @return {Promise} A Promise object
     */
    sigma.neo4j.query = function query (statement, params) {
    	return new Promise(function (resolve, reject) {
    		var session = driver.session();
            session.run(statement, params)
    			.then(function (response) {
                    resolve(onResolve(response));
                    session.close();
                })
    			.catch(reject);
    	});
    };

    /**
     * This function is for quering neo4j and draw the graph progressively
     * as records arrive by neo4j stream api
     *
     * @param {string} statement.   Neo4j Statement
     * @param {object} params.      Params of statement template
     *
     * @return {Promise} A Promise object
     */
    sigma.neo4j.queryStream = function queryStream (statement, params) {
    	return new Promise(function (resolve, reject) {
            var session = driver.session();
            session.run(statement, params)
                .subscribe({
                    onNext : function onNext (record) {
                        //console.log('onNext', record._fields);
                        onResolve({ records : [record] }, true);
                    },
                    onCompleted : function onCompleted () {
                        // Completed!
                        session.close();
                        resolve(sig);
                    },
                    onError : function onError (error) {
                        console.log('onError', error);
                        reject(error);
                    }
                });
        })
    };

    /**
     * This function collect all the node labels of the graph.
     *
     * @return {array} An array of labels
     */
    sigma.neo4j.getLabels = function() {
        //sigma.neo4j.send(neo4j, '/db/data/labels', 'GET', null, callback);
        return new Promise(function (resolve, reject) {
            var session = driver.session();
            session.run('match (n) with distinct labels(n) as labels unwind labels as label return label')
                .then(function (response) {
                    resolve(response.records.map(function (elem) { return elem._fields[0]; }));
                    session.close();
                })
                .catch(reject);

        });
    };

    /**
     * This function collect all the relationship types of the graph.
     *
     * @return {array} An array of relationships type
     */
    sigma.neo4j.getTypes = function() {
        //sigma.neo4j.send(neo4j, '/db/data/relationship/types', 'GET', null, callback);
        return new Promise(function (resolve, reject) {
            var session = driver.session();
            session.run('match (n)-[r]-() return distinct type(r)')
                .then(function (response) {
                    resolve(response.records.map(function (elem) { return elem._fields[0]; }));
                    session.close();
                })
                .catch(reject);
        });
    };

}).call(this);

<|endoftext|>


<commit_before>var children = require('child_process')
var delta = require('delta')
var cadence = require('cadence')
var interrupt = require('interrupt').createInterrupter('compassion.colleague')
var coalesce = require('extant')

var Signal = require('signal')
var Destructible = require('destructible')

function Monitor () {
    this.ready = new Signal
    this.child = null
    this.destroyed = false
    this._destructible = new Destructible
    this._destructible.markDestroyed(this)
    this._destructible.addDestructor('started', this.ready, 'unlatch')
    this._destructible.addDestructor('kill', this, '_kill')
}

Monitor.prototype._kill = function () {
    if (this.child != null) {
        this.child.kill()
        this.child = null
    }
}

Monitor.prototype.run = cadence(function (async, program) {
    this._destructible.stack(async, 'run')(function (ready) {
        var argv = program.argv.slice()
        var env = JSON.parse(JSON.stringify(program.env))
        env.COMPASSION_COLLEAGUE_FD = 3
        this.child = children.spawn(argv.shift(), argv, {
            stdio: [ 0, 1, 2, 'pipe' ],
            env: env
        })
        async(function () {
            delta(async()).ee(this.child).on('exit')
            ready.unlatch()
        }, function (exitCode, signal) {
            interrupt(exitCode == 0 || signal == 'SIGTERM', 'childExit', {
                exitCode: coalesce(exitCode),
                signal: coalesce(signal)
            })
        })
    })
    this._destructible.ready.wait(this.ready, 'unlatch')
})

Monitor.prototype.destroy = function () {
    this._destructible.destroy()
}

module.exports = Monitor
<commit_msg>Fix interrupt assertion.<commit_after>var children = require('child_process')
var delta = require('delta')
var cadence = require('cadence')
var interrupt = require('interrupt').createInterrupter('compassion.colleague')
var coalesce = require('extant')

var Signal = require('signal')
var Destructible = require('destructible')

function Monitor () {
    this.ready = new Signal
    this.child = null
    this.destroyed = false
    this._destructible = new Destructible
    this._destructible.markDestroyed(this)
    this._destructible.addDestructor('started', this.ready, 'unlatch')
    this._destructible.addDestructor('kill', this, '_kill')
}

Monitor.prototype._kill = function () {
    if (this.child != null) {
        this.child.kill()
        this.child = null
    }
}

Monitor.prototype.run = cadence(function (async, program) {
    this._destructible.stack(async, 'run')(function (ready) {
        var argv = program.argv.slice()
        var env = JSON.parse(JSON.stringify(program.env))
        env.COMPASSION_COLLEAGUE_FD = 3
        this.child = children.spawn(argv.shift(), argv, {
            stdio: [ 0, 1, 2, 'pipe' ],
            env: env
        })
        async(function () {
            delta(async()).ee(this.child).on('exit')
            ready.unlatch()
        }, function (exitCode, signal) {
            interrupt.assert(exitCode == 0 || signal == 'SIGTERM', 'childExit', {
                exitCode: coalesce(exitCode),
                signal: coalesce(signal)
            })
        })
    })
    this._destructible.ready.wait(this.ready, 'unlatch')
})

Monitor.prototype.destroy = function () {
    this._destructible.destroy()
}

module.exports = Monitor
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Memrise - Replace commas with semicolons
// @namespace    https://github.com/neoncube2/memrise
// @version      1.0
// @description  Replaces commas with semicolons for the textual values that are in a course.
// @author       Eli Black
// @match        http://www.memrise.com/course/*/*/edit/
// @grant        MIT
// @updateURL    https://raw.githubusercontent.com/neoncube2/memrise/master/replace_commas_with_semicolons/replace_commas_with_semicolons.js
// @downloadURL  https://raw.githubusercontent.com/neoncube2/memrise/master/replace_commas_with_semicolons/replace_commas_with_semicolons.js
// ==/UserScript==

function processRow(rows, rowIndex) {
    if(rowIndex >= rows.length) {
        alert('The script has mostly finished processing. Please wait a few seconds before leaving this page so that any updates that are currently in progress have a chance to finish.');

        return;
    }

    var row = $(rows[rowIndex]);

    row.css('background-color', 'lightgrey');

    var thingId = row.data('thing-id');

    console.log('Attempting to retrieve information about thing #' + thingId);

    // Is an _ parameter needed?
    $.get('//www.memrise.com/api/thing/get/?thing_id=' + encodeURIComponent(thingId), function(thingData) {
        var thing = thingData.thing;

        var encounteredError = false;

        for(var columnId in thing.columns) {
            var column = thing.columns[columnId];

            if(column.kind != 'text') {
                continue;
            }

            var entryValue = column.val;

            if(entryValue.indexOf(',') == -1) {
                continue;
            }

            entryValue = entryValue.replace(',', ';');

            $.post('http://www.memrise.com/ajax/thing/cell/update/', {
                'thing_id': thingId,
                'cell_id': columnId,
                'cell_type': 'column',
                'new_val': entryValue
            })
            .fail(function() {
                alert('Encountered an error posting a correction.');

                encounteredError = true;

                row.css('background-color', 'red');
            })
            .success(function() {
                if(!encounteredError) {
                    row.css('background-color', 'lightgreen');
                }
            });
        }
    })
    .fail(function() {
        alert('Encountered an error retrieving an item.');

        row.css('background-color', 'red');
    })
    .always(function() {
        processRow(rows, rowIndex + 1);
    });

}

function cleanLevels() {
    var rows = $('.level-things .thing');

    if(!rows.length) {
        console.log('Didn\'t find any items in the level.');
    }

    processRow(rows, 0);
}

(function() {
    var beginButton = $('<button class="show-hide btn">Replace commas with semicolons</button>').click(function() {
        $(this).attr('disabled', 'disabled');

        var levels = $('.level');

        levels.each(function() {
            var level = $(this);

            if(!level.find('.things').length) {
                level.find('.show-hide').click();
            }
        });

        // Thanks to http://stackoverflow.com/questions/1144805/scroll-to-the-top-of-the-page-using-javascript-jquery
        $("html, body").animate({ scrollTop: 0 }, "slow");

        setTimeout(cleanLevels, 10000);
    });

    beginButton.appendTo($('.add-level'));
})();<commit_msg>Made it so that the script starts when the levels have finished loading, instead of just waiting ten seconds and then beginning.<commit_after>// ==UserScript==
// @name         Memrise - Replace commas with semicolons
// @namespace    https://github.com/neoncube2/memrise
// @version      1.1
// @description  Replaces commas with semicolons for the textual values that are in a course.
// @author       Eli Black
// @match        http://www.memrise.com/course/*/*/edit/
// @grant        MIT
// @updateURL    https://raw.githubusercontent.com/neoncube2/memrise/master/replace_commas_with_semicolons/replace_commas_with_semicolons.js
// @downloadURL  https://raw.githubusercontent.com/neoncube2/memrise/master/replace_commas_with_semicolons/replace_commas_with_semicolons.js
// ==/UserScript==

var beginIfLevelsHaveLoadedInterval;

function processRow(rows, rowIndex) {
    if(rowIndex >= rows.length) {
        alert('The script has mostly finished processing. Please wait a few seconds before leaving this page so that any updates that are currently in progress have a chance to finish.');

        return;
    }

    var row = $(rows[rowIndex]);

    row.css('background-color', 'lightgrey');

    var thingId = row.data('thing-id');

    console.log('Attempting to retrieve information about thing #' + thingId);

    // Is an _ parameter needed?
    $.get('//www.memrise.com/api/thing/get/?thing_id=' + encodeURIComponent(thingId), function(thingData) {
        var thing = thingData.thing;

        var encounteredError = false;

        var columnsAndNewValues = [];

        for(var columnId in thing.columns) {
            var column = thing.columns[columnId];

            if(column.kind != 'text') {
                continue;
            }

            var entryValue = column.val;

            if(entryValue.indexOf(',') == -1) {
                continue;
            }

            columnsAndNewValues[columnId] = entryValue.replace(',', ';');
        }

        for(var columnId in columnsAndNewValues) {
            $.post('http://www.memrise.com/ajax/thing/cell/update/', {
                'thing_id': thingId,
                'cell_id': columnId,
                'cell_type': 'column',
                'new_val': columnsAndNewValues[columnId]
            })
            .fail(function() {
                alert('Encountered an error posting a correction.');

                encounteredError = true;

                row.css('background-color', 'red');
            })
            .success(function() {
                console.log(columnsAndNewValues);

                var columnIdIndex = columnsAndNewValues.indexOf(columnId);
                columnsAndNewValues.splice(columnIdIndex, 1);

                if(!encounteredError) {
                    row.css('background-color', 'lightgreen');
                }
            });
        }
    })
    .fail(function() {
        alert('Encountered an error retrieving an item.');

        row.css('background-color', 'red');
    })
    .always(function() {
        processRow(rows, rowIndex + 1);
    });

}

function cleanLevels() {
    var rows = $('.level-things .thing');

    if(!rows.length) {
        console.log('Didn\'t find any items.');
    }

    processRow(rows, 0);
}

function isLevelLoading(level) {
    level = $(level);

    return !level.hasClass('collapsed') && level.find('.level-loading').length;
}

function beginIfLevelsHaveLoaded() {
    var levels = $('.level');

    var isLoading = false;

    levels.each(function() {
        if(isLevelLoading(this)) {
            isLoading = true;

           return false;
        }
    });

    if(!isLoading) {
        console.log('Beginning');

        clearInterval(beginIfLevelsHaveLoadedInterval);

        cleanLevels();

    }
}

(function() {
    var beginButton = $('<button class="show-hide btn">Replace commas with semicolons</button>').click(function() {
        $(this).attr('disabled', 'disabled');

        var levels = $('.level');

        // Make it so that the levels are opened from bottom to top,
        // so that the page doesn't scroll down and then scroll back up.
        $(levels.get().reverse()).each(function() {
            var level = $(this);

            if(!isLevelLoading(level)) {
                level.find('.show-hide').click();
            }
        });

        // Thanks to http://stackoverflow.com/questions/1144805/scroll-to-the-top-of-the-page-using-javascript-jquery
        $("html, body").animate({ scrollTop: 0 }, "slow");

        beginIfLevelsHaveLoadedInterval = setInterval(beginIfLevelsHaveLoaded, 1000);
    });

    beginButton.appendTo($('.add-level'));
})();<|endoftext|>