<commit_before>const moment = require('moment-timezone');
const path = require('path');
const fs = require('fs-extra');
const membersCSV = require('@tryghost/members-csv');
const GhostMailer = require('../../mail').GhostMailer;
const urlUtils = require('../../../../shared/url-utils');
const db = require('../../../data/db');
const emailTemplate = require('./email-template');
const jobsService = require('../../jobs');
const labsService = require('../../../../shared/labs');

const ghostMailer = new GhostMailer();
module.exports = class MembersCSVImporter {
    /**
     * @param {Object} config
     * @param {string} config.storagePath - The path to store CSV's in before importing
     * @param {Object} settingsCache - An instance of the Ghost Settings Cache
     * @param {() => Object} getMembersApi
     */
    constructor(config, settingsCache, getMembersApi) {
        this._storagePath = config.storagePath;
        this._settingsCache = settingsCache;
        this._getMembersApi = getMembersApi;
    }

    /**
     * @typedef {string} JobID
     */

    /**
     * @typedef {Object} Job
     * @prop {string} filename
     * @prop {JobID} id
     * @prop {string} status
     */

    /**
     * Get the Job for a jobCode
     * @param {JobID} jobId
     * @returns {Promise<Job>}
     */
    async getJob(jobId) {
        return {
            id: jobId,
            filename: jobId,
            status: 'pending'
        };
    }

    /**
     * Prepares a CSV file for import
     * - Maps headers based on headerMapping, this allows for a non standard CSV
     *   to be imported, so long as a mapping exists between it and a standard CSV
     * - Stores the CSV to be imported in the storagePath
     * - Creates a MemberImport Job and associated MemberImportBatch's
     *
     * @param {string} inputFilePath - The path to the CSV to prepare
     * @param {Object.<string, string>} headerMapping - An object whos keys are headers in the input CSV and values are the header to replace it with
     * @param {Array<string>} defaultLabels - A list of labels to apply to every member
     *
     * @returns {Promise<{id: JobID, batches: number, metadata: Object.<string, any>}>} - A promise resolving to the id of the MemberImport Job
     */
    async prepare(inputFilePath, headerMapping, defaultLabels) {
        const batchSize = 1;

        const siteTimezone = this._settingsCache.get('timezone');
        const currentTime = moment().tz(siteTimezone).format('YYYY-MM-DD HH:mm:ss.SSS');
        const outputFileName = `Members Import ${currentTime}.csv`;
        const outputFilePath = path.join(this._storagePath, '/', outputFileName);

        const pathExists = await fs.pathExists(outputFilePath);

        if (pathExists) {
            throw new Error('Maybe we need better name generation');
        }

        const rows = await membersCSV.parse(inputFilePath, headerMapping, defaultLabels);
        const numberOfBatches = Math.ceil(rows.length / batchSize);
        const mappedCSV = membersCSV.unparse(rows);

        const hasStripeData = rows.find(function rowHasStripeData(row) {
            return !!row.stripe_customer_id || !!row.complimentary_plan;
        });

        await fs.writeFile(outputFilePath, mappedCSV);

        return {
            id: outputFilePath,
            batches: numberOfBatches,
            metadata: {
                hasStripeData
            }
        };
    }

    /**
     * Performs an import of a CSV file
     *
     * @param {JobID} id - The id of the job to perform
     */
    async perform(id) {
        const job = await this.getJob(id);

        if (job.status === 'complete') {
            throw new Error('Job is already complete');
        }

        const rows = membersCSV.parse(job.filename);

        const membersApi = await this._getMembersApi();

        const defaultProductPage = await membersApi.productRepository.list({
            limit: 1
        });

        const defaultProduct = defaultProductPage.data[0];

        const result = await rows.reduce(async (resultPromise, row) => {
            const resultAccumulator = await resultPromise;

            const trx = await db.knex.transaction();
            const options = {
                transacting: trx
            };

            try {
                const existingMember = await membersApi.members.get({email: row.email}, {
                    ...options,
                    withRelated: ['labels']
                });
                let member;
                if (existingMember) {
                    const existingLabels = existingMember.related('labels') ? existingMember.related('labels').toJSON() : [];
                    member = await membersApi.members.update({
                        ...row,
                        labels: existingLabels.concat(row.labels)
                    }, {
                        ...options,
                        id: existingMember.id
                    });
                } else {
                    member = await membersApi.members.create(row, options);
                }

                if (row.stripe_customer_id) {
                    await membersApi.members.linkStripeCustomer({
                        customer_id: row.stripe_customer_id,
                        member_id: member.id
                    }, options);
                } else if (row.complimentary_plan) {
                    if (!labsService.isSet('multipleProducts')) {
                        await membersApi.members.setComplimentarySubscription(member, options);
                    } else if (!row.products) {
                        await membersApi.members.update({
                            products: [{id: defaultProduct.id}]
                        }, {
                            ...options,
                            id: member.id
                        });
                    }
                }

                if (labsService.isSet('multipleProducts')) {
                    if (row.products) {
                        await membersApi.members.update({
                            products: row.products
                        }, {
                            ...options,
                            id: member.id
                        });
                    }
                }

                await trx.commit();
                return {
                    ...resultAccumulator,
                    imported: resultAccumulator.imported + 1
                };
            } catch (error) {
                // The model layer can sometimes throw arrays of errors
                const errors = [].concat(error);
                const errorMessage = errors.map(({message}) => message).join(', ');
                await trx.rollback();
                return {
                    ...resultAccumulator,
                    errors: [...resultAccumulator.errors, {
                        ...row,
                        error: errorMessage
                    }]
                };
            }
        }, Promise.resolve({
            imported: 0,
            errors: []
        }));

        return {
            total: result.imported + result.errors.length,
            ...result
        };
    }

    generateCompletionEmail(result, data) {
        const siteUrl = new URL(urlUtils.urlFor('home', null, true));
        const membersUrl = new URL('members', urlUtils.urlFor('admin', null, true));
        if (data.importLabel) {
            membersUrl.searchParams.set('label', data.importLabel.slug);
        }
        return emailTemplate({result, siteUrl, membersUrl, ...data});
    }

    generateErrorCSV(result) {
        const errorsWithFormattedMessages = result.errors.map((row) => {
            const formattedError = row.error
                .replace(
                    'Value in [members.email] cannot be blank.',
                    'Missing email address'
                )
                .replace(
                    'Value in [members.note] exceeds maximum length of 2000 characters.',
                    '"Note" exceeds maximum length of 2000 characters'
                )
                .replace(
                    'Value in [members.subscribed] must be one of true, false, 0 or 1.',
                    'Value in "Subscribed to emails" must be "true" or "false"'
                )
                .replace(
                    'Validation (isEmail) failed for email',
                    'Invalid email address'
                )
                .replace(
                    /No such customer:[^,]*/,
                    'Could not find Stripe customer'
                );

            return {
                ...row,
                error: formattedError
            };
        });
        return membersCSV.unparse(errorsWithFormattedMessages);
    }

    /**
     * Processes CSV file and imports member&label records depending on the size of the imported set
     *
     * @param {Object} config
     * @param {String} config.pathToCSV - path where imported csv with members records is stored
     * @param {Object} config.headerMapping - mapping of CSV headers to member record fields
     * @param {Object} [config.globalLabels] - labels to be applied to whole imported members set
     * @param {Object} config.importLabel -
     * @param {String} config.importLabel.name - label name
     * @param {Object} config.user
     * @param {String} config.user.email - calling user email
     * @param {Object} config.LabelModel - instance of Ghosts Label model
     */
    async process({pathToCSV, headerMapping, globalLabels, importLabel, user, LabelModel}) {
        const job = await this.prepare(pathToCSV, headerMapping, globalLabels);

        if (job.batches <= 500 && !job.metadata.hasStripeData) {
            const result = await this.perform(job.id);
            const importLabelModel = result.imported ? await LabelModel.findOne(importLabel) : null;
            return {
                meta: {
                    stats: {
                        imported: result.imported,
                        invalid: result.errors
                    },
                    import_label: importLabelModel
                }
            };
        } else {
            const emailRecipient = user.email;
            jobsService.addJob({
                job: async () => {
                    const result = await this.perform(job.id);
                    const importLabelModel = result.imported ? await LabelModel.findOne(importLabel) : null;
                    const emailContent = this.generateCompletionEmail(result, {
                        emailRecipient,
                        importLabel: importLabelModel ? importLabelModel.toJSON() : null
                    });
                    const errorCSV = this.generateErrorCSV(result);
                    const emailSubject = result.imported > 0 ? 'Your member import is complete' : 'Your member import was unsuccessful';

                    await ghostMailer.send({
                        to: emailRecipient,
                        subject: emailSubject,
                        html: emailContent,
                        forceTextContent: true,
                        attachments: [{
                            filename: `${importLabel.name} - Errors.csv`,
                            contents: errorCSV,
                            contentType: 'text/csv',
                            contentDisposition: 'attachment'
                        }]
                    });
                },
                offloaded: false
            });

            return {};
        }
    }
};
<commit_msg>Revert "Moved labs utlity to shared"<commit_after>const moment = require('moment-timezone');
const path = require('path');
const fs = require('fs-extra');
const membersCSV = require('@tryghost/members-csv');
const GhostMailer = require('../../mail').GhostMailer;
const urlUtils = require('../../../../shared/url-utils');
const db = require('../../../data/db');
const emailTemplate = require('./email-template');
const jobsService = require('../../jobs');
const labsService = require('../../labs');

const ghostMailer = new GhostMailer();
module.exports = class MembersCSVImporter {
    /**
     * @param {Object} config
     * @param {string} config.storagePath - The path to store CSV's in before importing
     * @param {Object} settingsCache - An instance of the Ghost Settings Cache
     * @param {() => Object} getMembersApi
     */
    constructor(config, settingsCache, getMembersApi) {
        this._storagePath = config.storagePath;
        this._settingsCache = settingsCache;
        this._getMembersApi = getMembersApi;
    }

    /**
     * @typedef {string} JobID
     */

    /**
     * @typedef {Object} Job
     * @prop {string} filename
     * @prop {JobID} id
     * @prop {string} status
     */

    /**
     * Get the Job for a jobCode
     * @param {JobID} jobId
     * @returns {Promise<Job>}
     */
    async getJob(jobId) {
        return {
            id: jobId,
            filename: jobId,
            status: 'pending'
        };
    }

    /**
     * Prepares a CSV file for import
     * - Maps headers based on headerMapping, this allows for a non standard CSV
     *   to be imported, so long as a mapping exists between it and a standard CSV
     * - Stores the CSV to be imported in the storagePath
     * - Creates a MemberImport Job and associated MemberImportBatch's
     *
     * @param {string} inputFilePath - The path to the CSV to prepare
     * @param {Object.<string, string>} headerMapping - An object whos keys are headers in the input CSV and values are the header to replace it with
     * @param {Array<string>} defaultLabels - A list of labels to apply to every member
     *
     * @returns {Promise<{id: JobID, batches: number, metadata: Object.<string, any>}>} - A promise resolving to the id of the MemberImport Job
     */
    async prepare(inputFilePath, headerMapping, defaultLabels) {
        const batchSize = 1;

        const siteTimezone = this._settingsCache.get('timezone');
        const currentTime = moment().tz(siteTimezone).format('YYYY-MM-DD HH:mm:ss.SSS');
        const outputFileName = `Members Import ${currentTime}.csv`;
        const outputFilePath = path.join(this._storagePath, '/', outputFileName);

        const pathExists = await fs.pathExists(outputFilePath);

        if (pathExists) {
            throw new Error('Maybe we need better name generation');
        }

        const rows = await membersCSV.parse(inputFilePath, headerMapping, defaultLabels);
        const numberOfBatches = Math.ceil(rows.length / batchSize);
        const mappedCSV = membersCSV.unparse(rows);

        const hasStripeData = rows.find(function rowHasStripeData(row) {
            return !!row.stripe_customer_id || !!row.complimentary_plan;
        });

        await fs.writeFile(outputFilePath, mappedCSV);

        return {
            id: outputFilePath,
            batches: numberOfBatches,
            metadata: {
                hasStripeData
            }
        };
    }

    /**
     * Performs an import of a CSV file
     *
     * @param {JobID} id - The id of the job to perform
     */
    async perform(id) {
        const job = await this.getJob(id);

        if (job.status === 'complete') {
            throw new Error('Job is already complete');
        }

        const rows = membersCSV.parse(job.filename);

        const membersApi = await this._getMembersApi();

        const defaultProductPage = await membersApi.productRepository.list({
            limit: 1
        });

        const defaultProduct = defaultProductPage.data[0];

        const result = await rows.reduce(async (resultPromise, row) => {
            const resultAccumulator = await resultPromise;

            const trx = await db.knex.transaction();
            const options = {
                transacting: trx
            };

            try {
                const existingMember = await membersApi.members.get({email: row.email}, {
                    ...options,
                    withRelated: ['labels']
                });
                let member;
                if (existingMember) {
                    const existingLabels = existingMember.related('labels') ? existingMember.related('labels').toJSON() : [];
                    member = await membersApi.members.update({
                        ...row,
                        labels: existingLabels.concat(row.labels)
                    }, {
                        ...options,
                        id: existingMember.id
                    });
                } else {
                    member = await membersApi.members.create(row, options);
                }

                if (row.stripe_customer_id) {
                    await membersApi.members.linkStripeCustomer({
                        customer_id: row.stripe_customer_id,
                        member_id: member.id
                    }, options);
                } else if (row.complimentary_plan) {
                    if (!labsService.isSet('multipleProducts')) {
                        await membersApi.members.setComplimentarySubscription(member, options);
                    } else if (!row.products) {
                        await membersApi.members.update({
                            products: [{id: defaultProduct.id}]
                        }, {
                            ...options,
                            id: member.id
                        });
                    }
                }

                if (labsService.isSet('multipleProducts')) {
                    if (row.products) {
                        await membersApi.members.update({
                            products: row.products
                        }, {
                            ...options,
                            id: member.id
                        });
                    }
                }

                await trx.commit();
                return {
                    ...resultAccumulator,
                    imported: resultAccumulator.imported + 1
                };
            } catch (error) {
                // The model layer can sometimes throw arrays of errors
                const errors = [].concat(error);
                const errorMessage = errors.map(({message}) => message).join(', ');
                await trx.rollback();
                return {
                    ...resultAccumulator,
                    errors: [...resultAccumulator.errors, {
                        ...row,
                        error: errorMessage
                    }]
                };
            }
        }, Promise.resolve({
            imported: 0,
            errors: []
        }));

        return {
            total: result.imported + result.errors.length,
            ...result
        };
    }

    generateCompletionEmail(result, data) {
        const siteUrl = new URL(urlUtils.urlFor('home', null, true));
        const membersUrl = new URL('members', urlUtils.urlFor('admin', null, true));
        if (data.importLabel) {
            membersUrl.searchParams.set('label', data.importLabel.slug);
        }
        return emailTemplate({result, siteUrl, membersUrl, ...data});
    }

    generateErrorCSV(result) {
        const errorsWithFormattedMessages = result.errors.map((row) => {
            const formattedError = row.error
                .replace(
                    'Value in [members.email] cannot be blank.',
                    'Missing email address'
                )
                .replace(
                    'Value in [members.note] exceeds maximum length of 2000 characters.',
                    '"Note" exceeds maximum length of 2000 characters'
                )
                .replace(
                    'Value in [members.subscribed] must be one of true, false, 0 or 1.',
                    'Value in "Subscribed to emails" must be "true" or "false"'
                )
                .replace(
                    'Validation (isEmail) failed for email',
                    'Invalid email address'
                )
                .replace(
                    /No such customer:[^,]*/,
                    'Could not find Stripe customer'
                );

            return {
                ...row,
                error: formattedError
            };
        });
        return membersCSV.unparse(errorsWithFormattedMessages);
    }

    /**
     * Processes CSV file and imports member&label records depending on the size of the imported set
     *
     * @param {Object} config
     * @param {String} config.pathToCSV - path where imported csv with members records is stored
     * @param {Object} config.headerMapping - mapping of CSV headers to member record fields
     * @param {Object} [config.globalLabels] - labels to be applied to whole imported members set
     * @param {Object} config.importLabel -
     * @param {String} config.importLabel.name - label name
     * @param {Object} config.user
     * @param {String} config.user.email - calling user email
     * @param {Object} config.LabelModel - instance of Ghosts Label model
     */
    async process({pathToCSV, headerMapping, globalLabels, importLabel, user, LabelModel}) {
        const job = await this.prepare(pathToCSV, headerMapping, globalLabels);

        if (job.batches <= 500 && !job.metadata.hasStripeData) {
            const result = await this.perform(job.id);
            const importLabelModel = result.imported ? await LabelModel.findOne(importLabel) : null;
            return {
                meta: {
                    stats: {
                        imported: result.imported,
                        invalid: result.errors
                    },
                    import_label: importLabelModel
                }
            };
        } else {
            const emailRecipient = user.email;
            jobsService.addJob({
                job: async () => {
                    const result = await this.perform(job.id);
                    const importLabelModel = result.imported ? await LabelModel.findOne(importLabel) : null;
                    const emailContent = this.generateCompletionEmail(result, {
                        emailRecipient,
                        importLabel: importLabelModel ? importLabelModel.toJSON() : null
                    });
                    const errorCSV = this.generateErrorCSV(result);
                    const emailSubject = result.imported > 0 ? 'Your member import is complete' : 'Your member import was unsuccessful';

                    await ghostMailer.send({
                        to: emailRecipient,
                        subject: emailSubject,
                        html: emailContent,
                        forceTextContent: true,
                        attachments: [{
                            filename: `${importLabel.name} - Errors.csv`,
                            contents: errorCSV,
                            contentType: 'text/csv',
                            contentDisposition: 'attachment'
                        }]
                    });
                },
                offloaded: false
            });

            return {};
        }
    }
};
<|endoftext|>


<commit_before>const _ = require('lodash');
const Promise = require('bluebird');
const fs = require('fs-extra');
const join = require('path').join;
const errors = require('@tryghost/errors');

const notAPackageRegex = /^\.|_messages|README.md|node_modules|bower_components/i;
const packageJSONPath = 'package.json';

/**
 * # Package Utils
 *
 * Ghost has / is in the process of gaining support for several different types of sub-packages:
 * - Themes: have always been packages, but we're going to lean more heavily on npm & package.json in future
 * - Adapters: replace fundamental pieces like storage, will become npm modules
 *
 * These utils facilitate loading, reading, managing etc, packages from the file system.
 */
module.exports = class PackageJson {
    constructor(i18n) {
        this.i18n = i18n;
    }

    /**
     * ### Filter Packages
     * Normalizes packages read by read-packages so that the themes module can use them.
     * Iterates over each package and return an array of objects which are simplified representations of the package
     * with 3 properties:
     * - `name`    - the package name
     * - `package` - contents of the package.json or false if there isn't one
     * - `active`  - set to true if this package is active
     * This data structure is used for listings of packages provided over the API and as such
     * deliberately combines multiple sources of information in order to be efficient.
     *
     * TODO: simplify the package.json representation to contain only fields we use
     *
     * @param   {object}            packages    as returned by read-packages
     * @param   {array/string}      active      as read from the settings object
     * @returns {Array}                         of objects with useful info about themes
     */
    filter(packages, active) {
        // turn active into an array if it isn't one, so this function can deal with lists and one-offs
        if (!Array.isArray(active)) {
            active = [active];
        }

        return _.reduce(packages, function (result, pkg, key) {
            let item = {};
            if (!key.match(notAPackageRegex)) {
                item = {
                    name: key,
                    package: pkg['package.json'] || false,
                    active: _.indexOf(active, key) !== -1
                };

                result.push(item);
            }

            return result;
        }, []);
    }

    /**
     * Parse package.json and validate it has
     * all the required fields
     */
    async parse(path) {
        let source;
        let json;

        try {
            source = await fs.readFile(path);
        } catch (readError) {
            const err = new Error(this.i18n.t('errors.utils.parsepackagejson.couldNotReadPackage'));
            err.context = path;
            err.err = readError;

            return Promise.reject(err);
        }

        try {
            json = JSON.parse(source);
        } catch (parseError) {
            const err = new Error(this.i18n.t('errors.utils.parsepackagejson.themeFileIsMalformed'));
            err.context = path;
            err.err = parseError;
            err.help = this.i18n.t('errors.utils.parsepackagejson.willBeRequired', {url: 'https://ghost.org/docs/api/handlebars-themes/'});

            return Promise.reject(err);
        }

        const hasRequiredKeys = json.name && json.version;

        if (!hasRequiredKeys) {
            const err = new Error(this.i18n.t('errors.utils.parsepackagejson.nameOrVersionMissing'));
            err.context = path;
            err.help = this.i18n.t('errors.utils.parsepackagejson.willBeRequired', {url: 'https://ghost.org/docs/api/handlebars-themes/'});

            return Promise.reject(err);
        }

        return json;
    }

    /**
     * Recursively read directory and find the packages in it
     *
     * @returns {object}
     */
    async processPackage(absolutePath, packageName) {
        const pkg = {
            name: packageName,
            path: absolutePath
        };

        try {
            const packageJSON = await this.parse(join(absolutePath, packageJSONPath));
            pkg['package.json'] = packageJSON;
        } catch (err) {
            // ignore invalid package.json for now,
            // because Ghost does not rely/use them at the moment
            // in the future, this .catch() will need to be removed,
            // so that error is thrown on invalid json syntax
            pkg['package.json'] = null;
        }

        return pkg;
    }

    async readPackage(packagePath, packageName) {
        const absolutePath = join(packagePath, packageName);

        try {
            const stat = await fs.stat(absolutePath);
            if (!stat.isDirectory()) {
                return {};
            }

            const pkg = await this.processPackage(absolutePath, packageName);
            const res = {};
            res[packageName] = pkg;
            return res;
        } catch (err) {
            return Promise.reject(new errors.NotFoundError({
                message: 'Package not found',
                err: err,
                help: 'path: ' + packagePath,
                context: 'name: ' + packageName
            }));
        }
    }

    readPackages(packagePath) {
        const self = this;

        return Promise.resolve(fs.readdir(packagePath))
            .filter(function (packageName) {
                // Filter out things which are not packages by regex
                if (packageName.match(notAPackageRegex)) {
                    return;
                }
                // Check the remaining items to ensure they are a directory
                return fs.stat(join(packagePath, packageName)).then(function (stat) {
                    return stat.isDirectory();
                });
            })
            .map(function readPackageJson(packageName) {
                const absolutePath = join(packagePath, packageName);
                return self.processPackage(absolutePath, packageName);
            })
            .then(function (packages) {
                return _.keyBy(packages, 'name');
            });
    }
};
<commit_msg>Added JSDoc comments into package-json lib<commit_after>const _ = require('lodash');
const Promise = require('bluebird');
const fs = require('fs-extra');
const join = require('path').join;
const errors = require('@tryghost/errors');

const notAPackageRegex = /^\.|_messages|README.md|node_modules|bower_components/i;
const packageJSONPath = 'package.json';

/**
 * # Package Utils
 *
 * Ghost has / is in the process of gaining support for several different types of sub-packages:
 * - Themes: have always been packages, but we're going to lean more heavily on npm & package.json in future
 * - Adapters: replace fundamental pieces like storage, will become npm modules
 *
 * These utils facilitate loading, reading, managing etc, packages from the file system.
 */
module.exports = class PackageJson {
    constructor(i18n) {
        this.i18n = i18n;
    }

    /**
     * ### Filter Packages
     * Normalizes packages read by read-packages so that the themes module can use them.
     * Iterates over each package and return an array of objects which are simplified representations of the package
     * with 3 properties:
     * - `name`    - the package name
     * - `package` - contents of the package.json or false if there isn't one
     * - `active`  - set to true if this package is active
     * This data structure is used for listings of packages provided over the API and as such
     * deliberately combines multiple sources of information in order to be efficient.
     *
     * TODO: simplify the package.json representation to contain only fields we use
     *
     * @param   {object}            packages    as returned by read-packages
     * @param   {array/string}      active      as read from the settings object
     * @returns {Array}                         of objects with useful info about themes
     */
    filter(packages, active) {
        // turn active into an array if it isn't one, so this function can deal with lists and one-offs
        if (!Array.isArray(active)) {
            active = [active];
        }

        return _.reduce(packages, function (result, pkg, key) {
            let item = {};
            if (!key.match(notAPackageRegex)) {
                item = {
                    name: key,
                    package: pkg['package.json'] || false,
                    active: _.indexOf(active, key) !== -1
                };

                result.push(item);
            }

            return result;
        }, []);
    }

    /**
     * Parse package.json and validate it has
     * all the required fields
     *
     * @param {string} path
     */
    async parse(path) {
        let source;
        let json;

        try {
            source = await fs.readFile(path);
        } catch (readError) {
            const err = new Error(this.i18n.t('errors.utils.parsepackagejson.couldNotReadPackage'));
            err.context = path;
            err.err = readError;

            return Promise.reject(err);
        }

        try {
            json = JSON.parse(source);
        } catch (parseError) {
            const err = new Error(this.i18n.t('errors.utils.parsepackagejson.themeFileIsMalformed'));
            err.context = path;
            err.err = parseError;
            err.help = this.i18n.t('errors.utils.parsepackagejson.willBeRequired', {url: 'https://ghost.org/docs/api/handlebars-themes/'});

            return Promise.reject(err);
        }

        const hasRequiredKeys = json.name && json.version;

        if (!hasRequiredKeys) {
            const err = new Error(this.i18n.t('errors.utils.parsepackagejson.nameOrVersionMissing'));
            err.context = path;
            err.help = this.i18n.t('errors.utils.parsepackagejson.willBeRequired', {url: 'https://ghost.org/docs/api/handlebars-themes/'});

            return Promise.reject(err);
        }

        return json;
    }

    /**
     * Recursively read directory and find the packages in it
     *
     * @param {string} absolutePath
     * @param {string} packageName
     * @returns {object}
     */
    async processPackage(absolutePath, packageName) {
        const pkg = {
            name: packageName,
            path: absolutePath
        };

        try {
            const packageJSON = await this.parse(join(absolutePath, packageJSONPath));
            pkg['package.json'] = packageJSON;
        } catch (err) {
            // ignore invalid package.json for now,
            // because Ghost does not rely/use them at the moment
            // in the future, this .catch() will need to be removed,
            // so that error is thrown on invalid json syntax
            pkg['package.json'] = null;
        }

        return pkg;
    }

    /**
     * @param {string} packagePath
     * @param {string} packageName
     */
    async readPackage(packagePath, packageName) {
        const absolutePath = join(packagePath, packageName);

        try {
            const stat = await fs.stat(absolutePath);
            if (!stat.isDirectory()) {
                return {};
            }

            const pkg = await this.processPackage(absolutePath, packageName);
            const res = {};
            res[packageName] = pkg;
            return res;
        } catch (err) {
            return Promise.reject(new errors.NotFoundError({
                message: 'Package not found',
                err: err,
                help: 'path: ' + packagePath,
                context: 'name: ' + packageName
            }));
        }
    }

    /**
     * @param {string} packagePath
     */
    readPackages(packagePath) {
        const self = this;

        return Promise.resolve(fs.readdir(packagePath))
            .filter(function (packageName) {
                // Filter out things which are not packages by regex
                if (packageName.match(notAPackageRegex)) {
                    return;
                }
                // Check the remaining items to ensure they are a directory
                return fs.stat(join(packagePath, packageName)).then(function (stat) {
                    return stat.isDirectory();
                });
            })
            .map(function readPackageJson(packageName) {
                const absolutePath = join(packagePath, packageName);
                return self.processPackage(absolutePath, packageName);
            })
            .then(function (packages) {
                return _.keyBy(packages, 'name');
            });
    }
};
<|endoftext|>


<commit_before>/**
 * @fileOverview Ensures that no imported module imports the linted module.
 * @author Ben Mosher
 */

import Exports from '../ExportMap'
import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'
import docsUrl from '../docsUrl'

// todo: cache cycles / deep relationships for faster repeat evaluation
module.exports = {
  meta: {
    docs: { url: docsUrl('no-cycle') },
    schema: [makeOptionsSchema({
      maxDepth:{
        description: 'maximum dependency depth to traverse',
        type: 'integer',
        minimum: 1,
      },
    })],
  },

  create: function (context) {
    const myPath = context.getFilename()
    if (myPath === '<text>') return {} // can't cycle-check a non-file

    const options = context.options[0] || {}
    const maxDepth = options.maxDepth || Infinity

    function checkSourceValue(sourceNode, importer) {
      const imported = Exports.get(sourceNode.value, context)

      if (sourceNode.parent.importKind === 'type') {
        return // no Flow import resolution
      }

      if (imported == null) {
        return  // no-unresolved territory
      }

      if (imported.path === myPath) {
        return  // no-self-import territory
      }

      const untraversed = [{mget: () => imported, route:[]}]
      const traversed = new Set()
      function detectCycle({mget, route}) {
        const m = mget()
        if (m == null) return
        if (traversed.has(m.path)) return
        traversed.add(m.path)

        for (let [path, { getter, source }] of m.imports) {
          if (path === myPath) return true
          if (traversed.has(path)) continue
          if (route.length + 1 < maxDepth) {
            untraversed.push({
              mget: getter,
              route: route.concat(source),
            })
          }
        }
      }

      while (untraversed.length > 0) {
        const next = untraversed.shift() // bfs!
        if (detectCycle(next)) {
          const message = (next.route.length > 0
            ? `Dependency cycle via ${routeString(next.route)}`
            : 'Dependency cycle detected.')
          context.report(importer, message)
          return
        }
      }
    }

    return moduleVisitor(checkSourceValue, context.options[0])
  },
}

function routeString(route) {
  return route.map(s => `${s.value}:${s.loc.start.line}`).join('=>')
}
<commit_msg>fix: handly possible undefined parent<commit_after>/**
 * @fileOverview Ensures that no imported module imports the linted module.
 * @author Ben Mosher
 */

import Exports from '../ExportMap'
import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'
import docsUrl from '../docsUrl'

// todo: cache cycles / deep relationships for faster repeat evaluation
module.exports = {
  meta: {
    docs: { url: docsUrl('no-cycle') },
    schema: [makeOptionsSchema({
      maxDepth:{
        description: 'maximum dependency depth to traverse',
        type: 'integer',
        minimum: 1,
      },
    })],
  },

  create: function (context) {
    const myPath = context.getFilename()
    if (myPath === '<text>') return {} // can't cycle-check a non-file

    const options = context.options[0] || {}
    const maxDepth = options.maxDepth || Infinity

    function checkSourceValue(sourceNode, importer) {
      const imported = Exports.get(sourceNode.value, context)

      if (sourceNode.parent && sourceNode.parent.importKind === 'type') {
        return // no Flow import resolution
      }

      if (imported == null) {
        return  // no-unresolved territory
      }

      if (imported.path === myPath) {
        return  // no-self-import territory
      }

      const untraversed = [{mget: () => imported, route:[]}]
      const traversed = new Set()
      function detectCycle({mget, route}) {
        const m = mget()
        if (m == null) return
        if (traversed.has(m.path)) return
        traversed.add(m.path)

        for (let [path, { getter, source }] of m.imports) {
          if (path === myPath) return true
          if (traversed.has(path)) continue
          if (route.length + 1 < maxDepth) {
            untraversed.push({
              mget: getter,
              route: route.concat(source),
            })
          }
        }
      }

      while (untraversed.length > 0) {
        const next = untraversed.shift() // bfs!
        if (detectCycle(next)) {
          const message = (next.route.length > 0
            ? `Dependency cycle via ${routeString(next.route)}`
            : 'Dependency cycle detected.')
          context.report(importer, message)
          return
        }
      }
    }

    return moduleVisitor(checkSourceValue, context.options[0])
  },
}

function routeString(route) {
  return route.map(s => `${s.value}:${s.loc.start.line}`).join('=>')
}
<|endoftext|>


<commit_before>
import enforceLimits from '../LimitEnforcer';
import { Vertex } from '../Geometry.js'

// Transform actions
export const addShape = ( shape ) => {
  return {
    type: 'ADD_SHAPE',
    shape: shape,
  };
}

export const setShape = ( shape ) => {
  return {
    type: 'SET_SHAPE',
    value: shape,
  };
}

export const setShapeSize = ( size ) => {
  return {
    type: 'SET_SHAPE_SIZE',
    value: size,
  };
}

export const setLoops = ( loops ) => {
  return {
    type: 'SET_LOOPS',
    value: loops,
  };
}

export const toggleSpin = ( ) => {
  return {
    type: 'TOGGLE_SPIN',
  };
}

export const setSpin = ( value ) => {
  return {
    type: 'SET_SPIN',
    value: value,
  };
}

export const toggleGrow = ( ) => {
  return {
    type: 'TOGGLE_GROW',
  };
}

export const setGrow = ( value ) => {
  return {
    type: 'SET_GROW',
    value: value,
  };
}

// Machine actions
export const setMachineMinX = ( value ) => {
  return {
    type: 'SET_MIN_X',
    value: value,
  };
}

export const setMachineMaxX = ( value ) => {
  return {
    type: 'SET_MAX_X',
    value: value,
  };
}

export const setMachineMinY = ( value ) => {
  return {
    type: 'SET_MIN_Y',
    value: value,
  };
}

export const setMachineMaxY = ( value ) => {
  return {
    type: 'SET_MAX_Y',
    value: value,
  };
}

// GCode Actions
export const setGCodePre = ( text ) => {
  return {
    type: 'SET_GCODE_PRE',
    value: text,
  };
}

export const setGCodePost = ( text ) => {
  return {
    type: 'SET_GCODE_POST',
    value: text,
  };
}

export const toggleGCodeReverse = ( ) => {
  return {
    type: 'TOGGLE_GCODE_REVERSE',
  };
}

export const setShowGCode = ( on ) => {
  return {
    type: 'SET_SHOW_GCODE',
    value: on,
  };
}

// Vertices Actions
export const clearVertices = ( ) => {
  return {
    type: 'CLEAR_VERTICES',
  };
}

export const setTurtleVertices = ( vertices ) => {
  return {
    type: 'SET_TURTLE_VERTICES',
    vertices: vertices,
  };
}

export const addVertex = ( vertex ) => {
  return {
    type: 'ADD_VERTEX',
    value: vertex,
  };
}

export const chooseInput = ( input ) => {
  return {
    type: 'CHOOSE_INPUT',
    value: input,
  };
}

const defaultState = {
  // Transform settings
  shapes: [],
  currentShape: undefined,
  startingSize: 10.0,
  numLoops: 10,
  spinEnabled: false,
  spinValue: 2,
  growEnabled: false,
  growValue: 100,

  turtleVertices: [],

  // Vertices
  vertices: [],
  input: 0,

  // Machine settings
  min_x: 0,
  max_x: 500,
  min_y: 0,
  max_y: 500,

  // GCode settings
  gcodePre: '',
  gcodePost: '',
  gcodeReverse: false,
  showGCode: false,
}

// Vertex functions
const setVerticesHelper = (state, vertices) => {
  vertices = enforceLimits(vertices,
                           (state.max_x - state.min_x)/2.0,
                           (state.max_y - state.min_y)/2.0
                           );
  state.vertices = vertices;
}

// Transform funtions
function rotate (vertex, angle_deg) {
  var angle = Math.PI / 180.0 * angle_deg;
  return Vertex(
           vertex.x * Math.cos(angle) - vertex.y * Math.sin(angle),
           vertex.x * Math.sin(angle) + vertex.y * Math.cos(angle),
           vertex.f);
}

function scale (vertex, scale_perc) {
  var scale = scale_perc / 100.0;
  return {
    x: vertex.x * scale,
    y: vertex.y * scale,
    f: vertex.f,
  }
}

const transform = (state, vertex, loop_index) => {
  var transformed_vertex = vertex
  if (state.spinEnabled)
  {
    transformed_vertex = rotate(transformed_vertex, state.spinValue * loop_index);
  }
  if (state.growEnabled)
  {
    transformed_vertex = scale(transformed_vertex, 100.0 + (state.growValue * loop_index));
  }
  return transformed_vertex;
}

const findShape = (shapes, name) => {
  for (let i=0; i<shapes.length; i++) {
    if (name === shapes[i].name) {
      return shapes[i];
    }
  }
  return null;
}

const transformShapes = (state) => {
  const shape = findShape(state.shapes, state.currentShape);
  var input = []
  if (shape) {
    input = shape.vertices.map( (vertex) => {
      return scale(vertex, 100.0 * state.startingSize);
    });
  }

  var num_loops = state.numLoops;
  var outputVertices = []
  for (var i=0; i<num_loops; i++) {
    for (var j=0; j<input.length; j++) {
      outputVertices.push(transform(state, input[j], i))
    }
  }
  setVerticesHelper(state, outputVertices);
  return state;
};

const computeInput = (state) => {
  if (state.input === 0) {
    return transformShapes(state);
  } else {
    let newState = {
      ...state,
    }
    setVerticesHelper(newState, state.turtleVertices);
    return Object.assign({}, state, {
      vertices: newState.vertices
    });
  }
}

const reducer  = (state = defaultState, action) => {
  switch (action.type) {

    // Transform actions
    case 'ADD_SHAPE':
      return computeInput({...state,
        shapes: [
          ...state.shapes,
          action.shape
        ],
      });

    case 'SET_SHAPE':
      return computeInput({...state,
        currentShape: action.value,
      });

    case 'SET_SHAPE_SIZE':
      return computeInput({...state,
        startingSize: action.value,
      });

    case 'SET_LOOPS':
      return computeInput({...state,
        numLoops: action.value,
      });

    case 'TOGGLE_SPIN':
      return computeInput({...state,
        spinEnabled: !state.spinEnabled,
      });

    case 'TOGGLE_GROW':
      return computeInput({...state,
        growEnabled: !state.growEnabled,
      });

    case 'SET_SPIN':
      return computeInput({...state,
        spinValue: action.value,
      });

    case 'SET_GROW':
      return computeInput({...state,
        growValue: action.value,
      });

    // Vertex actions
    case 'CLEAR_VERTICES':
      return Object.assign({}, state, {
        vertices: [],
      });
    case 'SET_TURTLE_VERTICES':
      return computeInput({...state,
        turtleVertices: action.vertices,
      });
    case 'ADD_VERTEX': {
      let newState = {
        ...state,
      }
      setVerticesHelper(newState, [
          ...state.vertices,
          action.value,
        ]);
      return Object.assign({}, state, {
        vertices: newState.vertices
      });
    }
    case 'CHOOSE_INPUT':
      return computeInput({...state,
        input: action.value,
      });

    // Machine Settings
    case 'SET_MIN_X':
      return {...state,
        min_x: action.value,
      };
    case 'SET_MAX_X':
      return {...state,
        max_x: action.value,
      };
    case 'SET_MIN_Y':
      return {...state,
        min_y: action.value,
      };
    case 'SET_MAX_Y':
      return {...state,
        max_y: action.value,
      };

    // GCode Settings
    case 'SET_GCODE_PRE':
      return {...state,
        gcodePre: action.value,
      };
    case 'SET_GCODE_POST':
      return {...state,
        gcodePost: action.value,
      };
    case 'TOGGLE_GCODE_REVERSE':
      return computeInput({...state,
        gcodeReverse: !state.gcodeReverse,
      });
    case 'SET_SHOW_GCODE':
      return {...state,
        showGCode: action.value,
      };

    case '@@redux/INIT':
      return state;

    default:
      console.log('unknown action.type: ' + action.type);
      return state;
  }
};

export default reducer;
<commit_msg>scale and rotate smoothly-ish.<commit_after>
import enforceLimits from '../LimitEnforcer';
import { Vertex } from '../Geometry.js'

// Transform actions
export const addShape = ( shape ) => {
  return {
    type: 'ADD_SHAPE',
    shape: shape,
  };
}

export const setShape = ( shape ) => {
  return {
    type: 'SET_SHAPE',
    value: shape,
  };
}

export const setShapeSize = ( size ) => {
  return {
    type: 'SET_SHAPE_SIZE',
    value: size,
  };
}

export const setLoops = ( loops ) => {
  return {
    type: 'SET_LOOPS',
    value: loops,
  };
}

export const toggleSpin = ( ) => {
  return {
    type: 'TOGGLE_SPIN',
  };
}

export const setSpin = ( value ) => {
  return {
    type: 'SET_SPIN',
    value: value,
  };
}

export const toggleGrow = ( ) => {
  return {
    type: 'TOGGLE_GROW',
  };
}

export const setGrow = ( value ) => {
  return {
    type: 'SET_GROW',
    value: value,
  };
}

// Machine actions
export const setMachineMinX = ( value ) => {
  return {
    type: 'SET_MIN_X',
    value: value,
  };
}

export const setMachineMaxX = ( value ) => {
  return {
    type: 'SET_MAX_X',
    value: value,
  };
}

export const setMachineMinY = ( value ) => {
  return {
    type: 'SET_MIN_Y',
    value: value,
  };
}

export const setMachineMaxY = ( value ) => {
  return {
    type: 'SET_MAX_Y',
    value: value,
  };
}

// GCode Actions
export const setGCodePre = ( text ) => {
  return {
    type: 'SET_GCODE_PRE',
    value: text,
  };
}

export const setGCodePost = ( text ) => {
  return {
    type: 'SET_GCODE_POST',
    value: text,
  };
}

export const toggleGCodeReverse = ( ) => {
  return {
    type: 'TOGGLE_GCODE_REVERSE',
  };
}

export const setShowGCode = ( on ) => {
  return {
    type: 'SET_SHOW_GCODE',
    value: on,
  };
}

// Vertices Actions
export const clearVertices = ( ) => {
  return {
    type: 'CLEAR_VERTICES',
  };
}

export const setTurtleVertices = ( vertices ) => {
  return {
    type: 'SET_TURTLE_VERTICES',
    vertices: vertices,
  };
}

export const addVertex = ( vertex ) => {
  return {
    type: 'ADD_VERTEX',
    value: vertex,
  };
}

export const chooseInput = ( input ) => {
  return {
    type: 'CHOOSE_INPUT',
    value: input,
  };
}

const defaultState = {
  // Transform settings
  shapes: [],
  currentShape: undefined,
  startingSize: 10.0,
  numLoops: 10,
  spinEnabled: false,
  spinValue: 2,
  growEnabled: false,
  growValue: 100,

  turtleVertices: [],

  // Vertices
  vertices: [],
  input: 0,

  // Machine settings
  min_x: 0,
  max_x: 500,
  min_y: 0,
  max_y: 500,

  // GCode settings
  gcodePre: '',
  gcodePost: '',
  gcodeReverse: false,
  showGCode: false,
}

// Vertex functions
const setVerticesHelper = (state, vertices) => {
  vertices = enforceLimits(vertices,
                           (state.max_x - state.min_x)/2.0,
                           (state.max_y - state.min_y)/2.0
                           );
  state.vertices = vertices;
}

// Transform funtions
function rotate (vertex, angle_deg) {
  var angle = Math.PI / 180.0 * angle_deg;
  return Vertex(
           vertex.x * Math.cos(angle) - vertex.y * Math.sin(angle),
           vertex.x * Math.sin(angle) + vertex.y * Math.cos(angle),
           vertex.f);
}

function scale (vertex, scale_perc) {
  var scale = scale_perc / 100.0;
  return {
    x: vertex.x * scale,
    y: vertex.y * scale,
    f: vertex.f,
  }
}

const transform = (state, vertex, loop_index) => {
  var transformed_vertex = vertex
  if (state.spinEnabled)
  {
    transformed_vertex = rotate(transformed_vertex, state.spinValue * loop_index);
  }
  if (state.growEnabled)
  {
    transformed_vertex = scale(transformed_vertex, 100.0 + (state.growValue * loop_index));
  }
  return transformed_vertex;
}

const findShape = (shapes, name) => {
  for (let i=0; i<shapes.length; i++) {
    if (name === shapes[i].name) {
      return shapes[i];
    }
  }
  return null;
}

const transformShapes = (state) => {
  const shape = findShape(state.shapes, state.currentShape);
  var input = []
  if (shape) {
    input = shape.vertices.map( (vertex) => {
      return scale(vertex, 100.0 * state.startingSize);
    });
  }

  var num_loops = state.numLoops;
  var outputVertices = []
  for (var i=0; i<num_loops; i++) {
    for (var j=0; j<input.length; j++) {
      let fraction = j/input.length;
      outputVertices.push(transform(state, input[j], i+fraction))
    }
  }
  setVerticesHelper(state, outputVertices);
  return state;
};

const computeInput = (state) => {
  if (state.input === 0) {
    return transformShapes(state);
  } else {
    let newState = {
      ...state,
    }
    setVerticesHelper(newState, state.turtleVertices);
    return Object.assign({}, state, {
      vertices: newState.vertices
    });
  }
}

const reducer  = (state = defaultState, action) => {
  switch (action.type) {

    // Transform actions
    case 'ADD_SHAPE':
      return computeInput({...state,
        shapes: [
          ...state.shapes,
          action.shape
        ],
      });

    case 'SET_SHAPE':
      return computeInput({...state,
        currentShape: action.value,
      });

    case 'SET_SHAPE_SIZE':
      return computeInput({...state,
        startingSize: action.value,
      });

    case 'SET_LOOPS':
      return computeInput({...state,
        numLoops: action.value,
      });

    case 'TOGGLE_SPIN':
      return computeInput({...state,
        spinEnabled: !state.spinEnabled,
      });

    case 'TOGGLE_GROW':
      return computeInput({...state,
        growEnabled: !state.growEnabled,
      });

    case 'SET_SPIN':
      return computeInput({...state,
        spinValue: action.value,
      });

    case 'SET_GROW':
      return computeInput({...state,
        growValue: action.value,
      });

    // Vertex actions
    case 'CLEAR_VERTICES':
      return Object.assign({}, state, {
        vertices: [],
      });
    case 'SET_TURTLE_VERTICES':
      return computeInput({...state,
        turtleVertices: action.vertices,
      });
    case 'ADD_VERTEX': {
      let newState = {
        ...state,
      }
      setVerticesHelper(newState, [
          ...state.vertices,
          action.value,
        ]);
      return Object.assign({}, state, {
        vertices: newState.vertices
      });
    }
    case 'CHOOSE_INPUT':
      return computeInput({...state,
        input: action.value,
      });

    // Machine Settings
    case 'SET_MIN_X':
      return {...state,
        min_x: action.value,
      };
    case 'SET_MAX_X':
      return {...state,
        max_x: action.value,
      };
    case 'SET_MIN_Y':
      return {...state,
        min_y: action.value,
      };
    case 'SET_MAX_Y':
      return {...state,
        max_y: action.value,
      };

    // GCode Settings
    case 'SET_GCODE_PRE':
      return {...state,
        gcodePre: action.value,
      };
    case 'SET_GCODE_POST':
      return {...state,
        gcodePost: action.value,
      };
    case 'TOGGLE_GCODE_REVERSE':
      return computeInput({...state,
        gcodeReverse: !state.gcodeReverse,
      });
    case 'SET_SHOW_GCODE':
      return {...state,
        showGCode: action.value,
      };

    case '@@redux/INIT':
      return state;

    default:
      console.log('unknown action.type: ' + action.type);
      return state;
  }
};

export default reducer;
<|endoftext|>


<commit_before>// LICENSE : MIT
"use strict";
/*
    - engine treat multiple files.
    - engine is wrapped of runner.
 */
import Promise from "bluebird"
import TestRunner from "./reftest-runner"
import defaultOptions from "./options/default-options"
import {EventEmitter} from "events"
import assert from "assert"
import deepmerge from "deepmerge"

var debug = require("debug")("reftest-runner:engine");
export default class ReftestEngine {
    constructor(options) {
        this.options = deepmerge(defaultOptions, options);
        this.serverEmitter = new EventEmitter();
        this.serverEmitter.setMaxListeners(100);
        process.setMaxListeners(100);
        debug("options : %o", this.options);
    }

    _setupServer() {
        if (!this.options.server || !this.options.server.script) {
            return Promise.resolve();
        }
        if (typeof this.options.server.script !== "function") {
            throw new Error("options.server.script should be function.");
        }
        return new Promise((resolve, reject)=> {
            this.serverEmitter.once("connection", ()=>{
                this.serverEmitter.removeAllListeners("error");
                resolve();
            });
            this.serverEmitter.once("error", (error)=>{
                this.serverEmitter.removeAllListeners("connection");
                reject(error);
            });
            var severImplement = this.options.server.script;
            severImplement(this.serverEmitter, this.options);
            assert(this.serverEmitter.listeners("close").length > 0, `${severImplement.name} should implement emitter.on("close", function({ ... })`);
        });
    }

    /**
     *
     * @param {IReftestEngineTarget} testTargetList
     * @private
     */
    _resolveFileList(testTargetList) {
        return testTargetList.map();
    }

    _closeServer() {
        this.serverEmitter.emit("close");
        this.serverEmitter.removeAllListeners();
    }

    _computeResultOperator(result, compareOperator) {
        // assign
        result.compareOperator = compareOperator;
        // reverser result
        if (compareOperator === "!=") {
            result.passed = !result.passed;
        }
        return result;
    }

    getTargetListFromFile(reftestListFilePath) {
        var parse = require("reftest-list-parser").parse;
        var fs = require("fs");
        var path = require("path");
        var data = fs.readFileSync(reftestListFilePath, "utf-8");
        var list = parse(data);
        var baseDir = path.dirname(reftestListFilePath);
        // resolve path from `rootDir`
        return list.map((item)=> {
            item.targetA = path.resolve(baseDir, item.targetA);
            item.targetB = path.resolve(baseDir, item.targetB);
            return item;
        })

    }

    /**
     * run test and return result promise which is filled with array of result.
     * @param {IReftestEngineTarget[]} testTargetList the targetList is defined URL and compareOperator for each test.
     * @returns {Promise.<IReftestCompareResult[]>}
     */
    runTests(testTargetList) {
        var close = (result)=> {
            if (!this.options.useExternalServer){
                this._closeServer();
            }
            if (result instanceof Error) {
                return Promise.reject(result);
            }
            return result;
        };

        if (this.options.useExternalServer){
            debug("TargetList: %o", testTargetList);
            return this._runTests(testTargetList)
                .then(close, close);
        } else {
            var resolve = require("./utils/option-utils").resolveTargetPath;
            var resolvedTargetList = testTargetList.map((target)=> {
                return resolve(target, this.options);
            });
            debug("TargetList: %o", testTargetList);
            return this._setupServer()
                .then(this._runTests.bind(this, resolvedTargetList))
                .then(close, close);
        }
    }

    /**
     *
     * @param {IReftestEngineTarget[]} testTargetList
     * @returns {Promise.<T>}
     * @private
     */
    _runTests(testTargetList) {
        var runner = new TestRunner(this.options);
        var isRunningTarget = require("./utils/option-utils").isRunningTarget;
        var taskPromiseList = testTargetList.map(function (testTarget) {
            if (isRunningTarget(testTarget)) {
                // IReftestForRunningTarget
                return runner.runTestWithTargets(testTarget.targetA, testTarget.targetB)
            } else {
                return runner.runTest(testTarget.targetA, testTarget.targetB)
            }
        });
        return Promise.all(taskPromiseList).then((resultList) => {
            return resultList.map((result, index) => {
                var targetItem = testTargetList[index];
                return this._computeResultOperator(result, targetItem.compareOperator);
            })
        });
    }

    getReporter(reporterName) {
        // TODO: implement other reporter
        return require("./reporter/tap-formatter");
    }
}<commit_msg>refactor debug<commit_after>// LICENSE : MIT
"use strict";
/*
    - engine treat multiple files.
    - engine is wrapped of runner.
 */
import Promise from "bluebird"
import TestRunner from "./reftest-runner"
import defaultOptions from "./options/default-options"
import {EventEmitter} from "events"
import assert from "assert"
import deepmerge from "deepmerge"

var debug = require("debug")("reftest-runner:engine");
export default class ReftestEngine {
    constructor(options) {
        this.options = deepmerge(defaultOptions, options);
        this.serverEmitter = new EventEmitter();
        this.serverEmitter.setMaxListeners(100);
        process.setMaxListeners(100);
        debug("options : %o", this.options);
    }

    _setupServer() {
        if (!this.options.server || !this.options.server.script) {
            return Promise.resolve();
        }
        if (typeof this.options.server.script !== "function") {
            throw new Error("options.server.script should be function.");
        }
        return new Promise((resolve, reject)=> {
            this.serverEmitter.once("connection", ()=>{
                this.serverEmitter.removeAllListeners("error");
                resolve();
            });
            this.serverEmitter.once("error", (error)=>{
                this.serverEmitter.removeAllListeners("connection");
                reject(error);
            });
            var severImplement = this.options.server.script;
            severImplement(this.serverEmitter, this.options);
            assert(this.serverEmitter.listeners("close").length > 0, `${severImplement.name} should implement emitter.on("close", function({ ... })`);
        });
    }

    /**
     *
     * @param {IReftestEngineTarget} testTargetList
     * @private
     */
    _resolveFileList(testTargetList) {
        return testTargetList.map();
    }

    _closeServer() {
        this.serverEmitter.emit("close");
        this.serverEmitter.removeAllListeners();
    }

    _computeResultOperator(result, compareOperator) {
        // assign
        result.compareOperator = compareOperator;
        // reverser result
        if (compareOperator === "!=") {
            result.passed = !result.passed;
        }
        return result;
    }

    getTargetListFromFile(reftestListFilePath) {
        var parse = require("reftest-list-parser").parse;
        var fs = require("fs");
        var path = require("path");
        var data = fs.readFileSync(reftestListFilePath, "utf-8");
        var list = parse(data);
        var baseDir = path.dirname(reftestListFilePath);
        // resolve path from `rootDir`
        return list.map((item)=> {
            item.targetA = path.resolve(baseDir, item.targetA);
            item.targetB = path.resolve(baseDir, item.targetB);
            return item;
        })

    }

    /**
     * run test and return result promise which is filled with array of result.
     * @param {IReftestEngineTarget[]} testTargetList the targetList is defined URL and compareOperator for each test.
     * @returns {Promise.<IReftestCompareResult[]>}
     */
    runTests(testTargetList) {
        var close = (result)=> {
            if (!this.options.useExternalServer){
                this._closeServer();
            }
            if (result instanceof Error) {
                return Promise.reject(result);
            }
            return result;
        };

        debug("TargetList: %o", testTargetList);
        if (this.options.useExternalServer){
            return this._runTests(testTargetList)
                .then(close, close);
        } else {
            var resolve = require("./utils/option-utils").resolveTargetPath;
            var resolvedTargetList = testTargetList.map((target)=> {
                return resolve(target, this.options);
            });
            return this._setupServer()
                .then(this._runTests.bind(this, resolvedTargetList))
                .then(close, close);
        }
    }

    /**
     *
     * @param {IReftestEngineTarget[]} testTargetList
     * @returns {Promise.<T>}
     * @private
     */
    _runTests(testTargetList) {
        var runner = new TestRunner(this.options);
        var isRunningTarget = require("./utils/option-utils").isRunningTarget;
        var taskPromiseList = testTargetList.map(function (testTarget) {
            if (isRunningTarget(testTarget)) {
                // IReftestForRunningTarget
                return runner.runTestWithTargets(testTarget.targetA, testTarget.targetB)
            } else {
                return runner.runTest(testTarget.targetA, testTarget.targetB)
            }
        });
        return Promise.all(taskPromiseList).then((resultList) => {
            return resultList.map((result, index) => {
                var targetItem = testTargetList[index];
                return this._computeResultOperator(result, targetItem.compareOperator);
            })
        });
    }

    getReporter(reporterName) {
        // TODO: implement other reporter
        return require("./reporter/tap-formatter");
    }
}<|endoftext|>


<commit_before>const functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
//  response.send("Hello from Firebase!");
// });
<commit_msg>Send notifications through firebase add<commit_after>const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.sendNotification = functions.database.ref('Messages/{roomId}/{messageId}').onCreate(event => {
    console.log('event.data: ', event.data);
    console.log('event.data.val() : ', event.data.val());

    const dataVal = event.data.val();
    if (!dataVal) {
        return console.log('Message data null! ');
    }

    const roomId = event.params.roomId; //이벤트가 발생한 방ID
    const sendMessageUserId = dataVal.uid; //메세지 발송자 ID
    const sendUserName = dataVal.userName; //메세지 발송자 이름
    const sendMsg = dataVal.message; //메세지
    const sendProfile = dataVal.profileImg ? dataVal.profileImg : ''; // 프로필 이미지
    const promiseRoomUserList = admin.database().ref(`RoomUsers/${roomId}`).once('value'); // 채팅방 유저리스트
    const promiseUsersConnection = admin.database().ref('UsersConnection').orderByChild('connection').equalTo(true).once('value'); // 접속자 데이터

    return Promise.all([promiseRoomUserList, promiseUsersConnection]).then(results => {
        const roomUsersSnapShot = results[0];
        const usersConnectionSnapShot = results[1];
        const arrRoomUserList =[];
        const arrConnectionUserList = [];

        if(roomUsersSnapShot.hasChildren()){
            roomUsersSnapShot.forEach(snapshot => {
                arrRoomUserList.push(snapshot.key);
            })
        }else{
            return console.log('RoomUserlist is null')
        }

        if(usersConnectionSnapShot.hasChildren()){
            usersConnectionSnapShot.forEach(snapshot => {
                const value  = snapshot.val();
                if(value){
                    arrConnectionUserList.push(snapshot.key);
                }
            })
        }else{
            return console.log('UserConnections Data가 없습니다');
        }

        const arrTargetUserList = arrRoomUserList.filter(item => {
            return arrConnectionUserList.indexOf(item) === -1;
        });

        console.log('arrTargetUserList : ',arrTargetUserList);
        const arrTargetUserListLength = arrTargetUserList.length;
        for(let i=0; i < arrTargetUserListLength; i++){
            console.log(`FcmId/${arrTargetUserList[i]}`);
            admin.database().ref(`FcmId/${arrTargetUserList[i]}`).once('value',fcmSnapshot => {
                console.log('FCM Token : ', fcmSnapshot.val());
                const token = fcmSnapshot.val();
                if(token){
                    const payload = {
                        notification: {
                            title: sendUserName,
                            body: sendMsg,
                            click_action :`http://nypcio-chat.firebaseapp.com/?roomId=${roomId}`,
                            icon: sendProfile
                        }
                    };
                    admin.messaging().sendToDevice(token, payload).then(response => {
                        response.results.forEach((result, index) => {
                            const error = result.error;
                            if (error) {
                                console.error('FCM 실패 :', error.code);
                            }else{
                                console.log('FCM 성공');
                            }
                        });
                    });
                }

            });
        }

    });
});<|endoftext|>


<commit_before>'use strict'

var calculator = require('./calculator')
var coloriser = require('./coloriser')
var deferAndDropExcess = require('./deferAndDropExcess')
var displayBlockStore = require('./displayBlockStore')
var scheduler = require('./scheduler')

module.exports = function Renderer(canvas) {
  var self = this

  self.depth = 500
  self.canvas = canvas
  self.ctx = canvas.getContext('2d')
  self.center = {x: -0.75, y: 0}
  self.width = 8

  self.coloriser = new coloriser()

  self.calculator = calculator()
  self.displayBlockStore = null

  self.pixelRatio = window.devicePixelRatio || 1

  self.scheduler = scheduler(20)

  self.updateSize = function() {
    canvas.style.width = canvas.parentNode.clientWidth + 'px'
    canvas.style.height = canvas.parentNode.clientHeight + 'px'

    canvas.width = self.pixelRatio * canvas.parentNode.clientWidth
    canvas.height = self.pixelRatio * canvas.parentNode.clientHeight
  }

  ;(function() {
    var lastMousedown = {x: 0, y: 0}

    self.updateSize()

    canvas.parentNode.addEventListener('keydown', function(evt) {
      if (evt.keyCode === 67) {
        self.coloriser.randomise(!evt.shiftKey ? 1 : -1)
        self.drawBlocksCached() // TODO: rename to redrawCurrentBlocks?
      }

      if (evt.keyCode === 187 || evt.keyCode === 189) {
        if (evt.shiftKey) {
          self.coloriser.shift(0.05 * (188 - evt.keyCode))
        } else {
          self.coloriser.multiplySpeed(Math.exp(0.05 * (188 - evt.keyCode)))
        }

        self.drawBlocksCached()
      }
    })

    canvas.addEventListener('mousedown', function(e) {
      lastMousedown.x = e.clientX
      lastMousedown.y = e.clientY
    })

    canvas.addEventListener('mouseup', function(e) {
      var diff = {x: e.clientX - lastMousedown.x, y: e.clientY - lastMousedown.y}
      var pixelSize = self.width / canvas.width
      var aspectRatio = canvas.width / canvas.height

      var pos = {
        x: (
          self.center.x -
          0.5 * self.width +
          lastMousedown.x * self.pixelRatio * pixelSize
        ),
        y: (
          self.center.y -
          0.5 / aspectRatio *
          self.width +
          lastMousedown.y * self.pixelRatio * pixelSize
        )
      }

      self.moveCenter({
        x: -diff.x * pixelSize * self.pixelRatio,
        y: -diff.y * pixelSize * self.pixelRatio
      })

      self.draw(pos)
    })

    canvas.addEventListener('wheel', function(e) {
      var pixelSize = self.width / canvas.width
      var aspectRatio = canvas.width / canvas.height

      self.scale(
        e.deltaY < 0 ? 2/3 : 3/2,
        {
          x: (
            self.center.x -
            0.5 * self.width +
            e.clientX * self.pixelRatio * pixelSize
          ),
          y: (
            self.center.y -
            0.5 / aspectRatio * self.width +
            self.pixelRatio * pixelSize * e.clientY
          )
        }
      )
    })

    canvas.parentNode.addEventListener('keydown', function(e) {
      if (e.keyCode === 68) {
        self.depth = parseInt(window.prompt('Enter new depth', '500'))
        self.draw()
      }
    })
  })()

  self.draw = deferAndDropExcess(function(pos) { // pos === referencePoint?
    pos = pos || self.center
    self.scheduler.clear()

    // self.coloriser.clearCache() // TODO: was this a good idea when it used to work?

    var pixelWidth = self.width / canvas.width
    var aspectRatio = canvas.width / canvas.height

    var topLeft = {
      x: self.center.x - 0.5 * self.width,
      y: self.center.y - 0.5 / aspectRatio * self.width
    }

    var bottomRight = {
      x: topLeft.x + self.width,
      y: topLeft.y + self.width / aspectRatio
    }

    var begin = Date.now()

    self.displayBlockStore = new displayBlockStore(
      self.center,
      self.canvas.width,
      self.canvas.height,
      self.width,
      self.width / self.canvas.width * self.canvas.height
    )

    Promise.all(
      self.calculator.getBlocksForScreen(
        pos,
        topLeft,
        bottomRight,
        pixelWidth,
        self.depth
      ).map(function(blockPromise) {
        return blockPromise.then(function(block) {
          if (!block) {
            return null
          }

          var scaledBlock = self.displayBlockStore.scaleBlock(block)
          self.displayBlockStore.add(scaledBlock)

          self.drawBlock(scaledBlock)

          return scaledBlock
        })
      })
    ).then(function(scaledBlocks) {
      var end = Date.now()
      console.log(end - begin)

      self.coloriser.updateReferenceColor(scaledBlocks)
    })
  })

  self.drawBlock = self.scheduler(function(block) {
    var pix = self.ctx.createImageData(block.size, block.size)

    // TODO: this belongs in the coloriser
    self.coloriser.blockDataToPixelData(block.data, pix)

    self.ctx.putImageData(
      pix,
      block.pixelPos.x,
      block.pixelPos.y
    )
  })

  self.drawBlocksCached = deferAndDropExcess(function() {
    if (!self.displayBlockStore) {
      self.draw()
      return
    }

    self.scheduler.clear()
    //self.displayBlockStore.blocks.forEach(self.scheduler(self.drawBlock))
    self.displayBlockStore.blocks.forEach(self.drawBlock)
  })

  self.scale = function(factor, pos) {
    pos = (pos || self.center)

    self.center = {
      x: factor * self.center.x + (1 - factor) * pos.x,
      y: factor * self.center.y + (1 - factor) * pos.y
    }

    self.width *= factor

    self.draw(pos)
  }

  self.moveCenter = function(p) {
    self.center.x += p.x
    self.center.y += p.y
  }
}
<commit_msg>Prevent re-drawing on drag when distance is zero<commit_after>'use strict'

var calculator = require('./calculator')
var coloriser = require('./coloriser')
var deferAndDropExcess = require('./deferAndDropExcess')
var displayBlockStore = require('./displayBlockStore')
var scheduler = require('./scheduler')

module.exports = function Renderer(canvas) {
  var self = this

  self.depth = 500
  self.canvas = canvas
  self.ctx = canvas.getContext('2d')
  self.center = {x: -0.75, y: 0}
  self.width = 8

  self.coloriser = new coloriser()

  self.calculator = calculator()
  self.displayBlockStore = null

  self.pixelRatio = window.devicePixelRatio || 1

  self.scheduler = scheduler(20)

  self.updateSize = function() {
    canvas.style.width = canvas.parentNode.clientWidth + 'px'
    canvas.style.height = canvas.parentNode.clientHeight + 'px'

    canvas.width = self.pixelRatio * canvas.parentNode.clientWidth
    canvas.height = self.pixelRatio * canvas.parentNode.clientHeight
  }

  ;(function() {
    var lastMousedown = {x: 0, y: 0}

    self.updateSize()

    canvas.parentNode.addEventListener('keydown', function(evt) {
      if (evt.keyCode === 67) {
        self.coloriser.randomise(!evt.shiftKey ? 1 : -1)
        self.drawBlocksCached() // TODO: rename to redrawCurrentBlocks?
      }

      if (evt.keyCode === 187 || evt.keyCode === 189) {
        if (evt.shiftKey) {
          self.coloriser.shift(0.05 * (188 - evt.keyCode))
        } else {
          self.coloriser.multiplySpeed(Math.exp(0.05 * (188 - evt.keyCode)))
        }

        self.drawBlocksCached()
      }
    })

    canvas.addEventListener('mousedown', function(e) {
      lastMousedown.x = e.clientX
      lastMousedown.y = e.clientY
    })

    canvas.addEventListener('mouseup', function(e) {
      var diff = {x: e.clientX - lastMousedown.x, y: e.clientY - lastMousedown.y}

      if (diff.x === 0 && diff.y === 0) {
        return;
      }

      var pixelSize = self.width / canvas.width
      var aspectRatio = canvas.width / canvas.height

      var pos = {
        x: (
          self.center.x -
          0.5 * self.width +
          lastMousedown.x * self.pixelRatio * pixelSize
        ),
        y: (
          self.center.y -
          0.5 / aspectRatio *
          self.width +
          lastMousedown.y * self.pixelRatio * pixelSize
        )
      }

      self.moveCenter({
        x: -diff.x * pixelSize * self.pixelRatio,
        y: -diff.y * pixelSize * self.pixelRatio
      })

      self.draw(pos)
    })

    canvas.addEventListener('wheel', function(e) {
      var pixelSize = self.width / canvas.width
      var aspectRatio = canvas.width / canvas.height

      self.scale(
        e.deltaY < 0 ? 2/3 : 3/2,
        {
          x: (
            self.center.x -
            0.5 * self.width +
            e.clientX * self.pixelRatio * pixelSize
          ),
          y: (
            self.center.y -
            0.5 / aspectRatio * self.width +
            self.pixelRatio * pixelSize * e.clientY
          )
        }
      )
    })

    canvas.parentNode.addEventListener('keydown', function(e) {
      if (e.keyCode === 68) {
        self.depth = parseInt(window.prompt('Enter new depth', '500'))
        self.draw()
      }
    })
  })()

  self.draw = deferAndDropExcess(function(pos) { // pos === referencePoint?
    pos = pos || self.center
    self.scheduler.clear()

    // self.coloriser.clearCache() // TODO: was this a good idea when it used to work?

    var pixelWidth = self.width / canvas.width
    var aspectRatio = canvas.width / canvas.height

    var topLeft = {
      x: self.center.x - 0.5 * self.width,
      y: self.center.y - 0.5 / aspectRatio * self.width
    }

    var bottomRight = {
      x: topLeft.x + self.width,
      y: topLeft.y + self.width / aspectRatio
    }

    var begin = Date.now()

    self.displayBlockStore = new displayBlockStore(
      self.center,
      self.canvas.width,
      self.canvas.height,
      self.width,
      self.width / self.canvas.width * self.canvas.height
    )

    Promise.all(
      self.calculator.getBlocksForScreen(
        pos,
        topLeft,
        bottomRight,
        pixelWidth,
        self.depth
      ).map(function(blockPromise) {
        return blockPromise.then(function(block) {
          if (!block) {
            return null
          }

          var scaledBlock = self.displayBlockStore.scaleBlock(block)
          self.displayBlockStore.add(scaledBlock)

          self.drawBlock(scaledBlock)

          return scaledBlock
        })
      })
    ).then(function(scaledBlocks) {
      var end = Date.now()
      console.log(end - begin)

      self.coloriser.updateReferenceColor(scaledBlocks)
    })
  })

  self.drawBlock = self.scheduler(function(block) {
    var pix = self.ctx.createImageData(block.size, block.size)

    // TODO: this belongs in the coloriser
    self.coloriser.blockDataToPixelData(block.data, pix)

    self.ctx.putImageData(
      pix,
      block.pixelPos.x,
      block.pixelPos.y
    )
  })

  self.drawBlocksCached = deferAndDropExcess(function() {
    if (!self.displayBlockStore) {
      self.draw()
      return
    }

    self.scheduler.clear()
    //self.displayBlockStore.blocks.forEach(self.scheduler(self.drawBlock))
    self.displayBlockStore.blocks.forEach(self.drawBlock)
  })

  self.scale = function(factor, pos) {
    pos = (pos || self.center)

    self.center = {
      x: factor * self.center.x + (1 - factor) * pos.x,
      y: factor * self.center.y + (1 - factor) * pos.y
    }

    self.width *= factor

    self.draw(pos)
  }

  self.moveCenter = function(p) {
    self.center.x += p.x
    self.center.y += p.y
  }
}
<|endoftext|>


<commit_before>'use strict';

const Resource = require('./Resource');
const api = require('../api');

class Data extends Resource {

  static get path () {
    return '/api/data';
  }

  get documentPath () {
    return this.constructor.path + '/' + encodeURIComponent(this.document.group) + '/' + encodeURIComponent(this.document.key);
  }

  get group () {
    return this.document.group;
  }

  get key () {
    return this.document.key;
  }

  get value () {
    return this.document.value;
  }

  set value (value) {
    this.document.value = value;
  }

  static get (group, key, context) {
    if (!key || !group) return Promise.reject(new Error('Key and group are required.'));
    let path = this.constructor.path + '/' + encodeURIComponent(group) + '/' + encodeURIComponent(key);
    return api.get(path).then(document => {
      return new this(document, context);
    });
  }

  static create (document, options, context) {
    options = options || {};
    const resource = new this(document, context);
    if (options.save === false) return resource;
    return api.put(resource.documentPath, null, resource.document.value).then(document => {
      resource.document = document;
      return resource;
    });
  }

  save () {
    return api.put(this.documentPath, null, this.document.value).then(document => this.document = document);
  }

  getChannelName () {
    return 'data' + ':' + this.key + ':' + this.group;
  }

}

module.exports = Data;
<commit_msg>adjusted use of static getter for `path`<commit_after>'use strict';

const Resource = require('./Resource');
const api = require('../api');

class Data extends Resource {

  static get path () {
    return '/api/data';
  }

  get documentPath () {
    return Data.path + '/' + encodeURIComponent(this.document.group) + '/' + encodeURIComponent(this.document.key);
  }

  get group () {
    return this.document.group;
  }

  get key () {
    return this.document.key;
  }

  get value () {
    return this.document.value;
  }

  set value (value) {
    this.document.value = value;
  }

  static get (group, key, context) {
    if (!key || !group) return Promise.reject(new Error('Key and group are required.'));
    let path = Data.path + '/' + encodeURIComponent(group) + '/' + encodeURIComponent(key);
    return api.get(path).then(document => {
      return new this(document, context);
    });
  }

  static create (document, options, context) {
    options = options || {};
    const resource = new this(document, context);
    if (options.save === false) return resource;
    return api.put(resource.documentPath, null, resource.document.value).then(document => {
      resource.document = document;
      return resource;
    });
  }

  save () {
    return api.put(this.documentPath, null, this.document.value).then(document => this.document = document);
  }

  getChannelName () {
    return 'data' + ':' + this.key + ':' + this.group;
  }

}

module.exports = Data;
<|endoftext|>


<commit_before>$(document).ready(function() {

  function reload() {
    $('.hidden').fadeOut();
    $('displayOutput').empty();
    $.get( '/words', function(data) {
      console.log("showing", data);

      var rendered = "
";
      data.forEach(function(item) {
        rendered = rendered + "
The word " + item.word + " is defined as " + item.definition + "
";
      });
      rendered = rendered + "
";

      $('#displayOutput').html(rendered);
    });
    $('.hidden').fadeIn();
  }

  $('#add-word').submit(function(e) {
    e.preventDefault();
    alert(this)
    $.ajax({
      url: '/words',
      type: 'PUT',
      data: $(this).serialize(),
      success: function(data) {
        reload();
      }
    });
  });

  // load data on start
  reload();

});
<commit_msg>Remove stray js alert()<commit_after>$(document).ready(function() {

  function reload() {
    $('.hidden').fadeOut();
    $('displayOutput').empty();
    $.get( '/words', function(data) {
      console.log("showing", data);

      var rendered = "
";
      data.forEach(function(item) {
        rendered = rendered + "
The word " + item.word + " is defined as " + item.definition + "
";
      });
      rendered = rendered + "
";

      $('#displayOutput').html(rendered);
    });
    $('.hidden').fadeIn();
  }

  $('#add-word').submit(function(e) {
    e.preventDefault();
    $.ajax({
      url: '/words',
      type: 'PUT',
      data: $(this).serialize(),
      success: function(data) {
        reload();
      }
    });
  });

  // load data on start
  reload();

});
<|endoftext|>


<commit_before>module.exports = {

    count: 4,

    /** @param {Creep} creep **/
    run: function(creep) {
        if(creep.carry.energy < creep.carryCapacity && creep.memory.harvest) {
            var sources = _.filter(this.findSource(creep.room), (source) => {
                return _.indexOf(creep.memory.blockSources, source.structureType) == -1
            });

            /* debug
            if(!_.isEmpty(creep.memory.blockSources)) {
                console.log('blocked sources:', creep.name, creep.memory.blockSources);
            }
            console.log('--', sources);
            _.forEach(sources, (source) => console.log(source.id, source.energy));
            */

            if (sources[0]) {
                if(creep.harvest(sources[0]) == ERR_NOT_IN_RANGE
                || creep.pickup(sources[0]) == ERR_NOT_IN_RANGE
                || creep.withdraw(sources[0], RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) {
                    creep.moveTo(sources[0].pos);
                }
            } else {
                creep.moveTo(20, 15);
                creep.sayDelay('noSources', 5);
                creep.memory.blockSources = [];
                creep.memory.harvest = false;

                //var msg = '
NoSources left
room['+creep.room.name+']';
                //Game.notify(msg, 5);
            }
        }
        else {
            creep.memory.harvest = false;
            var targets = creep.room.find(FIND_STRUCTURES, {
                    filter: (structure) => {
                        return (structure.structureType == STRUCTURE_EXTENSION ||
                                structure.structureType == STRUCTURE_SPAWN ||
                                structure.structureType == STRUCTURE_TOWER) && structure.energy < structure.energyCapacity;
                    }
            });
            if(targets.length > 0) {
                targets = _.sortBy(targets, (structure) => structure.structureType);
                var transfer = creep.transfer(targets[0], RESOURCE_ENERGY);
                if(transfer == ERR_NOT_IN_RANGE) {
                    creep.moveTo(targets[0]);
                }
                if(transfer == OK) {
                    creep.memory.blockSources = [];
                }
            } else {
                var container = creep.room.find(FIND_STRUCTURES, {
                    filter: (structure) => (structure.structureType == STRUCTURE_CONTAINER || structure.structureType == STRUCTURE_STORAGE) && structure.store[RESOURCE_ENERGY] < structure.storeCapacity
                });

                if (container) {
                    var transfer = creep.transfer(container[0], RESOURCE_ENERGY);
                    if(transfer == ERR_NOT_IN_RANGE) {
                        creep.sayDelay('storage', 2);
                        creep.moveTo(container[0]);
                    }
                    if(transfer == OK) {
                        if (creep.memory.blockSources == undefined) {
                            creep.memory.blockSources = [];
                        }
                        creep.memory.blockSources.push(STRUCTURE_CONTAINER);
                    }
                } else {
                    creep.moveTo(20, 15);
                    creep.sayDelay('idle', 5);
                    creep.memory.blockSources = [];
                }
            }
        }
        if (creep.carry.energy == 0 && !creep.memory.harvest) {
            creep.memory.harvest = true;
            creep.say('go harvest!');
        }
    },

    /** @param {Room} room **/
    findSource: function(room) {
        return _.sortByOrder(
            room.find(FIND_DROPPED_ENERGY).concat(
                room.find(FIND_SOURCES, { filter: (source) => source.energy > 0 }),
                room.find(FIND_STRUCTURES, { filter: (structure) =>
                    (structure.structureType == STRUCTURE_CONTAINER || structure.structureType == STRUCTURE_STORAGE) && structure.store[RESOURCE_ENERGY] > 0
                })
            ), (source) => !source ? 0 : source.energy || source.store[RESOURCE_ENERGY], 'asc');
    }
};
<commit_msg>autobuild-update<commit_after>module.exports = {

    count: 4,

    /** @param {Creep} creep **/
    run: function(creep) {
        if(creep.carry.energy < creep.carryCapacity && creep.memory.harvest) {
            var sources = _.filter(this.findSource(creep.room), (source) => {
                return _.indexOf(creep.memory.blockSources, source.structureType) == -1
            });

            /* debug
            if(!_.isEmpty(creep.memory.blockSources)) {
                console.log('blocked sources:', creep.name, creep.memory.blockSources);
            }
            console.log('--', sources);
            _.forEach(sources, (source) => console.log(source.id, source.energy));
            */

            if (sources[0]) {
                if(creep.harvest(sources[0]) == ERR_NOT_IN_RANGE
                || creep.pickup(sources[0]) == ERR_NOT_IN_RANGE
                || creep.withdraw(sources[0], RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) {
                    creep.moveTo(sources[0].pos);
                }
            } else {
                creep.moveTo(20, 15);
                creep.sayDelay('noSources', 5);
                creep.memory.blockSources = [];
                creep.memory.harvest = false;

                //var msg = '
NoSources left
room['+creep.room.name+']';
                //Game.notify(msg, 5);
            }
        }
        else {
            creep.memory.harvest = false;
            var targets = creep.room.find(FIND_STRUCTURES, {
                    filter: (structure) => {
                        return (structure.structureType == STRUCTURE_EXTENSION ||
                                structure.structureType == STRUCTURE_SPAWN ||
                                structure.structureType == STRUCTURE_TOWER) && structure.energy < structure.energyCapacity;
                    }
            });
            if(targets.length > 0) {
                targets = _.sortBy(targets, (structure) => structure.structureType);
                var transfer = creep.transfer(targets[0], RESOURCE_ENERGY);
                if(transfer == ERR_NOT_IN_RANGE) {
                    creep.moveTo(targets[0]);
                }
                if(transfer == OK) {
                    creep.memory.blockSources = [];
                }
            } else {
                var container = creep.room.find(FIND_STRUCTURES, {
                    filter: (structure) => (structure.structureType == STRUCTURE_CONTAINER || structure.structureType == STRUCTURE_STORAGE) && structure.store[RESOURCE_ENERGY] < structure.storeCapacity
                });

                if (container) {
                    var transfer = creep.transfer(container[0], RESOURCE_ENERGY);
                    if(transfer == ERR_NOT_IN_RANGE) {
                        creep.sayDelay('storage', 2);
                        creep.moveTo(container[0]);
                    }
                    if(transfer == OK) {
                        if (creep.memory.blockSources == undefined) {
                            creep.memory.blockSources = [];
                        }
                        creep.memory.blockSources.push(container[0].structureType);
                    }
                } else {
                    creep.moveTo(20, 15);
                    creep.sayDelay('idle', 5);
                    creep.memory.blockSources = [];
                }
            }
        }
        if (creep.carry.energy == 0 && !creep.memory.harvest) {
            creep.memory.harvest = true;
            creep.say('go harvest!');
        }
    },

    /** @param {Room} room **/
    findSource: function(room) {
        return _.sortByOrder(
            room.find(FIND_DROPPED_ENERGY).concat(
                room.find(FIND_SOURCES, { filter: (source) => source.energy > 0 }),
                room.find(FIND_STRUCTURES, { filter: (structure) =>
                    (structure.structureType == STRUCTURE_CONTAINER || structure.structureType == STRUCTURE_STORAGE) && structure.store[RESOURCE_ENERGY] > 0
                })
            ), (source) => !source ? 0 : source.energy || source.store[RESOURCE_ENERGY], 'asc');
    }
};
<|endoftext|>


<commit_before>// API Info Endpoint

const Router = require('koa-router');
const home = require('../controllers/home');

const v2 = new Router({
  prefix: '/v2',
});

// Returns API info
v2.get('/', home.get);

module.exports = v2;
<commit_msg>remove route prefix from home endpoint<commit_after>// API Info Endpoint

const Router = require('koa-router');
const home = require('../controllers/home');

const v2 = new Router();

// Returns API info
v2.get('/v2', home.get);

module.exports = v2;
<|endoftext|>


<commit_before>//  Dependancies
const express = require('express');
const request = require('request');
const path = require('path');
const Conversation = require('watson-developer-cloud/conversation/v1'); //  watson sdk

const AnswerFactory = require('../lib/AnswerFactory/AnswerFactory');
const factory = new AnswerFactory();

//  Router
const router = express.Router();

//  Send request to Watson
const pingWatson = require(path.join(__dirname, '../', 'watson', 'request'));

//  Watson convo config
const { USER_NAME, USER_PASS, WORKSPACE_ID, ACCESS_TOKEN } = process.env;

//  Facebook AUTH
router.get('/', (req, res) => {
    if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === 'alexander') {
        console.log("Validating webhook");
        return res
            .status(200)
            .send(req.query['hub.challenge']);
    }
    else {
        console.error("Failed validation. Make sure the validation tokens match.");
        return res
            .sendStatus(403);
    }
});

//  Webhook
router.post('/', function (req, res) {

    //  Extract the request body
    var data = req.body;

    // Make sure this is a page subscription
    if (data.object === 'page') {

        // Iterate over each entry - there may be multiple if batched
        data.entry.forEach(function (entry) {
            var pageID = entry.id;
            var timeOfEvent = entry.time;

            // Iterate over each messaging event
            entry.messaging.forEach(function (event) {
                if (event.message) {
                    contactWatson(event);
                } else {
                    console.log("Webhook received unknown event: ", event);
                }
            });
        });

        //  Return the status code of 200
        return res.sendStatus(200);
    }
});

function contactWatson(event) {
    var senderID = event.sender.id;
    var message = event.message;

    console.log(`
        Recieved message from the page!
    `);

    var messageText = message.text;
    var messageAttachments = message.attachments;

    pingWatson(messageText)
        .then(({intent, entities}) => {
            console.log(intent, entities);
            console.log(factory.factory(intent, entities));
        })
        .catch((err) => {

        });
}

//  Export the module
module.exports = router;<commit_msg>additional watson field<commit_after>//  Dependancies
const express = require('express');
const request = require('request');
const path = require('path');
const Conversation = require('watson-developer-cloud/conversation/v1'); //  watson sdk

const AnswerFactory = require('../lib/AnswerFactory/AnswerFactory');
const factory = new AnswerFactory();

//  Router
const router = express.Router();

//  Send request to Watson
const pingWatson = require(path.join(__dirname, '../', 'watson', 'request'));

//  Watson convo config
const { USER_NAME, USER_PASS, WORKSPACE_ID, ACCESS_TOKEN } = process.env;

//  Facebook AUTH
router.get('/', (req, res) => {
    if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === 'alexander') {
        console.log("Validating webhook");
        return res
            .status(200)
            .send(req.query['hub.challenge']);
    }
    else {
        console.error("Failed validation. Make sure the validation tokens match.");
        return res
            .sendStatus(403);
    }
});

//  Webhook
router.post('/', function (req, res) {

    //  Extract the request body
    var data = req.body;

    // Make sure this is a page subscription
    if (data.object === 'page') {

        // Iterate over each entry - there may be multiple if batched
        data.entry.forEach(function (entry) {
            var pageID = entry.id;
            var timeOfEvent = entry.time;

            // Iterate over each messaging event
            entry.messaging.forEach(function (event) {
                if (event.message) {
                    contactWatson(event);
                } else {
                    console.log("Webhook received unknown event: ", event);
                }
            });
        });

        //  Return the status code of 200
        return res.sendStatus(200);
    }
});

function contactWatson(event) {
    var senderID = event.sender.id;
    var message = event.message;

    console.log(`
        Recieved message from the page!
    `);

    var messageText = message.text;
    var messageAttachments = message.attachments;

    pingWatson(messageText)
        .then(({intent, entities}) => {
            console.log(intent, entities);
            console.log(factory.factory(senderId, intent, entities));
        })
        .catch((err) => {

        });
}

//  Export the module
module.exports = router;<|endoftext|>