<commit_before>'use strict';

const exec = require('cp-sugar').exec;
const pathJoin = require('path').join;
const readFile = require('fs').readFileSync;
const unlink = require('fs').unlinkSync;
const sep = require('path').sep;
const tempFolder = require('osenv').tmpdir();
const path = require('path');
const EOL = require('os').EOL;

// npm audit error codes
/**
 * Missing package-lock.json
 */
const EAUDITNOLOCK = 'EAUDITNOLOCK';

/**
 * Audit the project in directory projectDir
 * @param {(string | undefined)} projectDir - project directory to be analyzed by npm audit
 */
module.exports = function audit(projectDir) {
    try {
        const options = getDefaultOptionsFor(projectDir);
        process.chdir(options.directoryToAudit);
        const command = `npm audit --json > ${options.auditLogFilepath}`;

        return exec(command)
            .then(() => createResponseFromAuditLog(options.auditLogFilepath))
            .catch((err) =>
                createResponseFromAuditLogOrFromError(
                    options.auditLogFilepath,
                    err
                )
            )
            .then((response) => {
                if (packageLockHasBeenFound(response)) {
                    return removeIgnoredVulnerabilities(response, options);
                }

                const createLockFileCommand = `npm i --package-lock-only > ${
                    options.createLockLogFilepath
                }`;

                return exec(createLockFileCommand)
                    .then(() => exec(command))
                    .then(() =>
                        createResponseFromAuditLog(options.auditLogFilepath)
                    )
                    .catch((err) =>
                        createResponseFromAuditLogOrFromError(
                            options.auditLogFilepath,
                            err
                        )
                    )
                    .then((result) =>
                        processResult(result).whenErrorIs(EAUDITNOLOCK)
                    )
                    .then((result) =>
                        removePackageLockFrom(options.directoryToAudit, result)
                    )
                    .then((result) =>
                        removeIgnoredVulnerabilities(result, options)
                    );
            });
    } catch (error) {
        return Promise.reject(error.message);
    }
};

function packageLockHasNotBeenFound(response) {
    return response && response.error && response.error.code === EAUDITNOLOCK;
}

function packageLockHasBeenFound(response) {
    return !packageLockHasNotBeenFound(response);
}

function createResponseFromAuditLog(logFilePath) {
    try {
        const response = JSON.parse(readFile(logFilePath).toString());
        return response;
    } catch (err) {
        return {
            error: {
                summary: err.message,
            },
        };
    }
}

function createResponseFromAuditLogOrFromError(logFilePath, err) {
    try {
        const response = JSON.parse(readFile(logFilePath).toString());
        return response;
    } catch (err2) {
        // prettier-ignore
        return {
            error: {
                summary: err && err.message
                    ? err.message
                    : err2.message,
            },
        };
    }
}

/**
 * Middleware that intercept any input error object.
 * This middleware may modify the inital error message
 * @param {Object} result -  result of the npm audit command
 */
function processResult(result) {
    return {
        whenErrorIs: (errorCode) => {
            if (
                errorCode === EAUDITNOLOCK &&
                packageLockHasNotBeenFound(result)
            ) {
                const summary = result.error.summary || '';
                result.error.summary = `package.json file is missing or is badly formatted. ${summary}`;
                return result;
            }
            return result;
        },
    };
}

/**
 * Middleware that removes the auto-generated package-lock.json
 * @param {string} projectDir - folder where the package-lock.json file has been generated
 * @param {*} response - result of the npm audit command (eventually modified by previous middlewares execution)
 * @returns input response if file removal is ok
 * In case of error it adds or updates into the input response object the property 'internalErrors'
 */
function removePackageLockFrom(projectDir, response) {
    try {
        const file = pathJoin(projectDir, 'package-lock.json');
        unlink(file);
        return response;
    } catch (error) {
        if (error.code === 'ENOENT') {
            return response;
        }
        if (response) {
            response.internalErrors = response.internalErrors || [];
            response.internalErrors.push(error);
            return response;
        }
        return response;
    }
}

/**
 * exported for testing purposes
 */
module.exports.removePackageLockFrom = removePackageLockFrom;

/**
 * Remove, from npm audit command response, the vulnerabilities defined in .auditignore file
 * @param {*} response - result of the npm audit command (eventually modified by previous middlewares execution)
 * @param {DefaultOptions} options - options
 */
function removeIgnoredVulnerabilities(response, options) {
    try {
        const ignoredVulnerabilities = getIgnoredVulnerabilities(options);
        if (ignoredVulnerabilities && ignoredVulnerabilities.length == 0) {
            return response;
        }
        const filteredResponse = JSON.parse(JSON.stringify(response, null, 2));

        filteredResponse.actions = filteredResponse.actions
            .map((action) => {
                action.resolves = action.resolves.filter(
                    (resolve) =>
                        ignoredVulnerabilities.indexOf(`${resolve.id}`) < 0
                );
                return action;
            })
            .filter((action) => action.resolves.length > 0);

        ignoredVulnerabilities.forEach(
            (ignoredVulnerability) =>
                delete filteredResponse.advisories[ignoredVulnerability]
        );

        const vulnerabilitiesMetadata = {
            info: 0,
            low: 0,
            moderate: 0,
            high: 0,
            critical: 0,
        };

        const severities = {};
        const advisories = filteredResponse.advisories;
        for (const key in advisories) {
            if (advisories.hasOwnProperty(key)) {
                const advisory = advisories[key];
                severities[`${advisory.id}`] = advisory.severity;
            }
        }

        filteredResponse.actions.forEach((action) => {
            action.resolves.forEach((resolve) => {
                vulnerabilitiesMetadata[severities[`${resolve.id}`]] += 1;
            });
        });

        filteredResponse.metadata.vulnerabilities = vulnerabilitiesMetadata;

        return filteredResponse;
    } catch (error) {
        if (response) {
            response.internalErrors = response.internalErrors || [];
            response.internalErrors.push(error);
            return response;
        }
        return response;
    }
}

/**
 * exported for testing purposes
 */
module.exports.removeIgnoredVulnerabilities = removeIgnoredVulnerabilities;

/**
 * @typedef DefaultOptions
 * @type {Object}
 * @property {string} directoryToAudit - Folder to audit. Defaults to process.cwd()
 * @property {string} auditLogFilepath - Path of the file that will receive all output of the npm-audit command.
 * @property {string} createLockLogFilepath - Path of the file that will receive all output of the 'npm i --package-lock-only' command.
 */

/**
 * Get default options of this module
 * @param {string} projectDir - path to the directory that will be analyzed by npm audit
 * @returns {DefaultOptions}
 */
function getDefaultOptionsFor(projectDir) {
    const directoryToAudit = projectDir || process.cwd();
    const projectName = directoryToAudit.split(sep).pop() || '';
    const auditLogFilename = `npm-audit-${projectName.trim()}.log`;
    const auditLogFilepath = path.resolve(tempFolder, auditLogFilename);
    const createLockLogFilename = `npm-audit-${projectName.trim()}-create-lock.log`;
    const createLockLogFilepath = path.resolve(
        tempFolder,
        createLockLogFilename
    );

    return {
        directoryToAudit,
        auditLogFilepath,
        createLockLogFilepath,
    };
}

function getIgnoredVulnerabilities(options) {
    try {
        const auditIgnoreFile = path.resolve(
            options.directoryToAudit,
            '.auditignore'
        );
        const content = readFile(auditIgnoreFile).toString();
        return content
            .split(EOL)
            .filter((vulnerabilityUri) =>
                vulnerabilityUri.includes('nodesecurity.io/advisories')
            )
            .map((vulnerabilityUri) => vulnerabilityUri.split('/').pop());
    } catch (error) {
        return [];
    }
}
<commit_msg>test(npm): try fix test on windows CI<commit_after>'use strict';

const exec = require('cp-sugar').exec;
const pathJoin = require('path').join;
const readFile = require('fs').readFileSync;
const unlink = require('fs').unlinkSync;
const sep = require('path').sep;
const tempFolder = require('osenv').tmpdir();
const path = require('path');
const EOL = require('os').EOL;

// npm audit error codes
/**
 * Missing package-lock.json
 */
const EAUDITNOLOCK = 'EAUDITNOLOCK';

/**
 * Audit the project in directory projectDir
 * @param {(string | undefined)} projectDir - project directory to be analyzed by npm audit
 */
module.exports = function audit(projectDir) {
    try {
        const options = getDefaultOptionsFor(projectDir);
        process.chdir(options.directoryToAudit);
        const command = `npm audit --json > ${options.auditLogFilepath}`;

        return exec(command)
            .then(() => createResponseFromAuditLog(options.auditLogFilepath))
            .catch((err) =>
                createResponseFromAuditLogOrFromError(
                    options.auditLogFilepath,
                    err
                )
            )
            .then((response) => {
                if (packageLockHasBeenFound(response)) {
                    return removeIgnoredVulnerabilities(response, options);
                }

                const createLockFileCommand = `npm i --package-lock-only > ${
                    options.createLockLogFilepath
                }`;

                return exec(createLockFileCommand)
                    .then(() => exec(command))
                    .then(() =>
                        createResponseFromAuditLog(options.auditLogFilepath)
                    )
                    .catch((err) =>
                        createResponseFromAuditLogOrFromError(
                            options.auditLogFilepath,
                            err
                        )
                    )
                    .then((result) =>
                        processResult(result).whenErrorIs(EAUDITNOLOCK)
                    )
                    .then((result) =>
                        removePackageLockFrom(options.directoryToAudit, result)
                    )
                    .then((result) =>
                        removeIgnoredVulnerabilities(result, options)
                    );
            });
    } catch (error) {
        return Promise.reject(error.message);
    }
};

function packageLockHasNotBeenFound(response) {
    return response && response.error && response.error.code === EAUDITNOLOCK;
}

function packageLockHasBeenFound(response) {
    return !packageLockHasNotBeenFound(response);
}

function createResponseFromAuditLog(logFilePath) {
    try {
        const response = JSON.parse(readFile(logFilePath).toString());
        return response;
    } catch (err) {
        return {
            error: {
                summary: err.message,
            },
        };
    }
}

function createResponseFromAuditLogOrFromError(logFilePath, err) {
    try {
        const response = JSON.parse(readFile(logFilePath).toString());
        return response;
    } catch (err2) {
        // prettier-ignore
        return {
            error: {
                summary: err && err.message
                    ? err.message
                    : err2.message,
            },
        };
    }
}

/**
 * Middleware that intercept any input error object.
 * This middleware may modify the inital error message
 * @param {Object} result -  result of the npm audit command
 */
function processResult(result) {
    return {
        whenErrorIs: (errorCode) => {
            if (
                errorCode === EAUDITNOLOCK &&
                packageLockHasNotBeenFound(result)
            ) {
                const summary = result.error.summary || '';
                result.error.summary = `package.json file is missing or is badly formatted. ${summary}`;
                return result;
            }
            return result;
        },
    };
}

/**
 * Middleware that removes the auto-generated package-lock.json
 * @param {string} projectDir - folder where the package-lock.json file has been generated
 * @param {*} response - result of the npm audit command (eventually modified by previous middlewares execution)
 * @returns input response if file removal is ok
 * In case of error it adds or updates into the input response object the property 'internalErrors'
 */
function removePackageLockFrom(projectDir, response) {
    try {
        const file = pathJoin(projectDir, 'package-lock.json');
        unlink(file);
        return response;
    } catch (error) {
        if (error.code === 'ENOENT') {
            return response;
        }
        if (response) {
            response.internalErrors = response.internalErrors || [];
            response.internalErrors.push(error);
            return response;
        }
        return response;
    }
}

/**
 * exported for testing purposes
 */
module.exports.removePackageLockFrom = removePackageLockFrom;

/**
 * Remove, from npm audit command response, the vulnerabilities defined in .auditignore file
 * @param {*} response - result of the npm audit command (eventually modified by previous middlewares execution)
 * @param {DefaultOptions} options - options
 */
function removeIgnoredVulnerabilities(response, options) {
    try {
        const ignoredVulnerabilities = getIgnoredVulnerabilities(options);
        if (ignoredVulnerabilities && ignoredVulnerabilities.length == 0) {
            return response;
        }
        const filteredResponse = JSON.parse(JSON.stringify(response, null, 2));

        filteredResponse.actions = filteredResponse.actions
            .map((action) => {
                action.resolves = action.resolves.filter(
                    (resolve) =>
                        ignoredVulnerabilities.indexOf(`${resolve.id}`) < 0
                );
                return action;
            })
            .filter((action) => action.resolves.length > 0);

        ignoredVulnerabilities.forEach(
            (ignoredVulnerability) =>
                delete filteredResponse.advisories[ignoredVulnerability]
        );

        const vulnerabilitiesMetadata = {
            info: 0,
            low: 0,
            moderate: 0,
            high: 0,
            critical: 0,
        };

        const severities = {};
        const advisories = filteredResponse.advisories;
        for (const key in advisories) {
            if (advisories.hasOwnProperty(key)) {
                const advisory = advisories[key];
                severities[`${advisory.id}`] = advisory.severity;
            }
        }

        filteredResponse.actions.forEach((action) => {
            action.resolves.forEach((resolve) => {
                vulnerabilitiesMetadata[severities[`${resolve.id}`]] += 1;
            });
        });

        filteredResponse.metadata.vulnerabilities = vulnerabilitiesMetadata;

        return filteredResponse;
    } catch (error) {
        if (response) {
            response.internalErrors = response.internalErrors || [];
            response.internalErrors.push(error);
            return response;
        }
        return response;
    }
}

/**
 * exported for testing purposes
 */
module.exports.removeIgnoredVulnerabilities = removeIgnoredVulnerabilities;

/**
 * @typedef DefaultOptions
 * @type {Object}
 * @property {string} directoryToAudit - Folder to audit. Defaults to process.cwd()
 * @property {string} auditLogFilepath - Path of the file that will receive all output of the npm-audit command.
 * @property {string} createLockLogFilepath - Path of the file that will receive all output of the 'npm i --package-lock-only' command.
 */

/**
 * Get default options of this module
 * @param {string} projectDir - path to the directory that will be analyzed by npm audit
 * @returns {DefaultOptions}
 */
function getDefaultOptionsFor(projectDir) {
    const directoryToAudit = projectDir || process.cwd();
    const projectName = directoryToAudit.split(sep).pop() || '';
    const auditLogFilename = `npm-audit-${projectName.trim()}.log`;
    const auditLogFilepath = path.resolve(tempFolder, auditLogFilename);
    const createLockLogFilename = `npm-audit-${projectName.trim()}-create-lock.log`;
    const createLockLogFilepath = path.resolve(
        tempFolder,
        createLockLogFilename
    );

    return {
        directoryToAudit,
        auditLogFilepath,
        createLockLogFilepath,
    };
}

function getIgnoredVulnerabilities(options) {
    try {
        const auditIgnoreFile = pathJoin(
            options.directoryToAudit,
            '.auditignore'
        );
        const content = readFile(auditIgnoreFile).toString();
        console.log('found .auditignore file with content:');
        console.log(content);
        return content
            .split(EOL)
            .filter((vulnerabilityUri) =>
                vulnerabilityUri.includes('nodesecurity.io/advisories')
            )
            .map((vulnerabilityUri) => vulnerabilityUri.split('/').pop());
    } catch (error) {
        console.log(error.message);
        return [];
    }
}
<|endoftext|>


<commit_before>var assert = require('assert')
var util = require('util')

var slice = [].slice

var Vestibule = require('vestibule')
var signal = require('signal')
var cadence = require('cadence')

var Reactor = require('reactor')
var Legislator = require('paxos/legislator')
var Islander = require('islander')

var Monotonic = require('monotonic')
var interrupt = require('interrupt').createInterrupter('bigeasy.kibitz')

function Kibitzer (islandId, id, options) {
    assert(id != null, 'id is required')

    options || (options = {})

    options.ping || (options.ping = 250)
    options.timeout || (options.timeout = 1000)

    this.poll = options.poll || 5000
    this.ping = options.ping
    this.timeout = options.timeout

    this.syncLength = options.syncLength || 250

    this._reactor = new Reactor({ object: this, method: '_tick' })

    this._Date = options.Date || Date

    this.joining = []

    this.location = options.location
    this._ua = options.ua

// TODO Maybe have an auto-incrementing id as before.
    this.id = id

    this.legislator = new Legislator(islandId, id, this._Date.now(), {
        ping: this.ping,
        timeout: this.timeout
    })
    this.islander = new Islander(this.legislator.id)
    this.iterators = {
        legislator: this.legislator.log.min(),
        islander: { dummy: true }
    }
    this.cookies = {}

    this.discovery = options.discovery

    this.available = false

    this._advanced = new Vestibule
}

Kibitzer.prototype._logger = function (level, message, context) {
    var subscribers = signal.subscribers([ '', 'bigeasy', 'kibitz', 'log' ])
    for (var i = 0, I = subscribers.length; i < I; i++) {
        subscribers[i](this, level, message, context)
    }
}

Kibitzer.prototype._createLegislator = function (now) {
}

Kibitzer.prototype._tick = cadence(function (async) {
    var dirty = false
    async(function () {
        var outgoing = this.islander.outbox()
        if (outgoing.length) {
            var post
            async(function () {
                var location = this.legislator.locations[this.legislator.government.majority[0]]
                this._ua.send(location, post = {
                    type: 'enqueue',
                    entries: outgoing
                }, async())
            }, function (body) {
                assert.ok('---->', body.entries)
                this._logger('info', 'enqueued', {
                    kibitzerId: this.legislator.id,
                    sent: post,
                    received: body
                })
                this.islander.published(body.entries)
            })
            dirty = true
        }
    }, function () {
        var now = this._Date.now()
        var outbox = this.legislator.synchronize(now)
        if (outbox.length == 0) {
            var consensus = this.legislator.consensus(now)
            if (consensus != null) {
                outbox.push(consensus)
            }
        }
// TODO Put this in another outgoing queue.
        async.forEach(function (pulse) {
            dirty = true
            var responses = {}
            async(function () {
                async.forEach(function (id) {
                    async(function () {
                        if (id == this.legislator.id) {
                            this._receive(pulse, async())
                        } else {
                            var location = this.legislator.locations[id]
                            this._ua.send(location, { type: 'receive', pulse: pulse }, async())
                        }
                    }, function (response) {
                        this._logger('info', 'published', {
                            kibitzerId: this.legislator.id,
                            sent: pulse,
                            received: response
                        })
                        responses[id] = response
                    })
                })(pulse.route)
            }, function () {
                this.legislator.sent(this._Date.now(), pulse, responses)
            })
        })(outbox)
    }, function () {
        if (this.iterators.legislator.next != null) {
        /*
            this.iterators.legislator = this.iterators.legislator.next
            */
            this._advanced.notify()
        }
    }, function () {
        if (dirty) {
            this._reactor.check()
        }
    })
})

Kibitzer.prototype.locations = function () {
    var locations = []
    for (var key in this.legislator.locations) {
        locations.push(this.legislator.locations[key])
    }
    return locations
}

Kibitzer.prototype._prime = function (entry) {
    this.iterators.islander = {
        next: this.islander.prime(this.iterators.legislator = this.iterators.legislator.next)
    }
}

Kibitzer.prototype.bootstrap = cadence(function (async) {
    async(function () {
        this._logger('info', 'bootstrap', { kibitzerId: this.legislator.id })
        this.bootstrapped = true
        this._reactor.turnstile.turnstiles = 1
        this.legislator.bootstrap(this._Date.now(), this.location)
        this.available = true
        this._advanced.enter(async())
        this._reactor.check()
    }, function () {
        this._prime()
    })
})

Kibitzer.prototype.join = cadence(function (async) {
    async(function () {
        this._ua.discover(async())
    }, function (locations) {
        this._logger('info', 'locations', {
            kibitzerId: this.legislator.id,
            received: JSON.stringify(locations)
        })
        var location, loop = async(function () {
            location = locations.shift()
            if (!location) {
                throw interrupt(new Error('discover'))
            }
            async([function () {
                this._ua.send(location, { type: 'health' }, async())
            }, function (error) {
                this._logger('info', 'health', { location: location, healthy: false })
                return [ loop.continue ]
            }], function () {
                this._logger('info', 'health', { location: location, healthy: true })
                return [ loop.break, location ]
            })
        })()
    }, function (location) {
        var sent
        async(function () {
            this._ua.send(location, sent = {
                type: 'naturalize',
                islandId: this.legislator.islandId,
                id: this.legislator.id,
                cookie: this.legislator.cookie,
                location: this.location
            }, async())
        }, function (outcome) {
            this._logger('info', 'join', {
                kibitzerId: this.legislator.id,
                sent: JSON.stringify(sent),
                received: JSON.stringify(outcome)
            })
        })
        this._advanced.enter(async())
    }, function () {
        this.bootstrapped = false
// TODO Let Paxos handle concept availability, which is also changed islands.
        this.available = true
        this._prime()
    })
})

Kibitzer.prototype.dispatch = cadence(function (async, body) {
    switch (body.type) {
    case 'health':
        return {}
    case 'naturalize':
        this._naturalize(body, async())
        break
    case 'receive':
        this._receive(body.pulse, async())
        break
    case 'enqueue':
        this._enqueue(body, async())
        break
    }
})

Kibitzer.prototype.publish = function (entry) {
    var cookie = this.islander.publish(entry, false)
    this._reactor.check()
    return cookie
}

Kibitzer.prototype._naturalize = cadence(function (async, post) {
// TODO Available ever so dubious.
    if (!this.available) {
        this._logger('info', 'enqueue', {
            kibitzerId: this.legislator.id,
            available: this.available,
            received: JSON.stringify(post)
        })
        throw interrupt(new Error('unavailable'))
    }
    if (post.islandId != this.legislator.islandId) {
        throw interrupt(new Error('wrongIsland'))
    }
    var outcome = this.legislator.naturalize(this._Date.now(), post.id, post.cookie, post.location)
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        available: this.available,
        received: JSON.stringify(post),
        sent: JSON.stringify(outcome)
    })
    return outcome
})

Kibitzer.prototype._enqueue = cadence(function (async, post) {
    if (!this.available) {
        this._logger('info', 'enqueue', {
            kibitzerId: this.legislator.id,
            available: this.available,
            received: post
        })
        throw interrupt(new Error('unavailable'))
    }
    var response = { posted: false, entries: [] }
// TODO Redirect enqueue or wait for stability and retry.
// TODO Singular.
    post.entries.forEach(function (entry) {
        var outcome = this.legislator.enqueue(this._Date.now(), entry)
        if (outcome.enqueued) {
            response.posted = true
            response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
        }
    }, this)
    this._reactor.check()
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        available: this.available,
        received: JSON.stringify(post),
        sent: JSON.stringify(response)
    })
    return response
})

Kibitzer.prototype._receive = cadence(function (async, pulse) {
    this._logger('info', 'receive', {
        kibitzerId: this.legislator.id,
        available: this.available,
        received: pulse
    })
    var ret = [ this.legislator.receive(this._Date.now(), pulse, pulse.messages) ]
    this._reactor.check()
    return ret
})

Kibitzer.prototype.advance = cadence(function (async) {
    var loop = async(function () {
        while (this.iterators.legislator.next != null) {
            this.iterators.legislator = this.iterators.legislator.next
            this._logger('info', 'consuming', {
                kibitzerId: this.legislator.id,
                route: this.iterators.legislator.route,
                promise: this.iterators.legislator.promise,
                value: this.iterators.legislator.value
            })
        }
        if (this.iterators.islander.next == null) {
            this._advanced.enter(async())
        } else {
            this.iterators.islander = this.iterators.islander.next
            return [ loop.break, this.iterators.islander ]
        }
    })()
})

module.exports = Kibitzer
<commit_msg>Return empty array to indicate enqueue failure.<commit_after>var assert = require('assert')
var util = require('util')

var slice = [].slice

var Vestibule = require('vestibule')
var signal = require('signal')
var cadence = require('cadence')

var Reactor = require('reactor')
var Legislator = require('paxos/legislator')
var Islander = require('islander')

var Monotonic = require('monotonic')
var interrupt = require('interrupt').createInterrupter('bigeasy.kibitz')

function Kibitzer (islandId, id, options) {
    assert(id != null, 'id is required')

    options || (options = {})

    options.ping || (options.ping = 250)
    options.timeout || (options.timeout = 1000)

    this.poll = options.poll || 5000
    this.ping = options.ping
    this.timeout = options.timeout

    this.syncLength = options.syncLength || 250

    this._reactor = new Reactor({ object: this, method: '_tick' })

    this._Date = options.Date || Date

    this.joining = []

    this.location = options.location
    this._ua = options.ua

// TODO Maybe have an auto-incrementing id as before.
    this.id = id

    this.legislator = new Legislator(islandId, id, this._Date.now(), {
        ping: this.ping,
        timeout: this.timeout
    })
    this.islander = new Islander(this.legislator.id)
    this.iterators = {
        legislator: this.legislator.log.min(),
        islander: { dummy: true }
    }
    this.cookies = {}

    this.discovery = options.discovery

    this.available = false

    this._advanced = new Vestibule
}

Kibitzer.prototype._logger = function (level, message, context) {
    var subscribers = signal.subscribers([ '', 'bigeasy', 'kibitz', 'log' ])
    for (var i = 0, I = subscribers.length; i < I; i++) {
        subscribers[i](this, level, message, context)
    }
}

Kibitzer.prototype._createLegislator = function (now) {
}

Kibitzer.prototype._tick = cadence(function (async) {
    var dirty = false
    async(function () {
        var outgoing = this.islander.outbox()
        if (outgoing.length) {
            var post
            async(function () {
                var location = this.legislator.locations[this.legislator.government.majority[0]]
                this._ua.send(location, post = {
                    type: 'enqueue',
                    entries: outgoing
                }, async())
            }, function (body) {
                this._logger('info', 'enqueued', {
                    kibitzerId: this.legislator.id,
                    sent: post,
                    received: body
                })
                this.islander.published(body.entries)
            })
            dirty = true
        }
    }, function () {
        var now = this._Date.now()
        var outbox = this.legislator.synchronize(now)
        if (outbox.length == 0) {
            var consensus = this.legislator.consensus(now)
            if (consensus != null) {
                outbox.push(consensus)
            }
        }
// TODO Put this in another outgoing queue.
        async.forEach(function (pulse) {
            dirty = true
            var responses = {}
            async(function () {
                async.forEach(function (id) {
                    async(function () {
                        if (id == this.legislator.id) {
                            this._receive(pulse, async())
                        } else {
                            var location = this.legislator.locations[id]
                            this._ua.send(location, { type: 'receive', pulse: pulse }, async())
                        }
                    }, function (response) {
                        this._logger('info', 'published', {
                            kibitzerId: this.legislator.id,
                            sent: pulse,
                            received: response
                        })
                        responses[id] = response
                    })
                })(pulse.route)
            }, function () {
                this.legislator.sent(this._Date.now(), pulse, responses)
            })
        })(outbox)
    }, function () {
        if (this.iterators.legislator.next != null) {
        /*
            this.iterators.legislator = this.iterators.legislator.next
            */
            this._advanced.notify()
        }
    }, function () {
        if (dirty) {
            this._reactor.check()
        }
    })
})

Kibitzer.prototype.locations = function () {
    var locations = []
    for (var key in this.legislator.locations) {
        locations.push(this.legislator.locations[key])
    }
    return locations
}

Kibitzer.prototype._prime = function (entry) {
    this.iterators.islander = {
        next: this.islander.prime(this.iterators.legislator = this.iterators.legislator.next)
    }
}

Kibitzer.prototype.bootstrap = cadence(function (async) {
    async(function () {
        this._logger('info', 'bootstrap', { kibitzerId: this.legislator.id })
        this.bootstrapped = true
        this._reactor.turnstile.turnstiles = 1
        this.legislator.bootstrap(this._Date.now(), this.location)
        this.available = true
        this._advanced.enter(async())
        this._reactor.check()
    }, function () {
        this._prime()
    })
})

Kibitzer.prototype.join = cadence(function (async) {
    async(function () {
        this._ua.discover(async())
    }, function (locations) {
        this._logger('info', 'locations', {
            kibitzerId: this.legislator.id,
            received: JSON.stringify(locations)
        })
        var location, loop = async(function () {
            location = locations.shift()
            if (!location) {
                throw interrupt(new Error('discover'))
            }
            async([function () {
                this._ua.send(location, { type: 'health' }, async())
            }, function (error) {
                this._logger('info', 'health', { location: location, healthy: false })
                return [ loop.continue ]
            }], function () {
                this._logger('info', 'health', { location: location, healthy: true })
                return [ loop.break, location ]
            })
        })()
    }, function (location) {
        var sent
        async(function () {
            this._ua.send(location, sent = {
                type: 'naturalize',
                islandId: this.legislator.islandId,
                id: this.legislator.id,
                cookie: this.legislator.cookie,
                location: this.location
            }, async())
        }, function (outcome) {
            this._logger('info', 'join', {
                kibitzerId: this.legislator.id,
                sent: JSON.stringify(sent),
                received: JSON.stringify(outcome)
            })
        })
        this._advanced.enter(async())
    }, function () {
        this.bootstrapped = false
// TODO Let Paxos handle concept availability, which is also changed islands.
        this.available = true
        this._prime()
    })
})

Kibitzer.prototype.dispatch = cadence(function (async, body) {
    switch (body.type) {
    case 'health':
        return {}
    case 'naturalize':
        this._naturalize(body, async())
        break
    case 'receive':
        this._receive(body.pulse, async())
        break
    case 'enqueue':
        this._enqueue(body, async())
        break
    }
})

Kibitzer.prototype.publish = function (entry) {
    var cookie = this.islander.publish(entry, false)
    this._reactor.check()
    return cookie
}

Kibitzer.prototype._naturalize = cadence(function (async, post) {
// TODO Available ever so dubious.
    if (!this.available) {
        this._logger('info', 'enqueue', {
            kibitzerId: this.legislator.id,
            available: this.available,
            received: JSON.stringify(post)
        })
        throw interrupt(new Error('unavailable'))
    }
    if (post.islandId != this.legislator.islandId) {
        throw interrupt(new Error('wrongIsland'))
    }
    var outcome = this.legislator.naturalize(this._Date.now(), post.id, post.cookie, post.location)
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        available: this.available,
        received: JSON.stringify(post),
        sent: JSON.stringify(outcome)
    })
    return outcome
})

Kibitzer.prototype._enqueue = cadence(function (async, post) {
    if (!this.available) {
        this._logger('info', 'enqueue', {
            kibitzerId: this.legislator.id,
            available: this.available,
            received: post
        })
        throw interrupt(new Error('unavailable'))
    }
    var response = { posted: false, entries: [] }
// TODO Redirect enqueue or wait for stability and retry.
    for (var i = 0, I = post.entries.length; i < I; i++) {
        var entry = post.entries[i]
        var outcome = this.legislator.enqueue(this._Date.now(), entry)
        if (!outcome.enqueued) {
            response.entries.length = 0
            break
        }
        response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
    }
    this._reactor.check()
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        available: this.available,
        received: JSON.stringify(post),
        sent: JSON.stringify(response)
    })
    return response
})

Kibitzer.prototype._receive = cadence(function (async, pulse) {
    this._logger('info', 'receive', {
        kibitzerId: this.legislator.id,
        available: this.available,
        received: pulse
    })
    var ret = [ this.legislator.receive(this._Date.now(), pulse, pulse.messages) ]
    this._reactor.check()
    return ret
})

Kibitzer.prototype.advance = cadence(function (async) {
    var loop = async(function () {
        while (this.iterators.legislator.next != null) {
            this.iterators.legislator = this.iterators.legislator.next
            this._logger('info', 'consuming', {
                kibitzerId: this.legislator.id,
                route: this.iterators.legislator.route,
                promise: this.iterators.legislator.promise,
                value: this.iterators.legislator.value
            })
        }
        if (this.iterators.islander.next == null) {
            this._advanced.enter(async())
        } else {
            this.iterators.islander = this.iterators.islander.next
            return [ loop.break, this.iterators.islander ]
        }
    })()
})

module.exports = Kibitzer
<|endoftext|>


<commit_before>import React from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import injectSheet from "react-jss";
import { Link } from "react-router-dom";
import { Grid } from "react-bootstrap/lib";
import SubscribeButton from "./SubscribeButton";
import SectionsButton from "./SectionsButton";
import { Search } from "../icons";
import { openSidebar } from "../actions";
import { openSubscriptionModal } from "../../accounts/actions";
import { FeaturedSectionsBar } from "../../sections/components";
import DarkModeToggle from "./DarkModeToggle";

const styles = {
  Masthead: {
    paddingTop: "20px",
    display: "flex",
    flexDirection: "column",
    paddingBottom: "0%",
  },
  theSpectatorLogo: {
    flexGrow: "2",
    color: "#000",
    display: "flex",
    flexDirection: "column",
    alignItems: "center",
    fontFamily: "Old English Text MT",
    fontSize: "8rem",
    marginBottom: "10px",
    "&:hover, &:active, &:focus": {
      color: "#000",
      textDecoration: "none",
    },
    "@media (min-width: 768px)": {
      fontSize: "65px"
    },
  },
  userTools: {
    float: "right",
    margin: "0",
    padding: "0",
    "&:hover a": {
      textDecoration: "none",
    },
  },
  signedInNav: {
    top: "5px",
    display: "flex",
    position: "relative",
  },
  FeaturedSectionsBarContainer: {
    borderTop: "1px solid black",
    borderBottom: "1px solid gray",
    width: "100%",
    marginBottom: "2rem",
  },
  myAccount: {
    display: "inline",
    fontFamily: "Circular Std",
    fontSize: "14px",
    fontWeight: "bold",
    color: "#000",
    transitionDuration: ".3s",
    "&:hover, &:active, &:focus": {
      color: "#000",
    },
  },
  toggle: {
    marginRight: '20%',
    marginTop: '5%',
  },
  searchLink: {
    color: "#000",
    position: "relative",
    height: "17px",
    width: "17px",
    marginLeft: "15px",
    top: "1px",
    "& svg": {
      height: "100%",
      width: "100%",
    },
    "&:hover, &:active, &:focus": {
      color: "#000",
      textDecoration: "none",
    },
  },
  logo: {
    display: "flex",
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
  },
  positioning: {
    display: "flex",
    flexDirection: "row",
  },
};

const Masthead = ({ classes, openSidebar, session, openSubscriptionModal }) => {
  return (
    <Grid className={classes.Masthead}>

        <SectionsButton onClick={openSidebar} />
        <Link className={classes.theSpectatorLogo} to="/">
          The Spectator
        </Link>

          {session ? (

              <Link to="/myaccount/profile" className={classes.myAccount}>
                My Account
              </Link>
              <Link to="/search" className={classes.searchLink}>
                <Search />
              </Link>

          ) : (
            <>

              <DarkModeToggle/>

              <SubscribeButton onClick={openSubscriptionModal} />
            </>
          )}
          {/*!session && (

              <Link to="/myaccount/profile">
                <SignInButton />
              </Link>

          )*/}

        <FeaturedSectionsBar />

    </Grid>
  );
};

const mapStateToProps = state => ({
  session: state.accounts.session,
});

const mapDispatchToProps = dispatch => {
  return bindActionCreators({ openSubscriptionModal, openSidebar }, dispatch);
};

export default connect(mapStateToProps, mapDispatchToProps)(
  injectSheet(styles)(Masthead),
);
<commit_msg>fixed bordertop masthead<commit_after>import React from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import injectSheet from "react-jss";
import { Link } from "react-router-dom";
import { Grid } from "react-bootstrap/lib";
import SubscribeButton from "./SubscribeButton";
import SectionsButton from "./SectionsButton";
import { Search } from "../icons";
import { openSidebar } from "../actions";
import { openSubscriptionModal } from "../../accounts/actions";
import { FeaturedSectionsBar } from "../../sections/components";
import DarkModeToggle from "./DarkModeToggle";

const styles = {
  Masthead: {
    paddingTop: "20px",
    display: "flex",
    flexDirection: "column",
    paddingBottom: "0%",
  },
  theSpectatorLogo: {
    flexGrow: "2",
    color: "#000",
    display: "flex",
    flexDirection: "column",
    alignItems: "center",
    fontFamily: "Old English Text MT",
    fontSize: "8rem",
    marginBottom: "10px",
    "&:hover, &:active, &:focus": {
      color: "#000",
      textDecoration: "none",
    },
    "@media (min-width: 768px)": {
      fontSize: "65px"
    },
  },
  userTools: {
    float: "right",
    margin: "0",
    padding: "0",
    "&:hover a": {
      textDecoration: "none",
    },
  },
  signedInNav: {
    top: "5px",
    display: "flex",
    position: "relative",
  },
  FeaturedSectionsBarContainer: {
    borderTop: "1px solid",
    borderBottom: "1px solid gray",
    width: "100%",
    marginBottom: "2rem",
  },
  myAccount: {
    display: "inline",
    fontFamily: "Circular Std",
    fontSize: "14px",
    fontWeight: "bold",
    color: "#000",
    transitionDuration: ".3s",
    "&:hover, &:active, &:focus": {
      color: "#000",
    },
  },
  toggle: {
    marginRight: '20%',
    marginTop: '5%',
  },
  searchLink: {
    color: "#000",
    position: "relative",
    height: "17px",
    width: "17px",
    marginLeft: "15px",
    top: "1px",
    "& svg": {
      height: "100%",
      width: "100%",
    },
    "&:hover, &:active, &:focus": {
      color: "#000",
      textDecoration: "none",
    },
  },
  logo: {
    display: "flex",
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
  },
  positioning: {
    display: "flex",
    flexDirection: "row",
  },
};

const Masthead = ({ classes, openSidebar, session, openSubscriptionModal }) => {
  return (
    <Grid className={classes.Masthead}>

        <SectionsButton onClick={openSidebar} />
        <Link className={classes.theSpectatorLogo} to="/">
          The Spectator
        </Link>

          {session ? (

              <Link to="/myaccount/profile" className={classes.myAccount}>
                My Account
              </Link>
              <Link to="/search" className={classes.searchLink}>
                <Search />
              </Link>

          ) : (
            <>

              <DarkModeToggle/>

              <SubscribeButton onClick={openSubscriptionModal} />
            </>
          )}
          {/*!session && (

              <Link to="/myaccount/profile">
                <SignInButton />
              </Link>

          )*/}

        <FeaturedSectionsBar />

    </Grid>
  );
};

const mapStateToProps = state => ({
  session: state.accounts.session,
});

const mapDispatchToProps = dispatch => {
  return bindActionCreators({ openSubscriptionModal, openSidebar }, dispatch);
};

export default connect(mapStateToProps, mapDispatchToProps)(
  injectSheet(styles)(Masthead),
);
<|endoftext|>


<commit_before>'use strict';

var _ = require('underscore');

module.exports = {
	homeUrl: 'https://proxy-list.org/',
	defaultOptions: {
		numPagesToScrape: 10,
	},
	abstract: 'scraper-paginated-list',
	config: {
		startPageUrl: 'https://proxy-list.org/english/index.php',
		selectors: {
			item: '#proxy-table .table ul',
			itemAttributes: {
				ipAddress: 'li.proxy',
				port: 'li.proxy',
				protocols: 'li.https',
				anonymityLevel: 'li.type',
			},
			nextLink: '.pagination li:nth-child(2) a, .table-menu a.next',
		},
		parseAttributes: {
			ipAddress: function(str) {
				var match = str.match(/([0-9\.]+):[0-9]+$/);
				if (match && match[1]) {
					return match[1];
				}
				var base64 = str.split("'");
				if (base64 && base64.length === 3) {
					var addr = Buffer.from(base64[1], 'base64').toString('utf8');
					match = addr.match(/([0-9\.]+):[0-9]+$/);
					if (match && match[1]) {
						return match[1];
					}
				}
				return null;
			},
			port: function(port) {
				var match = port.match(/.+:([0-9]+)$/);
				if (match && match[1]) {
					port = parseInt(match[1]);
					if (!_.isNaN(port)) return port
				}
				var base64 = str.split("'");
				if (base64 && base64.length === 3) {
					var addr = Buffer.from(base64[1], 'base64').toString('utf8');
					match = addr.match(/.+:([0-9]+)$/);
					if (match && match[1]) {
						return match[1];
					}
				}
				return null
			},
			protocols: function(protocols) {
				return [protocols.trim().toLowerCase()];
			},
			anonymityLevel: function(anonymityLevel) {
				return anonymityLevel.trim().toLowerCase();
			},
		},
	},
};
<commit_msg>proxy-list-org: anonLevel, protocols<commit_after>'use strict';

var _ = require('underscore');

var convert = {
	anonymityLevels: {
		'elite': 'elite',
		'anonymous': 'anonymous',
		'transparent': 'transparent',
	},
	protocols: {
		'https': ['https'],
		'-': ['http'],
	},
};

var parseProxyText = function(text) {
	if (!text) return null;
	var parts = text.split('\'');
	if (!parts || parts.length !== 3) return null;
	var addr = Buffer.from(parts[1], 'base64').toString('utf8');
	var match = addr.match(/([0-9\.]+):([0-9]+)$/);
	if (!match || !match[1] || !match[2]) return null;
	return {
		ipAddress: match[1],
		port: match[2],
	};
};

module.exports = {
	homeUrl: 'https://proxy-list.org/',
	defaultOptions: {
		numPagesToScrape: 10,
	},
	abstract: 'scraper-paginated-list',
	config: {
		startPageUrl: 'https://proxy-list.org/english/index.php',
		selectors: {
			item: '#proxy-table .table ul',
			itemAttributes: {
				ipAddress: 'li.proxy',
				port: 'li.proxy',
				protocols: 'li.https',
				anonymityLevel: 'li.type',
			},
			nextLink: '.pagination li:nth-child(2) a, .table-menu a.next',
		},
		parseAttributes: {
			anonymityLevel: function(anonymityLevel) {
				anonymityLevel = anonymityLevel && anonymityLevel.trim().toLowerCase() || null;
				return anonymityLevel && convert.anonymityLevels[anonymityLevel] || null;
			},
			ipAddress: function(ipAddress) {
				var addr = parseProxyText(ipAddress);
				return addr && addr.ipAddress || null;
			},
			port: function(port) {
				var addr = parseProxyText(port);
				if (!addr || !addr.port) return null;
				port = parseInt(addr.port);
				if (!port || _.isNaN(port)) return null;
				return port;
			},
			protocols: function(protocols) {
				protocols = protocols && protocols.trim().toLowerCase() || null;
				return protocols && convert.protocols[protocols] || [];
			},
		},
	},
};
<|endoftext|>


<commit_before>import 'spectre-canjs/sp-form/sp-form';
import 'spectre-canjs/sp-form/fields/sp-text-field/';
import 'spectre-canjs/sp-form/fields/sp-select-field/';
import 'spectre-canjs/sp-form/fields/sp-dropzone-field/';
import 'spectre-canjs/sp-form/fields/sp-subform-field/';
import 'spectre-canjs/sp-form/fields/sp-check-field/';
import render from './full.stache';
import jsonMarkup from 'json-pretty-html';
import './full.less';
import flatpickr from 'flatpickr';
import 'flatpickr/dist/flatpickr.css';
import DefineMap from 'can-define/map/';

// mock a server
import './fixtures.js';

// a child sub-form object
const ChildObject = DefineMap.extend('ChildDemoObject', {
    json_field_1: 'string',
    json_field_2: {
        validate (props) {
            return props.value != 2 ? 'The value must be 2' : undefined;
        },
        default: 2,
        editTag: 'sp-select-field',
        defaultLabel: 'These values are loading asynchronously!',
        // async load options
        optionsPromise: new Promise((resolve) => {
            setTimeout(() => {
                resolve([{
                    value: 1,
                    label: 'Option 1'
                }, {
                    value: 2,
                    label: 'Option 2'
                }]);
            }, 5000);
        })
    },
    json_field_3: {
        type: 'number',
        validate (props) {
            const value = parseInt(props.value, 10);
            return !value || value < 10 ? 'Please enter a value greater than 10' : undefined;
        }
    },
    json_field_4: {
        type: 'boolean',
        editTag: 'sp-check-field',
        style: 'switch',
        serialize (val) {
            return String(val);
        },
        validate (props) {
            console.log(props.value);
            return props.value ? '' : 'You must agree to these terms';
        }
    }
});

const DemoObject = DefineMap.extend('DemoObject', {
    field1: {
        type: 'string',
        default: 'Default Value',
        name: 'field1',
        alias: 'Please enter at least 50 characters',
        validate (props) {

            // since this function is run whenever field1 changes, we can use
            // it to set additional properties
            // set an additional prop in the dirty to track additional data
            // this field is defined in the Model, but not a field so its not editable
            props.dirty.assign({
                field1Length: props.value ? props.value.length : 0
            });

            // return an error message if the length is < 50 chars
            return props.value.length < 50 ? 'This field must contain at least 50 characters' : false;
        }
    },
    field2: {
        type: 'string',
        name: 'field2',
        alias: 'A basic textarea field',
        textarea: true,
        placeholder: 'Enter the text: hello',
        validate (props) {
            if (props.value != 'hello') {
                return 'This field must contain the text, "hello"';
            }
            return '';
        }
    },
    field3: {
        type: 'number',
        default: 1,
        name: 'field3',
        alias: 'A select dropdown',
        editTag: 'sp-select-field',
        validate (props) {
            if (props.value != 1) {
                return 'This field must be 1';
            }
        },
        options: [{
            value: 1,
            label: 'Option 1'
        }, {
            value: 2,
            label: 'Option 2'
        }]
    },
    field4: {
        type: 'date',
        serialize (date) {
            return date ? date.toDateString() : 'Not set';
        },
        name: 'field4',
        onInsert (element) {
            flatpickr(element);
        },
        alias: 'A date field',
        ui: 'datepicker',
        placeholder: 'Enter a date...'
    },
    field5: {
        name: 'field5',
        alias: 'A file field',
        editTag: 'sp-dropzone-field',
        url: '/upload',

        // convert file list type back to list
        serialize (val) {
            if (!val) {
                return '';
            }
            return val.map((file) => {
                return file.id;
            }).join(',');
        }
    },
    field6: {
        Type: ChildObject,
        name: 'field6',
        alias: 'A Subform Field',
        editTag: 'sp-subform-field',
        Default:  ChildObject,
        ObjectTemplate: ChildObject
    },
    field1Length: {edit: false, type: 'number'},
    field7: {
        type: 'boolean',
        name: 'field7',
        alias: 'A checkbox',
        editTag: 'sp-check-field',
        default: false
    },
    cascade_1: {
        type: 'string',
        name: 'cascade_1',
        alias: 'Car Make - (cascade dropdown 1)',
        defaultLabel: 'First choose a make...',
        options: [{value: 'Ford'}, {value: 'Chevy'}, {value: 'Toyota'}],
        editTag: 'sp-select-field'
    },
    cascade_2: {
        type: 'string',
        name: 'cascade_2',
        alias: 'Car Model - (cascade dropdown 2)',
        defaultLabel: 'Then choose a model...',
        getOptions(obj){
            const options = {
                'Ford': [{value: 'Fiesta'}, {value: 'Focus'}, {value: 'Taurus'}],
                'Chevy': [{value: 'Spark'}, {value: 'Sonic'}, {value: 'Bolt'}],
                'Toyota': [{value: 'Prius'}, {value: 'Corolla'}, {value: 'Camry'}],
            }

            return options[obj.cascade_1] || [{value: '', label: 'Choose a make first...'}];
        },
        validate(props){
            if(!props.dirty.cascade_1){
                return 'You need to choose a make first';
            }
        },
        editTag: 'sp-select-field'
    }
});

const vm = new DefineMap({
    object: new DemoObject(),
    formSaving: false,
    onSubmit () {
        console.log(this.object);
        console.log('submitted data: ', this.object.serialize());
        alert('Form submitted! See the console for details');
        setTimeout(() => {
            this.formSaving = false;
        }, 3000);
    },
    onCancel () {
        console.log('Form canceled!');
    },
    stringify (obj) {
        if (!obj) {
            return;
        }
        return jsonMarkup(obj.serialize());
    }
});

export default function start () {
    const frag = render(vm);
    document.body.appendChild(frag);
}
<commit_msg>validate cascade dropdowns<commit_after>import 'spectre-canjs/sp-form/sp-form';
import 'spectre-canjs/sp-form/fields/sp-text-field/';
import 'spectre-canjs/sp-form/fields/sp-select-field/';
import 'spectre-canjs/sp-form/fields/sp-dropzone-field/';
import 'spectre-canjs/sp-form/fields/sp-subform-field/';
import 'spectre-canjs/sp-form/fields/sp-check-field/';
import render from './full.stache';
import jsonMarkup from 'json-pretty-html';
import './full.less';
import flatpickr from 'flatpickr';
import 'flatpickr/dist/flatpickr.css';
import DefineMap from 'can-define/map/';

// mock a server
import './fixtures.js';

// a child sub-form object
const ChildObject = DefineMap.extend('ChildDemoObject', {
    json_field_1: 'string',
    json_field_2: {
        validate (props) {
            return props.value != 2 ? 'The value must be 2' : undefined;
        },
        default: 2,
        editTag: 'sp-select-field',
        defaultLabel: 'These values are loading asynchronously!',
        // async load options
        optionsPromise: new Promise((resolve) => {
            setTimeout(() => {
                resolve([{
                    value: 1,
                    label: 'Option 1'
                }, {
                    value: 2,
                    label: 'Option 2'
                }]);
            }, 5000);
        })
    },
    json_field_3: {
        type: 'number',
        validate (props) {
            const value = parseInt(props.value, 10);
            return !value || value < 10 ? 'Please enter a value greater than 10' : undefined;
        }
    },
    json_field_4: {
        type: 'boolean',
        editTag: 'sp-check-field',
        style: 'switch',
        serialize (val) {
            return String(val);
        },
        validate (props) {
            console.log(props.value);
            return props.value ? '' : 'You must agree to these terms';
        }
    }
});

const DemoObject = DefineMap.extend('DemoObject', {
    field1: {
        type: 'string',
        default: 'Default Value',
        name: 'field1',
        alias: 'Please enter at least 50 characters',
        validate (props) {

            // since this function is run whenever field1 changes, we can use
            // it to set additional properties
            // set an additional prop in the dirty to track additional data
            // this field is defined in the Model, but not a field so its not editable
            props.dirty.assign({
                field1Length: props.value ? props.value.length : 0
            });

            // return an error message if the length is < 50 chars
            return props.value.length < 50 ? 'This field must contain at least 50 characters' : false;
        }
    },
    field2: {
        type: 'string',
        name: 'field2',
        alias: 'A basic textarea field',
        textarea: true,
        placeholder: 'Enter the text: hello',
        validate (props) {
            if (props.value != 'hello') {
                return 'This field must contain the text, "hello"';
            }
            return '';
        }
    },
    field3: {
        type: 'number',
        default: 1,
        name: 'field3',
        alias: 'A select dropdown',
        editTag: 'sp-select-field',
        validate (props) {
            if (props.value != 1) {
                return 'This field must be 1';
            }
        },
        options: [{
            value: 1,
            label: 'Option 1'
        }, {
            value: 2,
            label: 'Option 2'
        }]
    },
    field4: {
        type: 'date',
        serialize (date) {
            return date ? date.toDateString() : 'Not set';
        },
        name: 'field4',
        onInsert (element) {
            flatpickr(element);
        },
        alias: 'A date field',
        ui: 'datepicker',
        placeholder: 'Enter a date...'
    },
    field5: {
        name: 'field5',
        alias: 'A file field',
        editTag: 'sp-dropzone-field',
        url: '/upload',

        // convert file list type back to list
        serialize (val) {
            if (!val) {
                return '';
            }
            return val.map((file) => {
                return file.id;
            }).join(',');
        }
    },
    field6: {
        Type: ChildObject,
        name: 'field6',
        alias: 'A Subform Field',
        editTag: 'sp-subform-field',
        Default:  ChildObject,
        ObjectTemplate: ChildObject
    },
    field1Length: {edit: false, type: 'number'},
    field7: {
        type: 'boolean',
        name: 'field7',
        alias: 'A checkbox',
        editTag: 'sp-check-field',
        default: false
    },
    cascade_1: {
        type: 'string',
        name: 'cascade_1',
        alias: 'Car Make - (cascade dropdown 1)',
        defaultLabel: 'First choose a make...',
        options: [{value: 'Ford'}, {value: 'Chevy'}, {value: 'Toyota'}],
        editTag: 'sp-select-field'
    },
    cascade_2: {
        type: 'string',
        name: 'cascade_2',
        alias: 'Car Model - (cascade dropdown 2)',
        defaultLabel: 'Then choose a model...',
        getOptions(obj){
            const options = {
                'Ford': [{value: 'Fiesta'}, {value: 'Focus'}, {value: 'Taurus'}],
                'Chevy': [{value: 'Spark'}, {value: 'Sonic'}, {value: 'Bolt'}],
                'Toyota': [{value: 'Prius'}, {value: 'Corolla'}, {value: 'Camry'}],
            }

            return options[obj.cascade_1] || [{value: 'None', label: 'Choose a make first...'}];
        },
        validate(props){
            if(!props.dirty.cascade_1){
                return 'You need to choose a make first';
            }
        },
        editTag: 'sp-select-field'
    }
});

const vm = new DefineMap({
    object: new DemoObject(),
    formSaving: false,
    onSubmit () {
        console.log(this.object);
        console.log('submitted data: ', this.object.serialize());
        alert('Form submitted! See the console for details');
        setTimeout(() => {
            this.formSaving = false;
        }, 3000);
    },
    onCancel () {
        console.log('Form canceled!');
    },
    stringify (obj) {
        if (!obj) {
            return;
        }
        return jsonMarkup(obj.serialize());
    }
});

export default function start () {
    const frag = render(vm);
    document.body.appendChild(frag);
}
<|endoftext|>


<commit_before>'use strict';

const AccountsService = imports.gi.AccountsService;
const ExtensionUtils = imports.misc.extensionUtils;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Soup = imports.gi.Soup;

const Extension = ExtensionUtils.getCurrentExtension();
const Convenience = Extension.imports.lib.convenience;
const log = Extension.imports.log;
const utils = Extension.imports.utils;
const { md5 } = Extension.imports.lib.md5;

/* exported GravatarExtension */
const GravatarExtension = new Lang.Class({
  Name: 'GravatarExtension',

  /*
   ***********************************************
   * Constructor                                 *
   ***********************************************
   */
  _init: function () {
    this.TAG = this.__name__;

    this.settings = Convenience.getSettings();
    this.tmpDir = '/tmp';
    this.user = GLib.get_user_name();
    this.userManager = AccountsService.UserManager.get_default()
      .get_user(this.user);
    this.userManagerLoop = null;
  },

  /*
   ***********************************************
   * Public Methods                              *
   ***********************************************
   */
  enable: function () {
    this.waitForUserManager(function () {
      this.fetchGravatarIcon();
      this.settings.connect('changed', function (settings, key) {
        log.d(this.TAG, 'Setting changed: ' + key);
        if (key !== 'log-level') {
          this.fetchGravatarIcon();
        }
      }.bind(this));
    }.bind(this));
  },

  disable: function () {
    if (this.userManagerLoop !== null) {
      Mainloop.source_remove(this.userManagerLoop);
      this.userManagerLoop = null;
    }
    if (this.httpSession) {
      this.httpSession.abort();
    }
  },

  /*
   ***********************************************
   * Private Methods                             *
   ***********************************************
   */
  waitForUserManager: function (cb) {
    if (this.userManager.get_icon_file() !== null) {
      cb();
      return;
    }
    log.d(this.TAG, 'Waiting for userManager to initialize...');
    // This fixes an issue where this.userManager properties sometimes aren't
    // initialized when the extension loads
    this.userManagerLoop = Mainloop.timeout_add_seconds(1, function () {
      if (this.userManager.get_icon_file() !== null) {
        log.d(this.TAG, 'userManager initialized');
        this.userManagerLoop = null;
        cb();
        // exit loop
        return false;
      }
      // continue loop
      return true;
    }.bind(this));
  },

  /* Settings */
  getIconSize: function () {
    return this.settings.get_int('icon-size');
  },

  getHash: function () {
    let email = this.settings.get_string('gravatar-email').toLowerCase();
    if (!utils.isValidEmail(email)) {
      log.e(this.TAG, 'Unable to validate email "' + email + '"');
      return null;
    }
    log.d(this.TAG, 'Hashing "' + email + '"');
    return md5(email);
  },

  /* Set Icon */
  setIcon: function (icon) {
    log.d(this.TAG, 'Setting icon for "' + this.user + '" to "' + icon + '"');
    this.userManager.set_icon_file(icon);
  },

  /* Download From Gravatar */
  fetchGravatarIcon: function () {
    let hash = this.getHash();
    if (hash === null) {
      return;
    }
    try {
      let url =
        'http://www.gravatar.com/avatar/' + hash + '?s=' +
        this.getIconSize() + '&d=mm';
      let request = Soup.Message.new('GET', url);
      let icon = Gio.file_new_for_path(
        this.tmpDir + '/' + Date.now() + '_' + hash
      );
      log.i(this.TAG, 'Downloading gravatar icon from ' + url);
      log.d(this.TAG, 'Saving to ' + icon.get_path());
      // initialize session
      if (!this.httpSession) {
        log.d(this.TAG, 'Creating new http session');
        this.httpSession = new Soup.Session();
      }
      this.httpSession.abort();

      let fstream = icon.replace(null, false, Gio.FileCreateFlags.NONE, null);
      request.connect('got_chunk', function (msg, chunk) {
        fstream.write(chunk.get_data(), null, chunk.length);
      });

      // download file
      this.httpSession.queue_message(request, function (httpSession, msg) {
        fstream.close(null);
        switch (msg.status_code) {
        case 200:
          log.i(this.TAG, 'Download successful');
          this.setIcon(icon.get_path());
          break;
        default:
          log.w(this.TAG, 'Failed to download ' + url);
        }
        log.d(this.TAG, 'Deleting ' + icon.get_path());
        icon.delete(null);
      }.bind(this));
    } catch (e) {
      log.e(this.TAG, e.message);
    }
  },

});
<commit_msg>Disconnect settings changed handler<commit_after>'use strict';

const AccountsService = imports.gi.AccountsService;
const ExtensionUtils = imports.misc.extensionUtils;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Soup = imports.gi.Soup;

const Extension = ExtensionUtils.getCurrentExtension();
const Convenience = Extension.imports.lib.convenience;
const log = Extension.imports.log;
const utils = Extension.imports.utils;
const { md5 } = Extension.imports.lib.md5;

/* exported GravatarExtension */
const GravatarExtension = new Lang.Class({
  Name: 'GravatarExtension',

  /*
   ***********************************************
   * Constructor                                 *
   ***********************************************
   */
  _init: function () {
    this.TAG = this.__name__;

    this.settings = Convenience.getSettings();
    this.settingsChangedHandler = null;
    this.tmpDir = '/tmp';
    this.user = GLib.get_user_name();
    this.userManager = AccountsService.UserManager.get_default()
      .get_user(this.user);
    this.userManagerLoop = null;
  },

  /*
   ***********************************************
   * Public Methods                              *
   ***********************************************
   */
  enable: function () {
    this.waitForUserManager(function () {
      this.fetchGravatarIcon();
      this.settingsChangedHandler = this.settings.connect(
        'changed',
        function (settings, key) {
          log.d(this.TAG, 'Setting changed: ' + key);
          if (key !== 'log-level') {
            this.fetchGravatarIcon();
          }
        }.bind(this)
      );
    }.bind(this));
  },

  disable: function () {
    // Disconnect settings changed handler
    if (this.settingsChangedHandler !== null) {
      this.settings.disconnect(this.settingsChangedHandler);
      this.settingsChangedHandler = null;
    }

    // Remove userManager initialization loop
    if (this.userManagerLoop !== null) {
      Mainloop.source_remove(this.userManagerLoop);
      this.userManagerLoop = null;
    }

    // Abort httpSession
    if (this.httpSession) {
      this.httpSession.abort();
    }
  },

  /*
   ***********************************************
   * Private Methods                             *
   ***********************************************
   */
  waitForUserManager: function (cb) {
    if (this.userManager.get_icon_file() !== null) {
      cb();
      return;
    }
    log.d(this.TAG, 'Waiting for userManager to initialize...');
    // This fixes an issue where this.userManager properties sometimes aren't
    // initialized when the extension loads
    this.userManagerLoop = Mainloop.timeout_add_seconds(1, function () {
      if (this.userManager.get_icon_file() !== null) {
        log.d(this.TAG, 'userManager initialized');
        this.userManagerLoop = null;
        cb();
        // exit loop
        return false;
      }
      // continue loop
      return true;
    }.bind(this));
  },

  /* Settings */
  getIconSize: function () {
    return this.settings.get_int('icon-size');
  },

  getHash: function () {
    let email = this.settings.get_string('gravatar-email').toLowerCase();
    if (!utils.isValidEmail(email)) {
      log.e(this.TAG, 'Unable to validate email "' + email + '"');
      return null;
    }
    log.d(this.TAG, 'Hashing "' + email + '"');
    return md5(email);
  },

  /* Set Icon */
  setIcon: function (icon) {
    log.d(this.TAG, 'Setting icon for "' + this.user + '" to "' + icon + '"');
    this.userManager.set_icon_file(icon);
  },

  /* Download From Gravatar */
  fetchGravatarIcon: function () {
    let hash = this.getHash();
    if (hash === null) {
      return;
    }
    try {
      let url =
        'http://www.gravatar.com/avatar/' + hash + '?s=' +
        this.getIconSize() + '&d=mm';
      let request = Soup.Message.new('GET', url);
      let icon = Gio.file_new_for_path(
        this.tmpDir + '/' + Date.now() + '_' + hash
      );
      log.i(this.TAG, 'Downloading gravatar icon from ' + url);
      log.d(this.TAG, 'Saving to ' + icon.get_path());
      // initialize session
      if (!this.httpSession) {
        log.d(this.TAG, 'Creating new http session');
        this.httpSession = new Soup.Session();
      }
      this.httpSession.abort();

      let fstream = icon.replace(null, false, Gio.FileCreateFlags.NONE, null);
      request.connect('got_chunk', function (msg, chunk) {
        fstream.write(chunk.get_data(), null, chunk.length);
      });

      // download file
      this.httpSession.queue_message(request, function (httpSession, msg) {
        fstream.close(null);
        switch (msg.status_code) {
        case 200:
          log.i(this.TAG, 'Download successful');
          this.setIcon(icon.get_path());
          break;
        default:
          log.w(this.TAG, 'Failed to download ' + url);
        }
        log.d(this.TAG, 'Deleting ' + icon.get_path());
        icon.delete(null);
      }.bind(this));
    } catch (e) {
      log.e(this.TAG, e.message);
    }
  },

});
<|endoftext|>


<commit_before>// http://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_250,h_250,c_fit/sample.jpg
const defaultState = 'f_auto,q_auto,fl_lossy';

export default (url, { mode, maxWidth, effect, maxHeight, border, width, height, cropX, cropY, quality, blur, retina, crop: crop0 } = {}, crop) => {
  if (!crop) crop = crop0;
  if (!mode) mode = 'fill';

  // RETINA
  // if (retina && width) width *= 2; geht so nicht, da cloudinary einen Fehler wirft, wenn die angefragt Größe > als die tatsächlich ist
  // if (retina && height) height *= 2;
  if (retina && maxWidth && maxWidth * 2 < width) maxWidth *= 2;
  if (retina && maxHeight && maxHeight * 2 < height) maxHeight *= 2;

  if (!url) return url;
  if (crop) {
    width = crop[0];
    height = crop[1];
    cropX = crop[2];
    cropY = crop[3];
  }
  if (url.indexOf('http://res.cloudinary.com/') === 0) {
    url = url.split('ttp://res.cloudinary.com/').join('ttps://res.cloudinary.com/');
  }
  if (url.indexOf('https://res.cloudinary.com/') !== 0) return url;
  let part = defaultState;
  if (cropX !== undefined && cropY !== undefined) {
    part = `x_${cropX},y_${cropY},w_${width},h_${height},c_crop/${part}`;
  } else if (width && height) {
    part = `w_${width},h_${height},c_${mode}/${part}`;
  }

  if (maxWidth || maxHeight) {
    if (maxWidth) part += `,w_${maxWidth}`;
    if (maxHeight) part += `,h_${maxHeight}`;
    part += `,c_${mode}`;
  }
  if (quality) {
    part += `,q_${quality}`;
  }
  if (blur) {
    part += `,e_blur:${blur}`;
  }
  if (part === defaultState) {
    part += ',q_75';
  }
  if (border) {
    Object.keys(border).map((key) => {
      part = `bo_${border[key]}px_solid_${key}/${part}`;
    });
  }
  if (effect) {
    part = `e_${effect}/${part}`;
  }

  return url.replace('/upload/', `/upload/${part}/`);
};
<commit_msg>fix error with image-comp (doesn’t load image in full size)<commit_after>// http://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_250,h_250,c_fit/sample.jpg
const defaultState = 'f_auto,q_auto,fl_lossy';

export default (url, { mode, maxWidth, effect, maxHeight, border, width, height, cropX, cropY, quality, blur, retina, crop: crop0 } = {}, crop) => {
  if (!crop) crop = crop0;
  if (!mode) mode = 'fill';

  // RETINA
  // if (retina && width) width *= 2; geht so nicht, da cloudinary einen Fehler wirft, wenn die angefragt Größe > als die tatsächlich ist
  // if (retina && height) height *= 2;
  if (retina && maxWidth && maxWidth * 2 < width) maxWidth *= 2;
  if (retina && maxHeight && maxHeight * 2 < height) maxHeight *= 2;

  if (!url) return url;
  if (crop) {
    width = crop[0];
    height = crop[1];
    cropX = crop[2];
    cropY = crop[3];
  }
  if (url.indexOf('http://res.cloudinary.com/') === 0) {
    url = url.split('ttp://res.cloudinary.com/').join('ttps://res.cloudinary.com/');
  }
  if (url.indexOf('https://res.cloudinary.com/') !== 0) return url;
  let part = defaultState;
  if (cropX !== undefined && cropY !== undefined) {
    part = `x_${cropX},y_${cropY},w_${width},h_${height},c_crop/${part}`;
  } else if (width && height) {
    // part = `w_${width},h_${height},c_${mode}/${part}`;
    // beni, solltest du das hier ändern wollen, dann sag mir Bescheid. Mit dieser Zeile wird eine URL wie folgt erzeugt:
    // https://res.cloudinary.com/dhsf4vjjc/image/upload/w_2896,h_1944,c_fill/f_auto,q_auto,fl_lossy,w_960,c_fill/v1490025279/p0xdfvo73u7pj6oudtf4.tiff
    // d.h. die Bilder werden mit voller Dimension geladen, egal wie groß sie eigentlich sein sollten!
  }

  if (maxWidth || maxHeight) {
    if (maxWidth) part += `,w_${maxWidth}`;
    if (maxHeight) part += `,h_${maxHeight}`;
    part += `,c_${mode}`;
  }
  if (quality) {
    part += `,q_${quality}`;
  }
  if (blur) {
    part += `,e_blur:${blur}`;
  }
  if (part === defaultState) {
    part += ',q_75';
  }
  if (border) {
    Object.keys(border).map((key) => {
      part = `bo_${border[key]}px_solid_${key}/${part}`;
    });
  }
  if (effect) {
    part = `e_${effect}/${part}`;
  }

  return url.replace('/upload/', `/upload/${part}/`);
};
<|endoftext|>


<commit_before>// Load in dependencies
var fs = require('fs');
var path = require('path');
var _ = require('underscore');
var async = require('async');
var templater = require('spritesheet-templates');
var spritesmith = require('spritesmith');
var url = require('url2');

// Define class to contain different extension handlers
function ExtFormat() {
  this.formatObj = {};
}
ExtFormat.prototype = {
  add: function (name, val) {
    this.formatObj[name] = val;
  },
  get: function (filepath) {
    // Grab the extension from the filepath
    var ext = path.extname(filepath);
    var lowerExt = ext.toLowerCase();

    // Look up the file extenion from our format object
    var formatObj = this.formatObj;
    var format = formatObj[lowerExt];
    return format;
  }
};

// Create img and css formats
var imgFormats = new ExtFormat();
var cssFormats = new ExtFormat();

// Add our img formats
imgFormats.add('.png', 'png');
imgFormats.add('.jpg', 'jpeg');
imgFormats.add('.jpeg', 'jpeg');

// Add our css formats
cssFormats.add('.styl', 'stylus');
cssFormats.add('.stylus', 'stylus');
cssFormats.add('.sass', 'sass');
cssFormats.add('.scss', 'scss');
cssFormats.add('.less', 'less');
cssFormats.add('.json', 'json');
cssFormats.add('.css', 'css');

function getCoordinateName(filepath) {
  // Extract the image name (exlcuding extension)
  var fullname = path.basename(filepath);
  var nameParts = fullname.split('.');

  // If there is are more than 2 parts, pop the last one
  if (nameParts.length >= 2) {
    nameParts.pop();
  }

  // Return our modified filename
  return nameParts.join('.');
}

module.exports = function gruntSpritesmith (grunt) {
  // Create a SpriteMaker function
  function SpriteMaker() {
    // Grab the raw configuration
    var data = this.data;

    // If we were invoked via `grunt-newer`, re-localize the info
    if (data.src === undefined && data.files) {
      data = data.files[0] || {};
    }

    // Determine the origin and destinations
    var src = data.src;
    var destImg = data.dest;
    var destCss = data.destCss;
    var cssTemplate = data.cssTemplate;
    var that = this;

    // Verify all properties are here
    if (!src || !destImg || !destCss) {
      return grunt.fatal('grunt.sprite requires a src, dest (img), and destCss property');
    }

    // Expand all filepaths (e.g. `*.png` -> `home.png`)
    var srcFiles = grunt.file.expand(src);

    // If there are settings for retina
    var retinaSrcFiles;
    var retinaSrcFilter = data.retinaSrcFilter;
    var retinaDestImg = data.retinaDest;
    if (retinaSrcFilter || retinaDestImg) {
      grunt.log.debug('Retina settings detected');

      // Verify our required set is present
      if (!retinaSrcFilter || !retinaDestImg) {
        return grunt.fatal('Retina settings detected. We must have both `retinaSrcFilter` and `retinaDest` ' +
          'provided for retina to work');
      }

      // Filter out our retina files
      retinaSrcFiles = [];
      srcFiles = srcFiles.filter(function filterSrcFile (filepath) {
        // If we have a retina file, filter it out
        if (grunt.file.match(retinaSrcFilter, filepath).length) {
          retinaSrcFiles.push(filepath);
          return false;
        // Otherwise, keep it in the src files
        } else {
          return true;
        }
      });
      grunt.verbose.writeln('Retina images found: ' + retinaSrcFiles.join(', '));

      // If we have a different amount of normal and retina images, complain and leave
      if (srcFiles.length !== retinaSrcFiles.length) {
        return grunt.fatal('Retina settings detected but ' + retinaSrcFiles.length + ' retina images were found. ' +
          'We have ' + srcFiles.length + ' normal images and expect these numbers to line up. ' +
          'Please double check `retinaSrcFilter`.');
      }
    }

    // Create an async callback
    var cb = this.async();

    // Determine the format of the image
    var imgOpts = data.imgOpts || {};
    var imgFormat = imgOpts.format || imgFormats.get(destImg) || 'png';

    // Set up the defautls for imgOpts
    _.defaults(imgOpts, {format: imgFormat});

    // Prepare spritesmith parameters
    var spritesmithParams = {
      src: srcFiles,
      engine: data.engine,
      algorithm: data.algorithm,
      padding: data.padding || 0,
      algorithmOpts: data.algorithmOpts || {},
      engineOpts: data.engineOpts || {},
      exportOpts: imgOpts
    };

    // In parallel
    async.parallel([
      // Run our normal task
      function normalSpritesheet (callback) {
        spritesmith(spritesmithParams, callback);
      },
      // If we have a retina task, run it as well
      function retinaSpritesheet (callback) {
        // DEV: We don't check length since we could have no images passed in
        if (retinaSrcFiles) {
          var retinaParams = _.defaults({
            src: retinaSrcFiles
          }, spritesmithParams);
          spritesmith(retinaParams, callback);
        } else {
          process.nextTick(callback);
        }
      }
    ], function handleSpritesheets (err, resultArr) {
      // If an error occurred, callback with it
      if (err) {
        grunt.fatal(err);
        return cb(err);
      }

      // Otherwise, write out the result to destImg
      var result = resultArr[0];
      var destImgDir = path.dirname(destImg);
      grunt.file.mkdir(destImgDir);
      fs.writeFileSync(destImg, result.image, 'binary');

      // Generate a listing of CSS variables
      var coordinates = result.coordinates;
      var properties = result.properties;
      var spritePath = data.imgPath || url.relative(destCss, destImg);
      var spritesheetInfo = {
        width: properties.width,
        height: properties.height,
        image: spritePath
      };
      var cssVarMap = data.cssVarMap || function noop () {};
      var cleanCoords = [];

      // Clean up the file name of the file
      Object.getOwnPropertyNames(coordinates).sort().forEach(function prepareTemplateData (file) {
        // Extract out our name
        var name = getCoordinateName(file);
        var coords = coordinates[file];

        // Specify the image for the sprite
        coords.name = name;
        coords.source_image = file;
        // DEV: `image`, `total_width`, `total_height` are deprecated as they are overwritten in `spritesheet-templates`
        coords.image = spritePath;
        coords.total_width = properties.width;
        coords.total_height = properties.height;

        // Map the coordinates through cssVarMap
        coords = cssVarMap(coords) || coords;

        // Save the cleaned name and coordinates
        cleanCoords.push(coords);
      });

      // If we have retina sprites
      var retinaCleanCoords;
      var retinaResult = resultArr[1];
      var retinaSpritesheetInfo;
      if (retinaResult) {
        // Write out the result to destImg
        var retinaDestImgDir = path.dirname(retinaDestImg);
        grunt.file.mkdir(retinaDestImgDir);
        fs.writeFileSync(retinaDestImg, retinaResult.image, 'binary');

        // Generate a listing of CSS variables
        var retinaCoordinates = retinaResult.coordinates;
        var retinaProperties = retinaResult.properties;
        var retinaSpritePath = data.retinaImgPath || url.relative(destCss, retinaDestImg);
        retinaSpritesheetInfo = {
          width: retinaProperties.width,
          height: retinaProperties.height,
          image: retinaSpritePath
        };
        // DEV: We reuse cssVarMap
        retinaCleanCoords = [];

        // Clean up the file name of the file
        Object.getOwnPropertyNames(retinaCoordinates).sort().forEach(function prepareRetinaTemplateData (file) {
          var name = getCoordinateName(file);
          var coords = retinaCoordinates[file];
          coords.name = name;
          coords.source_image = file;
          coords.image = retinaSpritePath;
          coords.total_width = retinaProperties.width;
          coords.total_height = retinaProperties.height;
          coords = cssVarMap(coords) || coords;
          retinaCleanCoords.push(coords);
        });
      }

      // If there is a custom template, use it
      var cssFormat = 'spritesmith-custom';
      var cssOptions = data.cssOpts || {};
      if (cssTemplate) {
        if (typeof cssTemplate === 'function') {
          templater.addTemplate(cssFormat, cssTemplate);
        } else {
          templater.addHandlebarsTemplate(cssFormat, fs.readFileSync(cssTemplate, 'utf8'));
        }
      } else {
      // Otherwise, override the cssFormat and fallback to 'json'
      // TODO: Concatenate on `-retina` to `destCss`
        // cssFormat = data.cssFormat || cssFormats.get(destCss) || 'json';
        cssFormat = 'stylus-retina';
      }

      // Render the variables via `spritesheet-templates`
      // TODO: Move to `hogan.js` and pick up inheritance
      //   https://github.com/twitter/hogan.js
      // TODO: Or maybe handlebars with handlebars-layouts
      //   https://github.com/shannonmoeller/handlebars-layouts
      var cssStr = templater({
        sprites: cleanCoords,
        spritesheet: spritesheetInfo,
        retinaSprites: retinaCleanCoords,
        retinaSpritesheet: retinaSpritesheetInfo
      }, {
        format: cssFormat,
        formatOpts: cssOptions,
        spritesheetName: data.cssSpritesheetName,
        retinaSpritesheetName: data.retinaCssSpritesheetName
      });

      // Write it out to the CSS file
      var destCssDir = path.dirname(destCss);
      grunt.file.mkdir(destCssDir);
      fs.writeFileSync(destCss, cssStr, 'utf8');

      // Fail task if errors were logged.
      if (that.errorCount) { cb(false); }

      // Otherwise, print a success message.
      grunt.log.writeln('Files "' + destCss + '", "' + destImg + '" created.');

      // Callback
      cb(true);
    });
  }

  // Export the SpriteMaker function
  grunt.registerMultiTask('sprite', 'Spritesheet making utility', SpriteMaker);
};
<commit_msg>Added in retina pairs<commit_after>// Load in dependencies
var fs = require('fs');
var path = require('path');
var _ = require('underscore');
var async = require('async');
var templater = require('spritesheet-templates');
var spritesmith = require('spritesmith');
var url = require('url2');

// Define class to contain different extension handlers
function ExtFormat() {
  this.formatObj = {};
}
ExtFormat.prototype = {
  add: function (name, val) {
    this.formatObj[name] = val;
  },
  get: function (filepath) {
    // Grab the extension from the filepath
    var ext = path.extname(filepath);
    var lowerExt = ext.toLowerCase();

    // Look up the file extenion from our format object
    var formatObj = this.formatObj;
    var format = formatObj[lowerExt];
    return format;
  }
};

// Create img and css formats
var imgFormats = new ExtFormat();
var cssFormats = new ExtFormat();

// Add our img formats
imgFormats.add('.png', 'png');
imgFormats.add('.jpg', 'jpeg');
imgFormats.add('.jpeg', 'jpeg');

// Add our css formats
cssFormats.add('.styl', 'stylus');
cssFormats.add('.stylus', 'stylus');
cssFormats.add('.sass', 'sass');
cssFormats.add('.scss', 'scss');
cssFormats.add('.less', 'less');
cssFormats.add('.json', 'json');
cssFormats.add('.css', 'css');

function getCoordinateName(filepath) {
  // Extract the image name (exlcuding extension)
  var fullname = path.basename(filepath);
  var nameParts = fullname.split('.');

  // If there is are more than 2 parts, pop the last one
  if (nameParts.length >= 2) {
    nameParts.pop();
  }

  // Return our modified filename
  return nameParts.join('.');
}

module.exports = function gruntSpritesmith (grunt) {
  // Create a SpriteMaker function
  function SpriteMaker() {
    // Grab the raw configuration
    var data = this.data;

    // If we were invoked via `grunt-newer`, re-localize the info
    if (data.src === undefined && data.files) {
      data = data.files[0] || {};
    }

    // Determine the origin and destinations
    var src = data.src;
    var destImg = data.dest;
    var destCss = data.destCss;
    var cssTemplate = data.cssTemplate;
    var that = this;

    // Verify all properties are here
    if (!src || !destImg || !destCss) {
      return grunt.fatal('grunt.sprite requires a src, dest (img), and destCss property');
    }

    // Expand all filepaths (e.g. `*.png` -> `home.png`)
    var srcFiles = grunt.file.expand(src);

    // If there are settings for retina
    var retinaSrcFiles;
    var retinaSrcFilter = data.retinaSrcFilter;
    var retinaDestImg = data.retinaDest;
    if (retinaSrcFilter || retinaDestImg) {
      grunt.log.debug('Retina settings detected');

      // Verify our required set is present
      if (!retinaSrcFilter || !retinaDestImg) {
        return grunt.fatal('Retina settings detected. We must have both `retinaSrcFilter` and `retinaDest` ' +
          'provided for retina to work');
      }

      // Filter out our retina files
      retinaSrcFiles = [];
      srcFiles = srcFiles.filter(function filterSrcFile (filepath) {
        // If we have a retina file, filter it out
        if (grunt.file.match(retinaSrcFilter, filepath).length) {
          retinaSrcFiles.push(filepath);
          return false;
        // Otherwise, keep it in the src files
        } else {
          return true;
        }
      });
      grunt.verbose.writeln('Retina images found: ' + retinaSrcFiles.join(', '));

      // If we have a different amount of normal and retina images, complain and leave
      if (srcFiles.length !== retinaSrcFiles.length) {
        return grunt.fatal('Retina settings detected but ' + retinaSrcFiles.length + ' retina images were found. ' +
          'We have ' + srcFiles.length + ' normal images and expect these numbers to line up. ' +
          'Please double check `retinaSrcFilter`.');
      }
    }

    // Create an async callback
    var cb = this.async();

    // Determine the format of the image
    var imgOpts = data.imgOpts || {};
    var imgFormat = imgOpts.format || imgFormats.get(destImg) || 'png';

    // Set up the defautls for imgOpts
    _.defaults(imgOpts, {format: imgFormat});

    // Prepare spritesmith parameters
    var spritesmithParams = {
      src: srcFiles,
      engine: data.engine,
      algorithm: data.algorithm,
      padding: data.padding || 0,
      algorithmOpts: data.algorithmOpts || {},
      engineOpts: data.engineOpts || {},
      exportOpts: imgOpts
    };

    // In parallel
    async.parallel([
      // Run our normal task
      function normalSpritesheet (callback) {
        spritesmith(spritesmithParams, callback);
      },
      // If we have a retina task, run it as well
      function retinaSpritesheet (callback) {
        // DEV: We don't check length since we could have no images passed in
        if (retinaSrcFiles) {
          var retinaParams = _.defaults({
            src: retinaSrcFiles
          }, spritesmithParams);
          spritesmith(retinaParams, callback);
        } else {
          process.nextTick(callback);
        }
      }
    ], function handleSpritesheets (err, resultArr) {
      // If an error occurred, callback with it
      if (err) {
        grunt.fatal(err);
        return cb(err);
      }

      // Otherwise, write out the result to destImg
      var result = resultArr[0];
      var destImgDir = path.dirname(destImg);
      grunt.file.mkdir(destImgDir);
      fs.writeFileSync(destImg, result.image, 'binary');

      // Generate a listing of CSS variables
      var coordinates = result.coordinates;
      var properties = result.properties;
      var spritePath = data.imgPath || url.relative(destCss, destImg);
      var spritesheetInfo = {
        width: properties.width,
        height: properties.height,
        image: spritePath
      };
      var cssVarMap = data.cssVarMap || function noop () {};
      var cleanCoords = [];

      // Clean up the file name of the file
      Object.getOwnPropertyNames(coordinates).sort().forEach(function prepareTemplateData (file) {
        // Extract out our name
        var name = getCoordinateName(file);
        var coords = coordinates[file];

        // Specify the image for the sprite
        coords.name = name;
        coords.source_image = file;
        // DEV: `image`, `total_width`, `total_height` are deprecated as they are overwritten in `spritesheet-templates`
        coords.image = spritePath;
        coords.total_width = properties.width;
        coords.total_height = properties.height;

        // Map the coordinates through cssVarMap
        coords = cssVarMap(coords) || coords;

        // Save the cleaned name and coordinates
        cleanCoords.push(coords);
      });

      // If we have retina sprites
      var retinaCleanCoords;
      var retinaPairs;
      var retinaResult = resultArr[1];
      var retinaSpritesheetInfo;
      if (retinaResult) {
        // Write out the result to destImg
        var retinaDestImgDir = path.dirname(retinaDestImg);
        grunt.file.mkdir(retinaDestImgDir);
        fs.writeFileSync(retinaDestImg, retinaResult.image, 'binary');

        // Generate a listing of CSS variables
        var retinaCoordinates = retinaResult.coordinates;
        var retinaProperties = retinaResult.properties;
        var retinaSpritePath = data.retinaImgPath || url.relative(destCss, retinaDestImg);
        retinaSpritesheetInfo = {
          width: retinaProperties.width,
          height: retinaProperties.height,
          image: retinaSpritePath
        };
        // DEV: We reuse cssVarMap
        retinaCleanCoords = [];

        // Clean up the file name of the file
        Object.getOwnPropertyNames(retinaCoordinates).sort().forEach(function prepareRetinaTemplateData (file) {
          var name = getCoordinateName(file);
          var coords = retinaCoordinates[file];
          coords.name = name;
          coords.source_image = file;
          coords.image = retinaSpritePath;
          coords.total_width = retinaProperties.width;
          coords.total_height = retinaProperties.height;
          coords = cssVarMap(coords) || coords;
          retinaCleanCoords.push(coords);
        });

        // For all our coordinates, adjust the names of the normal ones and set up pairs
        // TODO: Should renaming occur here or within spritesheet-templates?
        retinaPairs = cleanCoords.map(function getSpritePairs (normalSprite, i) {
          var name = normalSprite.name;
          // TODO: This should come earlier at `cssVarMap`
          normalSprite.name += '-normal';
          return {
            name: name,
            normal: normalSprite,
            retina: retinaCleanCoords[i]
          };
        });
      }

      // If there is a custom template, use it
      var cssFormat = 'spritesmith-custom';
      var cssOptions = data.cssOpts || {};
      if (cssTemplate) {
        if (typeof cssTemplate === 'function') {
          templater.addTemplate(cssFormat, cssTemplate);
        } else {
          templater.addHandlebarsTemplate(cssFormat, fs.readFileSync(cssTemplate, 'utf8'));
        }
      } else {
      // Otherwise, override the cssFormat and fallback to 'json'
      // TODO: Concatenate on `-retina` to `destCss`
        // cssFormat = data.cssFormat || cssFormats.get(destCss) || 'json';
        cssFormat = 'stylus-retina';
      }

      // Render the variables via `spritesheet-templates`
      // TODO: Move to `hogan.js` and pick up inheritance
      //   https://github.com/twitter/hogan.js
      // TODO: Or maybe handlebars with handlebars-layouts
      //   https://github.com/shannonmoeller/handlebars-layouts
      var cssStr = templater({
        sprites: cleanCoords,
        spritesheet: spritesheetInfo,
        retinaPairs: retinaPairs,
        retinaSprites: retinaCleanCoords,
        retinaSpritesheet: retinaSpritesheetInfo
      }, {
        format: cssFormat,
        formatOpts: cssOptions,
        spritesheetName: data.cssSpritesheetName,
        retinaSpritesheetName: data.retinaCssSpritesheetName
      });

      // Write it out to the CSS file
      var destCssDir = path.dirname(destCss);
      grunt.file.mkdir(destCssDir);
      fs.writeFileSync(destCss, cssStr, 'utf8');

      // Fail task if errors were logged.
      if (that.errorCount) { cb(false); }

      // Otherwise, print a success message.
      grunt.log.writeln('Files "' + destCss + '", "' + destImg + '" created.');

      // Callback
      cb(true);
    });
  }

  // Export the SpriteMaker function
  grunt.registerMultiTask('sprite', 'Spritesheet making utility', SpriteMaker);
};
<|endoftext|>