code:
'use strict';

const generate = require('@babel/regjsgen').generate;
const parse = require('regjsparser').parse;
const regenerate = require('regenerate');
const unicodeMatchProperty = require('unicode-match-property-ecmascript');
const unicodeMatchPropertyValue = require('unicode-match-property-value-ecmascript');
const iuMappings = require('./data/iu-mappings.js');
const ESCAPE_SETS = require('./data/character-class-escape-sets.js');

function flatMap(array, callback) {
	const result = [];
	array.forEach(item => {
		const res = callback(item);
		if (Array.isArray(res)) {
			result.push.apply(result, res);
		} else {
			result.push(res);
		}
	});
	return result;
}

const SPECIAL_CHARS = /([\\^$.*+?()[\]{}|])/g;

// Prepare a Regenerate set containing all code points, used for negative
// character classes (if any).
const UNICODE_SET = regenerate().addRange(0x0, 0x10FFFF);

const ASTRAL_SET = regenerate().addRange(0x10000, 0x10FFFF);

const NEWLINE_SET = regenerate().add(
	// `LineTerminator`s (https://mths.be/es6#sec-line-terminators):
	0x000A, // Line Feed <LF>
	0x000D, // Carriage Return <CR>
	0x2028, // Line Separator <LS>
	0x2029  // Paragraph Separator <PS>
);

// Prepare a Regenerate set containing all code points that are supposed to be
// matched by `/./u`. https://mths.be/es6#sec-atom
const DOT_SET_UNICODE = UNICODE_SET.clone() // all Unicode code points
	.remove(NEWLINE_SET);

const getCharacterClassEscapeSet = (character, unicode, ignoreCase) => {
	if (unicode) {
		if (ignoreCase) {
			return ESCAPE_SETS.UNICODE_IGNORE_CASE.get(character);
		}
		return ESCAPE_SETS.UNICODE.get(character);
	}
	return ESCAPE_SETS.REGULAR.get(character);
};

const getUnicodeDotSet = (dotAll) => {
	return dotAll ? UNICODE_SET : DOT_SET_UNICODE;
};

const getUnicodePropertyValueSet = (property, value) => {
	const path = value ?
		`${ property }/${ value }` :
		`Binary_Property/${ property }`;
	try {
		return require(`regenerate-unicode-properties/${ path }.js`);
	} catch (exception) {
		throw new Error(
			`Failed to recognize value \`${ value }\` for property ` +
			`\`${ property }\`.`
		);
	}
};

const handleLoneUnicodePropertyNameOrValue = (value) => {
	// It could be a `General_Category` value or a binary property.
	// Note: `unicodeMatchPropertyValue` throws on invalid values.
	try {
		const property = 'General_Category';
		const category = unicodeMatchPropertyValue(property, value);
		return getUnicodePropertyValueSet(property, category);
	} catch (exception) {}
	// It’s not a `General_Category` value, so check if it’s a property
	// of strings.
	try {
		return getUnicodePropertyValueSet('Property_of_Strings', value);
	} catch (exception) {}
	// Lastly, check if it’s a binary property of single code points.
	// Note: `unicodeMatchProperty` throws on invalid properties.
	const property = unicodeMatchProperty(value);
	return getUnicodePropertyValueSet(property);
};

const getUnicodePropertyEscapeSet = (value, isNegative) => {
	const parts = value.split('=');
	const firstPart = parts[0];
	let set;
	if (parts.length == 1) {
		set = handleLoneUnicodePropertyNameOrValue(firstPart);
	} else {
		// The pattern consists of two parts, i.e. `Property=Value`.
		const property = unicodeMatchProperty(firstPart);
		const value = unicodeMatchPropertyValue(property, parts[1]);
		set = getUnicodePropertyValueSet(property, value);
	}
	if (isNegative) {
		if (set.strings) {
			throw new Error('Cannot negate Unicode property of strings');
		}
		return {
			characters: UNICODE_SET.clone().remove(set.characters),
			strings: new Set()
		};
	}
	return {
		characters: set.characters.clone(),
		strings: set.strings
			// We need to escape strings like *⃣ to make sure that they can be safely used in unions.
			? new Set(set.strings.map(str => str.replace(SPECIAL_CHARS, '\\$1')))
			: new Set()
	};
};

const getUnicodePropertyEscapeCharacterClassData = (property, isNegative) => {
	const set = getUnicodePropertyEscapeSet(property, isNegative);
	const data = getCharacterClassEmptyData();
	data.singleChars = set.characters;
	if (set.strings.size > 0) {
		data.longStrings = set.strings;
		data.maybeIncludesStrings = true;
	}
	return data;
};

function configNeedCaseFoldAscii() {
	return !!config.modifiersData.i;
}

function configNeedCaseFoldUnicode() {
	// config.modifiersData.i : undefined | false
	if (config.modifiersData.i === false) return false;
	if (!config.transform.unicodeFlag) return false;
	return Boolean(config.modifiersData.i || config.flags.ignoreCase);
}

// Given a range of code points, add any case-folded code points in that range
// to a set.
regenerate.prototype.iuAddRange = function(min, max) {
	const $this = this;
	do {
		const folded = caseFold(min, configNeedCaseFoldAscii(), configNeedCaseFoldUnicode());
		if (folded) {
			$this.add(folded);
		}
	} while (++min <= max);
	return $this;
};
regenerate.prototype.iuRemoveRange = function(min, max) {
	const $this = this;
	do {
		const folded = caseFold(min, configNeedCaseFoldAscii(), configNeedCaseFoldUnicode());
		if (folded) {
			$this.remove(folded);
		}
	} while (++min <= max);
	return $this;
};

const update = (item, pattern) => {
	let tree = parse(pattern, config.useUnicodeFlag ? 'u' : '', {
		lookbehind: true,
		namedGroups: true,
		unicodePropertyEscape: true,
		unicodeSet: true,
		modifiers: true,
	});
	switch (tree.type) {
		case 'characterClass':
		case 'group':
		case 'value':
			// No wrapping needed.
			break;
		default:
			// Wrap the pattern in a non-capturing group.
			tree = wrap(tree, pattern);
	}
	Object.assign(item, tree);
};

const wrap = (tree, pattern) => {
	// Wrap the pattern in a non-capturing group.
	return {
		'type': 'group',
		'behavior': 'ignore',
		'body': [tree],
		'raw': `(?:${ pattern })`
	};
};

const caseFold = (codePoint, includeAscii, includeUnicode) => {
	let folded = (includeUnicode ? iuMappings.get(codePoint) : undefined) || [];
	if (typeof folded === 'number') folded = [folded];
	if (includeAscii) {
		if (codePoint >= 0x41 && codePoint <= 0x5A) {
			folded.push(codePoint + 0x20);
		} else if (codePoint >= 0x61 && codePoint <= 0x7A) {
			folded.push(codePoint - 0x20);
		}
	}
	return folded.length == 0 ? false : folded;
};

const buildHandler = (action) => {
	switch (action) {
		case 'union':
			return {
				single: (data, cp) => {
					data.singleChars.add(cp);
				},
				regSet: (data, set2) => {
					data.singleChars.add(set2);
				},
				range: (data, start, end) => {
					data.singleChars.addRange(start, end);
				},
				iuRange: (data, start, end) => {
					data.singleChars.iuAddRange(start, end);
				},
				nested: (data, nestedData) => {
					data.singleChars.add(nestedData.singleChars);
					for (const str of nestedData.longStrings) data.longStrings.add(str);
					if (nestedData.maybeIncludesStrings) data.maybeIncludesStrings = true;
				}
			};
		case 'union-negative': {
			const regSet = (data, set2) => {
				data.singleChars = UNICODE_SET.clone().remove(set2).add(data.singleChars);
			};
			return {
				single: (data, cp) => {
					const unicode = UNICODE_SET.clone();
					data.singleChars = data.singleChars.contains(cp) ? unicode : unicode.remove(cp);
				},
				regSet: regSet,
				range: (data, start, end) => {
					data.singleChars = UNICODE_SET.clone().removeRange(start, end).add(data.singleChars);
				},
				iuRange: (data, start, end) => {
					data.singleChars = UNICODE_SET.clone().iuRemoveRange(start, end).add(data.singleChars);
				},
				nested: (data, nestedData) => {
					regSet(data, nestedData.singleChars);
					if (nestedData.maybeIncludesStrings) throw new Error('ASSERTION ERROR');
				}
			};
		}
		case 'intersection': {
			const regSet = (data, set2) => {
				if (data.first) data.singleChars = set2;
				else data.singleChars.intersection(set2);
			};
			return {
				single: (data, cp) => {
					data.singleChars = data.first || data.singleChars.contains(cp) ? regenerate(cp) : regenerate();
					data.longStrings.clear();
					data.maybeIncludesStrings = false;
				},
				regSet: (data, set) => {
					regSet(data, set);
					data.longStrings.clear();
					data.maybeIncludesStrings = false;
				},
				range: (data, start, end) => {
					if (data.first) data.singleChars.addRange(start, end);
					else data.singleChars.intersection(regenerate().addRange(start, end));
					data.longStrings.clear();
					data.maybeIncludesStrings = false;
				},
				iuRange: (data, start, end) => {
					if (data.first) data.singleChars.iuAddRange(start, end);
					else data.singleChars.intersection(regenerate().iuAddRange(start, end));
					data.longStrings.clear();
					data.maybeIncludesStrings = false;
				},
				nested: (data, nestedData) => {
					regSet(data, nestedData.singleChars);

					if (data.first) {
						data.longStrings = nestedData.longStrings;
						data.maybeIncludesStrings = nestedData.maybeIncludesStrings;
					} else {
						for (const str of data.longStrings) {
							if (!nestedData.longStrings.has(str)) data.longStrings.delete(str);
						}
						if (!nestedData.maybeIncludesStrings) data.maybeIncludesStrings = false;
					}
				}
			};
		}
		case 'subtraction': {
			const regSet = (data, set2) => {
				if (data.first) data.singleChars.add(set2);
				else data.singleChars.remove(set2);
			};
			return {
				single: (data, cp) => {
					if (data.first) data.singleChars.add(cp);
					else data.singleChars.remove(cp);
				},
				regSet: regSet,
				range: (data, start, end) => {
					if (data.first) data.singleChars.addRange(start, end);
					else data.singleChars.removeRange(start, end);
				},
				iuRange: (data, start, end) => {
					if (data.first) data.singleChars.iuAddRange(start, end);
					else data.singleChars.iuRemoveRange(start, end);
				},
				nested: (data, nestedData) => {
					regSet(data, nestedData.singleChars);

					if (data.first) {
						data.longStrings = nestedData.longStrings;
						data.maybeIncludesStrings = nestedData.maybeIncludesStrings;
					} else {
						for (const str of data.longStrings) {
							if (nestedData.longStrings.has(str)) data.longStrings.delete(str);
						}
					}
				}
			};
		}
		// The `default` clause is only here as a safeguard; it should never be
		// reached. Code coverage tools should ignore it.
		/* istanbul ignore next */
		default:
			throw new Error(`Unknown set action: ${ characterClassItem.kind }`);
	}
};

const getCharacterClassEmptyData = () => ({
	transformed: config.transform.unicodeFlag,
	singleChars: regenerate(),
	longStrings: new Set(),
	hasEmptyString: false,
	first: true,
	maybeIncludesStrings: false
});

const maybeFold = (codePoint) => {
	const caseFoldAscii = configNeedCaseFoldAscii();
	const caseFoldUnicode = configNeedCaseFoldUnicode();

	if (caseFoldAscii || caseFoldUnicode) {
		const folded = caseFold(codePoint, caseFoldAscii, caseFoldUnicode);
		if (folded) {
			return [codePoint, folded];
		}
	}
	return [codePoint];
};

const computeClassStrings = (classStrings, regenerateOptions) => {
	let data = getCharacterClassEmptyData();

	const caseFoldAscii = configNeedCaseFoldAscii();
	const caseFoldUnicode = configNeedCaseFoldUnicode();

	for (const string of classStrings.strings) {
		if (string.characters.length === 1) {
			maybeFold(string.characters[0].codePoint).forEach((cp) => {
				data.singleChars.add(cp);
			});
		} else {
			let stringifiedString;
			if (caseFoldUnicode || caseFoldAscii) {
				stringifiedString = '';
				for (const ch of string.characters) {
					let set = regenerate(ch.codePoint);
					const folded = maybeFold(ch.codePoint);
					if (folded) set.add(folded);
					stringifiedString += set.toString(regenerateOptions);
				}
			} else {
				stringifiedString = string.characters.map(ch => generate(ch)).join('')
			}

			data.longStrings.add(stringifiedString);
			data.maybeIncludesStrings = true;
		}
	}

	return data;
}

const computeCharacterClass = (characterClassItem, regenerateOptions) => {
	let data = getCharacterClassEmptyData();

	let handlePositive;
	let handleNegative;

	switch (characterClassItem.kind) {
		case 'union':
			handlePositive = buildHandler('union');
			handleNegative = buildHandler('union-negative');
			break;
		case 'intersection':
			handlePositive = buildHandler('intersection');
			handleNegative = buildHandler('subtraction');
			if (config.transform.unicodeSetsFlag) data.transformed = true;
			break;
		case 'subtraction':
			handlePositive = buildHandler('subtraction');
			handleNegative = buildHandler('intersection');
			if (config.transform.unicodeSetsFlag) data.transformed = true;
			break;
		// The `default` clause is only here as a safeguard; it should never be
		// reached. Code coverage tools should ignore it.
		/* istanbul ignore next */
		default:
			throw new Error(`Unknown character class kind: ${ characterClassItem.kind }`);
	}

	const caseFoldAscii = configNeedCaseFoldAscii();
	const caseFoldUnicode = configNeedCaseFoldUnicode();

	for (const item of characterClassItem.body) {
		switch (item.type) {
			case 'value':
				maybeFold(item.codePoint).forEach((cp) => {
					handlePositive.single(data, cp);
				});
				break;
			case 'characterClassRange':
				const min = item.min.codePoint;
				const max = item.max.codePoint;
				handlePositive.range(data, min, max);
				if (caseFoldAscii || caseFoldUnicode) {
					handlePositive.iuRange(data, min, max);
					data.transformed = true;
				}
				break;
			case 'characterClassEscape':
				handlePositive.regSet(data, getCharacterClassEscapeSet(
					item.value,
					config.flags.unicode,
					config.flags.ignoreCase
				));
				break;
			case 'unicodePropertyEscape':
				const nestedData = getUnicodePropertyEscapeCharacterClassData(item.value, item.negative);
				handlePositive.nested(data, nestedData);
				data.transformed =
					data.transformed ||
					config.transform.unicodePropertyEscapes ||
					(config.transform.unicodeSetsFlag && nestedData.maybeIncludesStrings);
				break;
			case 'characterClass':
				const handler = item.negative ? handleNegative : handlePositive;
				const res = computeCharacterClass(item, regenerateOptions);
				handler.nested(data, res);
				data.transformed = true;
				break;
			case 'classStrings':
				handlePositive.nested(data, computeClassStrings(item, regenerateOptions));
				data.transformed = true;
				break;
			// The `default` clause is only here as a safeguard; it should never be
			// reached. Code coverage tools should ignore it.
			/* istanbul ignore next */
			default:
				throw new Error(`Unknown term type: ${ item.type }`);
		}

		data.first = false;
	}

	if (characterClassItem.negative && data.maybeIncludesStrings) {
		throw new SyntaxError('Cannot negate set containing strings');
	}

	return data;
}

const processCharacterClass = (
	characterClassItem,
	regenerateOptions,
	computed = computeCharacterClass(characterClassItem, regenerateOptions)
) => {
	const negative = characterClassItem.negative;
	const { singleChars, transformed, longStrings } = computed;
	if (transformed) {
		const setStr = singleChars.toString(regenerateOptions);

		if (negative) {
			if (config.useUnicodeFlag) {
				update(characterClassItem, `[^${setStr[0] === '[' ? setStr.slice(1, -1) : setStr}]`)
			} else {
				if (config.flags.unicode) {
					if (config.flags.ignoreCase) {
						const astralCharsSet = singleChars.clone().intersection(ASTRAL_SET);
						// Assumption: singleChars do not contain lone surrogates.
						// Regex like /[^\ud800]/u is not supported
						const surrogateOrBMPSetStr = singleChars
							.clone()
							.remove(astralCharsSet)
							.addRange(0xd800, 0xdfff)
							.toString({ bmpOnly: true });
						// Don't generate negative lookahead for astral characters
						// because the case folding is not working anyway as we break
						// code points into surrogate pairs.
						const astralNegativeSetStr = ASTRAL_SET
							.clone()
							.remove(astralCharsSet)
							.toString(regenerateOptions);
						// The transform here does not support lone surrogates.
						update(
							characterClassItem,
							`(?!${surrogateOrBMPSetStr})[\\s\\S]|${astralNegativeSetStr}`
						);
					} else {
						// Generate negative set directly when case folding is not involved.
						update(
							characterClassItem,
							UNICODE_SET.clone().remove(singleChars).toString(regenerateOptions)
						);
					}
				} else {
					update(characterClassItem, `(?!${setStr})[\\s\\S]`);
				}
			}
		} else {
			const hasEmptyString = longStrings.has('');
			const pieces = Array.from(longStrings).sort((a, b) => b.length - a.length);

			if (setStr !== '[]' || longStrings.size === 0) {
				pieces.splice(pieces.length - (hasEmptyString ? 1 : 0), 0, setStr);
			}

			update(characterClassItem, pieces.join('|'));
		}
	}
	return characterClassItem;
};

const assertNoUnmatchedReferences = (groups) => {
	const unmatchedReferencesNames = Object.keys(groups.unmatchedReferences);
	if (unmatchedReferencesNames.length > 0) {
		throw new Error(`Unknown group names: ${unmatchedReferencesNames}`);
	}
};

const processModifiers = (item, regenerateOptions, groups) => {
	const enabling = item.modifierFlags.enabling;
	const disabling = item.modifierFlags.disabling;

	delete item.modifierFlags;
	item.behavior = 'ignore';

	const oldData = Object.assign({}, config.modifiersData);

	enabling.split('').forEach(flag => {
		config.modifiersData[flag] = true;
	});
	disabling.split('').forEach(flag => {
		config.modifiersData[flag] = false;
	});

	item.body = item.body.map(term => {
		return processTerm(term, regenerateOptions, groups);
	});

	config.modifiersData = oldData;

	return item;
}

const processTerm = (item, regenerateOptions, groups) => {
	switch (item.type) {
		case 'dot':
			if (config.transform.unicodeFlag) {
				update(
					item,
					getUnicodeDotSet(config.flags.dotAll || config.modifiersData.s).toString(regenerateOptions)
				);
			} else if (config.transform.dotAllFlag || config.modifiersData.s) {
				// TODO: consider changing this at the regenerate level.
				update(item, '[\\s\\S]');
			}
			break;
		case 'characterClass':
			item = processCharacterClass(item, regenerateOptions);
			break;
		case 'unicodePropertyEscape':
			const data = getUnicodePropertyEscapeCharacterClassData(item.value, item.negative);
			if (data.maybeIncludesStrings) {
				if (!config.flags.unicodeSets) {
					throw new Error(
						'Properties of strings are only supported when using the unicodeSets (v) flag.'
					);
				}
				if (config.transform.unicodeSetsFlag) {
					data.transformed = true;
					item = processCharacterClass(item, regenerateOptions, data);
				}
			} else if (config.transform.unicodePropertyEscapes) {
				update(
					item,
					data.singleChars.toString(regenerateOptions)
				);
			}
			break;
		case 'characterClassEscape':
			if (config.transform.unicodeFlag) {
				update(
					item,
					getCharacterClassEscapeSet(
						item.value,
						/* config.transform.unicodeFlag implies config.flags.unicode */ true,
						config.flags.ignoreCase
					).toString(regenerateOptions)
				);
			}
			break;
		case 'group':
			if (item.behavior == 'normal') {
				groups.lastIndex++;
			}
			if (item.name) {
				const name = item.name.value;

				if (groups.namesConflicts[name]) {
					throw new Error(
						`Group '${ name }' has already been defined in this context.`
					);
				}
				groups.namesConflicts[name] = true;

				if (config.transform.namedGroups) {
					delete item.name;
				}

				const index = groups.lastIndex;
				if (!groups.names[name]) {
					groups.names[name] = [];
				}
				groups.names[name].push(index);

				if (groups.onNamedGroup) {
					groups.onNamedGroup.call(null, name, index);
				}

				if (groups.unmatchedReferences[name]) {
					delete groups.unmatchedReferences[name];
				}
			}
			if (item.modifierFlags && config.transform.modifiers) {
				return processModifiers(item, regenerateOptions, groups);
			}
			/* falls through */
		case 'quantifier':
			item.body = item.body.map(term => {
				return processTerm(term, regenerateOptions, groups);
			});
			break;
		case 'disjunction':
			const outerNamesConflicts = groups.namesConflicts;
			item.body = item.body.map(term => {
				groups.namesConflicts = Object.create(outerNamesConflicts);
				return processTerm(term, regenerateOptions, groups);
			});
			break;
		case 'alternative':
			item.body = flatMap(item.body, term => {
				const res = processTerm(term, regenerateOptions, groups);
				// Alternatives cannot contain alternatives; flatten them.
				return res.type === 'alternative' ? res.body : res;
			});
			break;
		case 'value':
			const codePoint = item.codePoint;
			const set = regenerate(codePoint);
			const folded = maybeFold(codePoint);
			set.add(folded);
			update(item, set.toString(regenerateOptions));
			break;
		case 'reference':
			if (item.name) {
				const name = item.name.value;
				const indexes = groups.names[name];
				if (!indexes) {
					groups.unmatchedReferences[name] = true;
				}

				if (config.transform.namedGroups) {
					if (indexes) {
						const body = indexes.map(index => ({
							'type': 'reference',
							'matchIndex': index,
							'raw': '\\' + index,
						}));
						if (body.length === 1) {
							return body[0];
						}
						return {
							'type': 'alternative',
							'body': body,
							'raw': body.map(term => term.raw).join(''),
						};
					}

					// This named reference comes before the group where it’s defined,
					// so it’s always an empty match.
					return {
						'type': 'group',
						'behavior': 'ignore',
						'body': [],
						'raw': '(?:)',
					};
				}
			}
			break;
		case 'anchor':
			if (config.modifiersData.m) {
				if (item.kind == 'start') {
					update(item, `(?:^|(?<=${NEWLINE_SET.toString()}))`);
				} else if (item.kind == 'end') {
					update(item, `(?:$|(?=${NEWLINE_SET.toString()}))`);
				}
			}
		case 'empty':
			// Nothing to do here.
			break;
		// The `default` clause is only here as a safeguard; it should never be
		// reached. Code coverage tools should ignore it.
		/* istanbul ignore next */
		default:
			throw new Error(`Unknown term type: ${ item.type }`);
	}
	return item;
};

const config = {
	'flags': {
		'ignoreCase': false,
		'unicode': false,
		'unicodeSets': false,
		'dotAll': false,
		'multiline': false,
	},
	'transform': {
		'dotAllFlag': false,
		'unicodeFlag': false,
		'unicodeSetsFlag': false,
		'unicodePropertyEscapes': false,
		'namedGroups': false,
		'modifiers': false,
	},
	'modifiersData': {
		'i': undefined,
		's': undefined,
		'm': undefined,
	},
	get useUnicodeFlag() {
		return (this.flags.unicode || this.flags.unicodeSets) && !this.transform.unicodeFlag;
	}
};

const validateOptions = (options) => {
	if (!options) return;

	for (const key of Object.keys(options)) {
		const value = options[key];
		switch (key) {
			case 'dotAllFlag':
			case 'unicodeFlag':
			case 'unicodePropertyEscapes':
			case 'namedGroups':
				if (value != null && value !== false && value !== 'transform') {
					throw new Error(`.${key} must be false (default) or 'transform'.`);
				}
				break;
			case 'modifiers':
			case 'unicodeSetsFlag':
				if (value != null && value !== false && value !== 'parse' && value !== 'transform') {
					throw new Error(`.${key} must be false (default), 'parse' or 'transform'.`);
				}
				break;
			case 'onNamedGroup':
			case 'onNewFlags':
				if (value != null && typeof value !== 'function') {
					throw new Error(`.${key} must be a function.`);
				}
				break;
			default:
				throw new Error(`.${key} is not a valid regexpu-core option.`);
		}
	}
};

const hasFlag = (flags, flag) => flags ? flags.includes(flag) : false;
const transform = (options, name) => options ? options[name] === 'transform' : false;

const rewritePattern = (pattern, flags, options) => {
	validateOptions(options);

	config.flags.unicode = hasFlag(flags, 'u');
	config.flags.unicodeSets = hasFlag(flags, 'v');
	config.flags.ignoreCase = hasFlag(flags, 'i');
	config.flags.dotAll = hasFlag(flags, 's');
	config.flags.multiline = hasFlag(flags, 'm');

	config.transform.dotAllFlag = config.flags.dotAll && transform(options, 'dotAllFlag');
	config.transform.unicodeFlag = (config.flags.unicode || config.flags.unicodeSets) && transform(options, 'unicodeFlag');
	config.transform.unicodeSetsFlag = config.flags.unicodeSets && transform(options, 'unicodeSetsFlag');

	// unicodeFlag: 'transform' implies unicodePropertyEscapes: 'transform'
	config.transform.unicodePropertyEscapes = config.flags.unicode && (
		transform(options, 'unicodeFlag') || transform(options, 'unicodePropertyEscapes')
	);
	config.transform.namedGroups = transform(options, 'namedGroups');
	config.transform.modifiers = transform(options, 'modifiers');

	config.modifiersData.i = undefined;
	config.modifiersData.s = undefined;
	config.modifiersData.m = undefined;

	const regjsparserFeatures = {
		'unicodeSet': Boolean(options && options.unicodeSetsFlag),
		'modifiers': Boolean(options && options.modifiers),

		// Enable every stable RegExp feature by default
		'unicodePropertyEscape': true,
		'namedGroups': true,
		'lookbehind': true,
	};

	const regenerateOptions = {
		'hasUnicodeFlag': config.useUnicodeFlag,
		'bmpOnly': !config.flags.unicode
	};

	const groups = {
		'onNamedGroup': options && options.onNamedGroup,
		'lastIndex': 0,
		'names': Object.create(null), // { [name]: Array<index> }
		'namesConflicts': Object.create(null), // { [name]: true }
		'unmatchedReferences': Object.create(null) // { [name]: true }
	};

	const tree = parse(pattern, flags, regjsparserFeatures);

	if (config.transform.modifiers) {
		if (/\(\?[a-z]*-[a-z]+:/.test(pattern)) {
			// the pattern _likely_ contain inline disabled modifiers
			// we need to traverse to make sure that they are actually modifiers and to collect them
			const allDisabledModifiers = Object.create(null)
			const itemStack = [tree];
			let node;
			while (node = itemStack.pop(), node != undefined) {
				if (Array.isArray(node)) {
					Array.prototype.push.apply(itemStack, node);
				} else if (typeof node == 'object' && node != null) {
					for (const key of Object.keys(node)) {
						const value = node[key];
						if (key == 'modifierFlags') {
							if (value.disabling.length > 0){
								value.disabling.split('').forEach((flag)=>{
									allDisabledModifiers[flag] = true
								});
							}
						} else if (typeof value == 'object' && value != null) {
							itemStack.push(value);
						}
					}
				}
			}
			for (const flag of Object.keys(allDisabledModifiers)) {
				config.modifiersData[flag] = true;
			}
		}
	}

	// Note: `processTerm` mutates `tree` and `groups`.
	processTerm(tree, regenerateOptions, groups);
	assertNoUnmatchedReferences(groups);

	const onNewFlags = options && options.onNewFlags;
	if (onNewFlags) {
		let newFlags = flags.split('').filter((flag) => !config.modifiersData[flag]).join('');
		if (config.transform.unicodeSetsFlag) {
			newFlags = newFlags.replace('v', 'u');
		}
		if (config.transform.unicodeFlag) {
			newFlags = newFlags.replace('u', '');
		}
		if (config.transform.dotAllFlag === 'transform') {
			newFlags = newFlags.replace('s', '');
		}
		onNewFlags(newFlags);
	}

	return generate(tree);
};

module.exports = rewritePattern;


code:
Copyright (c) Julian Viereck and Contributors, All Rights Reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

license: mit


code:
# RegJSParser

Parsing the JavaScript's RegExp in JavaScript.

## Installation

```bash
npm install regjsparser
```

## Usage

```js
var parse = require('regjsparser').parse;

var parseTree = parse('^a'); // /^a/
console.log(parseTree);

// Toggle on/off additional features:
var parseTree = parse('^a', '', {
  // SEE: https://github.com/jviereck/regjsparser/pull/78
  unicodePropertyEscape: true,

  // SEE: https://github.com/jviereck/regjsparser/pull/83
  namedGroups: true,

  // SEE: https://github.com/jviereck/regjsparser/pull/89
  lookbehind: true
});
console.log(parseTree);
```

## Testing

To run the tests, run the following command:

```bash
npm test
```

To create a new reference file, execute…

```bash
node test/update-fixtures.js
```

…from the repo top directory.


code:
#!/usr/bin/env node
(function() {

  var fs = require('fs');
  var path = require('path');
  var parse = require('../parser').parse;
  var jsesc = require('jsesc');
  var regexes = process.argv.splice(2);
  var first = regexes[0];
  var data;
  var log = console.log;
  var flags = '';
  var main = function() {
    if (/^(?:-h|--help|undefined)$/.test(first)) {
      log([
        '\nUsage:\n',
        '\tregjsparser [regex ...]',
        '\tregjsparser [-f | --flags] u [regex ...]',
        '\tregjsparser [-v | --version]',
        '\tregjsparser [-h | --help]',
        '\nExamples:\n',
        '\tregjsparser \'^foo.bar$\'',
        '\tregjsparser \'[a-zA-Z0-9]\''
      ].join('\n'));
      return process.exit(1);
    }

    if (/^(?:-v|--version)$/.test(first)) {
      log('v%s', require(path.resolve(__dirname, '../package.json')).version);
      return process.exit(1);
    }

    if (/^(?:-f|--flags)$/.test(first)) {
      flags = regexes[1];
      regexes = regexes.slice(2);
    }

    regexes.forEach(function(snippet) {
      try {
        result = parse(snippet, flags);
        log(jsesc(result, {
          'json': true,
          'compact': false,
          'indent': '\t'
        }));
      } catch(error) {
        log(error.message + '\n');
        log('Error: failed to parse. Make sure the regular expression is valid.');
        log('If you think this is a bug in regjsparser, please report it:');
        log('\thttps://github.com/jviereck/regjsparser/issues/new');
        log('\nStack trace:\n');
        log(error.stack);
        return process.exit(1);
      }
    });
    // Return with exit status 0 outside of the `forEach` loop, in case
    // multiple regular expressions were passed in.
    return process.exit(0);
  };

  main();

}());


code:
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

case `uname` in
    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac

if [ -x "$basedir/node" ]; then
  exec "$basedir/node"  "$basedir/../jsesc/bin/jsesc" "$@"
else
  exec node  "$basedir/../jsesc/bin/jsesc" "$@"
fi


code:
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0

IF EXIST "%dp0%\node.exe" (
  SET "_prog=%dp0%\node.exe"
) ELSE (
  SET "_prog=node"
  SET PATHEXT=%PATHEXT:;.JS;=;%
)

endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\..\jsesc\bin\jsesc" %*


code:
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent

$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
  # Fix case when both the Windows and Linux builds of Node
  # are installed in the same directory
  $exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
  # Support pipeline input
  if ($MyInvocation.ExpectingInput) {
    $input | & "$basedir/node$exe"  "$basedir/../jsesc/bin/jsesc" $args
  } else {
    & "$basedir/node$exe"  "$basedir/../jsesc/bin/jsesc" $args
  }
  $ret=$LASTEXITCODE
} else {
  # Support pipeline input
  if ($MyInvocation.ExpectingInput) {
    $input | & "node$exe"  "$basedir/../jsesc/bin/jsesc" $args
  } else {
    & "node$exe"  "$basedir/../jsesc/bin/jsesc" $args
  }
  $ret=$LASTEXITCODE
}
exit $ret


code:
Copyright Mathias Bynens <http://mathiasbynens.be/>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


code:
# jsesc [![Build status](https://travis-ci.org/mathiasbynens/jsesc.svg?branch=master)](https://travis-ci.org/mathiasbynens/jsesc) [![Code coverage status](http://img.shields.io/coveralls/mathiasbynens/jsesc/master.svg)](https://coveralls.io/r/mathiasbynens/jsesc) [![Dependency status](https://gemnasium.com/mathiasbynens/jsesc.svg)](https://gemnasium.com/mathiasbynens/jsesc)

This is a JavaScript library for [escaping JavaScript strings](http://mathiasbynens.be/notes/javascript-escapes) while generating the shortest possible valid ASCII-only output. [Here’s an online demo.](http://mothereff.in/js-escapes)

This can be used to avoid [mojibake](http://en.wikipedia.org/wiki/Mojibake) and other encoding issues, or even to [avoid errors](https://twitter.com/annevk/status/380000829643571200) when passing JSON-formatted data (which may contain U+2028 LINE SEPARATOR, U+2029 PARAGRAPH SEPARATOR, or [lone surrogates](http://esdiscuss.org/topic/code-points-vs-unicode-scalar-values#content-14)) to a JavaScript parser or an UTF-8 encoder, respectively.

Feel free to fork if you see possible improvements!

## Installation

Via [Bower](http://bower.io/):

```bash
bower install jsesc
```

Via [Component](https://github.com/component/component):

```bash
component install mathiasbynens/jsesc
```

Via [npm](http://npmjs.org/):

```bash
npm install jsesc
```

In a browser:

```html
<script src="jsesc.js"></script>
```

In [Node.js](http://nodejs.org/) and [RingoJS](http://ringojs.org/):

```js
var jsesc = require('jsesc');
```

In [Narwhal](http://narwhaljs.org/):

```js
var jsesc = require('jsesc').jsesc;
```

In [Rhino](http://www.mozilla.org/rhino/):

```js
load('jsesc.js');
```

Using an AMD loader like [RequireJS](http://requirejs.org/):

```js
require(
  {
    'paths': {
      'jsesc': 'path/to/jsesc'
    }
  },
  ['jsesc'],
  function(jsesc) {
    console.log(jsesc);
  }
);
```

## API

### `jsesc(value, options)`

This function takes a value and returns an escaped version of the value where any characters that are not printable ASCII symbols are escaped using the shortest possible (but valid) [escape sequences for use in JavaScript strings](http://mathiasbynens.be/notes/javascript-escapes). The first supported value type is strings:

```js
jsesc('Ich ♥ Bücher');
// → 'Ich \\u2665 B\\xFCcher'

jsesc('foo 𝌆 bar');
// → 'foo \\uD834\\uDF06 bar'
```

Instead of a string, the `value` can also be an array, or an object. In such cases, `jsesc` will return a stringified version of the value where any characters that are not printable ASCII symbols are escaped in the same way.

```js
// Escaping an array
jsesc([
  'Ich ♥ Bücher', 'foo 𝌆 bar'
]);
// → '[\'Ich \\u2665 B\\xFCcher\',\'foo \\uD834\\uDF06 bar\']'

// Escaping an object
jsesc({
  'Ich ♥ Bücher': 'foo 𝌆 bar'
});
// → '{\'Ich \\u2665 B\\xFCcher\':\'foo \\uD834\\uDF06 bar\'}'
```

The optional `options` argument accepts an object with the following options:

#### `quotes`

The default value for the `quotes` option is `'single'`. This means that any occurences of `'` in the input string will be escaped as `\'`, so that the output can be used in a string literal wrapped in single quotes.

```js
jsesc('Lorem ipsum "dolor" sit \'amet\' etc.');
// → 'Lorem ipsum "dolor" sit \\\'amet\\\' etc.'

jsesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
  'quotes': 'single'
});
// → 'Lorem ipsum "dolor" sit \\\'amet\\\' etc.'
// → "Lorem ipsum \"dolor\" sit \\'amet\\' etc."
```

If you want to use the output as part of a string literal wrapped in double quotes, set the `quotes` option to `'double'`.

```js
jsesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
  'quotes': 'double'
});
// → 'Lorem ipsum \\"dolor\\" sit \'amet\' etc.'
// → "Lorem ipsum \\\"dolor\\\" sit 'amet' etc."
```

This setting also affects the output for arrays and objects:

```js
jsesc({ 'Ich ♥ Bücher': 'foo 𝌆 bar' }, {
  'quotes': 'double'
});
// → '{"Ich \\u2665 B\\xFCcher":"foo \\uD834\\uDF06 bar"}'

jsesc([ 'Ich ♥ Bücher', 'foo 𝌆 bar' ], {
  'quotes': 'double'
});
// → '["Ich \\u2665 B\\xFCcher","foo \\uD834\\uDF06 bar"]'
```

#### `wrap`

The `wrap` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, the output will be a valid JavaScript string literal wrapped in quotes. The type of quotes can be specified through the `quotes` setting.

```js
jsesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
  'quotes': 'single',
  'wrap': true
});
// → '\'Lorem ipsum "dolor" sit \\\'amet\\\' etc.\''
// → "\'Lorem ipsum \"dolor\" sit \\\'amet\\\' etc.\'"

jsesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
  'quotes': 'double',
  'wrap': true
});
// → '"Lorem ipsum \\"dolor\\" sit \'amet\' etc."'
// → "\"Lorem ipsum \\\"dolor\\\" sit \'amet\' etc.\""
```

#### `es6`

The `es6` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, any astral Unicode symbols in the input will be escaped using [ECMAScript 6 Unicode code point escape sequences](http://mathiasbynens.be/notes/javascript-escapes#unicode-code-point) instead of using separate escape sequences for each surrogate half. If backwards compatibility with ES5 environments is a concern, don’t enable this setting. If the `json` setting is enabled, the value for the `es6` setting is ignored (as if it was `false`).

```js
// By default, the `es6` option is disabled:
jsesc('foo 𝌆 bar 💩 baz');
// → 'foo \\uD834\\uDF06 bar \\uD83D\\uDCA9 baz'

// To explicitly disable it:
jsesc('foo 𝌆 bar 💩 baz', {
  'es6': false
});
// → 'foo \\uD834\\uDF06 bar \\uD83D\\uDCA9 baz'

// To enable it:
jsesc('foo 𝌆 bar 💩 baz', {
  'es6': true
});
// → 'foo \\u{1D306} bar \\u{1F4A9} baz'
```

#### `escapeEverything`

The `escapeEverything` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, all the symbols in the output will be escaped, even printable ASCII symbols.

```js
jsesc('lolwat"foo\'bar', {
  'escapeEverything': true
});
// → '\\x6C\\x6F\\x6C\\x77\\x61\\x74\\"\\x66\\x6F\\x6F\\\'\\x62\\x61\\x72'
// → "\\x6C\\x6F\\x6C\\x77\\x61\\x74\\\"\\x66\\x6F\\x6F\\'\\x62\\x61\\x72"
```

This setting also affects the output for arrays and objects:

```js
jsesc({ 'Ich ♥ Bücher': 'foo 𝌆 bar' }, {
  'escapeEverything': true
});
// → '{\'\x49\x63\x68\x20\u2665\x20\x42\xFC\x63\x68\x65\x72\':\'\x66\x6F\x6F\x20\uD834\uDF06\x20\x62\x61\x72\'}'
// → "{'\x49\x63\x68\x20\u2665\x20\x42\xFC\x63\x68\x65\x72':'\x66\x6F\x6F\x20\uD834\uDF06\x20\x62\x61\x72'}"

jsesc([ 'Ich ♥ Bücher': 'foo 𝌆 bar' ], {
  'escapeEverything': true
});
// → '[\'\x49\x63\x68\x20\u2665\x20\x42\xFC\x63\x68\x65\x72\',\'\x66\x6F\x6F\x20\uD834\uDF06\x20\x62\x61\x72\']'
```

#### `compact`

The `compact` option takes a boolean value (`true` or `false`), and defaults to `true` (enabled). When enabled, the output for arrays and objects will be as compact as possible; it won’t be formatted nicely.

```js
jsesc({ 'Ich ♥ Bücher': 'foo 𝌆 bar' }, {
  'compact': true // this is the default
});
// → '{\'Ich \u2665 B\xFCcher\':\'foo \uD834\uDF06 bar\'}'

jsesc({ 'Ich ♥ Bücher': 'foo 𝌆 bar' }, {
  'compact': false
});
// → '{\n\t\'Ich \u2665 B\xFCcher\': \'foo \uD834\uDF06 bar\'\n}'

jsesc([ 'Ich ♥ Bücher', 'foo 𝌆 bar' ], {
  'compact': false
});
// → '[\n\t\'Ich \u2665 B\xFCcher\',\n\t\'foo \uD834\uDF06 bar\'\n]'
```

This setting has no effect on the output for strings.

#### `indent`

The `indent` option takes a string value, and defaults to `'\t'`. When the `compact` setting is enabled (`true`), the value of the `indent` option is used to format the output for arrays and objects.

```js
jsesc({ 'Ich ♥ Bücher': 'foo 𝌆 bar' }, {
  'compact': false,
  'indent': '\t' // this is the default
});
// → '{\n\t\'Ich \u2665 B\xFCcher\': \'foo \uD834\uDF06 bar\'\n}'

jsesc({ 'Ich ♥ Bücher': 'foo 𝌆 bar' }, {
  'compact': false,
  'indent': '  '
});
// → '{\n  \'Ich \u2665 B\xFCcher\': \'foo \uD834\uDF06 bar\'\n}'

jsesc([ 'Ich ♥ Bücher', 'foo 𝌆 bar' ], {
  'compact': false,
  'indent': '  '
});
// → '[\n  \'Ich \u2665 B\xFCcher\',\n\  t\'foo \uD834\uDF06 bar\'\n]'
```

This setting has no effect on the output for strings.

#### `json`

The `json` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, the output is valid JSON. [Hexadecimal character escape sequences](http://mathiasbynens.be/notes/javascript-escapes#hexadecimal) and [the `\v` or `\0` escape sequences](http://mathiasbynens.be/notes/javascript-escapes#single) will not be used. Setting `json: true` implies `quotes: 'double', wrap: true, es6: false`, although these values can still be overridden if needed — but in such cases, the output won’t be valid JSON anymore.

```js
jsesc('foo\x00bar\xFF\uFFFDbaz', {
  'json': true
});
// → '"foo\\u0000bar\\u00FF\\uFFFDbaz"'

jsesc({ 'foo\x00bar\xFF\uFFFDbaz': 'foo\x00bar\xFF\uFFFDbaz' }, {
  'json': true
});
// → '{"foo\\u0000bar\\u00FF\\uFFFDbaz":"foo\\u0000bar\\u00FF\\uFFFDbaz"}'

jsesc([ 'foo\x00bar\xFF\uFFFDbaz', 'foo\x00bar\xFF\uFFFDbaz' ], {
  'json': true
});
// → '["foo\\u0000bar\\u00FF\\uFFFDbaz","foo\\u0000bar\\u00FF\\uFFFDbaz"]'

// Values that are acceptable in JSON but aren’t strings, arrays, or object
// literals can’t be escaped, so they’ll just be preserved:
jsesc([ 'foo\x00bar', [1, '©', { 'foo': true, 'qux': null }], 42 ], {
  'json': true
});
// → '["foo\\u0000bar",[1,"\\u00A9",{"foo":true,"qux":null}],42]'
// Values that aren’t allowed in JSON are run through `JSON.stringify()`:
jsesc([ undefined, -Infinity ], {
  'json': true
});
// → '[null,null]'
```

**Note:** Using this option on objects or arrays that contain non-string values relies on `JSON.stringify()`. For legacy environments like IE ≤ 7, use [a `JSON` polyfill](http://bestiejs.github.io/json3/).

### `jsesc.version`

A string representing the semantic version number.

### Using the `jsesc` binary

To use the `jsesc` binary in your shell, simply install jsesc globally using npm:

```bash
npm install -g jsesc
```

After that you will be able to escape strings from the command line:

```bash
$ jsesc 'föo ♥ bår 𝌆 baz'
f\xF6o \u2665 b\xE5r \uD834\uDF06 baz
```

To escape arrays or objects containing string values, use the `-o`/`--object` option:

```bash
$ jsesc --object '{ "föo": "♥", "bår": "𝌆 baz" }'
{'f\xF6o':'\u2665','b\xE5r':'\uD834\uDF06 baz'}
```

To prettify the output in such cases, use the `-p`/`--pretty` option:

```bash
$ jsesc --pretty '{ "föo": "♥", "bår": "𝌆 baz" }'
{
  'f\xF6o': '\u2665',
  'b\xE5r': '\uD834\uDF06 baz'
}
```

For valid JSON output, use the `-j`/`--json` option:

```bash
$ jsesc --json --pretty '{ "föo": "♥", "bår": "𝌆 baz" }'
{
  "f\u00F6o": "\u2665",
  "b\u00E5r": "\uD834\uDF06 baz"
}
```

Read a local JSON file, escape any non-ASCII symbols, and save the result to a new file:

```bash
$ jsesc --json --object < data-raw.json > data-escaped.json
```

Or do the same with an online JSON file:

```bash
$ curl -sL "http://git.io/aorKgQ" | jsesc --json --object > data-escaped.json
```

See `jsesc --help` for the full list of options.

## Support

This library has been tested in at least Chrome 27-29, Firefox 3-22, Safari 4-6, Opera 10-12, IE 6-10, Node.js v0.10.0, Narwhal 0.3.2, RingoJS 0.8-0.9, PhantomJS 1.9.0, and Rhino 1.7RC4.

**Note:** Using the `json` option on objects or arrays that contain non-string values relies on `JSON.parse()`. For legacy environments like IE ≤ 7, use [a `JSON` polyfill](http://bestiejs.github.io/json3/).

## Unit tests & code coverage

After cloning this repository, run `npm install` to install the dependencies needed for development and testing. You may want to install Istanbul _globally_ using `npm install istanbul -g`.

Once that’s done, you can run the unit tests in Node using `npm test` or `node tests/tests.js`. To run the tests in Rhino, Ringo, Narwhal, and web browsers as well, use `grunt test`.

To generate the code coverage report, use `grunt cover`.

## Author

| [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|
| [Mathias Bynens](http://mathiasbynens.be/) |

## License

This library is available under the [MIT](http://mths.be/mit) license.


code:
#!/usr/bin/env node
(function() {

	var fs = require('fs');
	var stringEscape = require('../jsesc.js');
	var strings = process.argv.splice(2);
	var stdin = process.stdin;
	var data;
	var timeout;
	var isObject = false;
	var options = {};
	var log = console.log;

	var main = function() {
		var option = strings[0];

		if (/^(?:-h|--help|undefined)$/.test(option)) {
			log(
				'jsesc v%s - http://mths.be/jsesc',
				stringEscape.version
			);
			log([
				'\nUsage:\n',
				'\tjsesc [string]',
				'\tjsesc [-s | --single-quotes] [string]',
				'\tjsesc [-d | --double-quotes] [string]',
				'\tjsesc [-w | --wrap] [string]',
				'\tjsesc [-e | --escape-everything] [string]',
				'\tjsesc [-6 | --es6] [string]',
				'\tjsesc [-j | --json] [string]',
				'\tjsesc [-o | --object] [stringified_object]', // `JSON.parse()` the argument
				'\tjsesc [-p | --pretty] [string]', // `compact: false`
				'\tjsesc [-v | --version]',
				'\tjsesc [-h | --help]',
				'\nExamples:\n',
				'\tjsesc \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'',
				'\tjsesc --json \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'',
				'\tjsesc --json --escape-everything \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'',
				'\tjsesc --double-quotes --wrap \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'',
				'\techo \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\' | jsesc'
			].join('\n'));
			return process.exit(1);
		}

		if (/^(?:-v|--version)$/.test(option)) {
			log('v%s', stringEscape.version);
			return process.exit(1);
		}

		strings.forEach(function(string) {
			// Process options
			if (/^(?:-s|--single-quotes)$/.test(string)) {
				options.quotes = 'single';
				return;
			}
			if (/^(?:-d|--double-quotes)$/.test(string)) {
				options.quotes = 'double';
				return;
			}
			if (/^(?:-w|--wrap)$/.test(string)) {
				options.wrap = true;
				return;
			}
			if (/^(?:-6|--es6)$/.test(string)) {
				options.es6 = true;
				return;
			}
			if (/^(?:-e|--escape-everything)$/.test(string)) {
				options.escapeEverything = true;
				return;
			}
			if (/^(?:-j|--json)$/.test(string)) {
				options.json = true;
				return;
			}
			if (/^(?:-o|--object)$/.test(string)) {
				isObject = true;
				return;
			}
			if (/^(?:-p|--pretty)$/.test(string)) {
				isObject = true;
				options.compact = false;
				return;
			}

			// Process string(s)
			var result;
			try {
				if (isObject) {
					string = JSON.parse(string);
				}
				result = stringEscape(string, options);
				log(result);
			} catch(error) {
				log(error.message + '\n');
				log('Error: failed to escape.');
				log('If you think this is a bug in jsesc, please report it:');
				log('https://github.com/mathiasbynens/jsesc/issues/new');
				log(
					'\nStack trace using jsesc@%s:\n',
					stringEscape.version
				);
				log(error.stack);
				return process.exit(1);
			}
		});
		// Return with exit status 0 outside of the `forEach` loop, in case
		// multiple strings were passed in.
		return process.exit(0);

	};

	if (stdin.isTTY) {
		// handle shell arguments
		main();
	} else {
		// Either the script is called from within a non-TTY context,
		// or `stdin` content is being piped in.
		if (!process.stdout.isTTY) { // called from a non-TTY context
			timeout = setTimeout(function() {
				// if no piped data arrived after a while, handle shell arguments
				main();
			}, 250);
		}

		data = '';
		stdin.on('data', function(chunk) {
			clearTimeout(timeout);
			data += chunk;
		});
		stdin.on('end', function() {
			strings.push(data.trim());
			main();
		});
		stdin.resume();
	}

}());


code:
/*! http://mths.be/jsesc v0.5.0 by @mathias */
;(function(root) {

	// Detect free variables `exports`
	var freeExports = typeof exports == 'object' && exports;

	// Detect free variable `module`
	var freeModule = typeof module == 'object' && module &&
		module.exports == freeExports && module;

	// Detect free variable `global`, from Node.js or Browserified code,
	// and use it as `root`
	var freeGlobal = typeof global == 'object' && global;
	if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
		root = freeGlobal;
	}

	/*--------------------------------------------------------------------------*/

	var object = {};
	var hasOwnProperty = object.hasOwnProperty;
	var forOwn = function(object, callback) {
		var key;
		for (key in object) {
			if (hasOwnProperty.call(object, key)) {
				callback(key, object[key]);
			}
		}
	};

	var extend = function(destination, source) {
		if (!source) {
			return destination;
		}
		forOwn(source, function(key, value) {
			destination[key] = value;
		});
		return destination;
	};

	var forEach = function(array, callback) {
		var length = array.length;
		var index = -1;
		while (++index < length) {
			callback(array[index]);
		}
	};

	var toString = object.toString;
	var isArray = function(value) {
		return toString.call(value) == '[object Array]';
	};
	var isObject = function(value) {
		// This is a very simple check, but it’s good enough for what we need.
		return toString.call(value) == '[object Object]';
	};
	var isString = function(value) {
		return typeof value == 'string' ||
			toString.call(value) == '[object String]';
	};
	var isFunction = function(value) {
		// In a perfect world, the `typeof` check would be sufficient. However,
		// in Chrome 1–12, `typeof /x/ == 'object'`, and in IE 6–8
		// `typeof alert == 'object'` and similar for other host objects.
		return typeof value == 'function' ||
			toString.call(value) == '[object Function]';
	};

	/*--------------------------------------------------------------------------*/

	// http://mathiasbynens.be/notes/javascript-escapes#single
	var singleEscapes = {
		'"': '\\"',
		'\'': '\\\'',
		'\\': '\\\\',
		'\b': '\\b',
		'\f': '\\f',
		'\n': '\\n',
		'\r': '\\r',
		'\t': '\\t'
		// `\v` is omitted intentionally, because in IE < 9, '\v' == 'v'.
		// '\v': '\\x0B'
	};
	var regexSingleEscape = /["'\\\b\f\n\r\t]/;

	var regexDigit = /[0-9]/;
	var regexWhitelist = /[ !#-&\(-\[\]-~]/;

	var jsesc = function(argument, options) {
		// Handle options
		var defaults = {
			'escapeEverything': false,
			'quotes': 'single',
			'wrap': false,
			'es6': false,
			'json': false,
			'compact': true,
			'indent': '\t',
			'__indent__': ''
		};
		var json = options && options.json;
		if (json) {
			defaults.quotes = 'double';
			defaults.wrap = true;
		}
		options = extend(defaults, options);
		if (options.quotes != 'single' && options.quotes != 'double') {
			options.quotes = 'single';
		}
		var quote = options.quotes == 'double' ? '"' : '\'';
		var compact = options.compact;
		var indent = options.indent;
		var oldIndent;
		var newLine = compact ? '' : '\n';
		var result;
		var isEmpty = true;

		if (json && argument && isFunction(argument.toJSON)) {
			argument = argument.toJSON();
		}

		if (!isString(argument)) {
			if (isArray(argument)) {
				result = [];
				options.wrap = true;
				oldIndent = options.__indent__;
				indent += oldIndent;
				options.__indent__ = indent;
				forEach(argument, function(value) {
					isEmpty = false;
					result.push(
						(compact ? '' : indent) +
						jsesc(value, options)
					);
				});
				if (isEmpty) {
					return '[]';
				}
				return '[' + newLine + result.join(',' + newLine) + newLine +
					(compact ? '' : oldIndent) + ']';
			} else if (!isObject(argument)) {
				if (json) {
					// For some values (e.g. `undefined`, `function` objects),
					// `JSON.stringify(value)` returns `undefined` (which isn’t valid
					// JSON) instead of `'null'`.
					return JSON.stringify(argument) || 'null';
				}
				return String(argument);
			} else { // it’s an object
				result = [];
				options.wrap = true;
				oldIndent = options.__indent__;
				indent += oldIndent;
				options.__indent__ = indent;
				forOwn(argument, function(key, value) {
					isEmpty = false;
					result.push(
						(compact ? '' : indent) +
						jsesc(key, options) + ':' +
						(compact ? '' : ' ') +
						jsesc(value, options)
					);
				});
				if (isEmpty) {
					return '{}';
				}
				return '{' + newLine + result.join(',' + newLine) + newLine +
					(compact ? '' : oldIndent) + '}';
			}
		}

		var string = argument;
		// Loop over each code unit in the string and escape it
		var index = -1;
		var length = string.length;
		var first;
		var second;
		var codePoint;
		result = '';
		while (++index < length) {
			var character = string.charAt(index);
			if (options.es6) {
				first = string.charCodeAt(index);
				if ( // check if it’s the start of a surrogate pair
					first >= 0xD800 && first <= 0xDBFF && // high surrogate
					length > index + 1 // there is a next code unit
				) {
					second = string.charCodeAt(index + 1);
					if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
						// http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
						codePoint = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
						result += '\\u{' + codePoint.toString(16).toUpperCase() + '}';
						index++;
						continue;
					}
				}
			}
			if (!options.escapeEverything) {
				if (regexWhitelist.test(character)) {
					// It’s a printable ASCII character that is not `"`, `'` or `\`,
					// so don’t escape it.
					result += character;
					continue;
				}
				if (character == '"') {
					result += quote == character ? '\\"' : character;
					continue;
				}
				if (character == '\'') {
					result += quote == character ? '\\\'' : character;
					continue;
				}
			}
			if (
				character == '\0' &&
				!json &&
				!regexDigit.test(string.charAt(index + 1))
			) {
				result += '\\0';
				continue;
			}
			if (regexSingleEscape.test(character)) {
				// no need for a `hasOwnProperty` check here
				result += singleEscapes[character];
				continue;
			}
			var charCode = character.charCodeAt(0);
			var hexadecimal = charCode.toString(16).toUpperCase();
			var longhand = hexadecimal.length > 2 || json;
			var escaped = '\\' + (longhand ? 'u' : 'x') +
				('0000' + hexadecimal).slice(longhand ? -4 : -2);
			result += escaped;
			continue;
		}
		if (options.wrap) {
			result = quote + result + quote;
		}
		return result;
	};

	jsesc.version = '0.5.0';

	/*--------------------------------------------------------------------------*/

	// Some AMD build optimizers, like r.js, check for specific condition patterns
	// like the following:
	if (
		typeof define == 'function' &&
		typeof define.amd == 'object' &&
		define.amd
	) {
		define(function() {
			return jsesc;
		});
	}	else if (freeExports && !freeExports.nodeType) {
		if (freeModule) { // in Node.js or RingoJS v0.8.0+
			freeModule.exports = jsesc;
		} else { // in Narwhal or RingoJS v0.7.0-
			freeExports.jsesc = jsesc;
		}
	} else { // in Rhino or a web browser
		root.jsesc = jsesc;
	}

}(this));


code:
.Dd October 25, 2013
.Dt jsesc 1
.Sh NAME
.Nm jsesc
.Nd escape strings for use in JavaScript string literals
.Sh SYNOPSIS
.Nm
.Op Fl s | -single-quotes Ar string
.br
.Op Fl d | -double-quotes Ar string
.br
.Op Fl w | -wrap Ar string
.br
.Op Fl 6 | -es6 Ar string
.br
.Op Fl e | -escape-everything Ar string
.br
.Op Fl j | -json Ar string
.br
.Op Fl p | -object Ar string
.br
.Op Fl p | -pretty Ar string
.br
.Op Fl v | -version
.br
.Op Fl h | -help
.Sh DESCRIPTION
.Nm
escapes strings for use in JavaScript string literals while generating the shortest possible valid ASCII-only output.
.Sh OPTIONS
.Bl -ohang -offset
.It Sy "-s, --single-quotes"
Escape any occurences of ' in the input string as \\', so that the output can be used in a JavaScript string literal wrapped in single quotes.
.It Sy "-d, --double-quotes"
Escape any occurences of " in the input string as \\", so that the output can be used in a JavaScript string literal wrapped in double quotes.
.It Sy "-w, --wrap"
Make sure the output is a valid JavaScript string literal wrapped in quotes. The type of quotes can be specified using the
.Ar -s | --single-quotes
or
.Ar -d | --double-quotes
settings.
.It Sy "-6, --es6"
Escape any astral Unicode symbols using ECMAScript 6 Unicode code point escape sequences.
.It Sy "-e, --escape-everything"
Escape all the symbols in the output, even printable ASCII symbols.
.It Sy "-j, --json"
Make sure the output is valid JSON. Hexadecimal character escape sequences and the \\v or \\0 escape sequences will not be used. Setting this flag enables the
.Ar -d | --double-quotes
and
.Ar -w | --wrap
settings.
.It Sy "-o, --object"
Treat the input as a JavaScript object rather than a string. Accepted values are flat arrays containing only string values, and flat objects containing only string values.
.It Sy "-p, --pretty"
Pretty-print the output for objects, using whitespace to make it more readable. Setting this flag enables the
.Ar -o | --object
setting.
.It Sy "-v, --version"
Print jsesc's version.
.It Sy "-h, --help"
Show the help screen.
.El
.Sh EXIT STATUS
The
.Nm jsesc
utility exits with one of the following values:
.Pp
.Bl -tag -width flag -compact
.It Li 0
.Nm
successfully escaped the given string and printed the result.
.It Li 1
.Nm
wasn't instructed to escape anything (for example, the
.Ar --help
flag was set); or, an error occurred.
.El
.Sh EXAMPLES
.Bl -ohang -offset
.It Sy "jsesc 'foo bar baz'"
Print an escaped version of the given string.
.It Sy echo\ 'foo bar baz'\ |\ jsesc
Print an escaped version of the string that gets piped in.
.El
.Sh BUGS
jsesc's bug tracker is located at <https://github.com/mathiasbynens/jsesc/issues>.
.Sh AUTHOR
Mathias Bynens <http://mathiasbynens.be/>
.Sh WWW
<http://mths.be/jsesc>


jsesc

description: A JavaScript library for escaping JavaScript strings while generating the shortest possible valid output.

bin: bin/jsesc

keywords: string escape javascript tool

type: MIT

type: git

test: tests

test: node tests/tests.js


regjsparser

main: ./parser

bin: bin/parser

lint: eslint --ext .js --max-warnings 0 .

test: run-p test:* && npm run lint

test:src: node test/index.js

test:types: tsc test/types.ts --noEmit


code:
type _If<Test, Then, Else> = Test extends true ? Then : Else;

export type Features = {
  lookbehind?: boolean;
  namedGroups?: boolean;
  unicodePropertyEscape?: boolean;
  unicodeSet?: boolean;
  modifiers?: boolean;
};

export type AstNodeType =
  | "alternative"
  | "anchor"
  | "characterClass"
  | "characterClassEscape"
  | "characterClassRange"
  | "disjunction"
  | "dot"
  | "group"
  | "quantifier"
  | "reference"
  | "unicodePropertyEscape"
  | "value";

export type Base<T extends AstNodeType> = {
  range: [number, number];
  raw: string;
  type: T;
};

export type AstNode<F extends Features = {}> =
  | Alternative<F>
  | Anchor
  | CharacterClass<F>
  | CharacterClassEscape
  | CharacterClassRange
  | Disjunction<F>
  | Dot
  | Group<F>
  | Quantifier<F>
  | Reference<F>
  | _If<F["unicodePropertyEscape"], UnicodePropertyEscape, never>
  | Value;

export type RootNode<F extends Features = {}> = Exclude<
  AstNode<F>,
  CharacterClassRange
>;

export type Anchor = Base<"anchor"> & {
  kind: "boundary" | "end" | "not-boundary" | "start";
};

export type CharacterClassEscape = Base<"characterClassEscape"> & {
  value: string;
};

export type Value = Base<"value"> & {
  codePoint: number;
  kind:
    | "controlLetter"
    | "hexadecimalEscape"
    | "identifier"
    | "null"
    | "octal"
    | "singleEscape"
    | "symbol"
    | "unicodeCodePointEscape"
    | "unicodeEscape";
};

export type Identifier = Base<"value"> & {
  value: string;
};

export type Alternative<F extends Features = {}> = Base<"alternative"> & {
  body: RootNode<F>[];
};

export type CharacterClassRange = Base<"characterClassRange"> & {
  max: Value;
  min: Value;
};

export type UnicodePropertyEscape = Base<"unicodePropertyEscape"> & {
  negative: boolean;
  value: string;
};

export type CharacterClassBody =
  | CharacterClassEscape
  | CharacterClassRange
  | UnicodePropertyEscape
  | Value;

export type CharacterClass<F extends Features = {}> = Base<"characterClass"> & {
  body: CharacterClassBody[];
  negative: boolean;
  kind: "union" | _If<F["unicodeSet"], "intersection" | "subtraction", never>;
};

export type ModifierFlags = {
  enabling: string,
  disabling: string
}

export type NonCapturingGroup<F extends Features = {}> = Base<"group"> &
  (
    | {
        behavior:
          | "lookahead"
          | "lookbehind"
          | "negativeLookahead"
          | "negativeLookbehind";
        body: RootNode<F>[];
      }
    | ({
        behavior: "ignore";
        body: RootNode<F>[];
      } & _If<
        F["modifiers"],
        {
          modifierFlags?: ModifierFlags;
        },
        {
          modifierFlags: undefined;
        }
      >)
  );

export type CapturingGroup<F extends Features = {}> = Base<"group"> & {
  behavior: "normal";
  body: RootNode<F>[];
} & _If<
    F["namedGroups"],
    {
      name?: Identifier;
    },
    {
      name: undefined;
    }
  >;

export type Group<F extends Features = {}> =
  | CapturingGroup<F>
  | NonCapturingGroup<F>;

export type Quantifier<F extends Features = {}> = Base<"quantifier"> & {
  body: [RootNode<F>];
  greedy: boolean;
  max?: number;
  min: number;
  symbol?: '?' | '*' | '+';
};

export type Disjunction<F extends Features = {}> = Base<"disjunction"> & {
  body: [RootNode<F>, RootNode<F>, ...RootNode<F>[]];
};

export type Dot = Base<"dot">;

export type NamedReference = Base<"reference"> & {
  matchIndex: undefined;
  name: Identifier;
};

export type IndexReference = Base<"reference"> & {
  matchIndex: number;
  name: undefined;
};

export type Reference<F extends Features = {}> = _If<
  F["namedGroups"],
  NamedReference,
  IndexReference
>;

export function parse<F extends Features = {}>(
  str: string,
  flags: string,
  features?: F
): RootNode<F>;


code:
# relateurl [![NPM Version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][david-image]][david-url]

> Minify URLs by converting them from absolute to relative.

If you were to use this library on a website like `http://example.com/dir1/dir1-1/`, you would get results such as:

| Before                                      | After                                |
| :------------------------------------------ | :----------------------------------- |
| `http://example.com/dir1/dir1-2/index.html` | `../dir1-2/`                         |
| `http://example.com/dir2/dir2-1/`           | `/dir2/dir2-1/`                      |
| `http://example.com/dir1/dir1-1/`           | ` `                                  |
| `https://example.com/dir1/dir1-1/`          | `https://example.com/dir1/dir1-1/`   |
| `http://google.com:80/dir/`                 | `//google.com/dir/`                  |
| `../../../../../../../../#anchor`           | `/#anchor`                           |

**All string parsing.** *No* directory browsing. It is thoroughly tested, very fast and lightweight with zero external dependencies.

## Getting Started

This utility requires [Node.js](http://nodejs.org/) `>= 0.10`. To install, type this at the command line:
```
npm install relateurl --save-dev
```

### Options

#### options.defaultPorts
Type: `Object`
Default value: `{ftp:21, http:80, https:443}`

Extend the list with any ports you need. Any URLs containing these default ports will have them removed. Example: `http://example.com:80/` will become `http://example.com/`.

#### options.directoryIndexes
Type: `Array`
Default value: `["index.html"]`

Extend the list with any resources you need. Works with [`options.removeDirectoryIndexes`](#options.removeDirectoryIndexes).

#### options.ignore_www
Type: `Boolean`
Default value: `false`

This will, for example, consider any domains containing `http://www.example.com/` to be related to any that contain `http://example.com/`.

#### options.output
Type: constant or `String`
Choices: `RelateUrl.ABSOLUTE`,`RelateUrl.PATH_RELATIVE`,`RelateUrl.ROOT_RELATIVE`,`RelateUrl.SHORTEST`
Choices: `"absolute"`,`"pathRelative"`,`"rootRelative"`,`"shortest"`
Default value: `RelateUrl.SHORTEST`

`RelateUrl.ABSOLUTE` will produce an absolute URL. Overrides [`options.schemeRelative`](#options.schemeRelative) with a value of `false`.
`RelateUrl.PATH_RELATIVE` will produce something like `../child-of-parent/etc/`.
`RelateUrl.ROOT_RELATIVE` will produce something like `/child-of-root/etc/`.
`RelateUrl.SHORTEST` will choose whichever is shortest between root- and path-relative.

#### options.rejectedSchemes
Type: `Array`
Default value: `["data","javascript","mailto"]`

Extend the list with any additional schemes. Example: `javascript:something` will not be modified.

#### options.removeAuth
Type: `Boolean`
Default value: `false`

Remove user authentication information from the output URL.

#### options.removeDirectoryIndexes
Type: `Boolean`
Default value: `true`

Remove any resources that match any found in [`options.directoryIndexes`](#options.directoryIndexes).

#### options.removeEmptyQueries
Type: `Boolean`
Default value: `false`

Remove empty query variables. Example: `http://domain.com/?var1&var2=&var3=asdf` will become `http://domain.com/?var3=adsf`. This does not apply to unrelated URLs (with other protocols, auths, hosts and/or ports).

#### options.removeRootTrailingSlash
Type: `Boolean`
Default value: `true`

Remove trailing slashes from root paths. Example: `http://domain.com/?var` will become `http://domain.com?var` while `http://domain.com/dir/?var` will not be modified.

#### options.schemeRelative
Type: `Boolean`
Default value: `true`

Output URLs relative to the scheme. Example: `http://example.com/` will become `//example.com/`.

#### options.site
Type: `String`
Default value: `undefined`

An options-based version of the [`from`](#examples) argument. If both are specified, `from` takes priority.

#### options.slashesDenoteHost
Type: `Boolean`
Default value: `true`

Passed to Node's [`url.parse`](http://nodejs.org/api/url.html#url_url_parse_urlstr_parsequerystring_slashesdenotehost).

### Examples
This library can be used as a [function for single-use](#single-instance) or as a [class for multiple conversions](#reusable-instances).

Upon successful conversion, a `String` will be returned. If an issue is encountered while parsing `from`, an error will be thrown.

#### Single Instance
```js
var RelateUrl = require("relateurl");

var result = RelateUrl.relate(from, to, options);
```

#### Reusable Instances
```js
var RelateUrl = require("relateurl");

var instance = new RelateUrl(from, options);

var result1 = instance.relate(to1);
var result2 = instance.relate(to2, customOptions);
var result3 = instance.relate(to3);
```

## FAQ
1. **Why bother writing/using this?**
To aid in further minifying HTML, mainly for the purpose of faster page loads and SEO. It's been integrated into [HTMLMinifier](https://github.com/kangax/html-minifier).

2. **Why not just use Node's `url.parse`, `url.resolve` and `path.relative`?**
`url.parse` *is* used, but `url.resolve` and `path.relative` are both slower and less powerful than this library.

## Release History
* 0.2.7 Node v6 support
* 0.2.6 minor enhancements
* 0.2.5 added `options.removeRootTrailingSlash`
* 0.2.4 added `options.site`
* 0.2.3 added browserify npm-script
* 0.2.2 removed task runner
* 0.2.1 shorten resource- and query-relative URLs, test variations list with other site URLs
* 0.2.0 code cleanup, `options.removeEmptyQueries=true` only applied to unrelated URLs
* 0.1.0 initial release

## Roadmap
* 0.2.8 check if queries are the same, regardless of param order
* 0.2.8 possible [scheme exclusions](http://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml) such as `tel:`
* 0.2.8 decipher and return invalid input (special cases) to complete test suite
* 0.3.0 test `options.slashesDenoteHost=false`, add something like `options.externalDirectoryIndexes=[]` for external sites

[npm-image]: https://img.shields.io/npm/v/relateurl.svg
[npm-url]: https://npmjs.org/package/relateurl
[travis-image]: https://img.shields.io/travis/stevenvachon/relateurl.svg
[travis-url]: https://travis-ci.org/stevenvachon/relateurl
[david-image]: https://img.shields.io/david/stevenvachon/relateurl.svg
[david-url]: https://david-dm.org/stevenvachon/relateurl


code:
"use strict";

module.exports =
{
	// Output
	ABSOLUTE:      "absolute",
	PATH_RELATIVE: "pathRelative",
	ROOT_RELATIVE: "rootRelative",
	SHORTEST:      "shortest"
};


code:
"use strict";

var constants = require("./constants");

function formatAuth(urlObj, options)
{
	if (urlObj.auth && !options.removeAuth && (urlObj.extra.relation.maximumHost || options.output===constants.ABSOLUTE))
	{
		return urlObj.auth + "@";
	}

	return "";
}

function formatHash(urlObj, options)
{
	return urlObj.hash ? urlObj.hash : "";
}

function formatHost(urlObj, options)
{
	if (urlObj.host.full && (urlObj.extra.relation.maximumAuth || options.output===constants.ABSOLUTE))
	{
		return urlObj.host.full;
	}

	return "";
}

function formatPath(urlObj, options)
{
	var str = "";

	var absolutePath = urlObj.path.absolute.string;
	var relativePath = urlObj.path.relative.string;
	var resource = showResource(urlObj, options);

	if (urlObj.extra.relation.maximumHost || options.output===constants.ABSOLUTE || options.output===constants.ROOT_RELATIVE)
	{
		str = absolutePath;
	}
	else if (relativePath.length<=absolutePath.length && options.output===constants.SHORTEST || options.output===constants.PATH_RELATIVE)
	{
		str = relativePath;

		if (str === "")
		{
			var query = showQuery(urlObj,options) && !!getQuery(urlObj,options);

			if (urlObj.extra.relation.maximumPath && !resource)
			{
				str = "./";
			}
			else if (urlObj.extra.relation.overridesQuery && !resource && !query)
			{
				str = "./";
			}
		}
	}
	else
	{
		str = absolutePath;
	}

	if ( str==="/" && !resource && options.removeRootTrailingSlash && (!urlObj.extra.relation.minimumPort || options.output===constants.ABSOLUTE) )
	{
		str = "";
	}

	return str;
}

function formatPort(urlObj, options)
{
	if (urlObj.port && !urlObj.extra.portIsDefault && urlObj.extra.relation.maximumHost)
	{
		return ":" + urlObj.port;
	}

	return "";
}

function formatQuery(urlObj, options)
{
	return showQuery(urlObj,options) ? getQuery(urlObj, options) : "";
}

function formatResource(urlObj, options)
{
	return showResource(urlObj,options) ? urlObj.resource : "";
}

function formatScheme(urlObj, options)
{
	var str = "";

	if (urlObj.extra.relation.maximumHost || options.output===constants.ABSOLUTE)
	{
		if (!urlObj.extra.relation.minimumScheme || !options.schemeRelative || options.output===constants.ABSOLUTE)
		{
			str += urlObj.scheme + "://";
		}
		else
		{
			str += "//";
		}
	}

	return str;
}

function formatUrl(urlObj, options)
{
	var url = "";

	url += formatScheme(urlObj, options);
	url += formatAuth(urlObj, options);
	url += formatHost(urlObj, options);
	url += formatPort(urlObj, options);
	url += formatPath(urlObj, options);
	url += formatResource(urlObj, options);
	url += formatQuery(urlObj, options);
	url += formatHash(urlObj, options);

	return url;
}

function getQuery(urlObj, options)
{
	var stripQuery = options.removeEmptyQueries && urlObj.extra.relation.minimumPort;

	return urlObj.query.string[ stripQuery ? "stripped" : "full" ];
}

function showQuery(urlObj, options)
{
	return !urlObj.extra.relation.minimumQuery || options.output===constants.ABSOLUTE || options.output===constants.ROOT_RELATIVE;
}

function showResource(urlObj, options)
{
	var removeIndex = options.removeDirectoryIndexes && urlObj.extra.resourceIsIndex;
	var removeMatchingResource = urlObj.extra.relation.minimumResource && options.output!==constants.ABSOLUTE && options.output!==constants.ROOT_RELATIVE;

	return !!urlObj.resource && !removeMatchingResource && !removeIndex;
}

module.exports = formatUrl;


code:
"use strict";

var constants  = require("./constants");
var formatUrl  = require("./format");
var getOptions = require("./options");
var objUtils   = require("./util/object");
var parseUrl   = require("./parse");
var relateUrl  = require("./relate");

function RelateUrl(from, options)
{
	this.options = getOptions(options,
	{
		defaultPorts: {ftp:21, http:80, https:443},
		directoryIndexes: ["index.html"],
		ignore_www: false,
		output: RelateUrl.SHORTEST,
		rejectedSchemes: ["data","javascript","mailto"],
		removeAuth: false,
		removeDirectoryIndexes: true,
		removeEmptyQueries: false,
		removeRootTrailingSlash: true,
		schemeRelative: true,
		site: undefined,
		slashesDenoteHost: true
	});

	this.from = parseUrl.from(from, this.options, null);
}

/*
	Usage: instance=new RelateUrl(); instance.relate();
*/
RelateUrl.prototype.relate = function(from, to, options)
{
	// relate(to,options)
	if ( objUtils.isPlainObject(to) )
	{
		options = to;
		to = from;
		from = null;
	}
	// relate(to)
	else if (!to)
	{
		to = from;
		from = null;
	}

	options = getOptions(options, this.options);
	from = from || options.site;
	from = parseUrl.from(from, options, this.from);

	if (!from || !from.href)
	{
		throw new Error("from value not defined.");
	}
	else if (from.extra.hrefInfo.minimumPathOnly)
	{
		throw new Error("from value supplied is not absolute: "+from.href);
	}

	to = parseUrl.to(to, options);

	if (to.valid===false) return to.href;

	to = relateUrl(from, to, options);
	to = formatUrl(to, options);

	return to;
}

/*
	Usage: RelateUrl.relate();
*/
RelateUrl.relate = function(from, to, options)
{
	return new RelateUrl().relate(from, to, options);
}

// Make constants accessible from API
objUtils.shallowMerge(RelateUrl, constants);

module.exports = RelateUrl;


code:
"use strict";

var objUtils = require("./util/object");

function getOptions(options, defaults)
{
	if ( objUtils.isPlainObject(options) )
	{
		var newOptions = {};

		for (var i in defaults)
		{
			if ( defaults.hasOwnProperty(i) )
			{
				if (options[i] !== undefined)
				{
					newOptions[i] = mergeOption(options[i], defaults[i]);
				}
				else
				{
					newOptions[i] = defaults[i];
				}
			}
		}

		return newOptions;
	}
	else
	{
		return defaults;
	}
}

function mergeOption(newValues, defaultValues)
{
	if (defaultValues instanceof Object && newValues instanceof Object)
	{
		if (defaultValues instanceof Array && newValues instanceof Array)
		{
			return defaultValues.concat(newValues);
		}
		else
		{
			return objUtils.shallowMerge(newValues, defaultValues);
		}
	}

	return newValues;
}

module.exports = getOptions;


code:
"use strict";

function parseHost(urlObj, options)
{
	// TWEAK :: condition only for speed optimization
	if (options.ignore_www)
	{
		var host = urlObj.host.full;

		if (host)
		{
			var stripped = host;

			if (host.indexOf("www.") === 0)
			{
				stripped = host.substr(4);
			}

			urlObj.host.stripped = stripped;
		}
	}
}

module.exports = parseHost;


code:
"use strict";

function hrefInfo(urlObj)
{
	var minimumPathOnly     = (!urlObj.scheme && !urlObj.auth && !urlObj.host.full && !urlObj.port);
	var minimumResourceOnly = (minimumPathOnly && !urlObj.path.absolute.string);
	var minimumQueryOnly    = (minimumResourceOnly && !urlObj.resource);
	var minimumHashOnly     = (minimumQueryOnly && !urlObj.query.string.full.length);
	var empty               = (minimumHashOnly && !urlObj.hash);

	urlObj.extra.hrefInfo.minimumPathOnly     = minimumPathOnly;
	urlObj.extra.hrefInfo.minimumResourceOnly = minimumResourceOnly;
	urlObj.extra.hrefInfo.minimumQueryOnly    = minimumQueryOnly;
	urlObj.extra.hrefInfo.minimumHashOnly     = minimumHashOnly;
	urlObj.extra.hrefInfo.empty = empty;
}

module.exports = hrefInfo;


code:
"use strict";

var hrefInfo   = require("./hrefInfo");
var parseHost  = require("./host");
var parsePath  = require("./path");
var parsePort  = require("./port");
var parseQuery = require("./query");
var parseUrlString = require("./urlstring");
var pathUtils      = require("../util/path");

function parseFromUrl(url, options, fallback)
{
	if (url)
	{
		var urlObj = parseUrl(url, options);

		// Because the following occurs in the relate stage for "to" URLs,
		// such had to be mostly duplicated here

		var pathArray = pathUtils.resolveDotSegments(urlObj.path.absolute.array);

		urlObj.path.absolute.array  = pathArray;
		urlObj.path.absolute.string = "/" + pathUtils.join(pathArray);

		return urlObj;
	}
	else
	{
		return fallback;
	}
}

function parseUrl(url, options)
{
	var urlObj = parseUrlString(url, options);

	if (urlObj.valid===false) return urlObj;

	parseHost(urlObj, options);
	parsePort(urlObj, options);
	parsePath(urlObj, options);
	parseQuery(urlObj, options);
	hrefInfo(urlObj);

	return urlObj;
}

module.exports =
{
	from: parseFromUrl,
	to:   parseUrl
};


code:
"use strict";

function isDirectoryIndex(resource, options)
{
	var verdict = false;

	options.directoryIndexes.every( function(index)
	{
		if (index === resource)
		{
			verdict = true;
			return false;
		}

		return true;
	});

	return verdict;
}

function parsePath(urlObj, options)
{
	var path = urlObj.path.absolute.string;

	if (path)
	{
		var lastSlash = path.lastIndexOf("/");

		if (lastSlash > -1)
		{
			if (++lastSlash < path.length)
			{
				var resource = path.substr(lastSlash);

				if (resource!=="." && resource!=="..")
				{
					urlObj.resource = resource;
					path = path.substr(0, lastSlash);
				}
				else
				{
					path += "/";
				}
			}

			urlObj.path.absolute.string = path;
			urlObj.path.absolute.array = splitPath(path);
		}
		else if (path==="." || path==="..")
		{
			// "..?var", "..#anchor", etc ... not "..index.html"
			path += "/";

			urlObj.path.absolute.string = path;
			urlObj.path.absolute.array = splitPath(path);
		}
		else
		{
			// Resource-only
			urlObj.resource = path;
			urlObj.path.absolute.string = null;
		}

		urlObj.extra.resourceIsIndex = isDirectoryIndex(urlObj.resource, options);
	}
	// Else: query/hash-only or empty
}

function splitPath(path)
{
	// TWEAK :: condition only for speed optimization
	if (path !== "/")
	{
		var cleaned = [];

		path.split("/").forEach( function(dir)
		{
			// Cleanup -- splitting "/dir/" becomes ["","dir",""]
			if (dir !== "")
			{
				cleaned.push(dir);
			}
		});

		return cleaned;
	}
	else
	{
		// Faster to skip the above block and just create an array
		return [];
	}
}

module.exports = parsePath;


code:
"use strict";

function parsePort(urlObj, options)
{
	var defaultPort = -1;

	for (var i in options.defaultPorts)
	{
		if ( i===urlObj.scheme && options.defaultPorts.hasOwnProperty(i) )
		{
			defaultPort = options.defaultPorts[i];
			break;
		}
	}

	if (defaultPort > -1)
	{
		// Force same type as urlObj.port
		defaultPort = defaultPort.toString();

		if (urlObj.port === null)
		{
			urlObj.port = defaultPort;
		}

		urlObj.extra.portIsDefault = (urlObj.port === defaultPort);
	}
}

module.exports = parsePort;


code:
"use strict";
var hasOwnProperty = Object.prototype.hasOwnProperty;

function parseQuery(urlObj, options)
{
	urlObj.query.string.full = stringify(urlObj.query.object, false);

	// TWEAK :: condition only for speed optimization
	if (options.removeEmptyQueries)
	{
		urlObj.query.string.stripped = stringify(urlObj.query.object, true);
	}
}

function stringify(queryObj, removeEmptyQueries)
{
	var count = 0;
	var str = "";

	for (var i in queryObj)
	{
		if ( i!=="" && hasOwnProperty.call(queryObj, i)===true )
		{
			var value = queryObj[i];

			if (value !== "" || !removeEmptyQueries)
			{
				str += (++count===1) ? "?" : "&";

				i = encodeURIComponent(i);

				if (value !== "")
				{
					str += i +"="+ encodeURIComponent(value).replace(/%20/g,"+");
				}
				else
				{
					str += i;
				}
			}
		}
	}

	return str;
}

module.exports = parseQuery;


code:
"use strict";

var _parseUrl = require("url").parse;

/*
	Customize the URL object that Node generates
	because:

	* necessary data for later
	* urlObj.host is useless
	* urlObj.hostname is too long
	* urlObj.path is useless
	* urlObj.pathname is too long
	* urlObj.protocol is inaccurate; should be called "scheme"
	* urlObj.search is mostly useless
*/
function clean(urlObj)
{
	var scheme = urlObj.protocol;

	if (scheme)
	{
		// Remove ":" suffix
		if (scheme.indexOf(":") === scheme.length-1)
		{
			scheme = scheme.substr(0, scheme.length-1);
		}
	}

	urlObj.host =
	{
		// TODO :: unescape(encodeURIComponent(s)) ? ... http://ecmanaut.blogspot.ca/2006/07/encoding-decoding-utf8-in-javascript.html
		full: urlObj.hostname,
		stripped: null
	};

	urlObj.path =
	{
		absolute:
		{
			array: null,
			string: urlObj.pathname
		},
		relative:
		{
			array: null,
			string: null
		}
	};

	urlObj.query =
	{
		object: urlObj.query,
		string:
		{
			full: null,
			stripped: null
		}
	};

	urlObj.extra =
	{
		hrefInfo:
		{
			minimumPathOnly: null,
			minimumResourceOnly: null,
			minimumQueryOnly: null,
			minimumHashOnly: null,
			empty: null,

			separatorOnlyQuery: urlObj.search==="?"
		},
		portIsDefault: null,
		relation:
		{
			maximumScheme: null,
			maximumAuth: null,
			maximumHost: null,
			maximumPort: null,
			maximumPath: null,
			maximumResource: null,
			maximumQuery: null,
			maximumHash: null,

			minimumScheme: null,
			minimumAuth: null,
			minimumHost: null,
			minimumPort: null,
			minimumPath: null,
			minimumResource: null,
			minimumQuery: null,
			minimumHash: null,

			overridesQuery: null
		},
		resourceIsIndex: null,
		slashes: urlObj.slashes
	};

	urlObj.resource = null;
	urlObj.scheme = scheme;
	delete urlObj.hostname;
	delete urlObj.pathname;
	delete urlObj.protocol;
	delete urlObj.search;
	delete urlObj.slashes;

	return urlObj;
}

function validScheme(url, options)
{
	var valid = true;

	options.rejectedSchemes.every( function(rejectedScheme)
	{
		valid = !(url.indexOf(rejectedScheme+":") === 0);

		// Break loop
		return valid;
	});

	return valid;
}

function parseUrlString(url, options)
{
	if ( validScheme(url,options) )
	{
		return clean( _parseUrl(url, true, options.slashesDenoteHost) );
	}
	else
	{
		return {href:url, valid:false};
	}
}

module.exports = parseUrlString;


code:
"use strict";

var findRelation = require("./findRelation");
var objUtils     = require("../util/object");
var pathUtils    = require("../util/path");

function absolutize(urlObj, siteUrlObj, options)
{
	findRelation.upToPath(urlObj, siteUrlObj, options);

	// Fill in relative URLs
	if (urlObj.extra.relation.minimumScheme) urlObj.scheme = siteUrlObj.scheme;
	if (urlObj.extra.relation.minimumAuth)   urlObj.auth   = siteUrlObj.auth;
	if (urlObj.extra.relation.minimumHost)   urlObj.host   = objUtils.clone(siteUrlObj.host);
	if (urlObj.extra.relation.minimumPort)   copyPort(urlObj, siteUrlObj);
	if (urlObj.extra.relation.minimumScheme) copyPath(urlObj, siteUrlObj);

	// Check remaining relativeness now that path has been copied and/or resolved
	findRelation.pathOn(urlObj, siteUrlObj, options);

	// Fill in relative URLs
	if (urlObj.extra.relation.minimumResource) copyResource(urlObj, siteUrlObj);
	if (urlObj.extra.relation.minimumQuery)    urlObj.query = objUtils.clone(siteUrlObj.query);
	if (urlObj.extra.relation.minimumHash)     urlObj.hash  = siteUrlObj.hash;
}

/*
	Get an absolute path that's relative to site url.
*/
function copyPath(urlObj, siteUrlObj)
{
	if (urlObj.extra.relation.maximumHost || !urlObj.extra.hrefInfo.minimumResourceOnly)
	{
		var pathArray = urlObj.path.absolute.array;
		var pathString = "/";

		// If not erroneous URL
		if (pathArray)
		{
			// If is relative path
			if (urlObj.extra.hrefInfo.minimumPathOnly && urlObj.path.absolute.string.indexOf("/")!==0)
			{
				// Append path to site path
				pathArray = siteUrlObj.path.absolute.array.concat(pathArray);
			}

			pathArray   = pathUtils.resolveDotSegments(pathArray);
			pathString += pathUtils.join(pathArray);
		}
		else
		{
			pathArray = [];
		}

		urlObj.path.absolute.array  = pathArray;
		urlObj.path.absolute.string = pathString;
	}
	else
	{
		// Resource-, query- or hash-only or empty
		urlObj.path = objUtils.clone(siteUrlObj.path);
	}
}

function copyPort(urlObj, siteUrlObj)
{
	urlObj.port = siteUrlObj.port;

	urlObj.extra.portIsDefault = siteUrlObj.extra.portIsDefault;
}

function copyResource(urlObj, siteUrlObj)
{
	urlObj.resource = siteUrlObj.resource;

	urlObj.extra.resourceIsIndex = siteUrlObj.extra.resourceIsIndex;
}

module.exports = absolutize;


code:
"use strict";

function findRelation_upToPath(urlObj, siteUrlObj, options)
{
	// Path- or root-relative URL
	var pathOnly = urlObj.extra.hrefInfo.minimumPathOnly;

	// Matching scheme, scheme-relative or path-only
	var minimumScheme = (urlObj.scheme===siteUrlObj.scheme || !urlObj.scheme);

	// Matching auth, ignoring auth or path-only
	var minimumAuth = minimumScheme && (urlObj.auth===siteUrlObj.auth || options.removeAuth || pathOnly);

	// Matching host or path-only
	var www = options.ignore_www ? "stripped" : "full";
	var minimumHost = minimumAuth && (urlObj.host[www]===siteUrlObj.host[www] || pathOnly);

	// Matching port or path-only
	var minimumPort = minimumHost && (urlObj.port===siteUrlObj.port || pathOnly);

	urlObj.extra.relation.minimumScheme = minimumScheme;
	urlObj.extra.relation.minimumAuth   = minimumAuth;
	urlObj.extra.relation.minimumHost   = minimumHost;
	urlObj.extra.relation.minimumPort   = minimumPort;

	urlObj.extra.relation.maximumScheme = !minimumScheme || minimumScheme && !minimumAuth;
	urlObj.extra.relation.maximumAuth   = !minimumScheme || minimumScheme && !minimumHost;
	urlObj.extra.relation.maximumHost   = !minimumScheme || minimumScheme && !minimumPort;
}

function findRelation_pathOn(urlObj, siteUrlObj, options)
{
	var queryOnly = urlObj.extra.hrefInfo.minimumQueryOnly;
	var hashOnly  = urlObj.extra.hrefInfo.minimumHashOnly;
	var empty     = urlObj.extra.hrefInfo.empty;	// not required, but self-documenting

	// From upToPath()
	var minimumPort   = urlObj.extra.relation.minimumPort;
	var minimumScheme = urlObj.extra.relation.minimumScheme;

	// Matching port and path
	var minimumPath = minimumPort && urlObj.path.absolute.string===siteUrlObj.path.absolute.string;

	// Matching resource or query/hash-only or empty
	var matchingResource = (urlObj.resource===siteUrlObj.resource || !urlObj.resource && siteUrlObj.extra.resourceIsIndex) || (options.removeDirectoryIndexes && urlObj.extra.resourceIsIndex && !siteUrlObj.resource);
	var minimumResource = minimumPath && (matchingResource || queryOnly || hashOnly || empty);

	// Matching query or hash-only/empty
	var query = options.removeEmptyQueries ? "stripped" : "full";
	var urlQuery = urlObj.query.string[query];
	var siteUrlQuery = siteUrlObj.query.string[query];
	var minimumQuery = (minimumResource && !!urlQuery && urlQuery===siteUrlQuery) || ((hashOnly || empty) && !urlObj.extra.hrefInfo.separatorOnlyQuery);

	var minimumHash = minimumQuery && urlObj.hash===siteUrlObj.hash;

	urlObj.extra.relation.minimumPath     = minimumPath;
	urlObj.extra.relation.minimumResource = minimumResource;
	urlObj.extra.relation.minimumQuery    = minimumQuery;
	urlObj.extra.relation.minimumHash     = minimumHash;

	urlObj.extra.relation.maximumPort     = !minimumScheme || minimumScheme && !minimumPath;
	urlObj.extra.relation.maximumPath     = !minimumScheme || minimumScheme && !minimumResource;
	urlObj.extra.relation.maximumResource = !minimumScheme || minimumScheme && !minimumQuery;
	urlObj.extra.relation.maximumQuery    = !minimumScheme || minimumScheme && !minimumHash;
	urlObj.extra.relation.maximumHash     = !minimumScheme || minimumScheme && !minimumHash;	// there's nothing after hash, so it's the same as maximumQuery

	// Matching path and/or resource with existing but non-matching site query
	urlObj.extra.relation.overridesQuery  = minimumPath && urlObj.extra.relation.maximumResource && !minimumQuery && !!siteUrlQuery;
}

module.exports =
{
	pathOn:   findRelation_pathOn,
	upToPath: findRelation_upToPath
};


code:
"use strict";

var absolutize = require("./absolutize");
var relativize = require("./relativize");

function relateUrl(siteUrlObj, urlObj, options)
{
	absolutize(urlObj, siteUrlObj, options);
	relativize(urlObj, siteUrlObj, options);

	return urlObj;
}

module.exports = relateUrl;


code:
"use strict";

var pathUtils = require("../util/path");

/*
	Get a path relative to the site path.
*/
function relatePath(absolutePath, siteAbsolutePath)
{
	var relativePath = [];

	// At this point, it's related to the host/port
	var related = true;
	var parentIndex = -1;

	// Find parents
	siteAbsolutePath.forEach( function(siteAbsoluteDir, i)
	{
		if (related)
		{
			if (absolutePath[i] !== siteAbsoluteDir)
			{
				related = false;
			}
			else
			{
				parentIndex = i;
			}
		}

		if (!related)
		{
			// Up one level
			relativePath.push("..");
		}
	});

	// Form path
	absolutePath.forEach( function(dir, i)
	{
		if (i > parentIndex)
		{
			relativePath.push(dir);
		}
	});

	return relativePath;
}

function relativize(urlObj, siteUrlObj, options)
{
	if (urlObj.extra.relation.minimumScheme)
	{
		var pathArray = relatePath(urlObj.path.absolute.array, siteUrlObj.path.absolute.array);

		urlObj.path.relative.array  = pathArray;
		urlObj.path.relative.string = pathUtils.join(pathArray);
	}
}

module.exports = relativize;


code:
"use strict";

var inspect = require("util").inspect;

function log(data)
{
	console.log( inspect(data, {depth:null, colors:true}) );
}

function logAll(data)
{
	console.log( inspect(data, {depth:null, showHidden:true, colors:true}) );
}

module.exports =
{
	log:    log,
	logAll: logAll
};


code:
"use strict";

/*
	Deep-clone an object.
*/
function clone(obj)
{
	if (obj instanceof Object)
	{
		var clonedObj = (obj instanceof Array) ? [] : {};

		for (var i in obj)
		{
			if ( obj.hasOwnProperty(i) )
			{
				clonedObj[i] = clone( obj[i] );
			}
		}

		return clonedObj;
	}

	return obj;
}

/*
	https://github.com/jonschlinkert/is-plain-object
*/
function isPlainObject(obj)
{
	return !!obj && typeof obj==="object" && obj.constructor===Object;
}

/*
	Shallow-merge two objects.
*/
function shallowMerge(target, source)
{
	if (target instanceof Object && source instanceof Object)
	{
		for (var i in source)
		{
			if ( source.hasOwnProperty(i) )
			{
				target[i] = source[i];
			}
		}
	}

	return target;
}

module.exports =
{
	clone: clone,
	isPlainObject: isPlainObject,
	shallowMerge: shallowMerge
};


code:
"use strict";

function joinPath(pathArray)
{
	if (pathArray.length > 0)
	{
		return pathArray.join("/") + "/";
	}
	else
	{
		return "";
	}
}

function resolveDotSegments(pathArray)
{
	var pathAbsolute = [];

	pathArray.forEach( function(dir)
	{
		if (dir !== "..")
		{
			if (dir !== ".")
			{
				pathAbsolute.push(dir);
			}
		}
		else
		{
			// Remove parent
			if (pathAbsolute.length > 0)
			{
				pathAbsolute.splice(pathAbsolute.length-1, 1);
			}
		}
	});

	return pathAbsolute;
}

module.exports =
{
	join: joinPath,
	resolveDotSegments: resolveDotSegments
};


relateurl

description: Minify URLs by converting them from absolute to relative.

license: MIT

main: lib

browserify: browserify lib/ --standalone RelateUrl | uglifyjs --compress --mangle -o relateurl-browser.js

test: mocha test/ --bail --reporter spec --check-leaks

files: lib license

keywords: uri url minifier minify lint relative absolute


code:
# Renderkid Changelog

## `3.0.0`

* **Breaking change**: Dropped support for Node `<12.x`


code:
# RenderKid
[![Build Status](https://secure.travis-ci.org/AriaMinaei/RenderKid.png)](http://travis-ci.org/AriaMinaei/RenderKid)

RenderKid allows you to use HTML and CSS to style your CLI output, making it easy to create a beautiful, readable, and consistent look for your nodejs tool.

## Installation

Install with npm:
```
$ npm install renderkid
```

## Usage

```coffeescript
RenderKid = require('renderkid')

r = new RenderKid()

r.style({
  "ul": {
    display: "block"
    margin: "2 0 2"
  }

  "li": {
    display: "block"
    marginBottom: "1"
  }

  "key": {
    color: "grey"
    marginRight: "1"
  }

  "value": {
    color: "bright-white"
  }
})

output = r.render("
<ul>
  <li>
    <key>Name:</key>
    <value>RenderKid</value>
  </li>
  <li>
    <key>Version:</key>
    <value>0.2</value>
  </li>
  <li>
    <key>Last Update:</key>
    <value>Jan 2015</value>
  </li>
</ul>
")

console.log(output)
```

![screenshot of usage](https://github.com/AriaMinaei/RenderKid/raw/master/docs/images/usage.png)

## Stylesheet properties

### Display mode

Elements can have a `display` of either `inline`, `block`, or `none`:
```coffeescript
r.style({
  "div": {
    display: "block"
  }

  "span": {
    display: "inline" # default
  }

  "hidden": {
    display: "none"
  }
})

output = r.render("
<div>This will fill one or more rows.</div>
<span>These</span> <span>will</span> <span>be</span> in the same <span>line.</span>
<hidden>This won't be displayed.</hidden>
")

console.log(output)
```

![screenshot of usage](https://github.com/AriaMinaei/RenderKid/raw/master/docs/images/display.png)

### Margin

Margins work just like they do in browsers:
```coffeescript
r.style({
  "li": {
    display: "block"

    marginTop: "1"
    marginRight: "2"
    marginBottom: "3"
    marginLeft: "4"

    # or the shorthand version:
    "margin": "1 2 3 4"
  },

  "highlight": {
    display: "inline"
    marginLeft: "2"
    marginRight: "2"
  }
})

r.render("
<ul>
  <li>Item <highlgiht>1</highlight></li>
  <li>Item <highlgiht>2</highlight></li>
  <li>Item <highlgiht>3</highlight></li>
</ul>
")
```

### Padding

See margins above. Paddings work the same way, only inward.

### Width and Height

Block elements can have explicit width and height:
```coffeescript
r.style({
  "box": {
    display: "block"
    "width": "4"
    "height": "2"
  }
})

r.render("<box>This is a box and some of its text will be truncated.</box>")
```

### Colors

You can set a custom color and background color for each element:

```coffeescript
r.style({
  "error": {
    color: "black"
    background: "red"
  }
})
```

List of colors currently supported are `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `grey`, `bright-red`, `bright-green`, `bright-yellow`, `bright-blue`, `bright-magenta`, `bright-cyan`, `bright-white`.

### Bullet points

Block elements can have bullet points on their margins. Let's start with an example:
```coffeescript
r.style({
  "li": {
    # To add bullet points to an element, first you
    # should make some room for the bullet point by
    # giving your element some margin to the left:
    marginLeft: "4",

    # Now we can add a bullet point to our margin:
    bullet: '"-"'
  }
})

# The four hyphens are there for visual reference
r.render("
----
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
----
")
```
And here is the result:

![screenshot of bullet points, 1](https://github.com/AriaMinaei/RenderKid/raw/master/docs/images/bullets-1.png)


code:
# Security Policy

## Supported Versions

| Version | Supported          |
| ------- | ------------------ |
| 2.0.x   | :white_check_mark: |

## Reporting a Vulnerability

Send security alerts to aria@theatrejs.com