<commit_before>const assert = require('assert');
const path = require('path');
const stateAbbrToName = require('./us-states');
const csvDir = path.dirname(__dirname) + '/csv';
const streetAbbrev = {
    STREET: 'ST',
    ROAD: 'RD',
    DRIVE: 'DR',
    AVENUE: 'AVE',
    COURT: 'CT',
    LANE: 'LN',
    TERRACE: 'TER',
    CIRCLE: 'CIR',
    BOULEVARD: 'BLVD',
    HIGHWAY: 'HWY',
    PLACE: 'PL',
};
const streetAbbrevRegexp = new RegExp('\\b(' + Object.keys(streetAbbrev).join('|') + ')\\b');
const ordinalAbbrev = {
    FIRST: '1ST',
    SECOND: '2ND',
    THIRD: '3RD',
    FOURTH: '4TH',
    FIFTH: '5TH',
    SIXTH: '6TH',
    SEVENTH: '7TH',
    EIGHTH: '8TH',
    NINTH: '9TH',
    TENTH: '10TH',
    ELEVENTH: '11TH',
    TWELFTH: '12TH',
    THIRTEENTH: '13TH',
    FOURTEENTH: '14TH',
    FIFTEENTH: '15TH',
    SIXTEENTH: '16TH',
    SEVENTEENTH: '17TH',
    EIGHTEENTH: '18TH',
    NINETEENTH: '19TH',
    TWENTIETH: '20TH',
};
const ordinalAbbrevRegexp = new RegExp('\\b(' + Object.keys(ordinalAbbrev).join('|') + ')\\b');

function normalizeNameAndAddress(name, address = name) {
    if (typeof name === 'object') {
        name = makeName(name);
    }
    if (typeof address === 'object') {
        address = makeAddress(address);
    }
    let normalized = name.toUpperCase()
        .replace(/[ ,]*,[ ,]*/g, ' ')
        .replace(/\./g, '')
        .replace(/'/g, '')
        .replace(/^(MR|MS|MRS|DR) /, '')
        .replace(/ AND | ?\+ ?/g, ' & ')
        .replace(/^THE /, '')
        .replace(/(?: (?:LTD|LLC|PLL?C|INC(?:ORPORATED)?|CORP(?:ORATION)?|L+P|PA|CO|PLLP|PC|ESQ|MD|PHD|DDS)+)$/, '')
        .replace(/ ?& ?/, ' ')
        // .replace(/ [A-Z] /g, ' ') // remove middle initials
        // .replace(/ (JR|SR|I{1,3})$/, '')
        .replace(/[- ]*-[- ]*/g, ' ');
    if (address) {
        normalized += ', ' + address.toUpperCase()
            .replace(/[ ,]*,[ ,]*/g, ' ')
            .replace(/\./g, '')
            .replace(/'/g, '')
            .replace(/[()]/, '')
            .replace(/ [\d -]+$/, '') // remove zip
            .replace(/[- ]*-[- ]*/g, ' ')
            .replace(/^(?:SUITE|STE|APT|APARTMENT|UNIT)[ #]+\S+ /, '')
            .replace(/\b(?:SUITE|STE|APT|APARTMENT|UNIT)[ #]+/, '#')
            .replace(/ 1\/2\b/, '')
            .replace(/# /, '#')
            .replace(/( [NS][EW] )\S+ (?=WASHINGTON)/, '$1') // apt number with no #
            .replace(/ FL(?:OOR)? \d\d?(?:[NR]?D|ST|TH)?(?: |$)/, ' ')
            .replace(/ \d\d?(?:[NR]?D|ST|TH)? FL(?:OOR)?(?: |$)/, ' ')
            .replace(/ E CAPITOL/, ' EAST CAPITOL')
            .replace(/ N (CAPITOL|CAROLINA|DAKOTA)/, ' NORTH $1')
            .replace(/ S (CAPITOL|CAROLINA|DAKOTA)/, ' SOUTH $1')
            .replace(/ N (HAMPSHIRE|JERSEY|MEXICO)/, ' NEW $1')
            .replace(/ VIRGINIA$/, ' VA')
            .replace(/ MARYLAND$/, ' MD')
            .replace(/ DISTRICT OF COLUMBIA$/, ' DC')
            .replace(/ ([NS]) ([EW]) /, ' $1$2 ')
            .replace(/^(\d+\S*) ([NS][EW]) (.+)(?= WASHINGTON DC$)/, '$1 $3 $2')
            .replace(/ MC LEAN /g, ' MCLEAN ')
            .replace(/( \w+)(\1 [A-Z]{2})$/, '$2') // remove repeated city
            .replace(/( \w+ [A-Z]{2})\1$/, '$1') // remove repeated city and state
            .replace(/ #\S+/, '') // remove apartment number
            .replace(/\W+/g, ' ')
            .replace(streetAbbrevRegexp, (m, p1) => streetAbbrev[p1])
            .replace(/ EYE ST /, ' I ST ')
            .replace(/ QUE ST /, ' Q ST ')
            .replace(/ AV /, ' AVE ')
            .replace(/ MASS AVE/, ' MASSACHUSETTS AVE')
            .replace(/ CONN AVE/, ' CONNECTICUT AVE')
            .replace(/ (?:MARTIN L(?:UTHER)? KING|MLK|M L K)(?:J\W+)?\b/, ' MLK')
            .replace(ordinalAbbrevRegexp, (m, p1) => ordinalAbbrev[p1])
            .replace(/\b([A-Z]{2})(?= AVE)/, (m, p1) => (stateAbbrToName[p1] || p1).toUpperCase())
            .trim();
    }
    // Messy fixes
    normalized = normalized.replace(/^JOHN V ZOTTOLI, /, 'JOHN ZOTTOLI, ')
        .replace(/ ZOT(?:OLLI|TOLO), /, ' ZOTTOLI, ')
        .replace(/(ZOTTOLI.*) 30125/, '$1 3025')
        .replace(/^LINDA W ZOTTOLI, /, 'LINDA ZOTTOLI, ')
        .replace(/^JOHN (?:J )?CAPOZZI?, 1?3612 .+ DC$/, 'JOHN CAPOZZI, 3612 AUSTIN ST SE WASHINGTON DC')
        .replace(/^(DYNAMIC CONCEPTS, 1730).*(?= WASHINGTON)/, '$1 17TH ST NE')
        .replace(/^DAVID A JANNARONE, /, 'DAVID JANNARONE, ')
        .replace(/(JANNARONE.*) 3765/, '$1 3715')
        .replace(/^BRETT\b.* GREENE?, 1330 GERAN.*/, 'BRETT GREENE, 1330 GERANIUM ST NW WASHINGTON DC')
        .replace(/^THORN.+ POZEN, 4822 UP.*/, 'THORN POZEN, 4822 UPTON ST NW WASHINGTON DC')
        .replace(/^KATH.* BRADLEY, 2211 30TH.*/, 'KATHERINE BRADLEY, 2211 30TH ST NW WASHINGTON DC')
        .replace(/^MANATT PHELPS.*, 1135.*/, 'MANATT PHELPS PHILLIPS, 11355 W OLYMPIC BLVD LOS ANGELES CA')
        .replace(/^(?:NEIL ALBERT|ALBERT NEIL), 135.+LOCUST.*/, 'NEIL ALBERT, 1358 LOCUST RD NW WASHINGTON DC')
        .replace(/^CLAUD.* BAILEY, .+BEACH .*DC$/, 'CLAUDE BAILEY, 1815 E BEACH DR NW WASHINGTON DC')
        .replace('DAVID MEADOWS, 305 K ST NW', 'DAVID MEADOWS, 305 K ST SE')
        .replace(/^PEDRO\b.* ALFONSO, 1809 PARKSIDE .* DC$/, 'PEDRO ALFONSO, 1809 PARKSIDE DR NW WASHINGTON DC')
        .replace(/^COREY\b.* GRIFFIN, 1515 LAW.* DC$/, 'COREY GRIFFIN, 1515 LAWRENCE ST NE WASHINGTON DC')
        .replace(/^BEVERLY PERRY, 17\d\d HOLL.* DC$/, 'BEVERLY PERRY, 1716 HOLLY ST NW WASHINGTON DC')
        .replace(/^MAX J BROWN, 475 H /, 'MAX BROWN, 475 H ')
        .replace(/^JAIR K LYNCH, /, 'JAIR LYNCH, ')
        .replace(/^REGAN.* LONG\b.* 1919 M .+ DC$/, 'REGAN ZAMBRI LONG, 1919 M ST NW WASHINGTON DC')
        .replace(/^WILLIAM B ALSUP\b/, 'WILLIAM ALSUP')
        .replace(/^TRANSCO.+ BENNING .+ WASH.+/, 'TRANSCO, 3399 BENNING RD NE WASHINGTON DC');
    return normalized;
}

function makeName(r) {
    const prefix = r.hasOwnProperty('contributor_last_name') ? 'contributor_' : 'payee_';
    return ['first_name', 'middle_name', 'last_name', 'organization_name']
        .map(c => r[prefix + c])
        .filter(v => !!v)
        .join(' ');
}

function makeAddress(r) {
    let address = r.number_and_street || '';
    if (r.city) {
        if (address) {
            address += ', ';
        }
        address += r.city;
    }
    if (r.state) {
        if (address) {
            address += ', ';
        }
        address += r.state;
    }
    if (r.zip) {
        if (address) {
            address += ' ';
        }
        address += r.zip;
    }
    return address;
}

function fixDate(date) {
    const fixedDate = date ? date.replace(/^(\d\d)\/(\d\d)\/(\d{4})$/, '$3-$1-$2') : null;
    if (fixedDate) {
        assert(fixedDate.match(/^\d{4}-\d\d-\d\d$/), `Unexpected date format "${date}"`);
    }
    return fixedDate;
}

function fixAmount(rawAmount) {
    const amount = rawAmount.replace(/^\((.+)\)$/, '-$1'); // change parens to minus
    return amount && /^-?[$.,\d]+$/.test(amount) ? +amount.replace(/[$,]/g, '') : amount;
}

function parseAddress(address) {
    address = address.replace(/(?:WA|DC)-`?200/, 'Washington, DC 200') // kluge for bad addresses
        .replace(/\b(?:0|ZZ)-\w{3} ?\w{3}$/, 'ZZ-00000') // UK address
        .replace(/( [A-Z][A-Z]-)\n(\d{5})/, '$1$2') // another kluge
        .replace(/(MA|NJ)-(\d{4})$/, '$1-0$2') // add missing 0
        .replace(/VI-(\d{3})$/, 'VA-00$1') // and more missing 0s
        .replace(/( [A-Z]{2}-)0\.(\d{5})/, '$1$2') // yet another kluge
        .replace(/ AA-0$/, ' AA-00000'); // Bowser missing data
    const lines = address.trim().split('\n');
    const lastLine = lines.pop();
    const m = lastLine.match(/^(\S.*)?\s*,\s*([A-Z]{2})[- ](\d{5}(?:-\d{4})?)$/);
    if (!m) {
        throw new Error(`Unexpected address format "${address}"`);
    }
    return {
        number_and_street: lines.join(', '),
        city: m[1],
        state: m[2],
        zip: m[3],
    };
}

function parseName(name) {
    const m = name.match(/^(?:(\S+)(?: (.+?))?? )?(\S+(?: (?:[JS]r|I+|IV|VI*)\.?)?)$/);
    if (!m) {
        throw new Error(`Unexpected name format "${name}"`);
    }
    return {
        first: m[1] || '',
        middle: m[2] || '',
        last: m[3],
    };
}

function getCsvFilename(recordType, filerType) {
    const basename = recordType + (filerType === 'principal' ? '' : ('-' + filerType));
    return `${csvDir}/${basename}.csv`;
}

function hyphenize(s) {
    return s.replace(/([a-z])(?=[A-Z])/g, '$1-')
        .toLowerCase()
        .replace(/[^a-z\d]+/g, '-')
        .replace(/^-|-$/g, '');
}

module.exports = {
    fixAmount,
    fixDate,
    getCsvFilename,
    hyphenize,
    makeAddress,
    makeName,
    normalizeNameAndAddress,
    parseAddress,
    parseName,
};
<commit_msg>avoid introducing extra "Washington" in addresses<commit_after>const assert = require('assert');
const path = require('path');
const stateAbbrToName = require('./us-states');
const csvDir = path.dirname(__dirname) + '/csv';
const streetAbbrev = {
    STREET: 'ST',
    ROAD: 'RD',
    DRIVE: 'DR',
    AVENUE: 'AVE',
    COURT: 'CT',
    LANE: 'LN',
    TERRACE: 'TER',
    CIRCLE: 'CIR',
    BOULEVARD: 'BLVD',
    HIGHWAY: 'HWY',
    PLACE: 'PL',
};
const streetAbbrevRegexp = new RegExp('\\b(' + Object.keys(streetAbbrev).join('|') + ')\\b');
const ordinalAbbrev = {
    FIRST: '1ST',
    SECOND: '2ND',
    THIRD: '3RD',
    FOURTH: '4TH',
    FIFTH: '5TH',
    SIXTH: '6TH',
    SEVENTH: '7TH',
    EIGHTH: '8TH',
    NINTH: '9TH',
    TENTH: '10TH',
    ELEVENTH: '11TH',
    TWELFTH: '12TH',
    THIRTEENTH: '13TH',
    FOURTEENTH: '14TH',
    FIFTEENTH: '15TH',
    SIXTEENTH: '16TH',
    SEVENTEENTH: '17TH',
    EIGHTEENTH: '18TH',
    NINETEENTH: '19TH',
    TWENTIETH: '20TH',
};
const ordinalAbbrevRegexp = new RegExp('\\b(' + Object.keys(ordinalAbbrev).join('|') + ')\\b');

function normalizeNameAndAddress(name, address = name) {
    if (typeof name === 'object') {
        name = makeName(name);
    }
    if (typeof address === 'object') {
        address = makeAddress(address);
    }
    let normalized = name.toUpperCase()
        .replace(/[ ,]*,[ ,]*/g, ' ')
        .replace(/\./g, '')
        .replace(/'/g, '')
        .replace(/^(MR|MS|MRS|DR) /, '')
        .replace(/ AND | ?\+ ?/g, ' & ')
        .replace(/^THE /, '')
        .replace(/(?: (?:LTD|LLC|PLL?C|INC(?:ORPORATED)?|CORP(?:ORATION)?|L+P|PA|CO|PLLP|PC|ESQ|MD|PHD|DDS)+)$/, '')
        .replace(/ ?& ?/, ' ')
        // .replace(/ [A-Z] /g, ' ') // remove middle initials
        // .replace(/ (JR|SR|I{1,3})$/, '')
        .replace(/[- ]*-[- ]*/g, ' ');
    if (address) {
        normalized += ', ' + address.toUpperCase()
            .replace(/[ ,]*,[ ,]*/g, ' ')
            .replace(/\./g, '')
            .replace(/'/g, '')
            .replace(/[()]/, '')
            .replace(/ [\d -]+$/, '') // remove zip
            .replace(/[- ]*-[- ]*/g, ' ')
            .replace(/^(?:SUITE|STE|APT|APARTMENT|UNIT)[ #]+\S+ /, '')
            .replace(/\b(?:SUITE|STE|APT|APARTMENT|UNIT)[ #]+/, '#')
            .replace(/ 1\/2\b/, '')
            .replace(/# /, '#')
            .replace(/( [NS][EW] )\S+ (?=WASHINGTON)/, '$1') // apt number with no #
            .replace(/ FL(?:OOR)? \d\d?(?:[NR]?D|ST|TH)?(?: |$)/, ' ')
            .replace(/ \d\d?(?:[NR]?D|ST|TH)? FL(?:OOR)?(?: |$)/, ' ')
            .replace(/ E CAPITOL/, ' EAST CAPITOL')
            .replace(/ N (CAPITOL|CAROLINA|DAKOTA)/, ' NORTH $1')
            .replace(/ S (CAPITOL|CAROLINA|DAKOTA)/, ' SOUTH $1')
            .replace(/ N (HAMPSHIRE|JERSEY|MEXICO)/, ' NEW $1')
            .replace(/ VIRGINIA$/, ' VA')
            .replace(/ MARYLAND$/, ' MD')
            .replace(/ DISTRICT OF COLUMBIA$/, ' DC')
            .replace(/ ([NS]) ([EW]) /, ' $1$2 ')
            .replace(/^(\d+\S*) ([NS][EW]) (.+)(?= WASHINGTON DC$)/, '$1 $3 $2')
            .replace(/ MC LEAN /g, ' MCLEAN ')
            .replace(/( \w+)(\1 [A-Z]{2})$/, '$2') // remove repeated city
            .replace(/( \w+ [A-Z]{2})\1$/, '$1') // remove repeated city and state
            .replace(/ #\S+/, '') // remove apartment number
            .replace(/\W+/g, ' ')
            .replace(streetAbbrevRegexp, (m, p1) => streetAbbrev[p1])
            .replace(/ EYE ST /, ' I ST ')
            .replace(/ QUE ST /, ' Q ST ')
            .replace(/ AV /, ' AVE ')
            .replace(/ MASS AVE/, ' MASSACHUSETTS AVE')
            .replace(/ CONN AVE/, ' CONNECTICUT AVE')
            .replace(/ (?:MARTIN L(?:UTHER)? KING|MLK|M L K)(?:J\W+)?\b/, ' MLK')
            .replace(ordinalAbbrevRegexp, (m, p1) => ordinalAbbrev[p1])
            .replace(/\b([A-Z]{2})(?= AVE)/, (m, p1) => (stateAbbrToName[p1] || p1).toUpperCase())
            .trim();
    }
    // Messy fixes
    normalized = normalized.replace(/^JOHN V ZOTTOLI, /, 'JOHN ZOTTOLI, ')
        .replace(/ ZOT(?:OLLI|TOLO), /, ' ZOTTOLI, ')
        .replace(/(ZOTTOLI.*) 30125/, '$1 3025')
        .replace(/^LINDA W ZOTTOLI, /, 'LINDA ZOTTOLI, ')
        .replace(/^JOHN (?:J )?CAPOZZI?, 1?3612 .+ DC$/, 'JOHN CAPOZZI, 3612 AUSTIN ST SE WASHINGTON DC')
        .replace(/^(DYNAMIC CONCEPTS, 1730).*(?= WASHINGTON)/, '$1 17TH ST NE')
        .replace(/^DAVID A JANNARONE, /, 'DAVID JANNARONE, ')
        .replace(/(JANNARONE.*) 3765/, '$1 3715')
        .replace(/^BRETT\b.* GREENE?, 1330 GERAN.*/, 'BRETT GREENE, 1330 GERANIUM ST NW WASHINGTON DC')
        .replace(/^THORN.+ POZEN, 4822 UP.*/, 'THORN POZEN, 4822 UPTON ST NW WASHINGTON DC')
        .replace(/^KATH.* BRADLEY, 2211 30TH.*/, 'KATHERINE BRADLEY, 2211 30TH ST NW WASHINGTON DC')
        .replace(/^MANATT PHELPS.*, 1135.*/, 'MANATT PHELPS PHILLIPS, 11355 W OLYMPIC BLVD LOS ANGELES CA')
        .replace(/^(?:NEIL ALBERT|ALBERT NEIL), 135.+LOCUST.*/, 'NEIL ALBERT, 1358 LOCUST RD NW WASHINGTON DC')
        .replace(/^CLAUD.* BAILEY, .+BEACH .*DC$/, 'CLAUDE BAILEY, 1815 E BEACH DR NW WASHINGTON DC')
        .replace('DAVID MEADOWS, 305 K ST NW', 'DAVID MEADOWS, 305 K ST SE')
        .replace(/^PEDRO\b.* ALFONSO, 1809 PARKSIDE .* DC$/, 'PEDRO ALFONSO, 1809 PARKSIDE DR NW WASHINGTON DC')
        .replace(/^COREY\b.* GRIFFIN, 1515 LAW.* DC$/, 'COREY GRIFFIN, 1515 LAWRENCE ST NE WASHINGTON DC')
        .replace(/^BEVERLY PERRY, 17\d\d HOLL.* DC$/, 'BEVERLY PERRY, 1716 HOLLY ST NW WASHINGTON DC')
        .replace(/^MAX J BROWN, 475 H /, 'MAX BROWN, 475 H ')
        .replace(/^JAIR K LYNCH, /, 'JAIR LYNCH, ')
        .replace(/^REGAN.* LONG\b.* 1919 M .+ DC$/, 'REGAN ZAMBRI LONG, 1919 M ST NW WASHINGTON DC')
        .replace(/^WILLIAM B ALSUP\b/, 'WILLIAM ALSUP')
        .replace(/^TRANSCO.+ BENNING .+ WASH.+/, 'TRANSCO, 3399 BENNING RD NE WASHINGTON DC');
    return normalized;
}

function makeName(r) {
    const prefix = r.hasOwnProperty('contributor_last_name') ? 'contributor_' : 'payee_';
    return ['first_name', 'middle_name', 'last_name', 'organization_name']
        .map(c => r[prefix + c])
        .filter(v => !!v)
        .join(' ');
}

function makeAddress(r) {
    let address = r.number_and_street || '';
    if (r.city) {
        if (address) {
            address += ', ';
        }
        address += r.city;
    }
    if (r.state) {
        if (address) {
            address += ', ';
        }
        address += r.state;
    }
    if (r.zip) {
        if (address) {
            address += ' ';
        }
        address += r.zip;
    }
    return address;
}

function fixDate(date) {
    const fixedDate = date ? date.replace(/^(\d\d)\/(\d\d)\/(\d{4})$/, '$3-$1-$2') : null;
    if (fixedDate) {
        assert(fixedDate.match(/^\d{4}-\d\d-\d\d$/), `Unexpected date format "${date}"`);
    }
    return fixedDate;
}

function fixAmount(rawAmount) {
    const amount = rawAmount.replace(/^\((.+)\)$/, '-$1'); // change parens to minus
    return amount && /^-?[$.,\d]+$/.test(amount) ? +amount.replace(/[$,]/g, '') : amount;
}

function parseAddress(address) {
    address = address.replace(/\bWA-200/, 'DC-200') // Washington confusion
        .replace(/DC-`200/, 'DC-200') // remove extraneous character
        .replace(/(?:^|\n)DC-(?=\d+)/, '$1Washington, DC-') // add missing city
        .replace(/\b(?:0|ZZ)-\w{3} ?\w{3}$/, 'ZZ-00000') // UK address
        .replace(/( [A-Z][A-Z]-)\n(\d{5})/, '$1$2') // another kluge
        .replace(/(MA|NJ)-(\d{4})$/, '$1-0$2') // add missing 0
        .replace(/VI-(\d{3})$/, 'VA-00$1') // and more missing 0s
        .replace(/( [A-Z]{2}-)0\.(\d{5})/, '$1$2') // yet another kluge
        .replace(/ AA-0$/, ' AA-00000'); // Bowser missing data
    const lines = address.trim().split('\n');
    const lastLine = lines.pop();
    const m = lastLine.match(/^(\S.*)?\s*,\s*([A-Z]{2})[- ](\d{5}(?:-\d{4})?)$/);
    if (!m) {
        throw new Error(`Unexpected address format "${address}"`);
    }
    return {
        number_and_street: lines.join(', '),
        city: m[1],
        state: m[2],
        zip: m[3],
    };
}

function parseName(name) {
    const m = name.match(/^(?:(\S+)(?: (.+?))?? )?(\S+(?: (?:[JS]r|I+|IV|VI*)\.?)?)$/);
    if (!m) {
        throw new Error(`Unexpected name format "${name}"`);
    }
    return {
        first: m[1] || '',
        middle: m[2] || '',
        last: m[3],
    };
}

function getCsvFilename(recordType, filerType) {
    const basename = recordType + (filerType === 'principal' ? '' : ('-' + filerType));
    return `${csvDir}/${basename}.csv`;
}

function hyphenize(s) {
    return s.replace(/([a-z])(?=[A-Z])/g, '$1-')
        .toLowerCase()
        .replace(/[^a-z\d]+/g, '-')
        .replace(/^-|-$/g, '');
}

module.exports = {
    fixAmount,
    fixDate,
    getCsvFilename,
    hyphenize,
    makeAddress,
    makeName,
    normalizeNameAndAddress,
    parseAddress,
    parseName,
};
<|endoftext|>


<commit_before>module.exports = async (ctx, task) => {
    const got = require('got');
    let response;

    try {
        response = await got.get('https://api.github.com/repos/TryGhost/Ghost/releases', {json: true, timeout: 5000});
    } catch (err) {
        task.title = 'Unable to fetch release notes';
        return;
    }

    const relevantNotes = response.body.filter(note => note.tag_name.replace('v', '') === ctx.version)[0];

    if (!relevantNotes) {
        task.title = 'Release notes were not found';
        return;
    }

    task.title = 'Fetched release notes';
    ctx.ui.log(`\n${relevantNotes.body}\n`, 'green');
};
<commit_msg>perf(release-notes): prefer find() over filter()[0]<commit_after>module.exports = async (ctx, task) => {
    const got = require('got');
    let response;

    try {
        response = await got.get('https://api.github.com/repos/TryGhost/Ghost/releases', {json: true, timeout: 5000});
    } catch (err) {
        task.title = 'Unable to fetch release notes';
        return;
    }

    const relevantNotes = response.body.find(note => note.tag_name.replace('v', '') === ctx.version);

    if (!relevantNotes) {
        task.title = 'Release notes were not found';
        return;
    }

    task.title = 'Fetched release notes';
    ctx.ui.log(`\n${relevantNotes.body}\n`, 'green');
};
<|endoftext|>


<commit_before>var chalk = require('chalk'),
	constants = require('./constants'),
	format = require('util').format,
	fs = require('fs-extra'),
	logger = require('./logger'),
	path = require('path'),
	server = require('./server');

var $u = chalk.underline,
	$c = chalk.cyan;

exports.addToApp = function addToApp(file, platform) {
	var src = path.resolve(file);

	if (platform === 'ios') {
		var relPath = path.join('__modules', src.replace(/^\//, '')),
			dst = path.resolve(constants.resourcesDir, path.basename(file)),
			dstModule = path.resolve(constants.resourcesDir, relPath);

		// make sure source file exists
		if (!fs.existsSync(src)) {
			logger.error('file does not exist: "' + src + '"');
			return;
		}

		// copy, or show error if it fails
		try {
			fs.ensureDirSync(path.dirname(dst));
			fs.copySync(src, dst);

			if (/\.(?:js|json)$/.test(dstModule)) {
				fs.ensureDirSync(path.dirname(dstModule));
				fs.copySync(src, dstModule);
			}
		} catch (e) {
			logger.error('Failed to copy file to app: "' + src + '"');
			return;
		}
	} else {
		server.emit('file', fs.readFileSync(src));
	}
};

exports.printCommands = function printCommands(padding) {
	var pad = new Array(padding+1 || 5).join(' '),
		log = function() {
			var args = Array.prototype.slice.call(arguments);
			args[0] && (args[0] = pad + args[0]);
			console.log.apply(console, args);
		};

	log(format('%s %s %s', $c('.add'), $u('file'), $u('...')));
	log('  Add file(s) to the app');
	log('');
	log(format('%s', $c('.break')));
	log('  Abort the current multi-line statement');
	log('');
	log(format('%s', $c('.clear')));
	log('  Create a new execution context for the current REPL');
	log('');
	log(format('%s', $c('.exit')));
	log('  Exit triple');
	log('');
	log(format('%s', $c('.help')));
	log('  Shows command help while in the REPL');
	log('');
	log(format('%s <%s> [delay]', $c('.load'), $u('file')));
	log('  Load a local or remote JS file into the REPL, with optional delay in ms');
	log('');
	log(format('%s <%s>', $c('.save'), $u('file')));
	log('  Save the current REPL session JS to a file');
	log('');
};
<commit_msg>return src from addToApp()<commit_after>var chalk = require('chalk'),
	constants = require('./constants'),
	format = require('util').format,
	fs = require('fs-extra'),
	logger = require('./logger'),
	path = require('path'),
	server = require('./server');

var $u = chalk.underline,
	$c = chalk.cyan;

exports.addToApp = function addToApp(file, platform) {
	var src = path.resolve(file);

	if (platform === 'ios') {
		var relPath = path.join('__modules', src.replace(/^\//, '')),
			dst = path.resolve(constants.resourcesDir, path.basename(file)),
			dstModule = path.resolve(constants.resourcesDir, relPath);

		// make sure source file exists
		if (!fs.existsSync(src)) {
			logger.error('file does not exist: "' + src + '"');
			return;
		}

		// copy, or show error if it fails
		try {
			fs.ensureDirSync(path.dirname(dst));
			fs.copySync(src, dst);

			if (/\.(?:js|json)$/.test(dstModule)) {
				fs.ensureDirSync(path.dirname(dstModule));
				fs.copySync(src, dstModule);
			}
		} catch (e) {
			logger.error('Failed to copy file to app: "' + src + '"');
			return;
		}
	} else {
		server.emit('file', fs.readFileSync(src));
	}

	return src;
};

exports.printCommands = function printCommands(padding) {
	var pad = new Array(padding+1 || 5).join(' '),
		log = function() {
			var args = Array.prototype.slice.call(arguments);
			args[0] && (args[0] = pad + args[0]);
			console.log.apply(console, args);
		};

	log(format('%s %s %s', $c('.add'), $u('file'), $u('...')));
	log('  Add file(s) to the app');
	log('');
	log(format('%s', $c('.break')));
	log('  Abort the current multi-line statement');
	log('');
	log(format('%s', $c('.clear')));
	log('  Create a new execution context for the current REPL');
	log('');
	log(format('%s', $c('.exit')));
	log('  Exit triple');
	log('');
	log(format('%s', $c('.help')));
	log('  Shows command help while in the REPL');
	log('');
	log(format('%s <%s> [delay]', $c('.load'), $u('file')));
	log('  Load a local or remote JS file into the REPL, with optional delay in ms');
	log('');
	log(format('%s <%s>', $c('.save'), $u('file')));
	log('  Save the current REPL session JS to a file');
	log('');
};
<|endoftext|>


<commit_before>/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * Authors: klao@google.com, napszel@gmail.com, errge@google.com
 */

var util = {};

util.global = this;

////////////////////////////////////////////////////////////////////////////////
// Console and logging.

// Please use util.c.debug, util.c.trace, util.c.etc through the
// project, since this is a safe way to log in Firebug and Chrome.
// If Firebug is not started, then the util.c.* won't cause any
// problems just simply do nothing.

(function() {
  function empty() {}

  if (util.global.console) {
    util.c = console;
  } else {
    util.c = {};
    var functions = "log debug info warning error assert trace profile profileEnd".split(" ");
    for (var i = 0; i < functions.length; ++i) {
      util.c[functions[i]] = empty;
    }
  }
})();

////////////////////////////////////////////////////////////////////////////////
// idoMatch

util.array = function(n, e) {
  switch (n) {
    case 1:  return [e];
    case 2:  return [e,e];
    case 3:  return [e,e,e];
    case 4:  return [e,e,e,e];
  }
  var ret = [];
  for (var i = 0; i < n; ++i) {
    ret[i] = e;
  }
  return ret;
};

util.sum = function(arr) {
  var sum = 0;
  for (i in arr) {
    sum += arr[i];
  }
  return sum;
};

util.max = function(arr) {
  return Math.max.apply(Math, arr);
};

util.isAlphaNum = function(chr) { return chr.match(/^[A-Za-z0-9]$/); };

util.isSep = function(chr) {
  return " ,./?;:'\"()[]{}=+-_!@#$%^&*\\\t\n".indexOf(chr) != -1;
};

util.toLowerCase = function(str) { return str.toLowerCase(); };

util.idoMatch = function(search_str, _strs, weights) {
  // Additional space at the end is to trigger word end handling after
  // the last word.
  search_str = search_str.toLowerCase() + " ";

  var n = _strs.length;
  var strs = _strs.map(util.toLowerCase);
  if (!weights) {
    weights = util.array(n, 1);
  } else {
    util.c.assert(weights.length == n);
  }

  // Helper array, only used to create a copy of.
  var zeroes = util.array(n, 0);

  var indexes = zeroes.slice(0);
  var scores = zeroes.slice(0);
  var mults = weights.slice(0);
  var totscore = 0;

  var matches = n;
  for (var i=0; i<search_str.length; ++i) {
    if (search_str.charAt(i) == " ") {
      // score bonus for word end.
      for (var j = 0; j < n; ++j) {
        var index = indexes[j];
        if (index > 0) {
          if (index == strs[j].length) {
            scores[j] += 1.8 * (mults[j] - weights[j]);
          } else if (util.isSep(strs[j].charAt(index))) {
            scores[j] += 1.6 * (mults[j] - weights[j]);
          }
        }
      }

      totscore += util.max(scores);
      // TODO(klao): profile if this is faster than recreating the arrays.
      for (var k = 0; k < n; ++k) {
        indexes[k] = 0;
        scores[k] = 0;
        mults[k] = weights[k];
      }
      matches = n;
      continue;
    }

    for (var j=0; j<n; ++j) {
      if (indexes[j] == -1) {
        continue;
      }
      var newindex = strs[j].indexOf(search_str.charAt(i), indexes[j]);
      if (newindex != -1) {
        // continuous match:
        if (newindex > 0 && newindex == indexes[j]) {
          mults[j] *= 2;
        } else {
          mults[j] = weights[j];
        }
        // beginning of a word
        if (newindex == 0) {
          mults[j] *= 2.2;
        } else if (util.isSep(strs[j].charAt(newindex-1))) {
          mults[j] *= 2.0;
        }
        scores[j] += mults[j];
        indexes[j] = newindex + 1;
      } else {
        scores[j] = 0;
        --matches;
        indexes[j] = -1;
      }
    }
    if (matches == 0) return -1;
  }
  return totscore;
};

util.zwws = function(str) {
  return str.replace(/([^ -]{6})([^ -])/g, "$1\u200B$2");
};

////////////////////////////////////////////////////////////////////////////////
// Misc

util.timer = function(timeout, fn) {
  return window.setTimeout(fn, timeout);
};

util.cancelTimer = function(timeout_id) {
  window.clearTimeout(timeout_id);
};

/**
 * Call a callback function after count number of invocations.
 */
util.barrierClosure = function(count, closure) {
  return function() {
    if (--count == 0 && closure) {
      closure.call();
    }
  };
};

////////////////////////////////////////////////////////////////////////////////
// Load-on-demand

util.loadFile = (function() {
    var scripts = {};

    function loadFile(file, callback) {
      if (!file) {
        if (callback) callback.call();
        return;
      }
      var s = scripts[file];
      if (s) {
        if (s.loaded && callback) {
          callback.call();
          return;
        }
        // Already added to DOM but still loading, so queue this
        // callback call on the load event.
        if (callback) {
          s.dom.load(callback);
        }
        return;
      }

      // We encountered this script the first time.
      s = {};
      s.loaded = false;

      s.dom = $("<script></script>");
      s.dom.attr("src", file);
      s.dom.attr("type", "text/javascript");
      var done = function() {
        s.loaded = true;
        if (callback) callback.call();
      };
      s.dom.load(done);
      // TODO(klao): we shouldn't just silently ignore load errors!
      s.dom.error(done);
      // This doesn't work. jQuery is too smart and transforms
      // <script> appends into ajax queries!
      //$("head").append(s.dom);
      $("head").get(0).appendChild(s.dom.get(0));
      scripts[file] = s;

      // In firefox error callback does not fire for files that fail
      // to load locally. This introduces hard to debug errors.
      util.timer(2000, function() {
          if (!s.loaded) {
            alert("Script " + file + " failed to load after 2 seconds! " +
                  "Please, check your config.");
          }
        });
    }

    return loadFile;
  })();

util.load = function(files, callback) {
  files = util.asarray(files);
  if (!files || files.length == 0) {
    if (callback) callback.call();
    return;
  }
  if (files.length == 1) {
    util.loadFile(files[0], callback);
    return;
  }
  var barrier = util.barrierClosure(files.length, callback);
  for (var i = 0; i < files.length; ++i) {
    util.loadFile(files[i], barrier);
  }
};

/**
 * Import other javascript files in order and then call back.
 *
 * If the imported javascript files use this method again, importing
 * will still happen in order.
 *
 * @param {Array.<string>} files The files to load.
 * @param {Function} callback Call this function at the end.
 */
util.loadInGlobalOrder = (function(/* files, callback */) {
    var loadStack = [];

    function processStack() {
      if (loadStack.length == 0) return;
      var top = loadStack.pop();
      if (typeof top == "string") {
        // A file name -> load it.
        util.loadFile(top, processStack);
      } else {
        // A callback -> call it and proceed immediately.
        if (top) top.call();
        processStack();
      }
    }

    function loadInGlobalOrder(files, callback) {
      if (callback) loadStack.push(callback);
      Array.prototype.push.apply(loadStack, files.reverse());
      processStack();
    }

    return loadInGlobalOrder;
  })();

////////////////////////////////////////////////////////////////////////////////
// Inheritance

util.extend = function(child, supertype) {
   child.prototype.__proto__ = supertype.prototype;
   child.prototype.__super = supertype;
};

////////////////////////////////////////////////////////////////////////////////
/**
 * Use as data.  If a function, then call it and use the result.
 */
util.asdata = function(obj) {
  return (typeof obj === "function"
          ? obj.call()
          : obj);
};

////////////////////////////////////////////////////////////////////////////////
/**
 * Use as array.  If not, then wrap it in a single element array.
 */
util.asarray = function(obj) {
  return (obj instanceof Array
          ? obj
          : [obj]);
};

////////////////////////////////////////////////////////////////////////////////
/**
 * Bind this.
 *
 * Creates a function in which the 'this' argument is bound.  Very
 * useful when this is not set according to your intention for some
 * event by the browser.
 *
 * @param {function} fn The function to wrap.
 * @param {object} self Bind "this" to self when running the wrapper.
 */
util.bind = (function(/*fn, self*/) {
    function bindNative(fn, self) {
      return fn.bind(self);
    }

    function bindJs(fn, self) {
      return function(var_args) {
        return fn.apply(self, arguments);
      };
    }

    if (Function.prototype.bind) {
      return bindNative;
    } else {
      return bindJs;
    }
  })();

////////////////////////////////////////////////////////////////////////////////
// Background tasks.
// Poor man's implementation of cooperative preempting.

util.backgroundCounter = 0;
util.backgroundTasks = {};

/**
 * Will automatically "cancel" any running background task with the
 * same id.
 *
 * @param {string} id Arbitrary string identifying the task. Must not
 * contain colon char.

 * @param {function} closure Code to run. On running should return
 * true if it wants to "yield" (i.e. it will be scheduled to run
 * again), or false to indicate that this background task has ended.
 * Will get a boolean argument, true: indicating that this task has
 * been "joined", so it can finish all the work in one slice; false:
 * regular operation, the task should "yield" after a short slice.

 * @param {object} self "this" will be bound to the self parameter
 * while running the closure.
 */
util.startBackgroundTask = function(id, closure, self) {
  util.c.assert(id.indexOf(":") == -1);
  ++util.backgroundCounter;
  self = self || util.global;
  var task = {c: util.backgroundCounter, run: closure, self: self};
  util.backgroundTasks[id] = task;
  // We do the first slice immediately.
  util.backgroundStep(id, task);
};

util.backgroundMessageHandler = function(event) {
  if (event.source != window || typeof event.data != "string"
      || event.data.indexOf("BT:") != 0) {
    return;
  }
  event.stopPropagation();
  var payload = event.data.substr(3).split(":");

  var task = util.backgroundTasks[payload[0]];
  if (!task) {
    // Task was joined or cancelled.
    return;
  }
  if (payload[1] != task.c) {
    // Task was overrun (user started another task with the same id).
    return;
  }
  util.backgroundStep(payload[0], task);
};

window.addEventListener("message", util.backgroundMessageHandler, true);

util.backgroundStep = function(id, task) {
  var yielded = task.run.call(task.self, false);
  if (yielded) {
    window.postMessage("BT:" + id + ":" + task.c, "*");
  } else {
    // The background task has terminated.
    util.backgroundTasks[id] = undefined;
  }
};

util.joinBackgroundTask = function(id) {
  var task = util.backgroundTasks[id];
  if (!task)
    return;
  util.backgroundTasks[id] = undefined;
  var running = true;
  while (running) {
    running = task.run.call(task.self, true);
  };
};

util.cancelBackgroundTask = function(id) {
  util.backgroundTasks[id] = undefined;
};

util.isBackgroundTaskRunning = function(id) {
  return Boolean(util.backgroundTasks[id]);
};
<commit_msg>Fix race in loadInGlobalOrder: don't start loading immediately if there is already a loading chain in progress.<commit_after>/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * Authors: klao@google.com, napszel@gmail.com, errge@google.com
 */

var util = {};

util.global = this;

////////////////////////////////////////////////////////////////////////////////
// Console and logging.

// Please use util.c.debug, util.c.trace, util.c.etc through the
// project, since this is a safe way to log in Firebug and Chrome.
// If Firebug is not started, then the util.c.* won't cause any
// problems just simply do nothing.

(function() {
  function empty() {}

  if (util.global.console) {
    util.c = console;
  } else {
    util.c = {};
    var functions = "log debug info warning error assert trace profile profileEnd".split(" ");
    for (var i = 0; i < functions.length; ++i) {
      util.c[functions[i]] = empty;
    }
  }
})();

////////////////////////////////////////////////////////////////////////////////
// idoMatch

util.array = function(n, e) {
  switch (n) {
    case 1:  return [e];
    case 2:  return [e,e];
    case 3:  return [e,e,e];
    case 4:  return [e,e,e,e];
  }
  var ret = [];
  for (var i = 0; i < n; ++i) {
    ret[i] = e;
  }
  return ret;
};

util.sum = function(arr) {
  var sum = 0;
  for (i in arr) {
    sum += arr[i];
  }
  return sum;
};

util.max = function(arr) {
  return Math.max.apply(Math, arr);
};

util.isAlphaNum = function(chr) { return chr.match(/^[A-Za-z0-9]$/); };

util.isSep = function(chr) {
  return " ,./?;:'\"()[]{}=+-_!@#$%^&*\\\t\n".indexOf(chr) != -1;
};

util.toLowerCase = function(str) { return str.toLowerCase(); };

util.idoMatch = function(search_str, _strs, weights) {
  // Additional space at the end is to trigger word end handling after
  // the last word.
  search_str = search_str.toLowerCase() + " ";

  var n = _strs.length;
  var strs = _strs.map(util.toLowerCase);
  if (!weights) {
    weights = util.array(n, 1);
  } else {
    util.c.assert(weights.length == n);
  }

  // Helper array, only used to create a copy of.
  var zeroes = util.array(n, 0);

  var indexes = zeroes.slice(0);
  var scores = zeroes.slice(0);
  var mults = weights.slice(0);
  var totscore = 0;

  var matches = n;
  for (var i=0; i<search_str.length; ++i) {
    if (search_str.charAt(i) == " ") {
      // score bonus for word end.
      for (var j = 0; j < n; ++j) {
        var index = indexes[j];
        if (index > 0) {
          if (index == strs[j].length) {
            scores[j] += 1.8 * (mults[j] - weights[j]);
          } else if (util.isSep(strs[j].charAt(index))) {
            scores[j] += 1.6 * (mults[j] - weights[j]);
          }
        }
      }

      totscore += util.max(scores);
      // TODO(klao): profile if this is faster than recreating the arrays.
      for (var k = 0; k < n; ++k) {
        indexes[k] = 0;
        scores[k] = 0;
        mults[k] = weights[k];
      }
      matches = n;
      continue;
    }

    for (var j=0; j<n; ++j) {
      if (indexes[j] == -1) {
        continue;
      }
      var newindex = strs[j].indexOf(search_str.charAt(i), indexes[j]);
      if (newindex != -1) {
        // continuous match:
        if (newindex > 0 && newindex == indexes[j]) {
          mults[j] *= 2;
        } else {
          mults[j] = weights[j];
        }
        // beginning of a word
        if (newindex == 0) {
          mults[j] *= 2.2;
        } else if (util.isSep(strs[j].charAt(newindex-1))) {
          mults[j] *= 2.0;
        }
        scores[j] += mults[j];
        indexes[j] = newindex + 1;
      } else {
        scores[j] = 0;
        --matches;
        indexes[j] = -1;
      }
    }
    if (matches == 0) return -1;
  }
  return totscore;
};

util.zwws = function(str) {
  return str.replace(/([^ -]{6})([^ -])/g, "$1\u200B$2");
};

////////////////////////////////////////////////////////////////////////////////
// Misc

util.timer = function(timeout, fn) {
  return window.setTimeout(fn, timeout);
};

util.cancelTimer = function(timeout_id) {
  window.clearTimeout(timeout_id);
};

/**
 * Call a callback function after count number of invocations.
 */
util.barrierClosure = function(count, closure) {
  return function() {
    if (--count == 0 && closure) {
      closure.call();
    }
  };
};

////////////////////////////////////////////////////////////////////////////////
// Load-on-demand

util.loadFile = (function() {
    var scripts = {};

    function loadFile(file, callback) {
      if (!file) {
        if (callback) callback.call();
        return;
      }
      var s = scripts[file];
      if (s) {
        if (s.loaded && callback) {
          callback.call();
          return;
        }
        // Already added to DOM but still loading, so queue this
        // callback call on the load event.
        if (callback) {
          s.dom.load(callback);
        }
        return;
      }

      // We encountered this script the first time.
      s = {};
      s.loaded = false;

      s.dom = $("<script></script>");
      s.dom.attr("src", file);
      s.dom.attr("type", "text/javascript");
      var done = function() {
        s.loaded = true;
        if (callback) callback.call();
      };
      s.dom.load(done);
      // TODO(klao): we shouldn't just silently ignore load errors!
      s.dom.error(done);
      // This doesn't work. jQuery is too smart and transforms
      // <script> appends into ajax queries!
      //$("head").append(s.dom);
      $("head").get(0).appendChild(s.dom.get(0));
      scripts[file] = s;

      // In firefox error callback does not fire for files that fail
      // to load locally. This introduces hard to debug errors.
      util.timer(2000, function() {
          if (!s.loaded) {
            alert("Script " + file + " failed to load after 2 seconds! " +
                  "Please, check your config.");
          }
        });
    }

    return loadFile;
  })();

util.load = function(files, callback) {
  files = util.asarray(files);
  if (!files || files.length == 0) {
    if (callback) callback.call();
    return;
  }
  if (files.length == 1) {
    util.loadFile(files[0], callback);
    return;
  }
  var barrier = util.barrierClosure(files.length, callback);
  for (var i = 0; i < files.length; ++i) {
    util.loadFile(files[i], barrier);
  }
};

/**
 * Import other javascript files in order and then call back.
 *
 * If the imported javascript files use this method again, importing
 * will still happen in order.
 *
 * @param {Array.<string>} files The files to load.
 * @param {Function} callback Call this function at the end.
 */
util.loadInGlobalOrder = (function(/* files, callback */) {
    var loadStack = [];
    var processing = false;

    function processStack() {
      if (loadStack.length == 0) {
        processing = false;
        return;
      }
      var top = loadStack.pop();
      if (typeof top == "string") {
        // A file name -> load it.
        util.loadFile(top, processStack);
      } else {
        // A callback -> call it and proceed immediately.
        if (top) top.call();
        processStack();
      }
    }

    function loadInGlobalOrder(files, callback) {
      if (callback) loadStack.push(callback);
      Array.prototype.push.apply(loadStack, files.reverse());
      if (!processing) {
        processing = true;
        processStack();
      }
    }

    return loadInGlobalOrder;
  })();

////////////////////////////////////////////////////////////////////////////////
// Inheritance

util.extend = function(child, supertype) {
   child.prototype.__proto__ = supertype.prototype;
   child.prototype.__super = supertype;
};

////////////////////////////////////////////////////////////////////////////////
/**
 * Use as data.  If a function, then call it and use the result.
 */
util.asdata = function(obj) {
  return (typeof obj === "function"
          ? obj.call()
          : obj);
};

////////////////////////////////////////////////////////////////////////////////
/**
 * Use as array.  If not, then wrap it in a single element array.
 */
util.asarray = function(obj) {
  return (obj instanceof Array
          ? obj
          : [obj]);
};

////////////////////////////////////////////////////////////////////////////////
/**
 * Bind this.
 *
 * Creates a function in which the 'this' argument is bound.  Very
 * useful when this is not set according to your intention for some
 * event by the browser.
 *
 * @param {function} fn The function to wrap.
 * @param {object} self Bind "this" to self when running the wrapper.
 */
util.bind = (function(/*fn, self*/) {
    function bindNative(fn, self) {
      return fn.bind(self);
    }

    function bindJs(fn, self) {
      return function(var_args) {
        return fn.apply(self, arguments);
      };
    }

    if (Function.prototype.bind) {
      return bindNative;
    } else {
      return bindJs;
    }
  })();

////////////////////////////////////////////////////////////////////////////////
// Background tasks.
// Poor man's implementation of cooperative preempting.

util.backgroundCounter = 0;
util.backgroundTasks = {};

/**
 * Will automatically "cancel" any running background task with the
 * same id.
 *
 * @param {string} id Arbitrary string identifying the task. Must not
 * contain colon char.

 * @param {function} closure Code to run. On running should return
 * true if it wants to "yield" (i.e. it will be scheduled to run
 * again), or false to indicate that this background task has ended.
 * Will get a boolean argument, true: indicating that this task has
 * been "joined", so it can finish all the work in one slice; false:
 * regular operation, the task should "yield" after a short slice.

 * @param {object} self "this" will be bound to the self parameter
 * while running the closure.
 */
util.startBackgroundTask = function(id, closure, self) {
  util.c.assert(id.indexOf(":") == -1);
  ++util.backgroundCounter;
  self = self || util.global;
  var task = {c: util.backgroundCounter, run: closure, self: self};
  util.backgroundTasks[id] = task;
  // We do the first slice immediately.
  util.backgroundStep(id, task);
};

util.backgroundMessageHandler = function(event) {
  if (event.source != window || typeof event.data != "string"
      || event.data.indexOf("BT:") != 0) {
    return;
  }
  event.stopPropagation();
  var payload = event.data.substr(3).split(":");

  var task = util.backgroundTasks[payload[0]];
  if (!task) {
    // Task was joined or cancelled.
    return;
  }
  if (payload[1] != task.c) {
    // Task was overrun (user started another task with the same id).
    return;
  }
  util.backgroundStep(payload[0], task);
};

window.addEventListener("message", util.backgroundMessageHandler, true);

util.backgroundStep = function(id, task) {
  var yielded = task.run.call(task.self, false);
  if (yielded) {
    window.postMessage("BT:" + id + ":" + task.c, "*");
  } else {
    // The background task has terminated.
    util.backgroundTasks[id] = undefined;
  }
};

util.joinBackgroundTask = function(id) {
  var task = util.backgroundTasks[id];
  if (!task)
    return;
  util.backgroundTasks[id] = undefined;
  var running = true;
  while (running) {
    running = task.run.call(task.self, true);
  };
};

util.cancelBackgroundTask = function(id) {
  util.backgroundTasks[id] = undefined;
};

util.isBackgroundTaskRunning = function(id) {
  return Boolean(util.backgroundTasks[id]);
};
<|endoftext|>


<commit_before>"use strict";

const Task = require('../Task'),
    gulp = require('gulp'),
    mocha = require('gulp-mocha'),
    istanbul = require('gulp-istanbul'),
    isparta = require('isparta');

class TestTask extends Task {

    constructor(buildManager) {
        super(buildManager);
        this.command = "test";
        let extension = null;
        if (this._buildManager.options.typescript) {
            require('ts-node/register');
            extension = 'ts';
        } else {
            require('babel-core/register');
            extension = 'js';
        }
        this.dependsOn = [() => {
            return gulp.src([this._buildManager.options.scripts + '*/*.' + extension])
                .pipe(istanbul({
                    instrumenter: isparta.Instrumenter,
                    includeUntested: true
                }))
                .pipe(istanbul.hookRequire());
        }];
    }

    action() {
        return gulp.src(this._buildManager.options.test, {read: false})
            .pipe(mocha({
                reporter: 'spec',
                compilers: {
                    ts: require('ts-node/register'),
                    js: require('babel-core/register')
                }
            }))
            .pipe(istanbul.writeReports({
                reporters: ['lcov', 'json', 'text', 'text-summary', 'cobertura']
            }))
    }
}

module.exports = TestTask;<commit_msg>Disable typescript instrument on tests<commit_after>"use strict";

const Task = require('../Task'),
    gulp = require('gulp'),
    mocha = require('gulp-mocha'),
    istanbul = require('gulp-istanbul'),
    isparta = require('isparta'),
    Promise = require('bluebird');

class TestTask extends Task {

    constructor(buildManager) {
        super(buildManager);
        this.command = "test";
        let extension = null;
        if (this._buildManager.options.typescript) {
            require('ts-node/register');
            extension = 'ts';
        } else {
            require('babel-core/register');
            extension = 'js';
        }
        this.dependsOn = [() => {
            if (this._buildManager.options.typescript) return Promise.resolve();
            return gulp.src([this._buildManager.options.scripts + '*/*.' + extension])
                .pipe(istanbul({
                    instrumenter: isparta.Instrumenter,
                    includeUntested: true
                }))
                .pipe(istanbul.hookRequire());
        }];
    }

    action() {
        return gulp.src(this._buildManager.options.test, {read: false})
            .pipe(mocha({
                reporter: 'spec',
                compilers: {
                    ts: require('ts-node/register'),
                    js: require('babel-core/register')
                }
            }))
            .pipe(istanbul.writeReports({
                reporters: ['lcov', 'json', 'text', 'text-summary', 'cobertura']
            }))
    }
}

module.exports = TestTask;<|endoftext|>


<commit_before>/**
 * Created by lizhiye on 11/26/14.
 */
var Q = require('q')
  , modelInfo = require('./activityModel')
  , Model = modelInfo.model
  , fs = require('fs')
  , path = require('path');

exports.ActivityModel = Model;

/**
 * append item to db
 * @param entry
 * @param cb
 */
function saveItem(entry, cb) {
  var urlPrefix = 'https://kindle.amazon.com';
  var activity = new Model({
    uid: entry[0],
    url: urlPrefix + entry[1],
    rawUrl: entry[1],
    content: entry[2],
    note: entry[3]
  });

  activity.save(function (err, item) {
    if (err) return console.error(err);

    log('item `' + item.uid + '` is inserted.');
    cb();
  });
}

/**
 * save entryList to db
 * @param entryList
 * @param cb called after all the entries been saved to db
 */
function save2db(entryList, cb) {
  // wrap the cb to make it called until the end of the iteration
  var cbwrap = function () {
    var count = entryList.length;
    return function () {
      count--;
      if (count === 0) {
        cb();
      }
    }
  }();

  entryList.reverse().forEach(function (entry) {
    saveItem(entry, cbwrap);
  });
}
exports.save2db = save2db;

/**
 * get the latest inserted item
 * @returns {r.promise|promise|d.promise|Q.promise|x.ready.promise}
 */
function getLatestItem() {
  var deferred = Q.defer();

  Model
    .find()
    .sort({ts: -1})
    .limit(1)
    .exec(function (error, items) {
      if (error) {
        deferred.reject(new Error(error));
      } else {
        deferred.resolve(items[0]);
      }
    });

  return deferred.promise;
}
exports.getLatestItem = getLatestItem;

/**
 * close db connect
 */
function disconnectDB() {
  modelInfo.db.close();
  log('Connect to DB is closed.')
}
exports.disconnectDB = disconnectDB;

/**
 * get item by item uid
 * @param id
 * @returns {*}
 */
function getItemById(id) {
  var deferred = Q.defer();
  Model.findOne({uid: id}, function (err, item) {
    if (err) {
      deferred.reject(err)
    }

    if (item) {
      deferred.resolve(item)
    } else {
      deferred.resolve('Activity Not Found');
    }

  });

  return deferred.promise;
}
exports.getItem = getItemById;

exports.searchItems = function searchItems(key) {
  var deferred = Q.defer();

  Model
    .find({ content: new RegExp(key, 'i')})
    .exec(function (err, items) {
      if (err) {
        deferred.reject(err)
      }

      if (items) {
        deferred.resolve(items)
      } else {
        deferred.resolve('No result');
      }
    });

  return deferred.promise;

};

/**
 * get items from pageIndex
 * @param pageIndex start from 0
 * @param itemPerPage count of items in every page
 */
exports.getItems = function (pageIndex, itemPerPage) {
  itemPerPage = itemPerPage | 10;
  pageIndex = pageIndex | 0;

  var startIndex = pageIndex * itemPerPage;
  var deferred = Q.defer();

  Model
    .find()
    .sort({ts: -1})
    .skip(startIndex)
    .limit(itemPerPage)
    .exec(function (err, items) {
      if (err) {
        deferred.reject(err);
      }

      if (items) {
        deferred.resolve(items);
      } else {
        deferred.resolve('Activity Not Found');
      }
    });

  return deferred.promise;
};

exports.tailorItems = function (items, len) {
  len = len | 200;
  return items.map(function (val) {
    var content = val.content;
    var more = content.length > len ? '...' : '';

    val.content = content.substr(0, len) + more;
    val.hasMore = true;
    return val;
  });
};

exports.getItemCount = function () {
  var deferred = Q.defer();

  Model
    .find()
    .exec(function (err, items) {
      if (err) {
        deferred.reject(err);
      }

      deferred.resolve(items.length);
    });

  return deferred.promise;
};

var yaml = require('js-yaml');
var loadConfig = function () {
  var file = path.resolve(__dirname, '../config.yml');
  var config = yaml.safeLoad(fs.readFileSync(file, 'utf8'));
  return config;
};

exports.loadConfig = loadConfig;

var later = require('later');
var exec = require('child_process').exec;
exports.crontab = function () {
  var conf = loadConfig();
  return later.setInterval(f, later.parse.cron(conf.crontab));

  function f() {
    var bin = path.resolve(__dirname, '../bin/fetch');
    log('Refetch activity...');
    exec(bin, function (err, stdout) {
      if (err) {
        console.log(err)
      }
      log(stdout);
    })
  }
};

var chalk = require('chalk');
var moment = require('moment');
/**
 * colored log with timestamp
 * @param msg
 */
function log(msg) {
  process.stdout.write('[' + chalk.green(moment().format()) + '] ');
  console.log.apply(console, arguments)
}

exports.log = log;
<commit_msg>reformat<commit_after>/**
 * Created by lizhiye on 11/26/14.
 */
var Q = require('q')
  , modelInfo = require('./activityModel')
  , Model = modelInfo.model
  , fs = require('fs')
  , path = require('path');

exports.ActivityModel = Model;

/**
 * append item to db
 * @param entry
 * @param cb
 */
function saveItem(entry, cb) {
  var urlPrefix = 'https://kindle.amazon.com';
  var activity = new Model({
    uid: entry[0],
    url: urlPrefix + entry[1],
    rawUrl: entry[1],
    content: entry[2],
    note: entry[3]
  });

  activity.save(function (err, item) {
    if (err) return console.error(err);

    log('item `' + item.uid + '` is inserted.');
    cb();
  });
}

/**
 * save entryList to db
 * @param entryList
 * @param cb called after all the entries been saved to db
 */
function save2db(entryList, cb) {
  // wrap the cb to make it called until the end of the iteration
  var cbwrap = function () {
    var count = entryList.length;
    return function () {
      count--;
      if (count === 0) {
        cb();
      }
    }
  }();

  entryList.reverse().forEach(function (entry) {
    saveItem(entry, cbwrap);
  });
}
exports.save2db = save2db;

/**
 * get the latest inserted item
 * @returns {r.promise|promise|d.promise|Q.promise|x.ready.promise}
 */
function getLatestItem() {
  var deferred = Q.defer();

  Model
    .find()
    .sort({ts: -1})
    .limit(1)
    .exec(function (error, items) {
      if (error) {
        deferred.reject(new Error(error));
      } else {
        deferred.resolve(items[0]);
      }
    });

  return deferred.promise;
}
exports.getLatestItem = getLatestItem;

/**
 * close db connect
 */
function disconnectDB() {
  modelInfo.db.close();
  log('Connect to DB is closed.')
}
exports.disconnectDB = disconnectDB;

/**
 * get item by item uid
 * @param id
 * @returns {*}
 */
function getItemById(id) {
  var deferred = Q.defer();
  Model.findOne({uid: id}, function (err, item) {
    if (err) {
      deferred.reject(err)
    }

    if (item) {
      deferred.resolve(item)
    } else {
      deferred.resolve('Activity Not Found');
    }

  });

  return deferred.promise;
}
exports.getItem = getItemById;

exports.searchItems = function searchItems(key) {
  var deferred = Q.defer();

  Model
    .find({content: new RegExp(key, 'i')})
    .exec(function (err, items) {
      if (err) {
        deferred.reject(err)
      }

      if (items) {
        deferred.resolve(items)
      } else {
        deferred.resolve('No result');
      }
    });

  return deferred.promise;
};

/**
 * get items from pageIndex
 * @param pageIndex start from 0
 * @param itemPerPage count of items in every page
 */
exports.getItems = function (pageIndex, itemPerPage) {
  itemPerPage = itemPerPage | 10;
  pageIndex = pageIndex | 0;

  var startIndex = pageIndex * itemPerPage;
  var deferred = Q.defer();

  Model
    .find()
    .sort({ts: -1})
    .skip(startIndex)
    .limit(itemPerPage)
    .exec(function (err, items) {
      if (err) {
        deferred.reject(err);
      }

      if (items) {
        deferred.resolve(items);
      } else {
        deferred.resolve('Activity Not Found');
      }
    });

  return deferred.promise;
};

exports.tailorItems = function (items, len) {
  len = len | 200;
  return items.map(function (val) {
    var content = val.content;
    var more = content.length > len ? '...' : '';

    val.content = content.substr(0, len) + more;
    val.hasMore = true;
    return val;
  });
};

exports.getItemCount = function () {
  var deferred = Q.defer();

  Model
    .find()
    .exec(function (err, items) {
      if (err) {
        deferred.reject(err);
      }

      deferred.resolve(items.length);
    });

  return deferred.promise;
};

var yaml = require('js-yaml');
var loadConfig = function () {
  var file = path.resolve(__dirname, '../config.yml');
  var config = yaml.safeLoad(fs.readFileSync(file, 'utf8'));
  return config;
};

exports.loadConfig = loadConfig;

var later = require('later');
var exec = require('child_process').exec;
exports.crontab = function () {
  var conf = loadConfig();
  return later.setInterval(f, later.parse.cron(conf.crontab));

  function f() {
    var bin = path.resolve(__dirname, '../bin/fetch');
    log('Refetch activity...');
    exec(bin, function (err, stdout) {
      if (err) {
        console.log(err)
      }
      log(stdout);
    })
  }
};

var chalk = require('chalk');
var moment = require('moment');
/**
 * colored log with timestamp
 * @param msg
 */
function log(msg) {
  process.stdout.write('[' + chalk.green(moment().format()) + '] ');
  console.log.apply(console, arguments)
}

exports.log = log;
<|endoftext|>


<commit_before>var _ = require('underscore');
var Backbone = require('backbone');
var DataviewModelBase = require('./dataview-model-base');

module.exports = DataviewModelBase.extend({

  defaults: _.extend(
    {
      type: 'histogram',
      bins: 10
    },
    DataviewModelBase.prototype.defaults
  ),

  url: function () {
    var params = [];

    if (this.get('column_type')) {
      params.push('column_type=' + this.get('column_type'));
    }
    if (_.isNumber(this.get('start'))) {
      params.push('start=' + this.get('start'));
    }
    if (_.isNumber(this.get('end'))) {
      params.push('end=' + this.get('end'));
    }
    if (_.isNumber(this.get('bins'))) {
      params.push('bins=' + this.get('bins'));
    }
    if (_.isNumber(this.get('own_filter'))) {
      params.push('own_filter=' + this.get('own_filter'));
    }
    if (this.get('boundingBox') && this.get('submitBBox')) {
      params.push('bbox=' + this.get('boundingBox'));
    }

    var url = this.get('url');
    if (params.length > 0) {
      url += '?' + params.join('&');
    }
    return url;
  },

  initialize: function (attrs, opts) {
    DataviewModelBase.prototype.initialize.apply(this, arguments);
    this._data = new Backbone.Collection(this.get('data'));

    // BBox should only be included until after the first fetch, since we want to get the range of the full dataset
    this.once('change:data', function () {
      this.set('submitBBox', true);
    }, this);
    this.listenTo(this.layer, 'change:meta', this._onChangeLayerMeta);
    this.on('change:column', this._reloadMap, this);
    this.on('change:bins', this.refresh, this);
  },

  getData: function () {
    return this._data.toJSON();
  },

  getSize: function () {
    return this._data.size();
  },

  parse: function (data) {
    var numberOfBins = data.bins_count;
    var width = data.bin_width;
    var start = data.bins_start;

    var buckets = new Array(numberOfBins);

    _.each(data.bins, function (b) {
      buckets[b.bin] = b;
    });

    for (var i = 0; i < numberOfBins; i++) {
      buckets[i] = _.extend({
        bin: i,
        start: start + (i * width),
        end: start + ((i + 1) * width),
        freq: 0
      }, buckets[i]);
    }

    // FIXME - Update the end of last bin due https://github.com/CartoDB/cartodb.js/issues/926
    var lastBucket = buckets[numberOfBins - 1];
    if (lastBucket.end < lastBucket.max) {
      lastBucket.end = lastBucket.max;
    }

    this._data.reset(buckets);

    return {
      data: buckets,
      nulls: data.nulls
    };
  },

  toJSON: function (d) {
    return {
      type: 'histogram',
      options: {
        column: this.get('column'),
        bins: this.get('bins')
      }
    };
  },

  _onChangeLayerMeta: function () {
    this.filter.set('column_type', this.layer.get('meta').column_type);
  },

  _onChangeBinds: function () {
    DataviewModelBase.prototype._onChangeBinds.call(this);
    this.bind('change:histogram_sizes', function (mdl, isSizesApplied, d) {
      if (isSizesApplied) {
        this.trigger('histogram_sizes', this);
      }
    }, this);
  }
},

  // Class props
  {
    ATTRS_NAMES: DataviewModelBase.ATTRS_NAMES.concat([
      'column',
      'column_type',
      'bins',
      'start',
      'end'
    ])
  }
);
<commit_msg>Ensure bucket has values<commit_after>var _ = require('underscore');
var Backbone = require('backbone');
var DataviewModelBase = require('./dataview-model-base');

module.exports = DataviewModelBase.extend({

  defaults: _.extend(
    {
      type: 'histogram',
      bins: 10
    },
    DataviewModelBase.prototype.defaults
  ),

  url: function () {
    var params = [];

    if (this.get('column_type')) {
      params.push('column_type=' + this.get('column_type'));
    }
    if (_.isNumber(this.get('start'))) {
      params.push('start=' + this.get('start'));
    }
    if (_.isNumber(this.get('end'))) {
      params.push('end=' + this.get('end'));
    }
    if (_.isNumber(this.get('bins'))) {
      params.push('bins=' + this.get('bins'));
    }
    if (_.isNumber(this.get('own_filter'))) {
      params.push('own_filter=' + this.get('own_filter'));
    }
    if (this.get('boundingBox') && this.get('submitBBox')) {
      params.push('bbox=' + this.get('boundingBox'));
    }

    var url = this.get('url');
    if (params.length > 0) {
      url += '?' + params.join('&');
    }
    return url;
  },

  initialize: function (attrs, opts) {
    DataviewModelBase.prototype.initialize.apply(this, arguments);
    this._data = new Backbone.Collection(this.get('data'));

    // BBox should only be included until after the first fetch, since we want to get the range of the full dataset
    this.once('change:data', function () {
      this.set('submitBBox', true);
    }, this);
    this.listenTo(this.layer, 'change:meta', this._onChangeLayerMeta);
    this.on('change:column', this._reloadMap, this);
    this.on('change:bins', this.refresh, this);
  },

  getData: function () {
    return this._data.toJSON();
  },

  getSize: function () {
    return this._data.size();
  },

  parse: function (data) {
    var numberOfBins = data.bins_count;
    var width = data.bin_width;
    var start = data.bins_start;

    var buckets = new Array(numberOfBins);

    _.each(data.bins, function (b) {
      buckets[b.bin] = b;
    });

    for (var i = 0; i < numberOfBins; i++) {
      buckets[i] = _.extend({
        bin: i,
        start: start + (i * width),
        end: start + ((i + 1) * width),
        freq: 0
      }, buckets[i]);
    }

    // FIXME - Update the end of last bin due https://github.com/CartoDB/cartodb.js/issues/926
    var lastBucket = buckets[numberOfBins - 1];
    if (lastBucket && lastBucket.end < lastBucket.max) {
      lastBucket.end = lastBucket.max;
    }

    this._data.reset(buckets);

    return {
      data: buckets,
      nulls: data.nulls
    };
  },

  toJSON: function (d) {
    return {
      type: 'histogram',
      options: {
        column: this.get('column'),
        bins: this.get('bins')
      }
    };
  },

  _onChangeLayerMeta: function () {
    this.filter.set('column_type', this.layer.get('meta').column_type);
  },

  _onChangeBinds: function () {
    DataviewModelBase.prototype._onChangeBinds.call(this);
    this.bind('change:histogram_sizes', function (mdl, isSizesApplied, d) {
      if (isSizesApplied) {
        this.trigger('histogram_sizes', this);
      }
    }, this);
  }
},

  // Class props
  {
    ATTRS_NAMES: DataviewModelBase.ATTRS_NAMES.concat([
      'column',
      'column_type',
      'bins',
      'start',
      'end'
    ])
  }
);
<|endoftext|>


<commit_before>import _ from 'lodash'
import Adapter from './adapter'
import CriteriaParser from 'waterline-sequel/sequel/lib/criteriaProcessor'
import SpatialUtil from './spatial'

const Util = {

  PG_MAX_INT: 2147483647,

  getTransaction (txn, query) {
    if (Util.isTransaction(txn)) {
      return txn
    }
    else {
      return query
    }
  },

  isTransaction (txn) {
    return txn && _.isFunction(txn.commit)
  },

  /**
   * Apply a primary key constraint to a table
   *
   * @param table - a knex table object
   * @param definition - a waterline attribute definition
   */
  applyPrimaryKeyConstraints (table, definition) {
    let primaryKeys = _.keys(_.pick(definition, attribute => {
      return attribute.primaryKey
    }))

    if (!primaryKeys.length) return

    return table.primary(primaryKeys)
  },

  applyCompositeUniqueConstraints (table, definition) {
    _.each(definition, (attribute, name) => {
      let uniqueDef = attribute.unique || { }
      if (attribute.primaryKey) return
      if (_.isEmpty(uniqueDef)) return
      if (!_.isArray(uniqueDef.composite)) return

      let uniqueKeys = _.unique([ name, ...uniqueDef.composite ])

      table.unique(uniqueKeys)
    })
  },

  applyTableConstraints (table, definition) {
    return Promise.all([
      Util.applyPrimaryKeyConstraints(table, definition),
      Util.applyCompositeUniqueConstraints(table, definition)
    ])
  },

  applyColumnConstraints (column, definition) {
    if (_.isString(definition)) {
      return
    }
    return _.map(definition, (value, key) => {
      if (key == 'defaultsTo' && definition.autoIncrement && value == 'AUTO_INCREMENT') {
        return
      }

      return Util.applyParticularColumnConstraint(column, key, value, definition)
    })
  },

  /**
   * Apply value constraints to a particular column
   */
  applyParticularColumnConstraint (column, constraintName, value, definition) {
    if (!value) return

    switch (constraintName) {

      case 'index':
        return column.index(_.get(value, 'indexName'), _.get(value, 'indexType'))

      /**
       * Acceptable forms:
       * attr: { unique: true }
       * attr: {
       *   unique: {
       *     unique: true, // or false
       *     composite: [ 'otherAttr' ]
       *   }
       * }
       */
      case 'unique':
        if ((value === true || _.get(value, 'unique') === true) && !definition.primaryKey) {
          column.unique()
        }
        return

      case 'notNull':
        return column.notNullable()

      case 'defaultsTo':
        return column.defaultTo(value)

      case 'geometry':
      case 'type':
      case 'primaryKey':
      case 'autoIncrement':
      case 'on':
      case 'via':
      case 'foreignKey':
      case 'references':
      case 'model':
      case 'alias':
        return

      default:
        console.error('Unknown constraint [', constraintName, '] on column')
    }
  },

  /**
   * Create a column for Knex from a Waterline atribute definition
   */
  toKnexColumn (table, _name, attrDefinition) {
    let attr = _.isObject(attrDefinition) ? attrDefinition : { type: attrDefinition }
    let type = attr.autoIncrement ? 'serial' : attr.type
    let name = attr.columnName || _name

    switch (type.toLowerCase()) {
      /**
       * table.text(name, [textType])
       * Adds a text column, with optional textType for MySql text datatype preference.
       * textType may be mediumtext or longtext, otherwise defaults to text.
       */
      case 'character varying':
      case 'text':
      case 'mediumtext':
      case 'longtext':
        return table.text(name, type)

      /**
       * table.string(name, [length])
       * Adds a string column, with optional length defaulting to 255.
       */
      case 'string':
        return table.string(name, attr.length)

      case 'serial':
      case 'smallserial':
        return table.specificType(name, 'serial')
      case 'bigserial':
        return table.specificType(name, 'bigserial')

      /**
       * table.boolean(name)
       * Adds a boolean column.
       */
      case 'boolean':
        return table.boolean(name)

      /**
       * table.integer(name)
       * Adds an integer column.
       */
      case 'int':
      case 'integer':
      case 'smallint':
        return table.integer(name)

      /**
       * table.bigInteger(name)
       * In MySQL or PostgreSQL, adds a bigint column, otherwise adds a normal integer.
       * Note that bigint data is returned as a string in queries because JavaScript may
       * be unable to parse them without loss of precision.
       */
      case 'bigint':
      case 'biginteger':
        return table.bigInteger(name)

      /**
       * table.float(column, [precision], [scale])
       * Adds a float column, with optional precision and scale.
       */
      case 'real':
      case 'float':
        return table.float(name, attr.precision, attr.scale)

      case 'double':
        return table.float(name, 15, attr.scale)

      /**
       * table.decimal(column, [precision], [scale])
       * Adds a decimal column, with optional precision and scale.
       */
      case 'decimal':
        return table.decimal(name, attr.precision, attr.scale)

      /**
       * table.time(name)
       * Adds a time column.
       */
      case 'time':
        return table.time(name)

      /**
       * table.date(name)
       * Adds a date column.
       */
      case 'date':
        return table.date(name)

      /**
       * table.timestamp(name, [standard])
       * Adds a timestamp column, defaults to timestamptz in PostgreSQL,
       * unless true is passed as the second argument.
       *
       * Note that the method for defaulting to the current datetime varies from one
       * database to another. For example: PostreSQL requires .defaultTo(knex.raw('now()')),
       * but SQLite3 requires .defaultTo(knex.raw("date('now')")).
       */
      case 'datestamp':
      case 'datetime':
        return table.timestamp(name, attr.standard)

      case 'array':
        return table.specificType(name, 'text ARRAY')

      /**
       * table.json(name, [jsonb])
       * Adds a json column, using the built-in json type in postgresql,
       * defaulting to a text column in older versions of postgresql or in unsupported databases.
       * jsonb can be used by passing true as the second argument.
       */
      case 'json':
        return table.json(name)

      case 'jsonb':
        return table.json(name, true)

      case 'binary':
      case 'bytea':
        return table.binary(name)

      /**
       * table.uuid(name)
       * Adds a uuid column - this uses the built-in uuid type in postgresql,
       * and falling back to a char(36) in other databases.
       */
      case 'uuid':
        return table.uuid(name).defaultTo('uuid_generate_v4()')

      /**
       * table.enu(col, values)
       * Adds a enum column, (aliased to enu, as enum is a reserved word in javascript).
       */
      case 'enum':
      case 'enu':
        return table.enu(name, attr.enum)

      case 'comment':
        return table.comment(attr.comment)

      case 'sqltype':
      case 'sqlType':
        return table.specificType(name, type)

      case 'geometry':
      case 'geography':
      case 'box2d':
      case 'box3d':
        return table.specificType(name, SpatialUtil.getSpatialType(attrDefinition))

      default:
        console.error('Unregistered type given for attribute. name=', name, '; type=', type)
        return table.text(name)
    }
  },

  /**
   * Convert a parameterized waterline query into a knex-compatible query string
   */
  toKnexRawQuery (sql) {
    return (sql || '').replace(/\$\d+/g, '?')
  },

  /**
   * Cast values to the correct type
   */
  castValues (values) {
    return _.map(values, value => {
      if (_.isString(value) && value[0] === '[') {
        let arr = JSON.parse(value)
        if (_.isArray(arr)) {
          return arr
        }
      }

      return value
    })
  },

  castResultRows (rows, schema) {
    return _.map(rows, row => {
      return Util.castResultValues(row, schema)
    })
  },

  castResultValues (values, schema) {
    return _.mapValues(values, (value, attr) => {
      let definition = schema.definition[attr]

      if (SpatialUtil.isSpatialColumn(definition)) {
        try {
          return JSON.parse(value)
        }
        catch (e) {
          return null
        }
      }

      return value
    })
  },

  sanitize (data, schema, cxn) {
    if (_.isArray(data)) {
      return _.map(data, record => {
        return Util.sanitizeRecord(record, schema, cxn)
      })
    }
    else {
      return Util.sanitizeRecord(data, schema, cxn)
    }
  },

  sanitizeRecord (data, schema, cxn) {
    _.each(data, (value, attr) => {
      let definition = schema.definition[attr]

      // remove any autoIncrement fields from data
      if (definition.autoIncrement) {
        delete data[attr]
      }
      if (SpatialUtil.isSpatialColumn(definition)) {
        data[attr] = SpatialUtil.fromGeojson(data[attr], definition, cxn)
      }
    })

    return data
  },

  /**
   * Construct a knex query that joins one or more tables for populate()
   */
  buildKnexJoinQuery (cxn, tableName, options) {
    let selectClause = cxn.knex.raw([
      `"${tableName}".*`,
      ...Util.buildSelectAggregationColumns(cxn, options)
    ].join(', '))

    let pk = Adapter.getPrimaryKey(cxn, tableName)

    let query = cxn.knex
      .select(selectClause)
      .from(tableName)
      .where(Util.buildWhereClause(cxn, tableName, options))
      .groupBy(`${tableName}.${pk}`)
      .orderByRaw(Util.buildOrderByClause(tableName, options))
      .limit(options.limit || Util.PG_MAX_INT)
      .offset(options.skip || 0)

    Util.buildKnexJoins(cxn, options, query)

    return query
  },

  addSelectColumns (columns, query) {
    let [ oldSelectClause, fromClause ] = query.split('FROM')
    let newSelectClause = [ oldSelectClause.split(','), ...columns ].join(',')

    return `${newSelectClause} FROM ${fromClause}`
  },

  buildKnexJoins (cxn, { joins }, query) {
    _.each(joins, join => {
      let subquery = Util.buildKnexJoinSubquery(cxn, join)
      let parentAlias = Util.getParentAlias(join)
      let alias = Util.getSubqueryAlias(join)
      query.leftJoin(
        cxn.knex.raw(`(${subquery}) as "${alias}"`),
        `${alias}.${join.childKey}`,
        `${parentAlias}.${join.parentKey}`
      )
    })
  },

  buildKnexJoinSubquery (cxn, { criteria, child }) {
    return cxn.knex
      .select()
      .from(child)
      .where(Util.buildWhereClause(cxn, child, criteria))
  },

  buildOrderByClause (tableName, { sort }) {
    if (_.isEmpty(sort)) {
      return '1'
    }

    let queryTokens = _.map(sort, (_direction, field) => {
      let direction = _direction === 1 ? '' : 'desc'
      return `"${tableName}"."${field}" ${direction}`
    })
    return queryTokens.join(', ')
  },

  buildWhereClause (cxn, tableName, options) {
    let parser = new CriteriaParser(tableName, cxn.schema, Adapter.wlSqlOptions)
    let { query, values } = parser.read(_.omit(options, [
      'sort', 'limit', 'groupBy', 'skip'
    ]))

    return cxn.knex.raw(Util.toKnexRawQuery(query), Util.castValues(values))
  },

  getJoinAlias ({ alias, parentKey, removeParentKey }) {
    if (alias != parentKey && removeParentKey === true) {
      return parentKey
    }
    else {
      return alias
    }
  },

  getParentAlias (join) {
    if (join.junctionTable) {
      return Util.getJoinAlias(join) + join.parent
    }
    else {
      return join.parent
    }
  },

  getSubqueryAlias (join) {
    return Util.getJoinAlias(join) + join.child
  },

  buildSelectAggregationColumns (cxn, { joins }) {
    return _.map(_.reject(joins, { select: false }), join => {

      let criteria = join.criteria || { }
      let subqueryAlias = Util.getSubqueryAlias(join)
      let asColumn = Util.getJoinAlias(join)
      let orderBy = Util.buildOrderByClause(subqueryAlias, criteria)
      let start = (criteria.skip || 0) + 1
      let end = (criteria.limit || (Util.PG_MAX_INT - start)) + start - 1

      return `
        array_to_json(
          (array_remove(array_agg("${subqueryAlias}".* order by ${orderBy}), null))[${start}:${end}]
        ) as "${asColumn}"
      `
    })
  },

  /**
   * Parse and validate a Postgres "select version()" result
   */
  validateVersion ([ major, minor, patch ]) {
    if (major < 9 || (major === 9 && minor < 2)) {
      throw new Error(`
        PostgreSQL ${major}.${minor}.${patch} detected. This adapter requires PostgreSQL 9.2 or higher.
        Please either:
        1. Upgrade your Postgres server to at least 9.2.0 -or-
        2. Use the sails-postgresql adapter instead: https://www.npmjs.com/package/sails-postgresql
      `)
    }

    return parseFloat(`${major}.${minor}`)
  }
}

export default Util
<commit_msg>update geom api<commit_after>import _ from 'lodash'
import Adapter from './adapter'
import CriteriaParser from 'waterline-sequel/sequel/lib/criteriaProcessor'
import SpatialUtil from './spatial'

const Util = {

  PG_MAX_INT: 2147483647,

  getTransaction (txn, query) {
    if (Util.isTransaction(txn)) {
      return txn
    }
    else {
      return query
    }
  },

  isTransaction (txn) {
    return txn && _.isFunction(txn.commit)
  },

  /**
   * Apply a primary key constraint to a table
   *
   * @param table - a knex table object
   * @param definition - a waterline attribute definition
   */
  applyPrimaryKeyConstraints (table, definition) {
    let primaryKeys = _.keys(_.pick(definition, attribute => {
      return attribute.primaryKey
    }))

    if (!primaryKeys.length) return

    return table.primary(primaryKeys)
  },

  applyCompositeUniqueConstraints (table, definition) {
    _.each(definition, (attribute, name) => {
      let uniqueDef = attribute.unique || { }
      if (attribute.primaryKey) return
      if (_.isEmpty(uniqueDef)) return
      if (!_.isArray(uniqueDef.composite)) return

      let uniqueKeys = _.unique([ name, ...uniqueDef.composite ])

      table.unique(uniqueKeys)
    })
  },

  applyTableConstraints (table, definition) {
    return Promise.all([
      Util.applyPrimaryKeyConstraints(table, definition),
      Util.applyCompositeUniqueConstraints(table, definition)
    ])
  },

  applyColumnConstraints (column, definition) {
    if (_.isString(definition)) {
      return
    }
    return _.map(definition, (value, key) => {
      if (key == 'defaultsTo' && definition.autoIncrement && value == 'AUTO_INCREMENT') {
        return
      }

      return Util.applyParticularColumnConstraint(column, key, value, definition)
    })
  },

  /**
   * Apply value constraints to a particular column
   */
  applyParticularColumnConstraint (column, constraintName, value, definition) {
    if (!value) return

    switch (constraintName) {

      case 'index':
        return column.index(_.get(value, 'indexName'), _.get(value, 'indexType'))

      /**
       * Acceptable forms:
       * attr: { unique: true }
       * attr: {
       *   unique: {
       *     unique: true, // or false
       *     composite: [ 'otherAttr' ]
       *   }
       * }
       */
      case 'unique':
        if ((value === true || _.get(value, 'unique') === true) && !definition.primaryKey) {
          column.unique()
        }
        return

      case 'notNull':
        return column.notNullable()

      case 'defaultsTo':
        return column.defaultTo(value)

      case 'geometry':
      case 'type':
      case 'primaryKey':
      case 'autoIncrement':
      case 'on':
      case 'via':
      case 'foreignKey':
      case 'references':
      case 'model':
      case 'alias':
        return

      default:
        console.error('Unknown constraint [', constraintName, '] on column')
    }
  },

  /**
   * Create a column for Knex from a Waterline atribute definition
   */
  toKnexColumn (table, _name, attrDefinition) {
    let attr = _.isObject(attrDefinition) ? attrDefinition : { type: attrDefinition }
    let type = attr.autoIncrement ? 'serial' : attr.type
    let name = attr.columnName || _name

    switch (type.toLowerCase()) {
      /**
       * table.text(name, [textType])
       * Adds a text column, with optional textType for MySql text datatype preference.
       * textType may be mediumtext or longtext, otherwise defaults to text.
       */
      case 'character varying':
      case 'text':
      case 'mediumtext':
      case 'longtext':
        return table.text(name, type)

      /**
       * table.string(name, [length])
       * Adds a string column, with optional length defaulting to 255.
       */
      case 'string':
        return table.string(name, attr.length)

      case 'serial':
      case 'smallserial':
        return table.specificType(name, 'serial')
      case 'bigserial':
        return table.specificType(name, 'bigserial')

      /**
       * table.boolean(name)
       * Adds a boolean column.
       */
      case 'boolean':
        return table.boolean(name)

      /**
       * table.integer(name)
       * Adds an integer column.
       */
      case 'int':
      case 'integer':
      case 'smallint':
        return table.integer(name)

      /**
       * table.bigInteger(name)
       * In MySQL or PostgreSQL, adds a bigint column, otherwise adds a normal integer.
       * Note that bigint data is returned as a string in queries because JavaScript may
       * be unable to parse them without loss of precision.
       */
      case 'bigint':
      case 'biginteger':
        return table.bigInteger(name)

      /**
       * table.float(column, [precision], [scale])
       * Adds a float column, with optional precision and scale.
       */
      case 'real':
      case 'float':
        return table.float(name, attr.precision, attr.scale)

      case 'double':
        return table.float(name, 15, attr.scale)

      /**
       * table.decimal(column, [precision], [scale])
       * Adds a decimal column, with optional precision and scale.
       */
      case 'decimal':
        return table.decimal(name, attr.precision, attr.scale)

      /**
       * table.time(name)
       * Adds a time column.
       */
      case 'time':
        return table.time(name)

      /**
       * table.date(name)
       * Adds a date column.
       */
      case 'date':
        return table.date(name)

      /**
       * table.timestamp(name, [standard])
       * Adds a timestamp column, defaults to timestamptz in PostgreSQL,
       * unless true is passed as the second argument.
       *
       * Note that the method for defaulting to the current datetime varies from one
       * database to another. For example: PostreSQL requires .defaultTo(knex.raw('now()')),
       * but SQLite3 requires .defaultTo(knex.raw("date('now')")).
       */
      case 'datestamp':
      case 'datetime':
        return table.timestamp(name, attr.standard)

      case 'array':
        return table.specificType(name, 'text ARRAY')

      /**
       * table.json(name, [jsonb])
       * Adds a json column, using the built-in json type in postgresql,
       * defaulting to a text column in older versions of postgresql or in unsupported databases.
       * jsonb can be used by passing true as the second argument.
       */
      case 'json':
        if (attrDefinition.geometry) {
          return table.specificType(name, SpatialUtil.getSpatialType(attrDefinition))
        }
        else {
          return table.json(name)
        }

      case 'jsonb':
        return table.json(name, true)

      case 'binary':
      case 'bytea':
        return table.binary(name)

      /**
       * table.uuid(name)
       * Adds a uuid column - this uses the built-in uuid type in postgresql,
       * and falling back to a char(36) in other databases.
       */
      case 'uuid':
        return table.uuid(name).defaultTo('uuid_generate_v4()')

      /**
       * table.enu(col, values)
       * Adds a enum column, (aliased to enu, as enum is a reserved word in javascript).
       */
      case 'enum':
      case 'enu':
        return table.enu(name, attr.enum)

      case 'comment':
        return table.comment(attr.comment)

      case 'sqltype':
      case 'sqlType':
        return table.specificType(name, type)

      default:
        console.error('Unregistered type given for attribute. name=', name, '; type=', type)
        return table.text(name)
    }
  },

  /**
   * Convert a parameterized waterline query into a knex-compatible query string
   */
  toKnexRawQuery (sql) {
    return (sql || '').replace(/\$\d+/g, '?')
  },

  /**
   * Cast values to the correct type
   */
  castValues (values) {
    return _.map(values, value => {
      if (_.isString(value) && value[0] === '[') {
        let arr = JSON.parse(value)
        if (_.isArray(arr)) {
          return arr
        }
      }

      return value
    })
  },

  castResultRows (rows, schema) {
    return _.map(rows, row => {
      return Util.castResultValues(row, schema)
    })
  },

  castResultValues (values, schema) {
    return _.mapValues(values, (value, attr) => {
      let definition = schema.definition[attr]

      if (SpatialUtil.isSpatialColumn(definition)) {
        try {
          return JSON.parse(value)
        }
        catch (e) {
          return null
        }
      }

      return value
    })
  },

  sanitize (data, schema, cxn) {
    if (_.isArray(data)) {
      return _.map(data, record => {
        return Util.sanitizeRecord(record, schema, cxn)
      })
    }
    else {
      return Util.sanitizeRecord(data, schema, cxn)
    }
  },

  sanitizeRecord (data, schema, cxn) {
    _.each(data, (value, attr) => {
      let definition = schema.definition[attr]

      // remove any autoIncrement fields from data
      if (definition.autoIncrement) {
        delete data[attr]
      }
      if (SpatialUtil.isSpatialColumn(definition)) {
        data[attr] = SpatialUtil.fromGeojson(data[attr], definition, cxn)
      }
    })

    return data
  },

  /**
   * Construct a knex query that joins one or more tables for populate()
   */
  buildKnexJoinQuery (cxn, tableName, options) {
    let selectClause = cxn.knex.raw([
      `"${tableName}".*`,
      ...Util.buildSelectAggregationColumns(cxn, options)
    ].join(', '))

    let pk = Adapter.getPrimaryKey(cxn, tableName)

    let query = cxn.knex
      .select(selectClause)
      .from(tableName)
      .where(Util.buildWhereClause(cxn, tableName, options))
      .groupBy(`${tableName}.${pk}`)
      .orderByRaw(Util.buildOrderByClause(tableName, options))
      .limit(options.limit || Util.PG_MAX_INT)
      .offset(options.skip || 0)

    Util.buildKnexJoins(cxn, options, query)

    return query
  },

  addSelectColumns (columns, query) {
    let [ oldSelectClause, fromClause ] = query.split('FROM')
    let newSelectClause = [ oldSelectClause.split(','), ...columns ].join(',')

    return `${newSelectClause} FROM ${fromClause}`
  },

  buildKnexJoins (cxn, { joins }, query) {
    _.each(joins, join => {
      let subquery = Util.buildKnexJoinSubquery(cxn, join)
      let parentAlias = Util.getParentAlias(join)
      let alias = Util.getSubqueryAlias(join)
      query.leftJoin(
        cxn.knex.raw(`(${subquery}) as "${alias}"`),
        `${alias}.${join.childKey}`,
        `${parentAlias}.${join.parentKey}`
      )
    })
  },

  buildKnexJoinSubquery (cxn, { criteria, child }) {
    return cxn.knex
      .select()
      .from(child)
      .where(Util.buildWhereClause(cxn, child, criteria))
  },

  buildOrderByClause (tableName, { sort }) {
    if (_.isEmpty(sort)) {
      return '1'
    }

    let queryTokens = _.map(sort, (_direction, field) => {
      let direction = _direction === 1 ? '' : 'desc'
      return `"${tableName}"."${field}" ${direction}`
    })
    return queryTokens.join(', ')
  },

  buildWhereClause (cxn, tableName, options) {
    let parser = new CriteriaParser(tableName, cxn.schema, Adapter.wlSqlOptions)
    let { query, values } = parser.read(_.omit(options, [
      'sort', 'limit', 'groupBy', 'skip'
    ]))

    return cxn.knex.raw(Util.toKnexRawQuery(query), Util.castValues(values))
  },

  getJoinAlias ({ alias, parentKey, removeParentKey }) {
    if (alias != parentKey && removeParentKey === true) {
      return parentKey
    }
    else {
      return alias
    }
  },

  getParentAlias (join) {
    if (join.junctionTable) {
      return Util.getJoinAlias(join) + join.parent
    }
    else {
      return join.parent
    }
  },

  getSubqueryAlias (join) {
    return Util.getJoinAlias(join) + join.child
  },

  buildSelectAggregationColumns (cxn, { joins }) {
    return _.map(_.reject(joins, { select: false }), join => {

      let criteria = join.criteria || { }
      let subqueryAlias = Util.getSubqueryAlias(join)
      let asColumn = Util.getJoinAlias(join)
      let orderBy = Util.buildOrderByClause(subqueryAlias, criteria)
      let start = (criteria.skip || 0) + 1
      let end = (criteria.limit || (Util.PG_MAX_INT - start)) + start - 1

      return `
        array_to_json(
          (array_remove(array_agg("${subqueryAlias}".* order by ${orderBy}), null))[${start}:${end}]
        ) as "${asColumn}"
      `
    })
  },

  /**
   * Parse and validate a Postgres "select version()" result
   */
  validateVersion ([ major, minor, patch ]) {
    if (major < 9 || (major === 9 && minor < 2)) {
      throw new Error(`
        PostgreSQL ${major}.${minor}.${patch} detected. This adapter requires PostgreSQL 9.2 or higher.
        Please either:
        1. Upgrade your Postgres server to at least 9.2.0 -or-
        2. Use the sails-postgresql adapter instead: https://www.npmjs.com/package/sails-postgresql
      `)
    }

    return parseFloat(`${major}.${minor}`)
  }
}

export default Util
<|endoftext|>