<commit_before>'use strict';
const ಠ = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='; // transparent image, used as accessor and replacing image
const propRegex = /(object-fit|object-position)\s*:\s*([-\w\s%]+)/g;
const testImg = new Image();
const supportsObjectFit = 'object-fit' in testImg.style;
const supportsObjectPosition = 'object-position' in testImg.style;
const supportsCurrentSrc = typeof testImg.currentSrc === 'string';
const nativeGetAttribute = testImg.getAttribute;
const nativeSetAttribute = testImg.setAttribute;
let autoModeEnabled = false;

function getStyle(el) {
	const style = getComputedStyle(el).fontFamily;
	let parsed;
	const props = {};
	while ((parsed = propRegex.exec(style)) !== null) {
		props[parsed[1]] = parsed[2];
	}
	return props;
}

function fixOne(el, requestedSrc) {
	if (el[ಠ].parsingSrcset) {
		return;
	}
	const style = getStyle(el);
	style['object-fit'] = style['object-fit'] || 'fill'; // default value

	// If the fix was already applied, don't try to skip fixing,
	// - because once you go ofi you never go back.
	// - Wait, that doesn't rhyme.
	// - This ain't rap, bro.
	if (!el[ಠ].s) {
		// fill is the default behavior so no action is necessary
		if (style['object-fit'] === 'fill') {
			return;
		}

		// Where object-fit is supported and object-position isn't (Safari < 10)
		if (
			!el[ಠ].skipTest && // unless user wants to apply regardless of browser support
			supportsObjectFit && // if browser already supports object-fit
			!style['object-position'] // unless object-position is used
		) {
			return;
		}
	}

	let src = el[ಠ].ios7src || el.currentSrc || el.src;

	if (requestedSrc) {
		// explicitly requested src takes precedence
		// TODO: this still should overwrite srcset
		src = requestedSrc;
	} else if (el.srcset && !supportsCurrentSrc && window.picturefill) {
		const pf = window.picturefill._;
		// prevent infinite loop
		// fillImg sets the src which in turn calls fixOne
		el[ಠ].parsingSrcset = true;

		// parse srcset with picturefill where currentSrc isn't available
		if (!el[pf.ns] || !el[pf.ns].evaled) {
			// force synchronous srcset parsing
			pf.fillImg(el, {reselect: true});
		}

		if (!el[pf.ns].curSrc) {
			// force picturefill to parse srcset
			el[pf.ns].supported = false;
			pf.fillImg(el, {reselect: true});
		}
		delete el[ಠ].parsingSrcset;

		// retrieve parsed currentSrc, if any
		src = el[pf.ns].curSrc || src;
	}

	// store info on object for later use
	if (el[ಠ].s) {
		el[ಠ].s = src;
		if (requestedSrc) {
			// the attribute reflects the user input
			// the property is the resolved URL
			el[ಠ].srcAttr = requestedSrc;
		}
	} else {
		el[ಠ] = {
			s: src,
			srcAttr: requestedSrc || nativeGetAttribute.call(el, 'src'),
			srcsetAttr: el.srcset
		};
		el.src = ಠ;

		try {
			// remove srcset because it overrides src
			if (el.srcset) {
				el.srcset = '';

				// restore non-browser-readable srcset property
				Object.defineProperty(el, 'srcset', {
					value: el[ಠ].srcsetAttr
				});
			}

			keepSrcUsable(el);
		} catch (err) {
			el[ಠ].ios7src = src;
		}
	}

	el.style.backgroundImage = 'url("' + src + '")';
	el.style.backgroundPosition = style['object-position'] || 'center';
	el.style.backgroundRepeat = 'no-repeat';

	if (/scale-down/.test(style['object-fit'])) {
		// `object-fit: scale-down` is either `contain` or `auto`
		if (!el[ಠ].i) {
			el[ಠ].i = new Image();
			el[ಠ].i.src = src;
		}

		// naturalWidth is only available when the image headers are loaded,
		// this loop will poll it every 100ms.
		// There's currently no check to prevent this loop from starting twice
		// as a consequence of calling ofi() twice on the same image, but it's light
		// and causes no issues, so it's not worth ensuring that it doesn't.
		(function loop() {
			// https://bugs.chromium.org/p/chromium/issues/detail?id=495908
			if (el[ಠ].i.naturalWidth) {
				if (el[ಠ].i.naturalWidth > el.width || el[ಠ].i.naturalHeight > el.height) {
					el.style.backgroundSize = 'contain';
				} else {
					el.style.backgroundSize = 'auto';
				}
				return;
			}
			setTimeout(loop, 100);
		})();
	} else {
		el.style.backgroundSize = style['object-fit'].replace('none', 'auto').replace('fill', '100% 100%');
	}
}

function keepSrcUsable(el) {
	const descriptors = {
		get() {
			return el[ಠ].s;
		},
		set(src) {
			delete el[ಠ].i; // scale-down's img sizes need to be updated too
			fixOne(el, src);
			return src;
		}
	};
	Object.defineProperty(el, 'src', descriptors);
	Object.defineProperty(el, 'currentSrc', {get: descriptors.get}); // it should be read-only
}

function hijackAttributes() {
	if (!supportsObjectPosition) {
		HTMLImageElement.prototype.getAttribute = function (name) {
			if (this[ಠ] && (name === 'src' || name === 'srcset')) {
				return this[ಠ][name + 'Attr'];
			}
			return nativeGetAttribute.call(this, name);
		};

		HTMLImageElement.prototype.setAttribute = function (name, value) {
			if (this[ಠ] && (name === 'src' || name === 'srcset')) {
				this[name === 'src' ? 'src' : name + 'Attr'] = String(value);
			} else {
				nativeSetAttribute.call(this, name, value);
			}
		};
	}
}

export default function fix(imgs, opts) {
	const startAutoMode = !autoModeEnabled && !imgs;
	opts = opts || {};
	imgs = imgs || 'img';
	if (supportsObjectPosition && !opts.skipTest) {
		return false;
	}

	// use imgs as a selector or just select all images
	if (typeof imgs === 'string') {
		imgs = document.querySelectorAll('img');
	} else if ('length' in imgs) {
		imgs = [imgs];
	}

	// apply fix to all
	for (let i = 0; i < imgs.length; i++) {
		imgs[i][ಠ] = imgs[i][ಠ] || opts;
		fixOne(imgs[i]);
	}

	if (startAutoMode) {
		document.body.addEventListener('load', e => {
			if (e.target.tagName === 'IMG') {
				fix(e.target, {
					skipTest: opts.skipTest
				});
			}
		}, true);
		autoModeEnabled = true;
		imgs = 'img'; // reset to a generic selector for watchMQ
	}

	// if requested, watch media queries for object-fit change
	if (opts.watchMQ) {
		window.addEventListener('resize', fix.bind(null, imgs, {
			skipTest: opts.skipTest
		}));
	}
}

fix.supportsObjectFit = supportsObjectFit;
fix.supportsObjectPosition = supportsObjectPosition;

hijackAttributes();
<commit_msg>Fix handling of img nodelist/array (#45)<commit_after>'use strict';
const ಠ = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='; // transparent image, used as accessor and replacing image
const propRegex = /(object-fit|object-position)\s*:\s*([-\w\s%]+)/g;
const testImg = new Image();
const supportsObjectFit = 'object-fit' in testImg.style;
const supportsObjectPosition = 'object-position' in testImg.style;
const supportsCurrentSrc = typeof testImg.currentSrc === 'string';
const nativeGetAttribute = testImg.getAttribute;
const nativeSetAttribute = testImg.setAttribute;
let autoModeEnabled = false;

function getStyle(el) {
	const style = getComputedStyle(el).fontFamily;
	let parsed;
	const props = {};
	while ((parsed = propRegex.exec(style)) !== null) {
		props[parsed[1]] = parsed[2];
	}
	return props;
}

function fixOne(el, requestedSrc) {
	if (el[ಠ].parsingSrcset) {
		return;
	}
	const style = getStyle(el);
	style['object-fit'] = style['object-fit'] || 'fill'; // default value

	// If the fix was already applied, don't try to skip fixing,
	// - because once you go ofi you never go back.
	// - Wait, that doesn't rhyme.
	// - This ain't rap, bro.
	if (!el[ಠ].s) {
		// fill is the default behavior so no action is necessary
		if (style['object-fit'] === 'fill') {
			return;
		}

		// Where object-fit is supported and object-position isn't (Safari < 10)
		if (
			!el[ಠ].skipTest && // unless user wants to apply regardless of browser support
			supportsObjectFit && // if browser already supports object-fit
			!style['object-position'] // unless object-position is used
		) {
			return;
		}
	}

	let src = el[ಠ].ios7src || el.currentSrc || el.src;

	if (requestedSrc) {
		// explicitly requested src takes precedence
		// TODO: this still should overwrite srcset
		src = requestedSrc;
	} else if (el.srcset && !supportsCurrentSrc && window.picturefill) {
		const pf = window.picturefill._;
		// prevent infinite loop
		// fillImg sets the src which in turn calls fixOne
		el[ಠ].parsingSrcset = true;

		// parse srcset with picturefill where currentSrc isn't available
		if (!el[pf.ns] || !el[pf.ns].evaled) {
			// force synchronous srcset parsing
			pf.fillImg(el, {reselect: true});
		}

		if (!el[pf.ns].curSrc) {
			// force picturefill to parse srcset
			el[pf.ns].supported = false;
			pf.fillImg(el, {reselect: true});
		}
		delete el[ಠ].parsingSrcset;

		// retrieve parsed currentSrc, if any
		src = el[pf.ns].curSrc || src;
	}

	// store info on object for later use
	if (el[ಠ].s) {
		el[ಠ].s = src;
		if (requestedSrc) {
			// the attribute reflects the user input
			// the property is the resolved URL
			el[ಠ].srcAttr = requestedSrc;
		}
	} else {
		el[ಠ] = {
			s: src,
			srcAttr: requestedSrc || nativeGetAttribute.call(el, 'src'),
			srcsetAttr: el.srcset
		};
		el.src = ಠ;

		try {
			// remove srcset because it overrides src
			if (el.srcset) {
				el.srcset = '';

				// restore non-browser-readable srcset property
				Object.defineProperty(el, 'srcset', {
					value: el[ಠ].srcsetAttr
				});
			}

			keepSrcUsable(el);
		} catch (err) {
			el[ಠ].ios7src = src;
		}
	}

	el.style.backgroundImage = 'url("' + src + '")';
	el.style.backgroundPosition = style['object-position'] || 'center';
	el.style.backgroundRepeat = 'no-repeat';

	if (/scale-down/.test(style['object-fit'])) {
		// `object-fit: scale-down` is either `contain` or `auto`
		if (!el[ಠ].i) {
			el[ಠ].i = new Image();
			el[ಠ].i.src = src;
		}

		// naturalWidth is only available when the image headers are loaded,
		// this loop will poll it every 100ms.
		// There's currently no check to prevent this loop from starting twice
		// as a consequence of calling ofi() twice on the same image, but it's light
		// and causes no issues, so it's not worth ensuring that it doesn't.
		(function loop() {
			// https://bugs.chromium.org/p/chromium/issues/detail?id=495908
			if (el[ಠ].i.naturalWidth) {
				if (el[ಠ].i.naturalWidth > el.width || el[ಠ].i.naturalHeight > el.height) {
					el.style.backgroundSize = 'contain';
				} else {
					el.style.backgroundSize = 'auto';
				}
				return;
			}
			setTimeout(loop, 100);
		})();
	} else {
		el.style.backgroundSize = style['object-fit'].replace('none', 'auto').replace('fill', '100% 100%');
	}
}

function keepSrcUsable(el) {
	const descriptors = {
		get() {
			return el[ಠ].s;
		},
		set(src) {
			delete el[ಠ].i; // scale-down's img sizes need to be updated too
			fixOne(el, src);
			return src;
		}
	};
	Object.defineProperty(el, 'src', descriptors);
	Object.defineProperty(el, 'currentSrc', {get: descriptors.get}); // it should be read-only
}

function hijackAttributes() {
	if (!supportsObjectPosition) {
		HTMLImageElement.prototype.getAttribute = function (name) {
			if (this[ಠ] && (name === 'src' || name === 'srcset')) {
				return this[ಠ][name + 'Attr'];
			}
			return nativeGetAttribute.call(this, name);
		};

		HTMLImageElement.prototype.setAttribute = function (name, value) {
			if (this[ಠ] && (name === 'src' || name === 'srcset')) {
				this[name === 'src' ? 'src' : name + 'Attr'] = String(value);
			} else {
				nativeSetAttribute.call(this, name, value);
			}
		};
	}
}

export default function fix(imgs, opts) {
	const startAutoMode = !autoModeEnabled && !imgs;
	opts = opts || {};
	imgs = imgs || 'img';
	if (supportsObjectPosition && !opts.skipTest) {
		return false;
	}

	// use imgs as a selector or just select all images
	if (typeof imgs === 'string') {
		imgs = document.querySelectorAll('img');
	} else if (!'length' in imgs) {
		imgs = [imgs];
	}

	// apply fix to all
	for (let i = 0; i < imgs.length; i++) {
		imgs[i][ಠ] = imgs[i][ಠ] || opts;
		fixOne(imgs[i]);
	}

	if (startAutoMode) {
		document.body.addEventListener('load', e => {
			if (e.target.tagName === 'IMG') {
				fix(e.target, {
					skipTest: opts.skipTest
				});
			}
		}, true);
		autoModeEnabled = true;
		imgs = 'img'; // reset to a generic selector for watchMQ
	}

	// if requested, watch media queries for object-fit change
	if (opts.watchMQ) {
		window.addEventListener('resize', fix.bind(null, imgs, {
			skipTest: opts.skipTest
		}));
	}
}

fix.supportsObjectFit = supportsObjectFit;
fix.supportsObjectPosition = supportsObjectPosition;

hijackAttributes();
<|endoftext|>


<commit_before>"use strict";

module.exports = (options) => {
	return new Formatter(options);
};

class Formatter {
	constructor(options = {}) {
		this.options = merge({
			decimal   : ",",
			thousands : " ",
			inprefix  : "",
			insuffix  : "",
			outprefix : "",
			outsuffix : "",
			negative  : "left",
			group     : 3,
			decimals  : -1,
			digits    : null
		}, options);
	}

	/**

	Format number according to these parts:

	- <outprefix>
	- (<negative>)?
	- <inprefix>
	- (<number:group><thousands>)*<number:group>
	- (<decimal><number:decimals>)?
	- <insuffix>
	- (<negative>)?
	- <outsuffix>

	1. option outprefix string
	2. if number is negative and:
	   - if option negative is paren, add open parenthesis
	   - else if option negative is left, add minus
	3. option inprefix string
	4. for every integer part in option group blocks, add option thousands string separator
	5. if decimals add option decimal string and decimals
	6. option insuffix string
	7. if number is negative and:
	   - if option negative is paren, add open parenthesis
	   - else if option negative is right, add minus
	8. option outsuffix string

	 **/
	format(number = 0, additional_options = {}) {
		if (isNaN(+number) || !isFinite(+number)) return "-";

		let options  = merge(this.options, additional_options);
		let negative = (number < 0);
		let integer, decimal;

		if (options.decimals !== -1) {
			// precision rounding
			let tmp = precision_round(Math.abs(number), options.decimals);
			// unprecision rounding
			number  = Math.abs(number).toFixed(options.decimals);
			// merge precision rounding with possible 0's at the right end
			number  = tmp + number.substr(tmp.length);

			[ integer, decimal ] = number.split(".");
		} else {
			if (number.toString().indexOf("e") > 0) {
				number               = Math.abs(number).toFixed(20).replace(/0+$/, "");
				[ integer, decimal ] = number.split(".");
			} else {
				integer = "" + Math.floor(Math.abs(number));
				decimal = ("" + Math.abs(number)).substr(integer.length + 1);
			}
		}

		let formatted = this.__format_decimals(options, integer, decimal);

		formatted = options.inprefix + formatted + options.insuffix;

		if (negative) {
			if (options.negative === "paren") {
				formatted = "(" + formatted + ")";
			} else if (options.negative === "right") {
				formatted += "-";
			} else {
				formatted = "-" + formatted;
			}
		}

		formatted = options.outprefix + formatted + options.outsuffix;

		if (options.digits && options.digits.length === 10) {
			formatted = formatted.replace(/\d/g, (d) => options.digits[d]);
		}

		return formatted;
	}

	unformat(formatted, additional_options = {}) {
		let options    = merge(this.options, additional_options);
		let negative   = false;

		// strip out prefix/suffix
		formatted = strip(formatted.trim(), options.outprefix, options.outsuffix);

		// detect negative format
		[ formatted, negative ] = this.__unformat_negative(options, formatted);

		// strip in prefix/suffix
		formatted = strip(formatted.trim(), options.inprefix, options.insuffix);

		// strip thousands and convert digits
		formatted = this.__unformat_decimals(options, formatted);

		formatted = parseFloat(formatted, 10);

		if (isNaN(formatted)) return null;

		if (negative) {
			formatted *= -1;
		}

		if (options.decimals === -1) {
			return formatted;
		}

		return +(formatted.toFixed(options.decimals));
	}

	__unformat_negative(options, formatted) {
		if (options.negative === "paren") {
			if (formatted[0] === "(" && formatted[formatted.length - 1] === ")") {
				return [ formatted.substr(1, formatted.length - 2), true ];
			}
		} else if (options.negative === "right") {
			if (formatted[formatted.length - 1] === "-") {
				return [ formatted.substr(0, formatted.length - 1), true ];
			}
		} else {
			if (formatted[0] === "-") {
				return [ formatted.substr(1), true ];
			}
		}

		return [ formatted, false ];
	}

	__unformat_decimals(options, formatted) {
		formatted = this.__unformat_decimal_thousands(options, formatted);

		if (options.decimal.length) {
			formatted = formatted.replace(options.decimal, ".");
		}

		if (options.digits && options.digits.length === 10) {
			formatted = formatted.replace(new RegExp("[" + options.digits + "]", "g"), (c) => options.digits.indexOf(c));
		}

		return formatted;
	}

	__unformat_decimal_thousands(options, formatted) {
		if (options.thousands.length) {
			formatted = formatted.split(options.thousands);

			if (options.decimal.length && options.decimal === options.thousands && options.decimals !== 0) {
				return formatted.slice(0, formatted.length - 1).join("") + options.decimal + formatted[formatted.length - 1];
			}

			return formatted.join("");
		}

		return formatted;
	}

	__format_decimals(options, integer, decimal) {
		let formatted = "";

		if (options.thousands.length && ((Array.isArray(options.group) && options.group.length) || options.group > 0)) {
			if (!Array.isArray(options.group)) {
				options.group = [ options.group ];
			}

			options.g    = 0;
			options.from = integer.length - options.group[options.g];

			for (let i = integer.length - 1; i >= 0; i -= options.group[options.g]) {
				formatted = this.__format_decimals_group(options, integer, formatted) + formatted;
			}
		} else  {
			formatted = integer;
		}

		if (options.decimals !== 0 && decimal.length) {
			formatted += options.decimal + decimal;
		}

		return formatted;
	}

	__format_decimals_group(options, integer) {
		let group = (options.from >= 0
		          ? integer.substr(options.from, options.group[options.g])
		          : integer.substr(0, options.group[options.g] + options.from));

		if (options.from > 0 && options.thousands.length) {
			group = options.thousands + group;
		}

		if (options.g < options.group.length - 1) {
			options.g += 1;
		}

		options.from -= options.group[options.g];

		return group;
	}
}

function merge(base, obj) {
	let ret = {};

	for (let k in base) {
		if (base.hasOwnProperty(k) && typeof obj[k] === "undefined") {
			ret[k] = base[k];
		}
	}

	Object.keys(obj).map((k) => {
		ret[k] = obj[k];
	});

	return ret;
}

function strip(text, left, right) {
	return strip_right(strip_left(text, left), right);
}

function strip_left(text, left) {
	if (left.length && text.substr(0, left.length) === left) {
		return text.substr(left.length);
	}

	return text;
}

function strip_right(text, right) {
	if (right.length && text.substr(text.length - right.length) === right) {
		return text.substr(0, text.length - right.length);
	}

	return text;
}

function precision_round(number, decimals) {
	if (decimals == 0) return "" + Math.round(number);

	let p1 = Math.pow(10, decimals + 1);
	let p2 = Math.pow(10, decimals);

	return "" + Math.round( Math.round( number * p1 ) / (p1 / p2) ) / p2;
}
<commit_msg>reduces complexity on __unformat_negative()<commit_after>"use strict";

module.exports = (options) => {
	return new Formatter(options);
};

class Formatter {
	constructor(options = {}) {
		this.options = merge({
			decimal   : ",",
			thousands : " ",
			inprefix  : "",
			insuffix  : "",
			outprefix : "",
			outsuffix : "",
			negative  : "left",
			group     : 3,
			decimals  : -1,
			digits    : null
		}, options);
	}

	/**

	Format number according to these parts:

	- <outprefix>
	- (<negative>)?
	- <inprefix>
	- (<number:group><thousands>)*<number:group>
	- (<decimal><number:decimals>)?
	- <insuffix>
	- (<negative>)?
	- <outsuffix>

	1. option outprefix string
	2. if number is negative and:
	   - if option negative is paren, add open parenthesis
	   - else if option negative is left, add minus
	3. option inprefix string
	4. for every integer part in option group blocks, add option thousands string separator
	5. if decimals add option decimal string and decimals
	6. option insuffix string
	7. if number is negative and:
	   - if option negative is paren, add open parenthesis
	   - else if option negative is right, add minus
	8. option outsuffix string

	 **/
	format(number = 0, additional_options = {}) {
		if (isNaN(+number) || !isFinite(+number)) return "-";

		let options  = merge(this.options, additional_options);
		let negative = (number < 0);
		let integer, decimal;

		if (options.decimals !== -1) {
			// precision rounding
			let tmp = precision_round(Math.abs(number), options.decimals);
			// unprecision rounding
			number  = Math.abs(number).toFixed(options.decimals);
			// merge precision rounding with possible 0's at the right end
			number  = tmp + number.substr(tmp.length);

			[ integer, decimal ] = number.split(".");
		} else {
			if (number.toString().indexOf("e") > 0) {
				number               = Math.abs(number).toFixed(20).replace(/0+$/, "");
				[ integer, decimal ] = number.split(".");
			} else {
				integer = "" + Math.floor(Math.abs(number));
				decimal = ("" + Math.abs(number)).substr(integer.length + 1);
			}
		}

		let formatted = this.__format_decimals(options, integer, decimal);

		formatted = options.inprefix + formatted + options.insuffix;

		if (negative) {
			if (options.negative === "paren") {
				formatted = "(" + formatted + ")";
			} else if (options.negative === "right") {
				formatted += "-";
			} else {
				formatted = "-" + formatted;
			}
		}

		formatted = options.outprefix + formatted + options.outsuffix;

		if (options.digits && options.digits.length === 10) {
			formatted = formatted.replace(/\d/g, (d) => options.digits[d]);
		}

		return formatted;
	}

	unformat(formatted, additional_options = {}) {
		let options    = merge(this.options, additional_options);
		let negative   = false;

		// strip out prefix/suffix
		formatted = strip(formatted.trim(), options.outprefix, options.outsuffix);

		// detect negative format
		[ formatted, negative ] = this.__unformat_negative(options, formatted);

		// strip in prefix/suffix
		formatted = strip(formatted.trim(), options.inprefix, options.insuffix);

		// strip thousands and convert digits
		formatted = this.__unformat_decimals(options, formatted);

		formatted = parseFloat(formatted, 10);

		if (isNaN(formatted)) return null;

		if (negative) {
			formatted *= -1;
		}

		if (options.decimals === -1) {
			return formatted;
		}

		return +(formatted.toFixed(options.decimals));
	}

	__unformat_negative(options, formatted) {
		if (options.negative === "paren" && formatted[0] === "(" && formatted[formatted.length - 1] === ")") {
			return [ formatted.substr(1, formatted.length - 2), true ];
		}
		if (options.negative === "right" && formatted[formatted.length - 1] === "-") {
			return [ formatted.substr(0, formatted.length - 1), true ];
		}
		if (formatted[0] === "-") {
			return [ formatted.substr(1), true ];
		}

		return [ formatted, false ];
	}

	__unformat_decimals(options, formatted) {
		formatted = this.__unformat_decimal_thousands(options, formatted);

		if (options.decimal.length) {
			formatted = formatted.replace(options.decimal, ".");
		}

		if (options.digits && options.digits.length === 10) {
			formatted = formatted.replace(new RegExp("[" + options.digits + "]", "g"), (c) => options.digits.indexOf(c));
		}

		return formatted;
	}

	__unformat_decimal_thousands(options, formatted) {
		if (options.thousands.length) {
			formatted = formatted.split(options.thousands);

			if (options.decimal.length && options.decimal === options.thousands && options.decimals !== 0) {
				return formatted.slice(0, formatted.length - 1).join("") + options.decimal + formatted[formatted.length - 1];
			}

			return formatted.join("");
		}

		return formatted;
	}

	__format_decimals(options, integer, decimal) {
		let formatted = "";

		if (options.thousands.length && ((Array.isArray(options.group) && options.group.length) || options.group > 0)) {
			if (!Array.isArray(options.group)) {
				options.group = [ options.group ];
			}

			options.g    = 0;
			options.from = integer.length - options.group[options.g];

			for (let i = integer.length - 1; i >= 0; i -= options.group[options.g]) {
				formatted = this.__format_decimals_group(options, integer, formatted) + formatted;
			}
		} else  {
			formatted = integer;
		}

		if (options.decimals !== 0 && decimal.length) {
			formatted += options.decimal + decimal;
		}

		return formatted;
	}

	__format_decimals_group(options, integer) {
		let group = (options.from >= 0
		          ? integer.substr(options.from, options.group[options.g])
		          : integer.substr(0, options.group[options.g] + options.from));

		if (options.from > 0 && options.thousands.length) {
			group = options.thousands + group;
		}

		if (options.g < options.group.length - 1) {
			options.g += 1;
		}

		options.from -= options.group[options.g];

		return group;
	}
}

function merge(base, obj) {
	let ret = {};

	for (let k in base) {
		if (base.hasOwnProperty(k) && typeof obj[k] === "undefined") {
			ret[k] = base[k];
		}
	}

	Object.keys(obj).map((k) => {
		ret[k] = obj[k];
	});

	return ret;
}

function strip(text, left, right) {
	return strip_right(strip_left(text, left), right);
}

function strip_left(text, left) {
	if (left.length && text.substr(0, left.length) === left) {
		return text.substr(left.length);
	}

	return text;
}

function strip_right(text, right) {
	if (right.length && text.substr(text.length - right.length) === right) {
		return text.substr(0, text.length - right.length);
	}

	return text;
}

function precision_round(number, decimals) {
	if (decimals == 0) return "" + Math.round(number);

	let p1 = Math.pow(10, decimals + 1);
	let p2 = Math.pow(10, decimals);

	return "" + Math.round( Math.round( number * p1 ) / (p1 / p2) ) / p2;
}
<|endoftext|>


<commit_before>var directions = require( 'directions' ),
    rafLoop = require( 'raf-loop' ),
    noop = require( 'no-op' );

module.exports = kimi;

function kimi( settings ) {

  if( !( this instanceof kimi) ) {

    return new kimi( settings );
  }

  settings = settings || {};
  this.onUpdate = settings.onUpdate || noop;
  this.onState = settings.onState || noop;
  this.allowReverse = settings.allowReverse || true;

  this.directions = directions();
  this.animator = {};
  this.states = {};
  this.isReversing = false;

  this.cTime = 0;
  this.cDuration = 0;
  this.cState = null;
  this.tState = null;
  this.cPath = [];
  this.cAnimator = null;
  this.cOnComplete = null;
  this.engine = rafLoop( tick.bind( this ) );
}

kimi.prototype = {

  state: function( name, value ) {

    this.states[ name ] = value;
  },

  fromTo: function( from, to, duration, animator ) {

    this.directions.fromTo( from, to, duration );

    setAnimator.call( this, from, to, animator );
  },

  init: function( initState ) {

    setCurrentState.call( this, initState );
  },

  go: function( to, onComplete ) {

    if( this.cState ) {

      this.cOnComplete = onComplete || noop;

      // if we're trying to go to our current state
      if( to == this.cState ) {

        // if we allow for reversing then we should reverse
        if( this.allowReverse ) {

          this.isReversing = true;
          this.cPath = [ this.cState, this.tState ];
          this.tState = null;

        // otherwise we'll get a path from our tState
        } else {

          this.isReversing = false;
          this.cPath = this.directions.getPath( this.cState, this.tState, to ).path;
          this.tState = null;
        }
      // if we're not going to our current state then just get the path
      } else {

        this.isReversing = false;
        this.cPath = this.directions.getPath( this.cState, to ).path;
        this.tState = null;
      }

      setFromTo.call( this );

      this.engine.start();
    } else {

      throw new Error( 'call init with your initial state before calling go' );
    }
  }
};

function setFromTo() {

  var from, to;

  if( this.tState ) {

    from = this.tState;
    to = this.cPath.shift();
  } else {

    from = this.cPath.shift();
    to = this.cPath.shift();
  }

  // if we have a to state
  if( to ) {

    setCurrentState.call( this, from );
    this.tState = to;
    this.cDuration = this.directions.fromTo( from, to ) * 1000;
    this.cAnimator = this.animator[ from ][ to ];
  } else {

    this.cTime = 0;

    if( !this.isReversing ) {

      setCurrentState.call( this, this.tState );
    } else {

      setCurrentState.call( this, this.cState );
    }

    this.tState = null;
    this.cDuration = 0;
  }
}

function tick( delta ) {

  var value;

  if( !this.isReversing ) {

    this.cTime += delta;
  } else {

    this.cTime -= delta;
  }

  // if we're not reversing then just call the animator or go to the next state
  if( !this.isReversing ) {

    if( this.cTime >= this.cDuration ) {

      this.cTime = this.cTime - this.cDuration;

      setFromTo.call( this );

      if( !this.tState ) {

        this.engine.stop();
        this.cTime = 0;
      }
    }
  // if we're reversing
  } else {

    if( this.cTime <= 0 ) {

      setFromTo.call( this );

      if( !this.tState ) {

        this.engine.stop();
        this.cTime = 0;
      }
    }
  }

  if( this.tState ) {

    this.onUpdate(
                    this.cAnimator( this.cTime / this.cDuration, this.states[ this.cState ], this.states[ this.tState ] ),
                    this.cState,
                    this.cTime
                  );
  } else {

    value = this.cAnimator( 0, this.states[ this.cState ], this.states[ this.cState ] );

    this.onUpdate( value, this.cState, this.cTime );
    this.cOnComplete( value, this.cState );
  }
}

function setCurrentState( state ) {

  if( this.cState != state ) {

    this.cState = state;
    this.onUpdate( this.states[ state ], state );
    this.onState( this.states[ state ], state );
  }
}

function setAnimator( from, to, animator ) {

  var animators = this.animator[ from ] || ( this.animator[ from ] = {} );

  animators[ to ] = animator;
}<commit_msg>Refactored code to be simpler. Renamed variables to be more descriptive. Fixed a bug where reversing didn't work properly.<commit_after>var directions = require( 'directions' ),
    rafLoop = require( 'raf-loop' ),
    noop = require( 'no-op' );

module.exports = kimi;

function kimi( settings ) {

  if( !( this instanceof kimi) ) {

    return new kimi( settings );
  }

  settings = settings || {};
  this.onUpdate = settings.onUpdate || noop;
  this.onState = settings.onState || noop;
  this.allowReverse = settings.allowReverse || true;

  this.directions = directions();
  this.animator = {};
  this.states = {};

  this.currentTime = 0;
  this.currentState = null;
  this.targetState = null;
  this.currentPath = [];
  this.onComplete = null;
  this.engine = rafLoop( tick.bind( this ) );
}

kimi.prototype = {

  state: function( name, value ) {

    this.states[ name ] = value;
  },

  fromTo: function( from, to, duration, animator ) {

    this.directions.fromTo( from, to, duration );

    setAnimator.call( this, from, to, animator );
  },

  init: function( initState ) {

    this.currentState = initState;

    sendUpdate.call( this );
  },

  go: function( to, onComplete ) {

    if( this.currentState ) {

      this.onComplete = onComplete || noop;

      // if we're trying to go to our current state
      if( to == this.currentState ) {

        if( !this.allowReverse ) {

          this.currentPath = this.directions.getPath( this.currentState, this.targetState, to ).path;
          this.currentPath.shift();
        } else {

          this.currentPath = [ to ];
        }
      // if we're not going to our current state then just get the path
      } else {

        this.currentPath = this.directions.getPath( this.currentState, to ).path;
        this.currentPath.shift();
      }

      if( !this.targetState ) {

        this.targetState = this.currentPath[ 0 ];
      }

      this.engine.start();
    } else {

      throw new Error( 'call init with your initial state before calling go' );
    }
  }
};

function tick( delta ) {

  var to = this.currentPath[ 0 ],
      isReversing = this.allowReverse && ( this.currentState == to || ( this.targetState && to && to != this.targetState ) ),
      duration = this.directions.fromTo( this.currentState, this.targetState ) * 1000,
      animator = this.animator[ this.currentState ][ this.targetState ];

  // we should reverse when we're trying to go from to the same place
  // or when the state we're going to isn't the same as the path to
  if( isReversing ) {

    this.currentTime = Math.max( this.currentTime - delta, 0 );

    sendUpdate.call( this, duration, animator );

    if( this.currentTime == 0 ) {

      // we were reversing to the same state
      if( this.currentState == to ) {

        // we've reached our target state which is the current state
        this.targetState = this.currentPath.shift();
      // we were reversing cause we need to go to a new state
      } else if( to != this.targetState ) {

        // now allow it to go to the target state
        this.targetState = to;
      }
    }
  } else {

    this.currentTime = Math.min( this.currentTime + delta, duration );

    sendUpdate.call( this, duration, animator );

    if( this.currentTime == duration ) {

      this.currentTime = 0;
      this.currentState = this.targetState;
      this.targetState = this.currentPath.shift();
    }
  }

  // we don't have anywhere to go anymore
  if( this.currentState == this.targetState && this.currentPath.length == 0 ) {

    this.engine.stop();

    this.onComplete(
      this.states[ this.currentState ],
      this.currentState
    );
  }
}

function sendUpdate( duration, animator ) {

  if( animator ) {

    this.onUpdate(
      animator( this.currentTime / duration, this.states[ this.currentState ], this.states[ this.targetState ] ),
      this.currentState,
      this.currentTime
    );
  } else {

    this.onUpdate(
      this.states[ this.currentState ],
      this.currentState,
      this.currentTime
    );

    this.onState(
      this.states[ this.currentState ],
      this.currentState,
      this.currentTime
    );
  }
}

function setAnimator( from, to, animator ) {

  var animators = this.animator[ from ] || ( this.animator[ from ] = {} );

  animators[ to ] = animator;
}<|endoftext|>


<commit_before>const S3rver = require('s3rver');
const fs = require('fs-extra'); // Using fs-extra to ensure destination directory exist
const AWS = require('aws-sdk');
const shell = require('shelljs');
const path = require('path');
const uuid = require('uuid/v1');
const functionHelper = require('serverless-offline/src/functionHelper');
const createLambdaContext = require('serverless-offline/src/createLambdaContext');

require("rxjs/add/operator/map");
require("rxjs/add/operator/mergeMap");

const defaultOptions = {
  port: 4569,
  host: 'localhost',
  location: '.'
}

class ServerlessS3Local {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.service = serverless.service;
    this.options = options;
    this.provider = 'aws';
    this.client = null;

    this.commands = {
      s3: {
        commands: {
          start: {
            usage: 'Start S3 local server.',
            lifecycleEvents: ['startHandler'],
            options: {
              port: {
                shortcut: 'p',
                usage:
                  'The port number that S3 will use to communicate with your application. If you do not specify this option, the default port is 4569',
              },
              directory: {
                shortcut: 'd',
                usage:
                  'The directory where S3 will store its objects. If you do not specify this option, the file will be written to the current directory.',
              },
              buckets: {
                shortcut: 'b',
                usage: 'After starting S3 local, create specified buckets',
              },
              cors: {
                shortcut: 'c',
                usage: 'Enable CORS',
              },
              noStart: {
                shortcut: 'n',
                default: false,
                usage: 'Do not start S3 local (in case it is already running)',
              },
            },
          },
          create: {
            usage: 'Create local S3 buckets.',
            lifecycleEvents: ['createHandler'],
            options: {
              port: {
                shortcut: 'p',
                usage:
                  'The port number that S3 will use to communicate with your application. If you do not specify this option, the default port is 4569',
              },
              buckets: {
                shortcut: 'b',
                usage: 'After starting S3 local, create specified buckets',
              },
            }
          },
          remove: {
            usage: 'Remove local S3 buckets.',
            lifecycleEvents: ['createHandler'],
            options: {
              port: {
                shortcut: 'p',
                usage:
                  'The port number that S3 will use to communicate with your application. If you do not specify this option, the default port is 4569',
              },
              buckets: {
                shortcut: 'b',
                usage: 'After starting S3 local, create specified buckets',
              },
            }
          }
        },
      },
    };

    this.hooks = {
      's3:start:startHandler': this.startHandler.bind(this),
      's3:create:createHandler': this.createHandler.bind(this),
      's3:remove:createHandler': this.removeHandler.bind(this),
      'before:offline:start:init': this.startHandler.bind(this),
      'before:offline:start': this.startHandler.bind(this),
      'before:offline:start:end': this.endHandler.bind(this),
    };
  }

  startHandler() {
    return new Promise((resolve, reject) => {
      this._setOptions();
      const { noStart, port, host, cors } = this.options;
      if (noStart) {
        return this.createBuckets().then(resolve, reject);
      }

      const dirPath = this.options.directory || './buckets';
      fs.ensureDirSync(dirPath); // Create destination directory if not exist
      const directory = fs.realpathSync(dirPath);

      const corsPolicy = cors ?
            fs.readFileSync(path.resolve(this.serverless.config.servicePath, cors) , 'utf8') :
            cors ;

      this.client = new S3rver({
        port,
        hostname: host,
        silent: false,
        directory,
        cors: corsPolicy,
      }).run((err, s3Host, s3Port) => {
        if (err) {
          console.error('Error occurred while starting S3 local.');
          reject(err);
          return;
        }

        this.options.port = s3Port
        console.log(`S3 local started ( port:${s3Port} )`);

        this.createBuckets().then(resolve, reject);
      });

      this.eventHandlers = this.getEventHandlers();

      this.client.s3Event.map((event) => {
          const bucketName = event.Records[0].s3.bucket.name;
          if(!(bucketName in this.eventHandlers)) {
              return [];
          }

          const eventName = event.Records[0].eventName;
          return Object.keys(this.eventHandlers[bucketName])
                  .filter(pattern => eventName.match(pattern) !== null)
                  .map(pattern => this.eventHandlers[bucketName][pattern])
                  .reduce((acc, x) => acc.concat(x), [])
                  .map(handler => () => handler(event));
      }).mergeMap((handlers) => {
          return handlers
      }).subscribe((handler) => {
          handler();
      });
    });
  }

  endHandler() {
    if (!this.options.noStart) {
      this.client.close();
      console.log('S3 local closed');
    }
  }

  createHandler() {
    this._setOptions();
    return this.createBuckets();
  }

  removeHandler() {
    this._setOptions();
    return this.removeBuckets();
  }

  createBuckets() {
    return Promise.resolve().then(() => {
      const buckets = this.buckets();
      if (!buckets.length) return;

      const s3Client = this.getClient();
      return Promise.all(buckets.map(Bucket => {
        this.serverless.cli.log(`creating bucket: ${Bucket}`);
        return s3Client.createBucket({ Bucket }).promise();
      }));
    })
    .catch(x => {});
  }

  removeBuckets() {
    return Promise.resolve().then(() => {
      const { port } = this.options;
      const buckets = this.buckets();
      if (!buckets.length) return;

      return Promise.all(buckets.map(bucket => {
        this.serverless.cli.log(`removing bucket: ${bucket}`);
        return removeBucket({ port, bucket });
      }));
    });
  }

  getClient() {
    return new AWS.S3({
      s3ForcePathStyle: true,
      endpoint: new AWS.Endpoint(`http://localhost:${this.options.port}`),
    });
  }

  getEventHandlers() {
      if (typeof this.service !== 'object' || typeof this.service.functions !== 'object') {
          return {}
      }

      const eventHandlers = {}
      const servicePath = path.join(this.serverless.config.servicePath, this.options.location);
      Object.keys(this.service.functions).forEach(key => {
          const serviceFunction = this.service.getFunction(key);

          serviceFunction.events.forEach(event => {
              const s3 = (event && event.s3) || undefined;
              if (!s3) {
                  return;
              }

              const handlerBucketName = (typeof s3 === 'object') ? s3.bucket : s3;
              const bucketResource = this.getResourceForBucket(handlerBucketName);
              const name = bucketResource ? bucketResource.Properties.BucketName : name ;
              const pattern = (typeof s3 === 'object') ? s3.event.replace(/^s3:/,'').replace('*', '.*') :'.*';

              const lambdaContext = createLambdaContext(serviceFunction);
              const funOptions = functionHelper.getFunctionOptions(serviceFunction, key, servicePath);
              const handler = functionHelper.createHandler(funOptions, this.options);
              const eventHandler = (s3Event) => handler(s3Event, lambdaContext)
              if(!(name in eventHandlers)) {
                  eventHandlers[name] = {}
              }
              if(!(pattern in eventHandlers[name])) {
                  eventHandlers[name][pattern] = []
              }
              eventHandlers[name][pattern].push(eventHandler)

              this.serverless.cli.log(`Found S3 event listener for ${name}`);
          });
      });

      return eventHandlers;
  }

  getResourceForBucket(bucketName){
    const logicalResourceName = `S3Bucket${bucketName.charAt(0).toUpperCase()}${bucketName.substr(1)}`;
    return !this.service.resources || this.service.resources.Resources[logicalResourceName];
  }

  getAdditionalStacks() {
    const serviceAdditionalStacks = this.service.custom.additionalStacks || {};
    const additionalStacks = [];
    Object.keys(serviceAdditionalStacks).forEach((stack) => {
      additionalStacks.push(serviceAdditionalStacks[stack]);
    });
    return additionalStacks;
  }

  hasAdditionalStacksPlugin() {
    return (
      this.service &&
      this.service.plugins &&
      this.service.plugins.indexOf('serverless-plugin-additional-stacks') >= 0
    );
  }

  /**
   * Get bucket list from serverless.yml resources and additional stacks
   *
   * @return {object} Array of bucket name
   */
  buckets() {
    const resources = (this.service.resources && this.service.resources.Resources) || {};
    if (this.hasAdditionalStacksPlugin()) {
      let additionalStacks = [];
      additionalStacks = additionalStacks.concat(this.getAdditionalStacks());
      additionalStacks.forEach((stack) => {
        if (stack.Resources) {
          Object.keys(stack.Resources).forEach((key) => {
            if (stack.Resources[key].Type === 'AWS::S3::Bucket') {
              resources[key] = stack.Resources[key];
            }
          });
        }
      });
    }
    return Object.keys(resources)
      .map((key) => {
        if (resources[key].Type === 'AWS::S3::Bucket' && resources[key].Properties && resources[key].Properties.BucketName) {
          return resources[key].Properties.BucketName;
        }
        return null;
      })
      .concat(this.options.buckets)
      .filter(n => n);
  }

  _setOptions() {
    const config = (this.serverless.service.custom && this.serverless.service.custom.s3) || {};
    this.options = Object.assign({}, defaultOptions, this.options, config);
  }
}

const removeBucket = ({ bucket, port }) => new Promise((resolve, reject) => {
  shell.exec(
    `aws --endpoint http://localhost:${port} s3 rb "s3://${bucket}" --force`,
    { silent: true },
    function (code, stdout, stderr) {
      if (code === 0) return resolve();
      if (stderr && stderr.indexOf('NoSuchBucket') !== -1) return resolve();

      reject(new Error(`failed to delete bucket ${bucket}: ${stderr || stdout}`));
    }
  );
});

module.exports = ServerlessS3Local;
<commit_msg>fix for when no resource<commit_after>const S3rver = require('s3rver');
const fs = require('fs-extra'); // Using fs-extra to ensure destination directory exist
const AWS = require('aws-sdk');
const shell = require('shelljs');
const path = require('path');
const uuid = require('uuid/v1');
const functionHelper = require('serverless-offline/src/functionHelper');
const createLambdaContext = require('serverless-offline/src/createLambdaContext');

require("rxjs/add/operator/map");
require("rxjs/add/operator/mergeMap");

const defaultOptions = {
  port: 4569,
  host: 'localhost',
  location: '.'
}

class ServerlessS3Local {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.service = serverless.service;
    this.options = options;
    this.provider = 'aws';
    this.client = null;

    this.commands = {
      s3: {
        commands: {
          start: {
            usage: 'Start S3 local server.',
            lifecycleEvents: ['startHandler'],
            options: {
              port: {
                shortcut: 'p',
                usage:
                  'The port number that S3 will use to communicate with your application. If you do not specify this option, the default port is 4569',
              },
              directory: {
                shortcut: 'd',
                usage:
                  'The directory where S3 will store its objects. If you do not specify this option, the file will be written to the current directory.',
              },
              buckets: {
                shortcut: 'b',
                usage: 'After starting S3 local, create specified buckets',
              },
              cors: {
                shortcut: 'c',
                usage: 'Enable CORS',
              },
              noStart: {
                shortcut: 'n',
                default: false,
                usage: 'Do not start S3 local (in case it is already running)',
              },
            },
          },
          create: {
            usage: 'Create local S3 buckets.',
            lifecycleEvents: ['createHandler'],
            options: {
              port: {
                shortcut: 'p',
                usage:
                  'The port number that S3 will use to communicate with your application. If you do not specify this option, the default port is 4569',
              },
              buckets: {
                shortcut: 'b',
                usage: 'After starting S3 local, create specified buckets',
              },
            }
          },
          remove: {
            usage: 'Remove local S3 buckets.',
            lifecycleEvents: ['createHandler'],
            options: {
              port: {
                shortcut: 'p',
                usage:
                  'The port number that S3 will use to communicate with your application. If you do not specify this option, the default port is 4569',
              },
              buckets: {
                shortcut: 'b',
                usage: 'After starting S3 local, create specified buckets',
              },
            }
          }
        },
      },
    };

    this.hooks = {
      's3:start:startHandler': this.startHandler.bind(this),
      's3:create:createHandler': this.createHandler.bind(this),
      's3:remove:createHandler': this.removeHandler.bind(this),
      'before:offline:start:init': this.startHandler.bind(this),
      'before:offline:start': this.startHandler.bind(this),
      'before:offline:start:end': this.endHandler.bind(this),
    };
  }

  startHandler() {
    return new Promise((resolve, reject) => {
      this._setOptions();
      const { noStart, port, host, cors } = this.options;
      if (noStart) {
        return this.createBuckets().then(resolve, reject);
      }

      const dirPath = this.options.directory || './buckets';
      fs.ensureDirSync(dirPath); // Create destination directory if not exist
      const directory = fs.realpathSync(dirPath);

      const corsPolicy = cors ?
            fs.readFileSync(path.resolve(this.serverless.config.servicePath, cors) , 'utf8') :
            cors ;

      this.client = new S3rver({
        port,
        hostname: host,
        silent: false,
        directory,
        cors: corsPolicy,
      }).run((err, s3Host, s3Port) => {
        if (err) {
          console.error('Error occurred while starting S3 local.');
          reject(err);
          return;
        }

        this.options.port = s3Port
        console.log(`S3 local started ( port:${s3Port} )`);

        this.createBuckets().then(resolve, reject);
      });

      this.eventHandlers = this.getEventHandlers();

      this.client.s3Event.map((event) => {
          const bucketName = event.Records[0].s3.bucket.name;
          if(!(bucketName in this.eventHandlers)) {
              return [];
          }

          const eventName = event.Records[0].eventName;
          return Object.keys(this.eventHandlers[bucketName])
                  .filter(pattern => eventName.match(pattern) !== null)
                  .map(pattern => this.eventHandlers[bucketName][pattern])
                  .reduce((acc, x) => acc.concat(x), [])
                  .map(handler => () => handler(event));
      }).mergeMap((handlers) => {
          return handlers
      }).subscribe((handler) => {
          handler();
      });
    });
  }

  endHandler() {
    if (!this.options.noStart) {
      this.client.close();
      console.log('S3 local closed');
    }
  }

  createHandler() {
    this._setOptions();
    return this.createBuckets();
  }

  removeHandler() {
    this._setOptions();
    return this.removeBuckets();
  }

  createBuckets() {
    return Promise.resolve().then(() => {
      const buckets = this.buckets();
      if (!buckets.length) return;

      const s3Client = this.getClient();
      return Promise.all(buckets.map(Bucket => {
        this.serverless.cli.log(`creating bucket: ${Bucket}`);
        return s3Client.createBucket({ Bucket }).promise();
      }));
    })
    .catch(x => {});
  }

  removeBuckets() {
    return Promise.resolve().then(() => {
      const { port } = this.options;
      const buckets = this.buckets();
      if (!buckets.length) return;

      return Promise.all(buckets.map(bucket => {
        this.serverless.cli.log(`removing bucket: ${bucket}`);
        return removeBucket({ port, bucket });
      }));
    });
  }

  getClient() {
    return new AWS.S3({
      s3ForcePathStyle: true,
      endpoint: new AWS.Endpoint(`http://localhost:${this.options.port}`),
    });
  }

  getEventHandlers() {
      if (typeof this.service !== 'object' || typeof this.service.functions !== 'object') {
          return {}
      }

      const eventHandlers = {}
      const servicePath = path.join(this.serverless.config.servicePath, this.options.location);
      Object.keys(this.service.functions).forEach(key => {
          const serviceFunction = this.service.getFunction(key);

          serviceFunction.events.forEach(event => {
              const s3 = (event && event.s3) || undefined;
              if (!s3) {
                  return;
              }

              const handlerBucketName = (typeof s3 === 'object') ? s3.bucket : s3;
              const bucketResource = this.getResourceForBucket(handlerBucketName);
              const name = bucketResource ? bucketResource.Properties.BucketName : handlerBucketName ;
              const pattern = (typeof s3 === 'object') ? s3.event.replace(/^s3:/,'').replace('*', '.*') :'.*';

              const lambdaContext = createLambdaContext(serviceFunction);
              const funOptions = functionHelper.getFunctionOptions(serviceFunction, key, servicePath);
              const handler = functionHelper.createHandler(funOptions, this.options);
              const eventHandler = (s3Event) => handler(s3Event, lambdaContext)
              if(!(name in eventHandlers)) {
                  eventHandlers[name] = {}
              }
              if(!(pattern in eventHandlers[name])) {
                  eventHandlers[name][pattern] = []
              }
              eventHandlers[name][pattern].push(eventHandler)

              this.serverless.cli.log(`Found S3 event listener for ${name}`);
          });
      });

      return eventHandlers;
  }

  getResourceForBucket(bucketName){
    const logicalResourceName = `S3Bucket${bucketName.charAt(0).toUpperCase()}${bucketName.substr(1)}`;
    return this.service.resources ? this.service.resources.Resources[logicalResourceName] : false ;
  }

  getAdditionalStacks() {
    const serviceAdditionalStacks = this.service.custom.additionalStacks || {};
    const additionalStacks = [];
    Object.keys(serviceAdditionalStacks).forEach((stack) => {
      additionalStacks.push(serviceAdditionalStacks[stack]);
    });
    return additionalStacks;
  }

  hasAdditionalStacksPlugin() {
    return (
      this.service &&
      this.service.plugins &&
      this.service.plugins.indexOf('serverless-plugin-additional-stacks') >= 0
    );
  }

  /**
   * Get bucket list from serverless.yml resources and additional stacks
   *
   * @return {object} Array of bucket name
   */
  buckets() {
    const resources = (this.service.resources && this.service.resources.Resources) || {};
    if (this.hasAdditionalStacksPlugin()) {
      let additionalStacks = [];
      additionalStacks = additionalStacks.concat(this.getAdditionalStacks());
      additionalStacks.forEach((stack) => {
        if (stack.Resources) {
          Object.keys(stack.Resources).forEach((key) => {
            if (stack.Resources[key].Type === 'AWS::S3::Bucket') {
              resources[key] = stack.Resources[key];
            }
          });
        }
      });
    }
    return Object.keys(resources)
      .map((key) => {
        if (resources[key].Type === 'AWS::S3::Bucket' && resources[key].Properties && resources[key].Properties.BucketName) {
          return resources[key].Properties.BucketName;
        }
        return null;
      })
      .concat(this.options.buckets)
      .filter(n => n);
  }

  _setOptions() {
    const config = (this.serverless.service.custom && this.serverless.service.custom.s3) || {};
    this.options = Object.assign({}, defaultOptions, this.options, config);
  }
}

const removeBucket = ({ bucket, port }) => new Promise((resolve, reject) => {
  shell.exec(
    `aws --endpoint http://localhost:${port} s3 rb "s3://${bucket}" --force`,
    { silent: true },
    function (code, stdout, stderr) {
      if (code === 0) return resolve();
      if (stderr && stderr.indexOf('NoSuchBucket') !== -1) return resolve();

      reject(new Error(`failed to delete bucket ${bucket}: ${stderr || stdout}`));
    }
  );
});

module.exports = ServerlessS3Local;
<|endoftext|>


<commit_before>'use strict';

var async    = require ('async');
var inquirer = require ('inquirer');

/**
 * Prompt constructor.
 */
function Prompt () {
  return [{
    type    : 'input',
    name    : 'command',
    message : '>'
  }];
}

/**
 * Command constructor.
 */
function Command (handler, params, desc) {
  this._parent  = null;
  this._name    = null;
  this._params  = params;
  this._desc    = desc;
  this._handler = handler;
  return this;
}

Command.prototype._setName = function (name) {
  this._name = name;
};

Command.prototype._setParent = function (parent) {
  this._parent = parent;
};

Command.prototype.isBuiltIn = function () {
  return this._params.builtIn ? true : false;
};

Command.prototype.isWizard = function () {
  return this._params.wizard ? true : false;
};

Command.prototype.help = function (onlyDesc) {
  var help = '';
  if (!onlyDesc) {
    help += ' ' + this._name;
    help += new Array (this._parent._helpWidth () - this._name.length).join (' ');
  }

  help += this._desc;
  return help;
};

Command.prototype.call = function () {
  return this._handler.apply (this, arguments);
};

/**
 * CommandsList constructor.
 */
function CommandsList () {
  this._helpLength = 0;
}

CommandsList.prototype._helpWidth = function () {
  return this._helpLength + 5;
};

CommandsList.prototype._add = function (cmd, obj) {
  if (cmd.length > this._helpLength) {
    this._helpLength = cmd.length;
  }

  obj._setName (cmd);
  obj._setParent (this);
  CommandsList.prototype[cmd] = obj;
};

/**
 * ShellCraft constructor.
 */
function ShellCraft () {
  var self = this;

  this._exit = false;

  this.commands = new CommandsList ();
  this.commands._add ('exit', new Command (function (callback) {
    self._exit = true;
    if (callback) {
      callback ();
    }
  }, {builtIn: true}, 'exit the shell'));
  this.commands._add ('help', new Command (function (callback) {
    Object.keys (Object.getPrototypeOf (self.commands)).forEach (function (fct) {
      if (!self.commands.hasOwnProperty (fct) && !/^_/.test(fct)) {
        console.log (self.commands[fct].help ());
      }
    });
    if (callback) {
      callback ();
    }
  }, {builtIn: true}, 'list of commands'));

  this.options = {
    version: '0.0.1'
  };
  this.prompt  = new Prompt ();
  this.extensions = [];
}

/**
 * Check if we must exit the shell.
 *
 * @api private
 * @returns {boolean} Exit status.
 */
ShellCraft.prototype.isExit = function () {
  return this._exit;
};

/**
 * Start the Shell interface.
 *
 * @api private
 * @param {function(results)} callback
 */
ShellCraft.prototype.shell = function (callback) {
  var self = this;

  if (this.options.prompt) {
    this.prompt[0].message = this.options.prompt;
  }

  var history = [];
  var iterator = 0;

  process.stdin.setRawMode (true);
  var uiPrompt = {};

  /*
   * Handle command history.
   */
  process.stdin.on ('keypress', function (chunk, key) {
    if (key) {
      switch (key.name) {
      case 'up': {
        if (iterator > 0) {
          --iterator;
        }
        uiPrompt.rl.write (null, {ctrl: true, name: 'u'});
        uiPrompt.rl.write (history[iterator]);
        break;
      }
      case 'down': {
        uiPrompt.rl.write (null, {ctrl: true, name: 'u'});
        if (iterator < history.length - 1) {
          ++iterator;
          uiPrompt.rl.write (history[iterator]);
        } else {
          iterator = history.length;
        }
        break;
      }
      }
    }
  });

  var inquirerCallback = null;
  var inquirerPrompt   = self.prompt;

  async.forever (function (next) {
    uiPrompt = inquirer.prompt (inquirerPrompt, function (answers) {
      /*
       * Special handling when the command returns an Inquirer definition. In
       * this case we must return the answers to the caller.
       */
      if (inquirerCallback) {
        inquirerCallback (answers);
        inquirerCallback = null;
        inquirerPrompt   = self.prompt;
        next (null);
        return;
      }

      /*
       * Normal shell handling for the commands.
       */
      var cmdArgs = answers.command.split (' ');
      var cmd = cmdArgs[0];
      cmdArgs.shift ();

      if (cmd.length && cmd !== history[history.length - 1]) {
        history.push (cmd);
      }
      iterator = history.length;

      try {
        self.commands[cmd].call (function (data, wizardCallback) {
          /* The next prompt we must start the wizard provided by the client. */
          if (self.commands[cmd].isWizard ()) {
            inquirerPrompt = data;
            inquirerCallback = wizardCallback;
          }
          next (self.isExit () ? 'good bye' : null);
        }, cmdArgs);
      } catch (ex) {
        if (answers.command.length) {
          console.log ('command ' + cmd + ' unknown');
        }

        next (null);
      }
    });
  }, function (results) {
    if (callback) {
      callback (results);
    }
  });
};

/**
 * Start the Command Line Interface (CLI).
 *
 * @api private
 * @param {function(results)} callback
 */
ShellCraft.prototype.cli = function (callback) {
  var self = this;
  var program = require ('commander');

  program.version (this.options.version);

  Object.keys (Object.getPrototypeOf (this.commands)).forEach (function (fct) {
    if (self.commands.hasOwnProperty (fct) || /^_/.test (fct) || self.commands[fct].isBuiltIn ()) {
      return;
    }

    program.option (fct, self.commands[fct].help (true), function (arg) {
      self.commands[fct].call (function (data, wizardCallback) {
        if (!self.commands[fct].isWizard ()) {
          if (callback) {
            callback ();
          }
          return;
        }

        /* Start the wizard. */
        inquirer.prompt (data, function (answers) {
          wizardCallback (answers);
          if (callback) {
            callback ();
          }
        });
      }, arg);
    });
  });

  program.parse (process.argv);
};

ShellCraft.prototype.shutdown = function (callback) {
  this.extensions.forEach (function (extension) {
    extension.unregister ();
  });

  callback ();
};

/**
 * Register external commands.
 *
 * The module must return an array like this:
 * [{
 *   name: 'foobar',
 *   desc: 'foobar description',
 *   params: {
 *     wizard: false
 *   },
 *   handler: function (callback) {
 *     console.log ('run foobar');
 *     callback ();
 *   }
 * }]
 *
 * The callback function must always be called.
 *
 * @param {string} shellExt - Path on the shell extension file.
 */
ShellCraft.prototype.registerExtension = function (shellExt) {
  var self = this;
  var ext = require (shellExt);

  ext.register (function (extension) {
    extension.forEach (function (cmd) {
      self.commands._add (cmd.name, new Command (cmd.handler, cmd.params, cmd.desc));
    });
  });

  this.extensions.push (ext);
};

/**
 * Begins the Shell or the CLI accordingly to the provided arguments.
 *
 * @param {Object} options
 * @param {function(results)} callback
 */
ShellCraft.prototype.begin = function (options, callback) {
  var self = this;
  this.options = options;

  /* Run the Shell. */
  if (process.argv.length === 2) {
    this.shell (function () {
      self.shutdown (callback);
    });
    return;
  }

  /* Run in command line. */
  this.cli (function () {
    self.shutdown (callback);
  });
};

exports = module.exports = new ShellCraft ();
<commit_msg>Provide a callback with the registerExtension function.<commit_after>'use strict';

var async    = require ('async');
var inquirer = require ('inquirer');

/**
 * Prompt constructor.
 */
function Prompt () {
  return [{
    type    : 'input',
    name    : 'command',
    message : '>'
  }];
}

/**
 * Command constructor.
 */
function Command (handler, params, desc) {
  this._parent  = null;
  this._name    = null;
  this._params  = params;
  this._desc    = desc;
  this._handler = handler;
  return this;
}

Command.prototype._setName = function (name) {
  this._name = name;
};

Command.prototype._setParent = function (parent) {
  this._parent = parent;
};

Command.prototype.isBuiltIn = function () {
  return this._params.builtIn ? true : false;
};

Command.prototype.isWizard = function () {
  return this._params.wizard ? true : false;
};

Command.prototype.help = function (onlyDesc) {
  var help = '';
  if (!onlyDesc) {
    help += ' ' + this._name;
    help += new Array (this._parent._helpWidth () - this._name.length).join (' ');
  }

  help += this._desc;
  return help;
};

Command.prototype.call = function () {
  return this._handler.apply (this, arguments);
};

/**
 * CommandsList constructor.
 */
function CommandsList () {
  this._helpLength = 0;
}

CommandsList.prototype._helpWidth = function () {
  return this._helpLength + 5;
};

CommandsList.prototype._add = function (cmd, obj) {
  if (cmd.length > this._helpLength) {
    this._helpLength = cmd.length;
  }

  obj._setName (cmd);
  obj._setParent (this);
  CommandsList.prototype[cmd] = obj;
};

/**
 * ShellCraft constructor.
 */
function ShellCraft () {
  var self = this;

  this._exit = false;

  this.commands = new CommandsList ();
  this.commands._add ('exit', new Command (function (callback) {
    self._exit = true;
    if (callback) {
      callback ();
    }
  }, {builtIn: true}, 'exit the shell'));
  this.commands._add ('help', new Command (function (callback) {
    Object.keys (Object.getPrototypeOf (self.commands)).forEach (function (fct) {
      if (!self.commands.hasOwnProperty (fct) && !/^_/.test(fct)) {
        console.log (self.commands[fct].help ());
      }
    });
    if (callback) {
      callback ();
    }
  }, {builtIn: true}, 'list of commands'));

  this.options = {
    version: '0.0.1'
  };
  this.prompt  = new Prompt ();
  this.extensions = [];
}

/**
 * Check if we must exit the shell.
 *
 * @api private
 * @returns {boolean} Exit status.
 */
ShellCraft.prototype.isExit = function () {
  return this._exit;
};

/**
 * Start the Shell interface.
 *
 * @api private
 * @param {function(results)} callback
 */
ShellCraft.prototype.shell = function (callback) {
  var self = this;

  if (this.options.prompt) {
    this.prompt[0].message = this.options.prompt;
  }

  var history = [];
  var iterator = 0;

  process.stdin.setRawMode (true);
  var uiPrompt = {};

  /*
   * Handle command history.
   */
  process.stdin.on ('keypress', function (chunk, key) {
    if (key) {
      switch (key.name) {
      case 'up': {
        if (iterator > 0) {
          --iterator;
        }
        uiPrompt.rl.write (null, {ctrl: true, name: 'u'});
        uiPrompt.rl.write (history[iterator]);
        break;
      }
      case 'down': {
        uiPrompt.rl.write (null, {ctrl: true, name: 'u'});
        if (iterator < history.length - 1) {
          ++iterator;
          uiPrompt.rl.write (history[iterator]);
        } else {
          iterator = history.length;
        }
        break;
      }
      }
    }
  });

  var inquirerCallback = null;
  var inquirerPrompt   = self.prompt;

  async.forever (function (next) {
    uiPrompt = inquirer.prompt (inquirerPrompt, function (answers) {
      /*
       * Special handling when the command returns an Inquirer definition. In
       * this case we must return the answers to the caller.
       */
      if (inquirerCallback) {
        inquirerCallback (answers);
        inquirerCallback = null;
        inquirerPrompt   = self.prompt;
        next (null);
        return;
      }

      /*
       * Normal shell handling for the commands.
       */
      var cmdArgs = answers.command.split (' ');
      var cmd = cmdArgs[0];
      cmdArgs.shift ();

      if (cmd.length && cmd !== history[history.length - 1]) {
        history.push (cmd);
      }
      iterator = history.length;

      try {
        self.commands[cmd].call (function (data, wizardCallback) {
          /* The next prompt we must start the wizard provided by the client. */
          if (self.commands[cmd].isWizard ()) {
            inquirerPrompt = data;
            inquirerCallback = wizardCallback;
          }
          next (self.isExit () ? 'good bye' : null);
        }, cmdArgs);
      } catch (ex) {
        if (answers.command.length) {
          console.log ('command ' + cmd + ' unknown');
        }

        next (null);
      }
    });
  }, function (results) {
    if (callback) {
      callback (results);
    }
  });
};

/**
 * Start the Command Line Interface (CLI).
 *
 * @api private
 * @param {function(results)} callback
 */
ShellCraft.prototype.cli = function (callback) {
  var self = this;
  var program = require ('commander');

  program.version (this.options.version);

  Object.keys (Object.getPrototypeOf (this.commands)).forEach (function (fct) {
    if (self.commands.hasOwnProperty (fct) || /^_/.test (fct) || self.commands[fct].isBuiltIn ()) {
      return;
    }

    program.option (fct, self.commands[fct].help (true), function (arg) {
      self.commands[fct].call (function (data, wizardCallback) {
        if (!self.commands[fct].isWizard ()) {
          if (callback) {
            callback ();
          }
          return;
        }

        /* Start the wizard. */
        inquirer.prompt (data, function (answers) {
          wizardCallback (answers);
          if (callback) {
            callback ();
          }
        });
      }, arg);
    });
  });

  program.parse (process.argv);
};

ShellCraft.prototype.shutdown = function (callback) {
  this.extensions.forEach (function (extension) {
    extension.unregister ();
  });

  callback ();
};

/**
 * Register external commands.
 *
 * The module must return an array like this:
 * [{
 *   name: 'foobar',
 *   desc: 'foobar description',
 *   params: {
 *     wizard: false
 *   },
 *   handler: function (callback) {
 *     console.log ('run foobar');
 *     callback ();
 *   }
 * }]
 *
 * The callback function must always be called.
 *
 * @param {string} shellExt - Path on the shell extension file.
 */
ShellCraft.prototype.registerExtension = function (shellExt, callback) {
  var self = this;
  var ext = require (shellExt);

  ext.register (function (extension) {
    extension.forEach (function (cmd) {
      self.commands._add (cmd.name, new Command (cmd.handler, cmd.params, cmd.desc));
    });

    if (callback ()) {
      callback ();
    }
  });

  this.extensions.push (ext);
};

/**
 * Begins the Shell or the CLI accordingly to the provided arguments.
 *
 * @param {Object} options
 * @param {function(results)} callback
 */
ShellCraft.prototype.begin = function (options, callback) {
  var self = this;
  this.options = options;

  /* Run the Shell. */
  if (process.argv.length === 2) {
    this.shell (function () {
      self.shutdown (callback);
    });
    return;
  }

  /* Run in command line. */
  this.cli (function () {
    self.shutdown (callback);
  });
};

exports = module.exports = new ShellCraft ();
<|endoftext|>


<commit_before>'use strict';

var fs            = require('fs'),
    path          = require('path'),
    util          = require('util'),
    mkdirp        = require('mkdirp'),
    quickTemp     = require('quick-temp'),
    symlinkOrCopy = require('symlink-or-copy'),
    walkSync      = require('walk-sync'),
    helpers       = require('broccoli-kitchen-sink-helpers'),
    Writer        = require('broccoli-writer');

var transpiler   = require('es6-module-transpiler'),
    Container    = transpiler.Container,
    FileResolver = transpiler.FileResolver,
    Module       = require('es6-module-transpiler/lib/module');

module.exports = CompileModules;

// -----------------------------------------------------------------------------

var hashFile    = helpers.hashTree;
var hashStrings = helpers.hashStrings;

// -- CompileModules -----------------------------------------------------------

function CompileModules(inputTree, options) {
    if (!(this instanceof CompileModules)) {
        return new CompileModules(inputTree, options);
    }

    options || (options = {});

    var formatter = options.formatter;

    if (!formatter) {
        formatter = transpiler.formatters.DEFAULT;
    }

    if (typeof formatter === 'string') {
        formatter = transpiler.formatters[formatter];
    }

    var resolverClasses = options.resolvers;

    if (!resolverClasses) {
        resolverClasses = [ FileResolver ];
    }

    this.inputTree       = inputTree;
    this.resolverClasses = resolverClasses;
    this.formatter       = formatter;
    this.output          = options.output || '.';
    this.description     = options.description;

    this._cache      = {};
    this._cacheIndex = 0;
}

util.inherits(CompileModules, Writer);

CompileModules.prototype.cleanup = function () {
    quickTemp.remove(this, 'tmpCacheDir');
    Writer.prototype.cleanup.apply(this, arguments);
};

CompileModules.prototype.getCacheDir = function () {
    return quickTemp.makeOrReuse(this, 'tmpCacheDir');
};

CompileModules.prototype.write = function (readTree, destDir) {
    return readTree(this.inputTree).then(function (srcDir) {
        var outputPath = path.join(destDir, this.output),
            modules    = [];

        function hash(filePaths) {
            if (Array.isArray(filePaths)) {
                return hashStrings(filePaths.map(function (filePath) {
                    return hashFile(path.join(srcDir, filePath));
                }));
            }

            return hashFile(path.join(srcDir, filePaths));
        }

        walkSync(srcDir).forEach(function (relPath) {
            // Skip doing anything with dir entries. When outputting a bundle
            // format some dirs may go away. For non-JavaScript files, their
            // containing dir will be created before they are copied over.
            if (relPath.charAt(relPath.length - 1) === '/') {
                return;
            }

            // Keep track of all the JavaScript modules.
            // path.extname does not take into account the trailing '/' when
            // checking for the file's extension.
            if (path.extname(relPath) === '.js') {
                modules.push(relPath);
                return;
            }

            var srcPath  = path.join(srcDir, relPath),
                destPath = path.join(destDir, relPath);

            // Copy over non-JavaScript files to the `destDir`.
            mkdirp.sync(path.dirname(destPath));
            symlinkOrCopy.sync(srcPath, destPath);
        });

        var modulesToCompile = [],
            cache            = this._cache,
            cacheEntry;

        // The specificed `output` can either be a file (for bundle formatters),
        // or a dir.
        //
        // When outputting to a single file, all the input files must must be
        // unchanged in order to use the cached compiled file.
        //
        // When outputting to a dir, we symlink/copy over the cached compiled
        // file for any modules that are unchanged. Any modified modules are
        // added to the `modulesToCompile` collection.
        if (path.extname(outputPath) === '.js') {
            cacheEntry = cache[path.basename(outputPath)];

            // Must hash _all_ modules when outputting to a single file.
            if (cacheEntry && cacheEntry.hash === hash(modules)) {
                this.copyFromCache(cacheEntry, path.dirname(outputPath));
            } else {
                // With a cache-miss, we need to re-generate the bundle output
                // file, so we have to visit all the modules. The CacheResolver
                // will make sure we don't have to re-read the modules that are
                // unchanged.
                modulesToCompile = modules;
            }
        } else {
            // Iterate over all the modules and copy compiled files from the
            // cache for the ones that are unchanged.
            modules.forEach(function (module) {
                cacheEntry = cache[module];

                if (cacheEntry && cacheEntry.hash === hash(module)) {
                    this.copyFromCache(cacheEntry, outputPath);
                } else {
                    modulesToCompile.push(module);
                }
            }, this);
        }

        this.compileAndCacheModules(modulesToCompile, srcDir, outputPath);
    }.bind(this));
};

CompileModules.prototype.compileAndCacheModules = function (modulePaths, srcDir, outputPath) {
    // Noop when no modules to compile.
    if (modulePaths.length < 1) { return; }

    var cache        = this._cache,
        outputIsFile = path.extname(outputPath) === '.js';

    // The container will first use the CacheResolver so that any unchanged
    // modules that need to be visited by the transpiler don't have to be
    // re-read from disk or re-parsed.
    //
    // If "foo" imports "bar", and "bar" is unchanged, the transpiler still will
    // need to vist it when re-processing "foo".
    var container = new Container({
        formatter: this.formatter,
        resolvers: this.getResolvers(cache, srcDir)
    });

    // Returns transpiler `Module` instances.
    var modules = modulePaths.map(function (modulePath) {
        return container.getModule(modulePath);
    });

    // Create a new cache sub-dir for this compile run.
    var cacheDir = path.join(this.getCacheDir(), String(this._cacheIndex++));

    // Determine target path to compile modules to.
    var target = outputIsFile ?
            path.join(cacheDir, path.basename(outputPath)) : cacheDir;

    // Creates the output dir, then outputs the compiled modules to the cache.
    mkdirp.sync(outputIsFile ? path.dirname(target) : target);
    container.write(target);

    var outputHashes = [],
        cacheEntry, outputFile;

    modules.forEach(function (module) {
        var hash    = hashFile(module.path),
            relPath = path.relative(srcDir, module.path),
            src     = module.src;

        // Adds an entry to the cache for the later use by the CacheResolver.
        // This holds the parsed and walked AST, so re-builds of unchanged
        // modules don't need to be re-read and re-parsed.
        var cacheEntry = cache[relPath] = {
            hash: hash,
            src : src
        };

        // Accumulate hashes if the final output is a single bundle file, and
        // return early.
        if (outputIsFile) {
            outputHashes.push(hash);
            return;
        }

        // When outputting to a dir, add the compiled files to the cache entry
        // and copy the files from the cache dir to the `outputPath`.

        cacheEntry.dir = cacheDir;

        cacheEntry.outputFiles = [
            relPath,
            relPath + '.map'
        ];

        this.copyFromCache(cacheEntry, outputPath);
    }, this);

    if (outputIsFile) {
        outputFile = path.basename(outputPath);

        // Create a cache entry for the entire bundle output file and copy it
        // from the cache to the `outputPath`. Compute the hash as a hash of
        // hashes.
        cacheEntry = cache[outputFile] = {
            hash: hashStrings(outputHashes),
            dir : cacheDir,

            outputFiles: [
                outputFile,
                outputFile + '.map'
            ]
        };

        this.copyFromCache(cacheEntry, path.dirname(outputPath));
    }
};

CompileModules.prototype.copyFromCache = function (cacheEntry, destDir) {
    var cacheDir = cacheEntry.dir;

    cacheEntry.outputFiles.forEach(function (outputFile) {
        var cachePath = path.join(cacheDir, outputFile),
            destPath  = path.join(destDir, outputFile);

        mkdirp.sync(path.dirname(destPath));
        symlinkOrCopy.sync(cachePath, destPath);
    });
};

CompileModules.prototype.getResolvers = function (cache, srcDir) {
    var includePaths = [ srcDir ],
        resolvers = [];

    resolvers.push(new CacheResolver(cache, srcDir));

    this.resolverClasses.forEach(function (resolverClass) {
        resolvers.push(new resolverClass(includePaths));
    });

    return resolvers;
};

// -- CacheResolver ------------------------------------------------------------

// Used to speed up transpiling process on re-builds. The `cache` contains the
// `ast`s and other info from perviously read-and-parsed modules. This data can
// be reused for unchanged modules that the transpiler still needs to visit when
// it's compiling during a re-build.
function CacheResolver(cache, srcDir) {
    this.cache  = cache;
    this.srcDir = srcDir;
}

CacheResolver.prototype.resolveModule = function (importedPath, fromModule, container) {
    var resolvedPath = this.resolvePath(importedPath, fromModule),
        cachedModule = container.getCachedModule(resolvedPath);

    if (cachedModule) {
        return cachedModule;
    }

    var cacheEntry = this.cache[path.relative(this.srcDir, resolvedPath)];

    // Gets a file-stats hash of the module file, then checks if there's a cache
    // entry with that hash.
    if (cacheEntry && cacheEntry.hash === hashFile(resolvedPath)) {
        return new CachedModule(resolvedPath, importedPath, container, cacheEntry);
    }

    return null;
};

CacheResolver.prototype.resolvePath = function (importedPath, fromModule) {
    var srcDir = this.srcDir,
        resolved;

    if (importedPath.charAt(0) === '.' && fromModule) {
        srcDir = path.dirname(fromModule.path);
    }

    resolved = path.resolve(srcDir, importedPath);

    if (path.extname(resolved) !== '.js') {
        resolved += '.js';
    }

    return resolved;
};

// -- CachedModule -------------------------------------------------------------

function CachedModule(resolvedPath, importedPath, container, cachedMeta) {
    Module.call(this, resolvedPath, importedPath, container);

    // Update the `Module` instance with the cached string srouce that so
    // the transpiler doesn't have re-read the file from disk.
    this.src = cachedMeta.src;
}

util.inherits(CachedModule, Module);
<commit_msg>Improve structure of cache dir<commit_after>'use strict';

var fs            = require('fs'),
    path          = require('path'),
    util          = require('util'),
    mkdirp        = require('mkdirp'),
    quickTemp     = require('quick-temp'),
    symlinkOrCopy = require('symlink-or-copy'),
    walkSync      = require('walk-sync'),
    helpers       = require('broccoli-kitchen-sink-helpers'),
    Writer        = require('broccoli-writer');

var transpiler   = require('es6-module-transpiler'),
    Container    = transpiler.Container,
    FileResolver = transpiler.FileResolver,
    Module       = require('es6-module-transpiler/lib/module');

module.exports = CompileModules;

// -----------------------------------------------------------------------------

var hashFile    = helpers.hashTree;
var hashStrings = helpers.hashStrings;

// -- CompileModules -----------------------------------------------------------

function CompileModules(inputTree, options) {
    if (!(this instanceof CompileModules)) {
        return new CompileModules(inputTree, options);
    }

    options || (options = {});

    var formatter = options.formatter;

    if (!formatter) {
        formatter = transpiler.formatters.DEFAULT;
    }

    if (typeof formatter === 'string') {
        formatter = transpiler.formatters[formatter];
    }

    var resolverClasses = options.resolvers;

    if (!resolverClasses) {
        resolverClasses = [ FileResolver ];
    }

    this.inputTree       = inputTree;
    this.resolverClasses = resolverClasses;
    this.formatter       = formatter;
    this.output          = options.output || '.';
    this.description     = options.description;

    this._cache = {};
}

util.inherits(CompileModules, Writer);

CompileModules.prototype.cleanup = function () {
    quickTemp.remove(this, 'tmpCacheDir');
    Writer.prototype.cleanup.apply(this, arguments);
};

CompileModules.prototype.getCacheDir = function () {
    return quickTemp.makeOrReuse(this, 'tmpCacheDir');
};

CompileModules.prototype.write = function (readTree, destDir) {
    return readTree(this.inputTree).then(function (srcDir) {
        var outputPath = path.join(destDir, this.output),
            modules    = [];

        function hash(filePaths) {
            if (Array.isArray(filePaths)) {
                return hashStrings(filePaths.map(function (filePath) {
                    return hashFile(path.join(srcDir, filePath));
                }));
            }

            return hashFile(path.join(srcDir, filePaths));
        }

        walkSync(srcDir).forEach(function (relPath) {
            // Skip doing anything with dir entries. When outputting a bundle
            // format some dirs may go away. For non-JavaScript files, their
            // containing dir will be created before they are copied over.
            if (relPath.charAt(relPath.length - 1) === '/') {
                return;
            }

            // Keep track of all the JavaScript modules.
            // path.extname does not take into account the trailing '/' when
            // checking for the file's extension.
            if (path.extname(relPath) === '.js') {
                modules.push(relPath);
                return;
            }

            var srcPath  = path.join(srcDir, relPath),
                destPath = path.join(destDir, relPath);

            // Copy over non-JavaScript files to the `destDir`.
            mkdirp.sync(path.dirname(destPath));
            symlinkOrCopy.sync(srcPath, destPath);
        });

        var modulesToCompile = [],
            cache            = this._cache,
            cacheEntry;

        // The specificed `output` can either be a file (for bundle formatters),
        // or a dir.
        //
        // When outputting to a single file, all the input files must must be
        // unchanged in order to use the cached compiled file.
        //
        // When outputting to a dir, we symlink/copy over the cached compiled
        // file for any modules that are unchanged. Any modified modules are
        // added to the `modulesToCompile` collection.
        if (path.extname(outputPath) === '.js') {
            cacheEntry = cache[path.basename(outputPath)];

            // Must hash _all_ modules when outputting to a single file.
            if (cacheEntry && cacheEntry.hash === hash(modules)) {
                this.copyFromCache(cacheEntry, path.dirname(outputPath));
            } else {
                // With a cache-miss, we need to re-generate the bundle output
                // file, so we have to visit all the modules. The CacheResolver
                // will make sure we don't have to re-read the modules that are
                // unchanged.
                modulesToCompile = modules;
            }
        } else {
            // Iterate over all the modules and copy compiled files from the
            // cache for the ones that are unchanged.
            modules.forEach(function (module) {
                cacheEntry = cache[module];

                if (cacheEntry && cacheEntry.hash === hash(module)) {
                    this.copyFromCache(cacheEntry, outputPath);
                } else {
                    modulesToCompile.push(module);
                }
            }, this);
        }

        this.compileAndCacheModules(modulesToCompile, srcDir, outputPath);
    }.bind(this));
};

CompileModules.prototype.compileAndCacheModules = function (modulePaths, srcDir, outputPath) {
    // Noop when no modules to compile.
    if (modulePaths.length < 1) { return; }

    var cache        = this._cache;
    var cacheDir     = this.getCacheDir();
    var outputIsFile = path.extname(outputPath) === '.js';

    // The container will first use the CacheResolver so that any unchanged
    // modules that need to be visited by the transpiler don't have to be
    // re-read from disk or re-parsed.
    //
    // If "foo" imports "bar", and "bar" is unchanged, the transpiler still will
    // need to vist it when re-processing "foo".
    var container = new Container({
        formatter: this.formatter,
        resolvers: this.getResolvers(cache, srcDir)
    });

    // Returns transpiler `Module` instances.
    var modules = modulePaths.map(function (modulePath) {
        return container.getModule(modulePath);
    });

    // Determine target path to compile modules to.
    var target = outputIsFile ?
            path.join(cacheDir, path.basename(outputPath)) : cacheDir;

    // Creates the output dir, then outputs the compiled modules to the cache.
    mkdirp.sync(outputIsFile ? path.dirname(target) : target);
    container.write(target);

    var outputHashes = [],
        cacheEntry, outputFile;

    modules.forEach(function (module) {
        var hash    = hashFile(module.path),
            relPath = path.relative(srcDir, module.path),
            src     = module.src;

        // Adds an entry to the cache for the later use by the CacheResolver.
        // This holds the parsed and walked AST, so re-builds of unchanged
        // modules don't need to be re-read and re-parsed.
        var cacheEntry = cache[relPath] = {
            hash: hash,
            src : src
        };

        // Accumulate hashes if the final output is a single bundle file, and
        // return early.
        if (outputIsFile) {
            outputHashes.push(hash);
            return;
        }

        // When outputting to a dir, add the compiled files to the cache entry
        // and copy the files from the cache dir to the `outputPath`.

        cacheEntry.outputFiles = [
            relPath,
            relPath + '.map'
        ];

        this.copyFromCache(cacheEntry, outputPath);
    }, this);

    if (outputIsFile) {
        outputFile = path.basename(outputPath);

        // Create a cache entry for the entire bundle output file and copy it
        // from the cache to the `outputPath`. Compute the hash as a hash of
        // hashes.
        cacheEntry = cache[outputFile] = {
            hash: hashStrings(outputHashes),
            dir : cacheDir,

            outputFiles: [
                outputFile,
                outputFile + '.map'
            ]
        };

        this.copyFromCache(cacheEntry, path.dirname(outputPath));
    }
};

CompileModules.prototype.copyFromCache = function (cacheEntry, destDir) {
    var cacheDir = this.getCacheDir();

    cacheEntry.outputFiles.forEach(function (outputFile) {
        var cachePath = path.join(cacheDir, outputFile),
            destPath  = path.join(destDir, outputFile);

        mkdirp.sync(path.dirname(destPath));
        symlinkOrCopy.sync(cachePath, destPath);
    });
};

CompileModules.prototype.getResolvers = function (cache, srcDir) {
    var includePaths = [ srcDir ],
        resolvers = [];

    resolvers.push(new CacheResolver(cache, srcDir));

    this.resolverClasses.forEach(function (resolverClass) {
        resolvers.push(new resolverClass(includePaths));
    });

    return resolvers;
};

// -- CacheResolver ------------------------------------------------------------

// Used to speed up transpiling process on re-builds. The `cache` contains the
// `ast`s and other info from perviously read-and-parsed modules. This data can
// be reused for unchanged modules that the transpiler still needs to visit when
// it's compiling during a re-build.
function CacheResolver(cache, srcDir) {
    this.cache  = cache;
    this.srcDir = srcDir;
}

CacheResolver.prototype.resolveModule = function (importedPath, fromModule, container) {
    var resolvedPath = this.resolvePath(importedPath, fromModule),
        cachedModule = container.getCachedModule(resolvedPath);

    if (cachedModule) {
        return cachedModule;
    }

    var cacheEntry = this.cache[path.relative(this.srcDir, resolvedPath)];

    // Gets a file-stats hash of the module file, then checks if there's a cache
    // entry with that hash.
    if (cacheEntry && cacheEntry.hash === hashFile(resolvedPath)) {
        return new CachedModule(resolvedPath, importedPath, container, cacheEntry);
    }

    return null;
};

CacheResolver.prototype.resolvePath = function (importedPath, fromModule) {
    var srcDir = this.srcDir,
        resolved;

    if (importedPath.charAt(0) === '.' && fromModule) {
        srcDir = path.dirname(fromModule.path);
    }

    resolved = path.resolve(srcDir, importedPath);

    if (path.extname(resolved) !== '.js') {
        resolved += '.js';
    }

    return resolved;
};

// -- CachedModule -------------------------------------------------------------

function CachedModule(resolvedPath, importedPath, container, cachedMeta) {
    Module.call(this, resolvedPath, importedPath, container);

    // Update the `Module` instance with the cached string srouce that so
    // the transpiler doesn't have re-read the file from disk.
    this.src = cachedMeta.src;
}

util.inherits(CachedModule, Module);
<|endoftext|>


<commit_before>"use strict";

module.exports = function contains(src, searchString) {
    return src.indexOf(searchString) >= 0;
};<commit_msg>Removed function name<commit_after>"use strict";

module.exports = function (src, searchString) {
    return src.indexOf(searchString) >= 0;
};<|endoftext|>


<commit_before>'use strict';
var gutil = require('gulp-util');
var through = require('through2');
var path = require('path');
var minimatch = require('minimatch');
var glob = require('glob');
var _ = require('lodash');
var vinyl_fs = require('vinyl-fs');

function processPatterns(patterns, fn) {
  // Filepaths to return.
  var result = [];
  // Iterate over flattened patterns array.
  _.flatten(patterns).forEach(function(pattern) {
    // If the first character is ! it should be omitted
    var exclusion = pattern.indexOf('!') === 0;
    // If the pattern is an exclusion, remove the !
    if (exclusion) { pattern = pattern.slice(1); }
    // Find all matching files for this pattern.
    var matches = fn(pattern);
    if (exclusion) {
      // If an exclusion, remove matching files.
      result = _.difference(result, matches);
    } else {
      // Otherwise add matching files.
      result = _.union(result, matches);
    }
  });
  return result;
};

function lookForImport(options) {
	var lessToInclude = [];

	if (!options.import) {
		throw new gutil.PluginError('gulp-git-changed', '`src` required');
	}

	return through.obj(
		function(file, enc, cb) {
      if(minimatch(file.relative, options.import)) {
        var folderToLookFor = file.base + path.sep;
        var position = file.relative.indexOf(path.sep);
        if(position != -1)
          folderToLookFor += file.relative.substring(0, position);
        folderToLookFor += path.sep;

        var matches = processPatterns([
          folderToLookFor + '**/*.less',
          '!' + folderToLookFor + options.import
        ], function(pattern) {
          // Find all matching files for this pattern.
          return glob.sync(pattern);
        });
        lessToInclude = lessToInclude.concat(matches);
        cb();
      }
      else {
        this.push(file);
        cb();
      }
    },
    function(cb) {
      var that=this;
      lessToInclude = _.uniq(lessToInclude);
      console.log(lessToInclude);
      vinyl_fs.src(lessToInclude)
        .pipe(through.obj(function(file, enc, cb) {
          that.push(file);
          cb();
        }, function(icb) {
          icb();
          cb();
        }));
    }
	);
};

module.exports = lookForImport;
<commit_msg>remove console<commit_after>'use strict';
var gutil = require('gulp-util');
var through = require('through2');
var path = require('path');
var minimatch = require('minimatch');
var glob = require('glob');
var _ = require('lodash');
var vinyl_fs = require('vinyl-fs');

function processPatterns(patterns, fn) {
  // Filepaths to return.
  var result = [];
  // Iterate over flattened patterns array.
  _.flatten(patterns).forEach(function(pattern) {
    // If the first character is ! it should be omitted
    var exclusion = pattern.indexOf('!') === 0;
    // If the pattern is an exclusion, remove the !
    if (exclusion) { pattern = pattern.slice(1); }
    // Find all matching files for this pattern.
    var matches = fn(pattern);
    if (exclusion) {
      // If an exclusion, remove matching files.
      result = _.difference(result, matches);
    } else {
      // Otherwise add matching files.
      result = _.union(result, matches);
    }
  });
  return result;
};

function lookForImport(options) {
	var lessToInclude = [];

	if (!options.import) {
		throw new gutil.PluginError('gulp-git-changed', '`src` required');
	}

	return through.obj(
		function(file, enc, cb) {
      if(minimatch(file.relative, options.import)) {
        var folderToLookFor = file.base + path.sep;
        var position = file.relative.indexOf(path.sep);
        if(position != -1)
          folderToLookFor += file.relative.substring(0, position);
        folderToLookFor += path.sep;

        var matches = processPatterns([
          folderToLookFor + '**/*.less',
          '!' + folderToLookFor + options.import
        ], function(pattern) {
          // Find all matching files for this pattern.
          return glob.sync(pattern);
        });
        lessToInclude = lessToInclude.concat(matches);
        cb();
      }
      else {
        this.push(file);
        cb();
      }
    },
    function(cb) {
      var that=this;
      lessToInclude = _.uniq(lessToInclude);
      vinyl_fs.src(lessToInclude)
        .pipe(through.obj(function(file, enc, cb) {
          that.push(file);
          cb();
        }, function(icb) {
          icb();
          cb();
        }));
    }
	);
};

module.exports = lookForImport;
<|endoftext|>