<commit_before>var Lang = A.Lang,
	UA = A.UA,
	LString = Lang.String,
	isArray = Lang.isArray,
	isDate = Lang.isDate,
	isString = Lang.isString,
	isObject = Lang.isObject,
	isValue = Lang.isValue,

	REGEX_TPL = /<tpl\b[^>]*?((for|if|exec)="([^"]+)")*?>((?:(?=([^<]+))\5|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
	REGEX_NEWLINE = /\r\n|\n/g,
	REGEX_QUOTE = /'/g,
	REGEX_QUOTE_ESCAPED = /\\'/g,

	REGEX_TPL_VAR = /\{([\w-.#$]+)(?:\:([\w.]*)(?:\((.*?)?\))?)?(\s?[+\-*\/]\s?[\d.+\-*\/()]+)?\}/g,
	REGEX_TPL_SCRIPTLET = /\{\[((?:\\\]|.|\n)*?)\]\}/g,

	STR_BLANK = '',
	STR_COLON = ':',
	STR_COMMA = ',',
	STR_DOT = '.',
	STR_FOR = 'for',
	STR_EXEC = 'exec',
	STR_IF = 'if',
	STR_QUOTE = '\'',
	STR_BRACE_OPEN = '{',
	STR_BRACE_CLOSE = '}',
	STR_PAREN_OPEN = '(',
	STR_PAREN_CLOSE = ')',

	STR_JOIN_OPEN = STR_QUOTE + STR_COMMA,
	STR_JOIN_CLOSE = STR_COMMA + STR_QUOTE,

	STR_JOIN_GROUP_OPEN = STR_JOIN_OPEN + STR_PAREN_OPEN,
	STR_JOIN_GROUP_CLOSE = STR_PAREN_CLOSE + STR_JOIN_CLOSE,

	STR_COMPILE_TPL_ARGS = 'values, parent, $index, $i, $count, $last',

	BUFFER_HTML = ['<tpl>', null, '</tpl>'],

	BUFFER_STR_COMPILE_SUB_TPL = [
		STR_JOIN_OPEN + 'this._compileSubTpl(',
		null,
		STR_COMMA + STR_COMPILE_TPL_ARGS + STR_PAREN_CLOSE + STR_JOIN_CLOSE
	],

	BUFFER_COMPILED_TPL_FN = [
		'compiledTplFn = function(' + STR_COMPILE_TPL_ARGS + ') { return [' + STR_QUOTE,
		null,
		STR_QUOTE + '].join("");};'
	],

	BUFFER_VALUES_LOOKUP = [
		'values["',
		null,
		'"]'
	],

	STR_INVOKE_METHOD_NAME_OPEN = 'this._invokeMethod("',
	STR_INVOKE_METHOD_NAME_CLOSE = '"' + STR_COMMA,

	STR_UNDEFINED_OUT = ' === undefined ? "" : ',

	STR_REPLACE_NEWLINE = '\\n',
	STR_REPLACE_QUOTE =  "\\'",

	STR_VALUES = 'values',
	STR_PARENT = 'parent',
	STR_SPECIAL_I = '$i',
	STR_SPECIAL_INDEX = '$index',
	STR_SPECIAL_COUNT = '$count',
	STR_SPECIAL_LAST = '$last',
	STR_RETURN = 'return ',
	STR_WITHVALUES = 'with(values){ ',
	STR_WITHCLOSE = '; }',

	STR_LANGSTRING_VAR = 'LString.',

	TOKEN_TPL = 'AUITPL',
	TOKEN_TPL_LENGTH = TOKEN_TPL.length,
	TOKEN_PARENT_PROP = STR_PARENT + STR_DOT,
	TOKEN_THIS_PROP = 'this.',
	TOKEN_THIS_PROP_LENGTH = TOKEN_THIS_PROP.length,
	TOKEN_VALUES_PROP = STR_VALUES + STR_DOT,

	MAP_TPL_FN = {
		'.': new Function(
			STR_VALUES,
			STR_PARENT,
			STR_WITHVALUES + STR_RETURN + STR_VALUES + STR_WITHCLOSE
		),
		'..': new Function(
			STR_VALUES,
			STR_PARENT,
			STR_WITHVALUES + STR_RETURN + STR_PARENT + STR_WITHCLOSE
		)
	},

	MAP_TPL_FILTERED_TYPES = {
		'boolean': true,
		'number': true,
		'string': true
	},

	MAP_TPL_VALUES = {
		'.': 'this._getValidValues(values)',
		'#': STR_SPECIAL_INDEX,
		'$index': STR_SPECIAL_INDEX,
		'$i': STR_SPECIAL_I,
		'$count': STR_SPECIAL_COUNT,
		'$last': STR_SPECIAL_LAST
	},

	SRC_CREATE = {};

	var Template = function(html, src) {
		var instance = this;

		var tpl;
		var fromStaticCall = (src === SRC_CREATE);

		if (fromStaticCall || A.instanceOf(instance, Template)) {
			if (!fromStaticCall) {
				html = instance._parseArgs(arguments);
			}

			tpl = instance._parse(html);
		}
		else {
			html = Template.prototype._parseArgs(arguments);

			tpl = new Template(html, SRC_CREATE);
		}

		return tpl;
	};

	Template.prototype = {
		html: function(tpl) {
			var instance = this;

			var retVal = instance;

			if (isValue(tpl)) {
				instance._parse(instance._parseArgs(arguments));
			}
			else {
				retVal = instance._html;
			}

			return retVal;
		},

		parse: function(values) {
			var instance = this;

			return instance._parentTpl.compiled.call(instance, values, {}, 1, 1, 1);
		},

		render: function(values, node) {
			var instance = this;

			var rendered = A.Node.create(instance.parse(values));

			node = node && A.one(node);

			if (node) {
				node.setContent(rendered);
			}

			return rendered;
		},

		_compile: function(tpl) {
			var instance = this;

			var compiledTplFn;

			var fnBody = tpl.tplBody;

			fnBody = fnBody.replace(REGEX_NEWLINE, STR_REPLACE_NEWLINE);
			fnBody = fnBody.replace(REGEX_QUOTE, STR_REPLACE_QUOTE);
			fnBody = fnBody.replace(REGEX_TPL_VAR, instance._compileTpl);
			fnBody = fnBody.replace(REGEX_TPL_SCRIPTLET, instance._compileCode);

			BUFFER_COMPILED_TPL_FN[1] = fnBody;

			var body = BUFFER_COMPILED_TPL_FN.join(STR_BLANK);

			eval(body);

			tpl.compiled = function(values, parent, $index, $i, $count, $last) {
				var buffer = [];

				var subTpl = STR_BLANK;

				var testFn = tpl.testFn;

				if (!testFn || testFn.call(instance, values, parent, $index, $i, $count, $last)) {
					var subValues = values;

					var tplFn = tpl.tplFn;

					if (tplFn) {
						subValues = tplFn.call(instance, values, parent);
						parent = values;
					}

					if (tplFn && isArray(subValues)) {
						var length = subValues.length;

						var execFn = tpl.execFn;

						for (var i = 0; i < length; i++) {
							var index = i + 1;
							var last = (index == length);
							var subValue = subValues[i];

							buffer[buffer.length] = compiledTplFn.call(instance, subValue, parent, index, i, length, last);

							if (execFn) {
								execFn.call(instance, subValues[i]);
							}
						}

						subTpl = buffer.join(STR_BLANK);
					}
					else {
						subTpl = compiledTplFn.call(instance, subValues, parent, $index, $i, $count, $last);
					}
				}

				return subTpl;
			};

			return instance;
		},

		_compileCode: function(match, code) {
			return STR_JOIN_GROUP_OPEN + code.replace(REGEX_QUOTE_ESCAPED, STR_QUOTE) + STR_JOIN_GROUP_CLOSE;
		},

		_compileSubTpl: function(id, values, parent, $index, $i, $count, $last) {
			var instance = this;

			var length;
			var tpl = instance.tpls[id];

			return tpl.compiled.call(instance, values, parent, $index, $i, $count, $last);
		},

		_compileTpl: function(match, name, methodName, args, math, offset, str) {
			var compiled;

			if (name.indexOf(TOKEN_TPL) === 0) {
				BUFFER_STR_COMPILE_SUB_TPL[1] = name.substr(TOKEN_TPL_LENGTH);

				compiled = BUFFER_STR_COMPILE_SUB_TPL.join(STR_BLANK);
			}
			else {
				var value = MAP_TPL_VALUES[name];

				if (!value) {
					if (name.indexOf(TOKEN_PARENT_PROP) === 0) {
						value = name;
					}
					else if (name.indexOf(STR_DOT) > -1) {
						value = TOKEN_VALUES_PROP + name;
					}
					else {
						BUFFER_VALUES_LOOKUP[1] = name;

						value = BUFFER_VALUES_LOOKUP.join(STR_BLANK);
					}
				}

				if (math) {
					value = STR_PAREN_OPEN + value + math + STR_PAREN_CLOSE;
				}

				if (methodName) {
					args = args ? STR_COMMA + args : STR_BLANK;

					if (methodName.indexOf(TOKEN_THIS_PROP) !== 0) {
						methodName = STR_LANGSTRING_VAR + methodName + STR_PAREN_OPEN;
					}
					else {
						methodName = STR_INVOKE_METHOD_NAME_OPEN + methodName.substr(TOKEN_THIS_PROP_LENGTH) + STR_INVOKE_METHOD_NAME_CLOSE;

						args = STR_COMMA + STR_VALUES;
					}
				}
				else {
					args = STR_BLANK;

					methodName = STR_PAREN_OPEN + value + STR_UNDEFINED_OUT;
				}

				compiled = STR_JOIN_OPEN + methodName + value + args + STR_JOIN_GROUP_CLOSE;
			}

			return compiled;
		},

		_getValidValues: function(values) {
			var instance = this;

			var val = STR_BLANK;

			if (MAP_TPL_FILTERED_TYPES[typeof values] || isDate(values)) {
				val = values;
			}

			return val;
		},

		_invokeMethod: function(methodName, value, allValues) {
			var instance = this;

			return instance[methodName](value, allValues);
		},

		_parse: function(html) {
			var instance = this;

			instance._html = html;

			var match;
			var id = 0;
			var tpls = [];

			BUFFER_HTML[1] = html;

			html = BUFFER_HTML.join(STR_BLANK);

			while (match = html.match(REGEX_TPL)) {
				var testFn = null;
				var execFn = null;
				var tplFn = null;
				var expression = match[2];
				var expressionValue = match[3];

				if (expressionValue) {
					if (expression == STR_FOR) {
						tplFn = MAP_TPL_FN[expressionValue] || new Function(
							STR_VALUES,
							STR_PARENT,
							STR_WITHVALUES + STR_RETURN + expressionValue + STR_WITHCLOSE
						);
					}
					else {
						expressionValue = LString.unescapeEntities(expressionValue);

						if (expression == STR_IF) {
							testFn = new Function(
								STR_VALUES,
								STR_PARENT,
								STR_SPECIAL_INDEX,
								STR_SPECIAL_I,
								STR_SPECIAL_COUNT,
								STR_SPECIAL_LAST,
								STR_WITHVALUES + STR_RETURN + expressionValue + STR_WITHCLOSE
							);
						}
						else if (expression == STR_EXEC) {
							execFn = new Function(
								STR_VALUES,
								STR_PARENT,
								STR_SPECIAL_INDEX,
								STR_SPECIAL_I,
								STR_SPECIAL_COUNT,
								STR_SPECIAL_LAST,
								STR_WITHVALUES + expressionValue + STR_WITHCLOSE
							);
						}
					}
				}

				var tplBody = match[4] || STR_BLANK;

				html = html.replace(match[0], STR_BRACE_OPEN + TOKEN_TPL + id + STR_BRACE_CLOSE);

				id = tpls.push(
					{
						execFn: execFn,
						id: id,
						testFn: testFn,
						tplBody: tplBody,
						tplFn: tplFn
					}
				);
			}

			var lastIndex = id - 1;

			while (id--) {
				instance._compile(tpls[id]);
			}

			instance._parentTpl = tpls[lastIndex];

			instance.tpls = tpls;

			return instance;
		},

		_parseArgs: function(args) {
			var instance = this;

			var config;

			var tpl = args[0];

			if (isArray(tpl)) {
				tpl = tpl.join(STR_BLANK);

				if (isObject(args[1])) {
					config = args[1];
				}
			}
			else if (args.length > 1) {
				var buffer = [];

				args = A.Array(args, 0, true);

				var length = args.length;
				var item;

				var lastItem = args[length - 1];

				if (isObject(lastItem)) {
					config = args.pop();
				}

				for (var i = 0; i < length; i++) {
					item = args[i];

					buffer.push(item);
				}

				tpl = buffer.join(STR_BLANK);
			}

			if (config) {
				A.mix(instance, config, true);
			}

			return tpl;
		}
	};

	var NODE_PROTO = A.Node.prototype;

	NODE_PROTO.toTemplate = function() {
		return Template.from(this);
	};

	NODE_PROTO.renderTemplate = function(tpl, data) {
		var instance = this;

		if (isString(tpl) || isArray(tpl)) {
			tpl = new Template(tpl);
		}

		if (tpl && data) {
			tpl.render(data, instance);
		}

		return instance;
	};

	A.NodeList.importMethod(
		NODE_PROTO,
		[
			'renderTemplate'
		]
	);

	Template.from = function(node) {
		node = A.one(node);

		var content = STR_BLANK;

		if (node) {
			node = node.getDOM();

			var nodeName = String(node && node.nodeName).toLowerCase();

			if (nodeName != 'script') {
				content = node.value || node.innerHTML;
			}
			else {
				content = node.text || node.textContent || node.innerHTML;
			}
		}

		return new Template(content);
	};

	A.Template = Template;<commit_msg>AUI-484 - Adding Globals support and default NS support<commit_after>var Lang = A.Lang,
	UA = A.UA,
	LString = Lang.String,
	isArray = Lang.isArray,
	isDate = Lang.isDate,
	isString = Lang.isString,
	isObject = Lang.isObject,
	isValue = Lang.isValue,
	isUndefined = Lang.isUndefined,

	REGEX_TPL = /<tpl\b[^>]*?((for|if|exec)="([^"]+)")*?>((?:(?=([^<]+))\5|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
	REGEX_NEWLINE = /\r\n|\n/g,
	REGEX_QUOTE = /'/g,
	REGEX_QUOTE_ESCAPED = /\\'/g,
	REGEX_PREFIX_GLOBAL_REPLACE = /^(?!\$)/,

	REGEX_TPL_VAR = /\{([\w-.#$]+)(?:\:([\w.]*)(?:\((.*?)?\))?)?(\s?[+\-*\/]\s?[\d.+\-*\/()]+)?\}/g,
	REGEX_TPL_SCRIPTLET = /\{\[((?:\\\]|.|\n)*?)\]\}/g,

	STR_BLANK = '',
	STR_COLON = ':',
	STR_COMMA = ',',
	STR_DOT = '.',
	STR_FOR = 'for',
	STR_EXEC = 'exec',
	STR_IF = 'if',
	STR_QUOTE = '\'',
	STR_BRACE_OPEN = '{',
	STR_BRACE_CLOSE = '}',
	STR_PAREN_OPEN = '(',
	STR_PAREN_CLOSE = ')',
	STR_GLOBAL_SYMBOL = '$',

	STR_JOIN_OPEN = STR_QUOTE + STR_COMMA,
	STR_JOIN_CLOSE = STR_COMMA + STR_QUOTE,

	STR_JOIN_GROUP_OPEN = STR_JOIN_OPEN + STR_PAREN_OPEN,
	STR_JOIN_GROUP_CLOSE = STR_PAREN_CLOSE + STR_JOIN_CLOSE,

	STR_COMPILE_TPL_ARGS = 'values, parent, $index, $i, $count, $last, $ns, $ans, $yns',

	BUFFER_HTML = ['<tpl>', null, '</tpl>'],

	BUFFER_STR_COMPILE_SUB_TPL = [
		STR_JOIN_OPEN + 'this._compileSubTpl(',
		null,
		STR_COMMA + STR_COMPILE_TPL_ARGS + STR_PAREN_CLOSE + STR_JOIN_CLOSE
	],

	BUFFER_COMPILED_TPL_FN = [
		'compiledTplFn = function(' + STR_COMPILE_TPL_ARGS + ') { return [' + STR_QUOTE,
		null,
		STR_QUOTE + '].join("");};'
	],

	BUFFER_GLOBAL_PROP = ['MAP_GLOBALS["', null, '"]'],

	BUFFER_VALUES_LOOKUP = [
		'values["',
		null,
		'"]'
	],

	STR_INVOKE_METHOD_NAME_OPEN = 'this._invokeMethod("',
	STR_INVOKE_METHOD_NAME_CLOSE = '"' + STR_COMMA,

	STR_UNDEFINED_OUT = ' === undefined ? "" : ',

	STR_REPLACE_NEWLINE = '\\n',
	STR_REPLACE_QUOTE =  "\\'",

	STR_VALUES = 'values',
	STR_PARENT = 'parent',
	STR_SPECIAL_I = '$i',
	STR_SPECIAL_INDEX = '$index',
	STR_SPECIAL_COUNT = '$count',
	STR_SPECIAL_LAST = '$last',
	STR_SPECIAL_ANS = '$ans',
	STR_SPECIAL_NS = '$ns',
	STR_SPECIAL_YNS = '$yns',
	STR_RETURN = 'return ',
	STR_WITHVALUES = 'with(values){ ',
	STR_WITHCLOSE = '; }',

	STR_LANGSTRING_VAR = 'LString.',

	TOKEN_TPL = 'AUITPL',
	TOKEN_TPL_LENGTH = TOKEN_TPL.length,
	TOKEN_PARENT_PROP = STR_PARENT + STR_DOT,
	TOKEN_THIS_PROP = 'this.',
	TOKEN_THIS_PROP_LENGTH = TOKEN_THIS_PROP.length,
	TOKEN_VALUES_PROP = STR_VALUES + STR_DOT,

	MAP_TPL_FN = {
		'.': new Function(
			STR_VALUES,
			STR_PARENT,
			STR_WITHVALUES + STR_RETURN + STR_VALUES + STR_WITHCLOSE
		),
		'..': new Function(
			STR_VALUES,
			STR_PARENT,
			STR_WITHVALUES + STR_RETURN + STR_PARENT + STR_WITHCLOSE
		)
	},

	MAP_TPL_FILTERED_TYPES = {
		'boolean': true,
		'number': true,
		'string': true
	},

	MAP_TPL_VALUES = {
		'.': 'this._getValidValues(values)',
		'#': STR_SPECIAL_INDEX,
		'$index': STR_SPECIAL_INDEX,
		'$i': STR_SPECIAL_I,
		'$count': STR_SPECIAL_COUNT,
		'$last': STR_SPECIAL_LAST,
		'$ans': STR_SPECIAL_ANS,
		'$ns': STR_SPECIAL_NS,
		'$yns': STR_SPECIAL_YNS
	},

	MAP_GLOBALS = {},

	SRC_CREATE = {},

	AUI_NS = A.getClassName(STR_BLANK),
	YUI_NS = A.ClassNameManager.getClassName(STR_BLANK);

	var Template = function(html, src) {
		var instance = this;

		var tpl;
		var fromStaticCall = (src === SRC_CREATE);

		if (fromStaticCall || A.instanceOf(instance, Template)) {
			if (!fromStaticCall) {
				html = instance._parseArgs(arguments);
			}

			tpl = instance._parse(html);
		}
		else {
			html = Template.prototype._parseArgs(arguments);

			tpl = new Template(html, SRC_CREATE);
		}

		return tpl;
	};

	Template.prototype = {
		html: function(tpl) {
			var instance = this;

			var retVal = instance;

			if (isValue(tpl)) {
				instance._parse(instance._parseArgs(arguments));
			}
			else {
				retVal = instance._html;
			}

			return retVal;
		},

		parse: function(values) {
			var instance = this;

			return instance._parentTpl.compiled.call(instance, values, {}, 1, 1, 1);
		},

		render: function(values, node) {
			var instance = this;

			var rendered = A.Node.create(instance.parse(values));

			node = node && A.one(node);

			if (node) {
				node.setContent(rendered);
			}

			return rendered;
		},

		_compile: function(tpl) {
			var instance = this;

			var compiledTplFn;

			var fnBody = tpl.tplBody;

			fnBody = fnBody.replace(REGEX_NEWLINE, STR_REPLACE_NEWLINE);
			fnBody = fnBody.replace(REGEX_QUOTE, STR_REPLACE_QUOTE);
			fnBody = fnBody.replace(REGEX_TPL_VAR, instance._compileTpl);
			fnBody = fnBody.replace(REGEX_TPL_SCRIPTLET, instance._compileCode);

			BUFFER_COMPILED_TPL_FN[1] = fnBody;

			var body = BUFFER_COMPILED_TPL_FN.join(STR_BLANK);

			var $yns = instance.$yns;
			var $ans = instance.$ans;
			var $ns = instance.$ns;

			eval(body);

			tpl.compiled = function(values, parent, $index, $i, $count, $last) {
				var buffer = [];

				var subTpl = STR_BLANK;

				var testFn = tpl.testFn;

				if (!testFn || testFn.call(instance, values, parent, $index, $i, $count, $last, $ns, $ans, $yns)) {
					var subValues = values;

					var tplFn = tpl.tplFn;

					if (tplFn) {
						subValues = tplFn.call(instance, values, parent);
						parent = values;
					}

					if (tplFn && isArray(subValues)) {
						var length = subValues.length;

						var execFn = tpl.execFn;

						for (var i = 0; i < length; i++) {
							var index = i + 1;
							var last = (index == length);
							var subValue = subValues[i];

							buffer[buffer.length] = compiledTplFn.call(instance, subValue, parent, index, i, length, last, $ns, $ans, $yns);

							if (execFn) {
								execFn.call(instance, subValues[i]);
							}
						}

						subTpl = buffer.join(STR_BLANK);
					}
					else {
						subTpl = compiledTplFn.call(instance, subValues, parent, $index, $i, $count, $last, $ns, $ans, $yns);
					}
				}

				return subTpl;
			};

			return instance;
		},

		_compileCode: function(match, code) {
			return STR_JOIN_GROUP_OPEN + code.replace(REGEX_QUOTE_ESCAPED, STR_QUOTE) + STR_JOIN_GROUP_CLOSE;
		},

		_compileSubTpl: function(id, values, parent, $index, $i, $count, $last, $ns, $ans, $yns) {
			var instance = this;

			var length;
			var tpl = instance.tpls[id];

			return tpl.compiled.call(instance, values, parent, $index, $i, $count, $last, $ns, $ans, $yns);
		},

		_compileTpl: function(match, name, methodName, args, math, offset, str) {
			var compiled;

			if (name.indexOf(TOKEN_TPL) === 0) {
				BUFFER_STR_COMPILE_SUB_TPL[1] = name.substr(TOKEN_TPL_LENGTH);

				compiled = BUFFER_STR_COMPILE_SUB_TPL.join(STR_BLANK);
			}
			else {
				var value = MAP_TPL_VALUES[name];

				if (!value) {
					if (name.indexOf(TOKEN_PARENT_PROP) === 0) {
						value = name;
					}
					else if (name.indexOf(STR_DOT) > -1) {
						value = TOKEN_VALUES_PROP + name;
					}
					else if (name.indexOf(STR_GLOBAL_SYMBOL) === 0 && (name in MAP_GLOBALS)) {
						BUFFER_GLOBAL_PROP[1] = name;

						value = BUFFER_GLOBAL_PROP.join(STR_BLANK);

					}
					else {
						BUFFER_VALUES_LOOKUP[1] = name;

						value = BUFFER_VALUES_LOOKUP.join(STR_BLANK);
					}
				}

				if (math) {
					value = STR_PAREN_OPEN + value + math + STR_PAREN_CLOSE;
				}

				if (methodName) {
					args = args ? STR_COMMA + args : STR_BLANK;

					if (methodName.indexOf(TOKEN_THIS_PROP) !== 0) {
						methodName = STR_LANGSTRING_VAR + methodName + STR_PAREN_OPEN;
					}
					else {
						methodName = STR_INVOKE_METHOD_NAME_OPEN + methodName.substr(TOKEN_THIS_PROP_LENGTH) + STR_INVOKE_METHOD_NAME_CLOSE;

						args = STR_COMMA + STR_VALUES;
					}
				}
				else {
					args = STR_BLANK;

					methodName = STR_PAREN_OPEN + value + STR_UNDEFINED_OUT;
				}

				compiled = STR_JOIN_OPEN + methodName + value + args + STR_JOIN_GROUP_CLOSE;
			}

			return compiled;
		},

		_getValidValues: function(values) {
			var instance = this;

			var val = STR_BLANK;

			if (MAP_TPL_FILTERED_TYPES[typeof values] || isDate(values)) {
				val = values;
			}

			return val;
		},

		_invokeMethod: function(methodName, value, allValues) {
			var instance = this;

			return instance[methodName](value, allValues);
		},

		_parse: function(html) {
			var instance = this;

			instance._html = html;

			var match;
			var id = 0;
			var tpls = [];

			BUFFER_HTML[1] = html;

			html = BUFFER_HTML.join(STR_BLANK);

			while (match = html.match(REGEX_TPL)) {
				var testFn = null;
				var execFn = null;
				var tplFn = null;
				var expression = match[2];
				var expressionValue = match[3];

				if (expressionValue) {
					if (expression == STR_FOR) {
						tplFn = MAP_TPL_FN[expressionValue] || new Function(
							STR_VALUES,
							STR_PARENT,
							STR_WITHVALUES + STR_RETURN + expressionValue + STR_WITHCLOSE
						);
					}
					else {
						expressionValue = LString.unescapeEntities(expressionValue);

						if (expression == STR_IF) {
							testFn = new Function(
								STR_VALUES,
								STR_PARENT,
								STR_SPECIAL_INDEX,
								STR_SPECIAL_I,
								STR_SPECIAL_COUNT,
								STR_SPECIAL_LAST,
								STR_SPECIAL_NS,
								STR_SPECIAL_ANS,
								STR_SPECIAL_YNS,
								STR_WITHVALUES + STR_RETURN + expressionValue + STR_WITHCLOSE
							);
						}
						else if (expression == STR_EXEC) {
							execFn = new Function(
								STR_VALUES,
								STR_PARENT,
								STR_SPECIAL_INDEX,
								STR_SPECIAL_I,
								STR_SPECIAL_COUNT,
								STR_SPECIAL_LAST,
								STR_SPECIAL_NS,
								STR_SPECIAL_ANS,
								STR_SPECIAL_YNS,
								STR_WITHVALUES + expressionValue + STR_WITHCLOSE
							);
						}
					}
				}

				var tplBody = match[4] || STR_BLANK;

				html = html.replace(match[0], STR_BRACE_OPEN + TOKEN_TPL + id + STR_BRACE_CLOSE);

				id = tpls.push(
					{
						execFn: execFn,
						id: id,
						testFn: testFn,
						tplBody: tplBody,
						tplFn: tplFn
					}
				);
			}

			var lastIndex = id - 1;

			while (id--) {
				instance._compile(tpls[id]);
			}

			instance._parentTpl = tpls[lastIndex];

			instance.tpls = tpls;

			return instance;
		},

		_parseArgs: function(args) {
			var instance = this;

			var config;

			var tpl = args[0];

			if (isArray(tpl)) {
				tpl = tpl.join(STR_BLANK);

				if (isObject(args[1])) {
					config = args[1];
				}
			}
			else if (args.length > 1) {
				var buffer = [];

				args = A.Array(args, 0, true);

				var length = args.length;
				var item;

				var lastItem = args[length - 1];

				if (isObject(lastItem)) {
					config = args.pop();
				}

				for (var i = 0; i < length; i++) {
					item = args[i];

					buffer.push(item);
				}

				tpl = buffer.join(STR_BLANK);
			}

			if (config) {
				A.mix(instance, config, true);
			}

			return tpl;
		},

		$ans: AUI_NS,
		$yns: YUI_NS
	};

	var TEMPLATE_PROTO = Template.prototype;

	TEMPLATE_PROTO.$ns = AUI_NS;

	var globalVar = function(key, value) {
		var retVal = null;

		if (isUndefined(value) && key) {
			retVal = MAP_GLOBALS[key];
		}
		else {
			if (key) {
				key = String(key).replace(REGEX_PREFIX_GLOBAL_REPLACE, STR_GLOBAL_SYMBOL);

				if (value !== null) {
					MAP_GLOBALS[key] = value;
					retVal = value;
				}
				else {
					delete MAP_GLOBALS[key];
				}
			}
		}

		return retVal;
	};

	Template.globalVar = TEMPLATE_PROTO.globalVar = globalVar;

	Template._GLOBALS = MAP_GLOBALS;

	var NODE_PROTO = A.Node.prototype;

	NODE_PROTO.toTemplate = function() {
		return Template.from(this);
	};

	NODE_PROTO.renderTemplate = function(tpl, data) {
		var instance = this;

		if (isString(tpl) || isArray(tpl)) {
			tpl = new Template(tpl);
		}

		if (tpl && data) {
			tpl.render(instance, data);
		}

		return instance;
	};

	A.NodeList.importMethod(
		NODE_PROTO,
		[
			'renderTemplate'
		]
	);

	Template.from = function(node) {
		node = A.one(node);

		var content = STR_BLANK;

		if (node) {
			node = node.getDOM();

			var nodeName = String(node && node.nodeName).toLowerCase();

			if (nodeName != 'script') {
				content = node.value || node.innerHTML;
			}
			else {
				content = node.text || node.textContent || node.innerHTML;
			}
		}

		return new Template(content);
	};

	A.Template = Template;<|endoftext|>


<commit_before>import { FS } from 'grind-support'

const path = require('path')
const crypto = require('crypto')
const dateFormat = require('dateformat')

const HTTP_DATE_FORMAT = 'ddd, dd mmm yyyy HH:MM:ss Z'

export class CompileController {
	app = null
	factory = null
	local = null
	resources = null

	constructor(app, factory) {
		this.app = app
		this.factory = factory
		this.local = app.env() === 'local'
		this.resources = app.paths.base('resources')

		if(app.cache.isNil) {
			Log.error('WARNING: grind-cache not detected, assets will be recompiled every time they’re loaded.')
		}
	}

	async compile(req, res) {
		if(req.path.indexOf('..') >= 0) {
			throw new NotFoundError()
		}

		const pathname = path.join(this.resources, req.path)
		const exists = await FS.exists(pathname)

		if(!exists) {
			throw new NotFoundError()
		}

		return this._serve(req, res, this.factory.make(pathname))
	}

	async _serve(req, res, asset) {
		const lastModified = await asset.lastModified()

		const sha1 = crypto.createHash('sha1')
		sha1.update(`${asset.path}_${lastModified}`)
		const etag = `"${sha1.digest('hex')}"`

		if(!req.headers.http_if_none_match.isNil && req.headers.http_if_none_match === etag) {
			res.send(304)
			return
		}

		const expires = new Date((lastModified + 31536000) * 1000.0)
		const lastModifiedDate = new Date(lastModified * 1000.0)

		const compile = async () => {
			res.header('X-Cached', 'false')

			let result = await asset.compile().then(result => {
				if(!(result instanceof Buffer)) {
					return new Buffer(result)
				}

				return result
			})

			const postProcessors = this.factory.getPostProcessorsFromPath(`out.${asset.extension}`).filter(
				processor => processor.shouldOptimize
			)

			if(postProcessors.length > 0) {
				for(const postProcessor of postProcessors) {
					result = await postProcessor.process(asset.path, null, result)

					if(!(result instanceof Buffer)) {
						result = new Buffer(result)
					}
				}
			}

			return new Buffer(result)
		}

		let promise = null

		if(this.app.cache.isNil || req.query['ignore-cache'] === 'true') {
			promise = compile()
		} else {
			const key = `${req.path.replace(/[^a-z0-9]+/, '-')}-${lastModifiedDate.getTime()}`
			promise = this.app.cache.wrap(key, compile, { ttl: 86400 }).then(data => {
				if(!(data instanceof Buffer) && !data.data.isNil) {
					return Buffer.from(data)
				}

				return data
			})
		}

		return promise.then(content => {
			if(!this.app.debug) {
				res.header('Cache-Control', 'public, max-age=31536000')
				res.header('Expires', dateFormat(expires, HTTP_DATE_FORMAT))
			}

			res.header('Last-Modified', dateFormat(lastModifiedDate, HTTP_DATE_FORMAT))
			res.header('ETag', etag)
			res.header('Content-Type', `${asset.mime}; charset=utf-8`)

			res.send(content)
		}).catch(err => {
			res.status(500)
			res.header('Content-Type', 'text/plain')

			const body = this._buildErrorString(err)
			Log.error('Asset compilation error', body)
			res.send(`/*\n${body}\n*/`)
		})
	}

	_buildErrorString(err) {
		let body = ''

		if(!err.file.isNil) {
			body += err.file

			if(err.line) {
				body += `:${err.line}`

				if(err.column) {
					body += `:${err.column}`
				}
			}

			body += '\n\t'
		}

		body += err.message

		return body
	}

}
<commit_msg>Changed caching in app.debug Now uses caching via `global.__grindAssetsCache` to maintain memory cache during watch reloads<commit_after>import { FS } from 'grind-support'

const path = require('path')
const crypto = require('crypto')
const dateFormat = require('dateformat')

const HTTP_DATE_FORMAT = 'ddd, dd mmm yyyy HH:MM:ss Z'

export class CompileController {
	app = null
	factory = null
	local = null
	resources = null

	constructor(app, factory) {
		this.app = app
		this.factory = factory
		this.local = app.env() === 'local'
		this.resources = app.paths.base('resources')

		if(app.cache.isNil) {
			Log.error('WARNING: grind-cache not detected, assets will be recompiled every time they’re loaded.')
		}
	}

	async compile(req, res) {
		if(req.path.indexOf('..') >= 0) {
			throw new NotFoundError()
		}

		const pathname = path.join(this.resources, req.path)
		const exists = await FS.exists(pathname)

		if(!exists) {
			throw new NotFoundError()
		}

		return this._serve(req, res, this.factory.make(pathname))
	}

	async _serve(req, res, asset) {
		const lastModified = await asset.lastModified()

		const sha1 = crypto.createHash('sha1')
		sha1.update(`${asset.path}_${lastModified}`)
		const etag = `"${sha1.digest('hex')}"`

		if(!req.headers.http_if_none_match.isNil && req.headers.http_if_none_match === etag) {
			res.send(304)
			return
		}

		const expires = new Date((lastModified + 31536000) * 1000.0)
		const lastModifiedDate = new Date(lastModified * 1000.0)

		const compile = async () => {
			res.header('X-Cached', 'false')

			let result = await asset.compile().then(result => {
				if(!(result instanceof Buffer)) {
					return new Buffer(result)
				}

				return result
			})

			const postProcessors = this.factory.getPostProcessorsFromPath(`out.${asset.extension}`).filter(
				processor => processor.shouldOptimize
			)

			if(postProcessors.length > 0) {
				for(const postProcessor of postProcessors) {
					result = await postProcessor.process(asset.path, null, result)

					if(!(result instanceof Buffer)) {
						result = new Buffer(result)
					}
				}
			}

			return new Buffer(result)
		}

		let promise = null

		if(this.app.cache.isNil || req.query['ignore-cache'] === 'true') {
			promise = compile()
		} else {
			const key = `${req.path.replace(/[^a-z0-9]+/, '-')}-${lastModifiedDate.getTime()}`
			const memoryCache = this.app.debug ? (global.__grindAssetsCache = global.__grindAssetsCache || { }) : { }

			if(this.app.debug && memoryCache[key] instanceof Buffer) {
				promise = Promise.resolve(memoryCache[key])
			} else {
				promise = this.app.cache.wrap(key, compile, { ttl: 86400 }).then(data => {
					if(!(data instanceof Buffer) && !data.data.isNil) {
						return Buffer.from(data)
					}

					return data
				})

				if(this.app.debug) {
					promise = promise.then(data => {
						if(this.app.debug) {
							memoryCache[key] = data
						}

						return data
					})
				}
			}
		}

		return promise.then(content => {
			if(!this.app.debug) {
				res.header('Cache-Control', 'public, max-age=31536000')
				res.header('Expires', dateFormat(expires, HTTP_DATE_FORMAT))
			}

			res.header('Last-Modified', dateFormat(lastModifiedDate, HTTP_DATE_FORMAT))
			res.header('ETag', etag)
			res.header('Content-Type', `${asset.mime}; charset=utf-8`)

			res.send(content)
		}).catch(err => {
			res.status(500)
			res.header('Content-Type', 'text/plain')

			const body = this._buildErrorString(err)
			Log.error('Asset compilation error', body)
			res.send(`/*\n${body}\n*/`)
		})
	}

	_buildErrorString(err) {
		let body = ''

		if(!err.file.isNil) {
			body += err.file

			if(err.line) {
				body += `:${err.line}`

				if(err.column) {
					body += `:${err.column}`
				}
			}

			body += '\n\t'
		}

		body += err.message

		return body
	}

}
<|endoftext|>


<commit_before>(function (factory) {
  if (typeof breeze === "object") {
    factory(breeze);
  } else if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
    // CommonJS or Node: hard-coded dependency on "breeze"
    factory(require("breeze-client"));
  } else if (typeof define === "function" && define["amd"]) {
    // AMD anonymous module with hard-coded dependency on "breeze"
    define(["breeze"], factory);
  }
}(function (breeze) {
  "use strict";
  var core = breeze.core;

  var MetadataStore = breeze.MetadataStore;
  var JsonResultsAdapter = breeze.JsonResultsAdapter;
  var DataProperty = breeze.DataProperty;

  var OData;

  var ctor = function DataServiceODataAdapter() {
    this.name = "OData";
  };

  var proto = ctor.prototype; // minifies better (as seen in jQuery)

  proto.initialize = function () {
    OData = core.requireLib("OData", "Needed to support remote OData services");
    OData.jsonHandler.recognizeDates = true;
  };
  // borrow from AbstractDataServiceAdapter
  var abstractDsaProto = breeze.AbstractDataServiceAdapter.prototype;
  proto._catchNoConnectionError = abstractDsaProto._catchNoConnectionError;
  proto.changeRequestInterceptor = abstractDsaProto.changeRequestInterceptor;
  proto._createChangeRequestInterceptor = abstractDsaProto._createChangeRequestInterceptor;
  proto.headers = { "DataServiceVersion": "2.0" };

  proto.getAbsoluteUrl = function (dataService, url){
    var serviceName = dataService.qualifyUrl('');
    // only prefix with serviceName if not already on the url
    var base = (core.stringStartsWith(url, serviceName)) ? '' : serviceName;
    // If no protocol, turn base into an absolute URI
    if (window && serviceName.indexOf('//') < 0) {
      // no protocol; make it absolute
      base = window.location.protocol + '//' + window.location.host +
            (core.stringStartsWith(serviceName, '/') ? '' : '/') +
            base;
    }
    return base + url;
  };

  // getRoutePrefix deprecated in favor of getAbsoluteUrl which seems to work for all OData providers; doubt anyone ever changed it; we'll see
  // TODO: Remove from code base soon (15 June 2015)
  // proto.getRoutePrefix = function (dataService) { return '';}

  proto.executeQuery = function (mappingContext) {

    var deferred = Q.defer();
    var url = this.getAbsoluteUrl(mappingContext.dataService, mappingContext.getUrl());

    OData.read({
          requestUri: url,
          headers: this.headers
        },
        function (data, response) {
          var inlineCount;
          if (data.__count) {
            // OData can return data.__count as a string
            inlineCount = parseInt(data.__count, 10);
          }
          return deferred.resolve({ results: data.results, inlineCount: inlineCount, httpResponse: response });
        },
        function (error) {
          return deferred.reject(createError(error, url));
        }
    );
    return deferred.promise;
  };

  proto.fetchMetadata = function (metadataStore, dataService) {

    var deferred = Q.defer();

    var serviceName = dataService.serviceName;
    //var url = dataService.qualifyUrl('$metadata');
    var url = this.getAbsoluteUrl(dataService, '$metadata');
    // OData.read(url,
    OData.read({
          requestUri: url,
          // headers: { "Accept": "application/json"}
          headers: { Accept: 'application/json;odata.metadata=full' }
        },
        function (data) {
          // data.dataServices.schema is an array of schemas. with properties of
          // entityContainer[], association[], entityType[], and namespace.
          if (!data || !data.dataServices) {
            var error = new Error("Metadata query failed for: " + url);
            return deferred.reject(error);
          }
          var csdlMetadata = data.dataServices;

          // might have been fetched by another query
          if (!metadataStore.hasMetadataFor(serviceName)) {
            try {
              metadataStore.importMetadata(csdlMetadata);
            } catch (e) {
              return deferred.reject(new Error("Metadata query failed for " + url + "; Unable to process returned metadata: " + e.message));
            }

            metadataStore.addDataService(dataService);
          }

          return deferred.resolve(csdlMetadata);

        }, function (error) {
          var err = createError(error, url);
          err.message = "Metadata query failed for: " + url + "; " + (err.message || "");
          return deferred.reject(err);
        },
        OData.metadataHandler
    );

    return deferred.promise;

  };

  proto.saveChanges = function (saveContext, saveBundle) {
    var adapter = saveContext.adapter = this;
    var deferred = Q.defer();
    //saveContext.routePrefix = adapter.getRoutePrefix(saveContext.dataService);
    //var url = saveContext.dataService.qualifyUrl("$batch");
    saveContext.routePrefix = adapter.getAbsoluteUrl(saveContext.dataService, '');
    var url = saveContext.routePrefix + '$batch';

    var requestData = createChangeRequests(saveContext, saveBundle);
    var tempKeys = saveContext.tempKeys;
    var contentKeys = saveContext.contentKeys;

    OData.request({
      headers: { "DataServiceVersion": "2.0" },
      requestUri: url,
      method: "POST",
      data: requestData
    }, function (data, response) {
      var entities = [];
      var keyMappings = [];
      var saveResult = { entities: entities, keyMappings: keyMappings };
      data.__batchResponses.forEach(function (br) {
        br.__changeResponses.forEach(function (cr) {
          var response = cr.response || cr;
          var statusCode = response.statusCode;
          if ((!statusCode) || statusCode >= 400) {
            deferred.reject(createError(cr, url));
            return;
          }

          var contentId = cr.headers["Content-ID"];

          var rawEntity = cr.data;
          if (rawEntity) {
            var tempKey = tempKeys[contentId];
            if (tempKey) {
              var entityType = tempKey.entityType;
              if (entityType.autoGeneratedKeyType !== AutoGeneratedKeyType.None) {
                var tempValue = tempKey.values[0];
                var realKey = entityType.getEntityKeyFromRawEntity(rawEntity, DataProperty.getRawValueFromServer);
                var keyMapping = { entityTypeName: entityType.name, tempValue: tempValue, realValue: realKey.values[0] };
                keyMappings.push(keyMapping);
              }
            }
            entities.push(rawEntity);
          } else {
            var origEntity = contentKeys[contentId];
            entities.push(origEntity);
          }
        });
      });
      return deferred.resolve(saveResult);
    }, function (err) {
      return deferred.reject(createError(err, url));
    }, OData.batchHandler);

    return deferred.promise;

  };

  proto.jsonResultsAdapter = new JsonResultsAdapter({
    name: "OData_default",

    visitNode: function (node, mappingContext, nodeContext) {
      var result = {};
      if (node == null) return result;
      var metadata = node.__metadata;
      if (metadata != null) {
        // TODO: may be able to make this more efficient by caching of the previous value.
        var entityTypeName = MetadataStore.normalizeTypeName(metadata.type);
        var et = entityTypeName && mappingContext.entityManager.metadataStore.getEntityType(entityTypeName, true);
        // OData response doesn't distinguish a projection from a whole entity.
        // We'll assume that whole-entity data would have at least as many properties  (<=)
        // as the EntityType has mapped properties on the basis that
        // most projections remove properties rather than add them.
        // If not, assume it's a projection and do NOT treat as an entity
        if (et && et._mappedPropertiesCount <= Object.keys(node).length - 1) {
          // if (et && et._mappedPropertiesCount === Object.keys(node).length - 1) { // OLD
          result.entityType = et;
          var uriKey = metadata.uri || metadata.id;
          if (uriKey) {
            // Strip baseUri to make uriKey a relative uri
            // Todo: why is this necessary when absolute works for every OData source tested?
            var re = new RegExp('^' + mappingContext.dataService.serviceName, 'i');
            uriKey = uriKey.replace(re, '');
          }
          result.extraMetadata = {
            uriKey: uriKey,
            etag: metadata.etag
          }
        }
      }
      // OData v3 - projection arrays will be enclosed in a results array
      if (node.results) {
        result.node = node.results;
      }

      var propertyName = nodeContext.propertyName;
      result.ignore = node.__deferred != null || propertyName === "__metadata" ||
        // EntityKey properties can be produced by EDMX models
          (propertyName === "EntityKey" && node.$type && core.stringStartsWith(node.$type, "System.Data"));
      return result;
    }

  });

  function transformValue(prop, val) {
    if (prop.isUnmapped) return undefined;
    if (prop.dataType === DataType.DateTimeOffset) {
      // The datajs lib tries to treat client dateTimes that are defined as DateTimeOffset on the server differently
      // from other dateTimes. This fix compensates before the save.
      val = val && new Date(val.getTime() - (val.getTimezoneOffset() * 60000));
    } else if (prop.dataType.quoteJsonOData) {
      val = val != null ? val.toString() : val;
    }
    return val;
  }

  function createChangeRequests(saveContext, saveBundle) {
    var changeRequestInterceptor = saveContext.adapter._createChangeRequestInterceptor(saveContext, saveBundle);
    var changeRequests = [];
    var tempKeys = [];
    var contentKeys = [];
    var entityManager = saveContext.entityManager;
    var helper = entityManager.helper;
    var id = 0;
    var routePrefix = saveContext.routePrefix;

    saveBundle.entities.forEach(function (entity, index) {
      var aspect = entity.entityAspect;
      id = id + 1; // we are deliberately skipping id=0 because Content-ID = 0 seems to be ignored.
      var request = { headers: { "Content-ID": id, "DataServiceVersion": "2.0" } };
      contentKeys[id] = entity;
      if (aspect.entityState.isAdded()) {
        request.requestUri = routePrefix + entity.entityType.defaultResourceName;
        request.method = "POST";
        request.data = helper.unwrapInstance(entity, transformValue);
        tempKeys[id] = aspect.getKey();
      } else if (aspect.entityState.isModified()) {
        updateDeleteMergeRequest(request, aspect, routePrefix);
        request.method = "MERGE";
        request.data = helper.unwrapChangedValues(entity, entityManager.metadataStore, transformValue);
        // should be a PATCH/MERGE
      } else if (aspect.entityState.isDeleted()) {
        updateDeleteMergeRequest(request, aspect, routePrefix);
        request.method = "DELETE";
      } else {
        return;
      }
      request = changeRequestInterceptor.getRequest(request, entity, index);
      changeRequests.push(request);
    });
    saveContext.contentKeys = contentKeys;
    saveContext.tempKeys = tempKeys;
    changeRequestInterceptor.done(changeRequests);
    return {
      __batchRequests: [
        {
          __changeRequests: changeRequests
        }
      ]
    };

  }

  function updateDeleteMergeRequest(request, aspect, routePrefix) {
    var uriKey;
    var extraMetadata = aspect.extraMetadata;
    if (extraMetadata == null) {
      uriKey = getUriKey(aspect);
      aspect.extraMetadata = {
        uriKey: uriKey
      }
    } else {
      uriKey = extraMetadata.uriKey;
      if (extraMetadata.etag) {
        request.headers["If-Match"] = extraMetadata.etag;
      }
    }
    request.requestUri =
      // use routePrefix if uriKey lacks protocol (i.e., relative uri)
      uriKey.indexOf('//') > 0 ? uriKey : routePrefix + uriKey;
  }

  function getUriKey(aspect) {
    var entityType = aspect.entity.entityType;
    var resourceName = entityType.defaultResourceName;
    var kps = entityType.keyProperties;
    var uriKey = resourceName + "(";
    if (kps.length === 1) {
      uriKey = uriKey + fmtProperty(kps[0], aspect) + ")";
    } else {
      var delim = "";
      kps.forEach(function (kp) {
        uriKey = uriKey + delim + kp.nameOnServer + "=" + fmtProperty(kp, aspect);
        delim = ",";
      });
      uriKey = uriKey + ")";
    }
    return uriKey;
  }

  function fmtProperty(prop, aspect) {
    return prop.dataType.fmtOData(aspect.getPropertyValue(prop.name));
  }

  function createError(error, url) {
    // OData errors can have the message buried very deeply - and nonobviously
    // this code is tricky so be careful changing the response.body parsing.
    var result = new Error();
    var response = error && error.response;
    if (!response) {
      // in case DataJS returns "No handler for this data"
      result.message = error;
      result.statusText = error;
      return result;
    }
    result.message = response.statusText;
    result.statusText = response.statusText;
    result.status = response.statusCode;
    // non std
    if (url) result.url = url;
    result.body = response.body;
    if (response.body) {
      var nextErr;
      try {
        var body = JSON.parse(response.body);
        result.body = body;
        // OData v3 logic
        if (body['odata.error']) {
          body = body['odata.error'];
        }
        var msg = "";
        do {
          nextErr = body.error || body.innererror;
          if (!nextErr) msg = msg + getMessage(body);
          nextErr = nextErr || body.internalexception;
          body = nextErr || body;
        } while (nextErr);
        if (msg.length > 0) {
          result.message = msg;
        }
      } catch (e) {

      }
    }
    proto._catchNoConnectionError(result);
    return result;
  }

  function getMessage(body) {
    var msg = body.message || "";
    return ((typeof (msg) === "string") ? msg : msg.value) + "; ";
  }

  breeze.config.registerAdapter("dataService", ctor);

  var webApiODataCtor = function () {
    this.name = "webApiOData";
  }

  breeze.core.extend(webApiODataCtor.prototype, proto);

/*
  // Deprecated in favor of getAbsoluteUrl
  // TODO: Remove from code base soon (15 June 2015)
  webApiODataCtor.prototype.getRoutePrefix = function (dataService) {
    // Get the routePrefix from a Web API OData service name.
    // The routePrefix is presumed to be the pathname within the dataService.serviceName
    // Examples of servicename -> routePrefix:
    //   'http://localhost:55802/odata/' -> 'odata/'
    //   'http://198.154.121.75/service/odata/' -> 'service/odata/'
    var parser;
    if (typeof document === 'object') { // browser
      parser = document.createElement('a');
      parser.href = dataService.serviceName;
    } else { // node
      parser = url.parse(dataService.serviceName);
    }
    var prefix = parser.pathname;
    if (prefix[0] === '/') {
      prefix = prefix.substr(1);
    } // drop leading '/'  (all but IE)
    if (prefix.substr(-1) !== '/') {
      prefix += '/';
    }      // ensure trailing '/'
    return prefix;
  };
  */

  breeze.config.registerAdapter("dataService", webApiODataCtor);
  // OData 4 adapter
  var webApiOData4Ctor = function () {
    this.name = "webApiOData4";
  }
  breeze.core.extend(webApiOData4Ctor.prototype, webApiODataCtor.prototype);
  webApiOData4Ctor.prototype.initialize = function () {
    // Aargh... they moved the cheese.
    var datajs = core.requireLib("datajs", "Needed to support remote OData v4 services");
    OData = datajs.V4.oData;
    OData.json.jsonHandler.recognizeDates = true;
  };
  webApiOData4Ctor.prototype.headers = { "OData-Version": "4.0" };
  breeze.config.registerAdapter("dataService", webApiOData4Ctor);

}));<commit_msg>odata adapter fix<commit_after>(function (factory) {
  if (typeof breeze === "object") {
    factory(breeze);
  } else if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
    // CommonJS or Node: hard-coded dependency on "breeze"
    factory(require("breeze-client"));
  } else if (typeof define === "function" && define["amd"]) {
    // AMD anonymous module with hard-coded dependency on "breeze"
    define(["breeze"], factory);
  }
}(function (breeze) {
  "use strict";
  var core = breeze.core;

  var MetadataStore = breeze.MetadataStore;
  var JsonResultsAdapter = breeze.JsonResultsAdapter;
  var DataProperty = breeze.DataProperty;
  var DataType = breeze.DataType;
  var AutoGeneratedKeyType = breeze.AutoGeneratedKeyType;

  var OData;

  var ctor = function DataServiceODataAdapter() {
    this.name = "OData";
  };

  var proto = ctor.prototype; // minifies better (as seen in jQuery)

  proto.initialize = function () {
    OData = core.requireLib("OData", "Needed to support remote OData services");
    OData.jsonHandler.recognizeDates = true;
  };
  // borrow from AbstractDataServiceAdapter
  var abstractDsaProto = breeze.AbstractDataServiceAdapter.prototype;
  proto._catchNoConnectionError = abstractDsaProto._catchNoConnectionError;
  proto.changeRequestInterceptor = abstractDsaProto.changeRequestInterceptor;
  proto._createChangeRequestInterceptor = abstractDsaProto._createChangeRequestInterceptor;
  proto.headers = { "DataServiceVersion": "2.0" };

  proto.getAbsoluteUrl = function (dataService, url){
    var serviceName = dataService.qualifyUrl('');
    // only prefix with serviceName if not already on the url
    var base = (core.stringStartsWith(url, serviceName)) ? '' : serviceName;
    // If no protocol, turn base into an absolute URI
    if (window && serviceName.indexOf('//') < 0) {
      // no protocol; make it absolute
      base = window.location.protocol + '//' + window.location.host +
            (core.stringStartsWith(serviceName, '/') ? '' : '/') +
            base;
    }
    return base + url;
  };

  // getRoutePrefix deprecated in favor of getAbsoluteUrl which seems to work for all OData providers; doubt anyone ever changed it; we'll see
  // TODO: Remove from code base soon (15 June 2015)
  // proto.getRoutePrefix = function (dataService) { return '';}

  proto.executeQuery = function (mappingContext) {

    var deferred = breeze.Q.defer();
    var url = this.getAbsoluteUrl(mappingContext.dataService, mappingContext.getUrl());

    OData.read({
          requestUri: url,
          headers: this.headers
        },
        function (data, response) {
          var inlineCount;
          if (data.__count) {
            // OData can return data.__count as a string
            inlineCount = parseInt(data.__count, 10);
          }
          return deferred.resolve({ results: data.results, inlineCount: inlineCount, httpResponse: response });
        },
        function (error) {
          return deferred.reject(createError(error, url));
        }
    );
    return deferred.promise;
  };

  proto.fetchMetadata = function (metadataStore, dataService) {

    var deferred = breeze.Q.defer();

    var serviceName = dataService.serviceName;
    //var url = dataService.qualifyUrl('$metadata');
    var url = this.getAbsoluteUrl(dataService, '$metadata');
    // OData.read(url,
    OData.read({
          requestUri: url,
          // headers: { "Accept": "application/json"}
          headers: { Accept: 'application/json;odata.metadata=full' }
        },
        function (data) {
          // data.dataServices.schema is an array of schemas. with properties of
          // entityContainer[], association[], entityType[], and namespace.
          if (!data || !data.dataServices) {
            var error = new Error("Metadata query failed for: " + url);
            return deferred.reject(error);
          }
          var csdlMetadata = data.dataServices;

          // might have been fetched by another query
          if (!metadataStore.hasMetadataFor(serviceName)) {
            try {
              metadataStore.importMetadata(csdlMetadata);
            } catch (e) {
              return deferred.reject(new Error("Metadata query failed for " + url + "; Unable to process returned metadata: " + e.message));
            }

            metadataStore.addDataService(dataService);
          }

          return deferred.resolve(csdlMetadata);

        }, function (error) {
          var err = createError(error, url);
          err.message = "Metadata query failed for: " + url + "; " + (err.message || "");
          return deferred.reject(err);
        },
        OData.metadataHandler
    );

    return deferred.promise;

  };

  proto.saveChanges = function (saveContext, saveBundle) {
    var adapter = saveContext.adapter = this;
    var deferred = breeze.Q.defer();
    //saveContext.routePrefix = adapter.getRoutePrefix(saveContext.dataService);
    //var url = saveContext.dataService.qualifyUrl("$batch");
    saveContext.routePrefix = adapter.getAbsoluteUrl(saveContext.dataService, '');
    var url = saveContext.routePrefix + '$batch';

    var requestData = createChangeRequests(saveContext, saveBundle);
    var tempKeys = saveContext.tempKeys;
    var contentKeys = saveContext.contentKeys;

    OData.request({
      headers: { "DataServiceVersion": "2.0" },
      requestUri: url,
      method: "POST",
      data: requestData
    }, function (data, response) {
      var entities = [];
      var keyMappings = [];
      var saveResult = { entities: entities, keyMappings: keyMappings };
      data.__batchResponses.forEach(function (br) {
        br.__changeResponses.forEach(function (cr) {
          var response = cr.response || cr;
          var statusCode = response.statusCode;
          if ((!statusCode) || statusCode >= 400) {
            deferred.reject(createError(cr, url));
            return;
          }

          var contentId = cr.headers["Content-ID"];

          var rawEntity = cr.data;
          if (rawEntity) {
            var tempKey = tempKeys[contentId];
            if (tempKey) {
              var entityType = tempKey.entityType;
              if (entityType.autoGeneratedKeyType !== AutoGeneratedKeyType.None) {
                var tempValue = tempKey.values[0];
                var realKey = entityType.getEntityKeyFromRawEntity(rawEntity, DataProperty.getRawValueFromServer);
                var keyMapping = { entityTypeName: entityType.name, tempValue: tempValue, realValue: realKey.values[0] };
                keyMappings.push(keyMapping);
              }
            }
            entities.push(rawEntity);
          } else {
            var origEntity = contentKeys[contentId];
            entities.push(origEntity);
          }
        });
      });
      return deferred.resolve(saveResult);
    }, function (err) {
      return deferred.reject(createError(err, url));
    }, OData.batchHandler);

    return deferred.promise;

  };

  proto.jsonResultsAdapter = new JsonResultsAdapter({
    name: "OData_default",

    visitNode: function (node, mappingContext, nodeContext) {
      var result = {};
      if (node == null) return result;
      var metadata = node.__metadata;
      if (metadata != null) {
        // TODO: may be able to make this more efficient by caching of the previous value.
        var entityTypeName = MetadataStore.normalizeTypeName(metadata.type);
        var et = entityTypeName && mappingContext.entityManager.metadataStore.getEntityType(entityTypeName, true);
        // OData response doesn't distinguish a projection from a whole entity.
        // We'll assume that whole-entity data would have at least as many properties  (<=)
        // as the EntityType has mapped properties on the basis that
        // most projections remove properties rather than add them.
        // If not, assume it's a projection and do NOT treat as an entity
        if (et && et._mappedPropertiesCount <= Object.keys(node).length - 1) {
          // if (et && et._mappedPropertiesCount === Object.keys(node).length - 1) { // OLD
          result.entityType = et;
          var uriKey = metadata.uri || metadata.id;
          if (uriKey) {
            // Strip baseUri to make uriKey a relative uri
            // Todo: why is this necessary when absolute works for every OData source tested?
            var re = new RegExp('^' + mappingContext.dataService.serviceName, 'i');
            uriKey = uriKey.replace(re, '');
          }
          result.extraMetadata = {
            uriKey: uriKey,
            etag: metadata.etag
          }
        }
      }
      // OData v3 - projection arrays will be enclosed in a results array
      if (node.results) {
        result.node = node.results;
      }

      var propertyName = nodeContext.propertyName;
      result.ignore = node.__deferred != null || propertyName === "__metadata" ||
        // EntityKey properties can be produced by EDMX models
          (propertyName === "EntityKey" && node.$type && core.stringStartsWith(node.$type, "System.Data"));
      return result;
    }

  });

  function transformValue(prop, val) {
    if (prop.isUnmapped) return undefined;
    if (prop.dataType === DataType.DateTimeOffset) {
      // The datajs lib tries to treat client dateTimes that are defined as DateTimeOffset on the server differently
      // from other dateTimes. This fix compensates before the save.
      val = val && new Date(val.getTime() - (val.getTimezoneOffset() * 60000));
    } else if (prop.dataType.quoteJsonOData) {
      val = val != null ? val.toString() : val;
    }
    return val;
  }

  function createChangeRequests(saveContext, saveBundle) {
    var changeRequestInterceptor = saveContext.adapter._createChangeRequestInterceptor(saveContext, saveBundle);
    var changeRequests = [];
    var tempKeys = [];
    var contentKeys = [];
    var entityManager = saveContext.entityManager;
    var helper = entityManager.helper;
    var id = 0;
    var routePrefix = saveContext.routePrefix;

    saveBundle.entities.forEach(function (entity, index) {
      var aspect = entity.entityAspect;
      id = id + 1; // we are deliberately skipping id=0 because Content-ID = 0 seems to be ignored.
      var request = { headers: { "Content-ID": id, "DataServiceVersion": "2.0" } };
      contentKeys[id] = entity;
      if (aspect.entityState.isAdded()) {
        request.requestUri = routePrefix + entity.entityType.defaultResourceName;
        request.method = "POST";
        request.data = helper.unwrapInstance(entity, transformValue);
        tempKeys[id] = aspect.getKey();
      } else if (aspect.entityState.isModified()) {
        updateDeleteMergeRequest(request, aspect, routePrefix);
        request.method = "MERGE";
        request.data = helper.unwrapChangedValues(entity, entityManager.metadataStore, transformValue);
        // should be a PATCH/MERGE
      } else if (aspect.entityState.isDeleted()) {
        updateDeleteMergeRequest(request, aspect, routePrefix);
        request.method = "DELETE";
      } else {
        return;
      }
      request = changeRequestInterceptor.getRequest(request, entity, index);
      changeRequests.push(request);
    });
    saveContext.contentKeys = contentKeys;
    saveContext.tempKeys = tempKeys;
    changeRequestInterceptor.done(changeRequests);
    return {
      __batchRequests: [
        {
          __changeRequests: changeRequests
        }
      ]
    };

  }

  function updateDeleteMergeRequest(request, aspect, routePrefix) {
    var uriKey;
    var extraMetadata = aspect.extraMetadata;
    if (extraMetadata == null) {
      uriKey = getUriKey(aspect);
      aspect.extraMetadata = {
        uriKey: uriKey
      }
    } else {
      uriKey = extraMetadata.uriKey;
      if (extraMetadata.etag) {
        request.headers["If-Match"] = extraMetadata.etag;
      }
    }
    request.requestUri =
      // use routePrefix if uriKey lacks protocol (i.e., relative uri)
      uriKey.indexOf('//') > 0 ? uriKey : routePrefix + uriKey;
  }

  function getUriKey(aspect) {
    var entityType = aspect.entity.entityType;
    var resourceName = entityType.defaultResourceName;
    var kps = entityType.keyProperties;
    var uriKey = resourceName + "(";
    if (kps.length === 1) {
      uriKey = uriKey + fmtProperty(kps[0], aspect) + ")";
    } else {
      var delim = "";
      kps.forEach(function (kp) {
        uriKey = uriKey + delim + kp.nameOnServer + "=" + fmtProperty(kp, aspect);
        delim = ",";
      });
      uriKey = uriKey + ")";
    }
    return uriKey;
  }

  function fmtProperty(prop, aspect) {
    return prop.dataType.fmtOData(aspect.getPropertyValue(prop.name));
  }

  function createError(error, url) {
    // OData errors can have the message buried very deeply - and nonobviously
    // this code is tricky so be careful changing the response.body parsing.
    var result = new Error();
    var response = error && error.response;
    if (!response) {
      // in case DataJS returns "No handler for this data"
      result.message = error;
      result.statusText = error;
      return result;
    }
    result.message = response.statusText;
    result.statusText = response.statusText;
    result.status = response.statusCode;
    // non std
    if (url) result.url = url;
    result.body = response.body;
    if (response.body) {
      var nextErr;
      try {
        var body = JSON.parse(response.body);
        result.body = body;
        // OData v3 logic
        if (body['odata.error']) {
          body = body['odata.error'];
        }
        var msg = "";
        do {
          nextErr = body.error || body.innererror;
          if (!nextErr) msg = msg + getMessage(body);
          nextErr = nextErr || body.internalexception;
          body = nextErr || body;
        } while (nextErr);
        if (msg.length > 0) {
          result.message = msg;
        }
      } catch (e) {

      }
    }
    proto._catchNoConnectionError(result);
    return result;
  }

  function getMessage(body) {
    var msg = body.message || "";
    return ((typeof (msg) === "string") ? msg : msg.value) + "; ";
  }

  breeze.config.registerAdapter("dataService", ctor);

  var webApiODataCtor = function () {
    this.name = "webApiOData";
  }

  breeze.core.extend(webApiODataCtor.prototype, proto);

/*
  // Deprecated in favor of getAbsoluteUrl
  // TODO: Remove from code base soon (15 June 2015)
  webApiODataCtor.prototype.getRoutePrefix = function (dataService) {
    // Get the routePrefix from a Web API OData service name.
    // The routePrefix is presumed to be the pathname within the dataService.serviceName
    // Examples of servicename -> routePrefix:
    //   'http://localhost:55802/odata/' -> 'odata/'
    //   'http://198.154.121.75/service/odata/' -> 'service/odata/'
    var parser;
    if (typeof document === 'object') { // browser
      parser = document.createElement('a');
      parser.href = dataService.serviceName;
    } else { // node
      parser = url.parse(dataService.serviceName);
    }
    var prefix = parser.pathname;
    if (prefix[0] === '/') {
      prefix = prefix.substr(1);
    } // drop leading '/'  (all but IE)
    if (prefix.substr(-1) !== '/') {
      prefix += '/';
    }      // ensure trailing '/'
    return prefix;
  };
  */

  breeze.config.registerAdapter("dataService", webApiODataCtor);
  // OData 4 adapter
  var webApiOData4Ctor = function () {
    this.name = "webApiOData4";
  }
  breeze.core.extend(webApiOData4Ctor.prototype, webApiODataCtor.prototype);
  webApiOData4Ctor.prototype.initialize = function () {
    // Aargh... they moved the cheese.
    var datajs = core.requireLib("datajs", "Needed to support remote OData v4 services");
    OData = datajs.V4.oData;
    OData.json.jsonHandler.recognizeDates = true;
  };
  webApiOData4Ctor.prototype.headers = { "OData-Version": "4.0" };
  breeze.config.registerAdapter("dataService", webApiOData4Ctor);

}));<|endoftext|>


<commit_before>/**
 * This is the only object exported by this package. It contains all it's the API.
 * @locus anywhere
 * @namespace
 */
IsoRouter = {}

/**
 * All routes are saved inside this object
 * @locus anywhere
 * @type {array.<Route>}
 */
IsoRouter.routes = []

/**
 * The route the client is currently on. This is a reac
 * @locus anywhere
 * @type {ReactiveVar.<Route>}
 */
IsoRouter.currentRoute = new ReactiveVar()

/**
 * The prototype of all routes. You can access it to set global defaults. See {@link Route.enter}, {@link Route.action} and {@link Route.exit}.
 * @locus anywhere
 * @type {object}
 */
IsoRouter.Route = Route

/**
 * With this function you can navigate to a cerain URL. You can't simply do a `location.href = '/internal/url'`. This will initiate a new HTTP request to that location. You have to go through this function. Server-side, this function does a [302]{@link https://tools.ietf.org/html/rfc7231#section-6.4.3} _redirect_. So you could use it for a moved page. When you URL schema changes you can simply use this to dynamically redirect the client. The URL is passed to the client by setting the `Location`-header. The function is called asynchronously using `_.defer`.
 * @function
 * @locus anywhere
 * @param {string} url - The url to navigate to
 * @param {301|302|307} [statusCode=302] - HTTP status code to respond with.
 */
IsoRouter.navigate = navigate

/**
 * Creates a new iso-router route. It's created with {@link Route} as its prototype.
 * @locus client
 * @param {string} path - The path-to-regex path the route is for
 * @return {Route} The newly created route
 */
IsoRouter.route = function isoRouterRoute (path) {
  return (
    this.routes[this.routes.push(Object.create(Route)) - 1]
      .set('path', path)
      .set('pathRegex', function () {
        this.parameters = []
        return pathToRegexp(path, this.parameters)
      })
  )
}

/**
 * Returns the first route with a path matching the passed url.
 * @locus anywhere
 * @param {string} - A relative url
 * @return {Route} The matching route
 */
IsoRouter.getRouteForUrl = function isoRouterGetRouteForUrl (url) {
  return _.last(
    _.filter(
      this.routes,
      caller('match', getCleanPath(url))
    )
  )
}

IsoRouter.exit = function () {
  if(this.currentRoute.get()) this.currentRoute.get().callAll('exit')
}

IsoRouter.location = function IsoRouterLocation (req) {
  return (req && req.url) || location.href
}

/**
 * Serves a route. It first sets all connectHandle properties (req, res, next). Then it get's the current location and a matching route. If there's not route for the url `next` is called.
 * @locus anywhere
 * @return {{ ?req: connectHandle.req, ?res: connectHandle.res, ?next: connectHandle.next }} IsoRouter
 */
IsoRouter.serve = function isoRouterServe () {
  if(Meteor.isClient) setParams({}, this)
  var location = this.location(this.req)
  var currentRoute = this.getRouteForUrl(location)
  this.currentRoute.set(currentRoute)
  if(!currentRoute) return this.next()
  setParams(this, currentRoute)
  currentRoute.parameters = currentRoute.match(location)
  this.currentRoute.set(currentRoute)
  currentRoute
    .callAll('enter', currentRoute.parameters)
    .call('action', currentRoute.parameters)
  return this
}

eventTarget.addEventListener(
  'load',
  IsoRouter.serve.bind(IsoRouter)
)

eventTarget.addEventListener(
  'isoRouter-enter',
  IsoRouter.exit.bind(IsoRouter)
)

eventTarget.addEventListener(
  'isoRouter-navigate',
  IsoRouter.serve.bind(IsoRouter)
)

if(Meteor.isServer) {
  /* Install the global listener */
  WebApp.connectHandlers.use(function (req, res, next) {
    setParams(arguments, IsoRouter)
    IsoRouter.navigate()
  })
}
<commit_msg>Bug fix in regex for parameters<commit_after>/**
 * This is the only object exported by this package. It contains all it's the API.
 * @locus anywhere
 * @namespace
 */
IsoRouter = {}

/**
 * All routes are saved inside this object
 * @locus anywhere
 * @type {array.<Route>}
 */
IsoRouter.routes = []

/**
 * The route the client is currently on. This is a reac
 * @locus anywhere
 * @type {ReactiveVar.<Route>}
 */
IsoRouter.currentRoute = new ReactiveVar()

/**
 * The prototype of all routes. You can access it to set global defaults. See {@link Route.enter}, {@link Route.action} and {@link Route.exit}.
 * @locus anywhere
 * @type {object}
 */
IsoRouter.Route = Route

/**
 * With this function you can navigate to a cerain URL. You can't simply do a `location.href = '/internal/url'`. This will initiate a new HTTP request to that location. You have to go through this function. Server-side, this function does a [302]{@link https://tools.ietf.org/html/rfc7231#section-6.4.3} _redirect_. So you could use it for a moved page. When you URL schema changes you can simply use this to dynamically redirect the client. The URL is passed to the client by setting the `Location`-header. The function is called asynchronously using `_.defer`.
 * @function
 * @locus anywhere
 * @param {string} url - The url to navigate to
 * @param {301|302|307} [statusCode=302] - HTTP status code to respond with.
 */
IsoRouter.navigate = navigate

/**
 * Creates a new iso-router route. It's created with {@link Route} as its prototype.
 * @locus client
 * @param {string} path - The path-to-regex path the route is for
 * @return {Route} The newly created route
 */
IsoRouter.route = function isoRouterRoute (path) {
  return (
    this.routes[this.routes.push(Object.create(Route)) - 1]
      .set('path', path)
      .set('pathRegex', function () {
        this.parameters = []
        return pathToRegexp(path, this.parameters)
      })
  )
}

/**
 * Returns the first route with a path matching the passed url.
 * @locus anywhere
 * @param {string} - A relative url
 * @return {Route} The matching route
 */
IsoRouter.getRouteForUrl = function isoRouterGetRouteForUrl (url) {
  return _.last(
    _.filter(
      this.routes,
      caller('match', getCleanPath(url))
    )
  )
}

IsoRouter.exit = function () {
  if(this.currentRoute.get()) this.currentRoute.get().callAll('exit')
}

IsoRouter.location = function IsoRouterLocation (req) {
  return (req && req.url) || location.href
}

/**
 * Serves a route. It first sets all connectHandle properties (req, res, next). Then it get's the current location and a matching route. If there's not route for the url `next` is called.
 * @locus anywhere
 * @return {{ ?req: connectHandle.req, ?res: connectHandle.res, ?next: connectHandle.next }} IsoRouter
 */
IsoRouter.serve = function isoRouterServe () {
  if(Meteor.isClient) setParams({}, this)
  var location = this.location(this.req)
  var currentRoute = this.getRouteForUrl(location)
  this.currentRoute.set(currentRoute)
  if(!currentRoute) return this.next()
  setParams(this, currentRoute)
  currentRoute.parameters = currentRoute.match(getCleanPath(location))
  this.currentRoute.set(currentRoute)
  currentRoute
    .callAll('enter', currentRoute.parameters)
    .call('action', currentRoute.parameters)
  return this
}

eventTarget.addEventListener(
  'load',
  IsoRouter.serve.bind(IsoRouter)
)

eventTarget.addEventListener(
  'isoRouter-enter',
  IsoRouter.exit.bind(IsoRouter)
)

eventTarget.addEventListener(
  'isoRouter-navigate',
  IsoRouter.serve.bind(IsoRouter)
)

if(Meteor.isServer) {
  /* Install the global listener */
  WebApp.connectHandlers.use(function (req, res, next) {
    setParams(arguments, IsoRouter)
    IsoRouter.navigate()
  })
}
<|endoftext|>


<commit_before>const { IndexedCramFile, CraiIndex } = cjsRequire('@gmod/cram/src')
const { Buffer } = cjsRequire('buffer')

define( [
            'dojo/_base/declare',
            'JBrowse/Store/SeqFeature',
            'JBrowse/Store/DeferredStatsMixin',
            'JBrowse/Store/DeferredFeaturesMixin',
            'JBrowse/Store/SeqFeature/GlobalStatsEstimationMixin',
            'JBrowse/Model/XHRBlob',
            'JBrowse/Model/SimpleFeature',
        ],
        function(
            declare,
            SeqFeatureStore,
            DeferredStatsMixin,
            DeferredFeaturesMixin,
            GlobalStatsEstimationMixin,
            XHRBlob,
            SimpleFeature,
        ) {

// wrapper class to make old JBrowse *Blob data access classes work with
// the new-style filehandle API expected by the cram code

class BlobWrapper {
    constructor(oldStyleBlob) {
        this.blob = oldStyleBlob
    }

    read(buffer, offset = 0, length, position) {
        return new Promise((resolve,reject) => {
            this.blob.read(
                position,
                length,
                dataArrayBuffer => {
                    const data = Buffer.from(dataArrayBuffer)
                    data.copy(buffer, offset)
                    resolve()
                },
                reject,
            )
        })
    }

    readFile() {
        return new Promise((resolve, reject) => {
            this.blob.fetch( dataArrayBuffer => {
                resolve(Buffer.from(dataArrayBuffer))
            }, reject)
        })
    }

    stat() {
        return new Promise((resolve, reject) => {
            this.blob.stat(resolve, reject)
        })
    }
}

return declare( [ SeqFeatureStore, DeferredStatsMixin, DeferredFeaturesMixin, GlobalStatsEstimationMixin ],

/**
 * @lends JBrowse.Store.SeqFeature.CRAM
 */
{
    /**
     * Data backend for reading feature data directly from a
     * web-accessible CRAM file.
     *
     * @constructs
     */
    constructor: function( args ) {
        const cramArgs = {}

        let dataBlob
        if (args.cram)
            dataBlob = args.cram
        else if (args.urlTemplate)
            dataBlob = new XHRBlob(this.resolveUrl(args.urlTemplate || 'data.cram'))
        else throw new Error('must provide either `cram` or `urlTemplate`')
        cramArgs.cramFilehandle = new BlobWrapper(dataBlob)

        if (args.crai)
            cramArgs.index = new CraiIndex({ filehandle: new BlobWrapper(args.crai)})
        else if (args.craiUrlTemplate)
            cramArgs.index = new CraiIndex({filehandle: new BlobWrapper(new XHRBlob(this.resolveUrl(args.craiUrlTemplate)))})
        else if (args.urlTemplate)
            cramArgs.index = new CraiIndex({filehandle: new BlobWrapper(new XHRBlob(this.resolveUrl(args.urlTemplate+'.crai')))})
        else throw new Error('no index provided, must provide a CRAM index')
        // TODO: need to add .csi index support

        cramArgs.seqFetch = this._seqFetch.bind(this)
        cramArgs.checkSequenceMD5 = false

        this.cram = new IndexedCramFile(cramArgs)

        this.source = ( dataBlob.url  ? dataBlob.url.match( /\/([^/\#\?]+)($|[\#\?])/ )[1] :
                        dataBlob.blob ? dataBlob.blob.name : undefined ) || undefined;

        // pre-download the index before running the statistics estimation so that the stats
        // estimation doesn't time out
        this.cram.hasDataForReferenceSequence(0)
            .then( () => {
                this._deferred.features.resolve({success:true});
            })
            .then( () => this._estimateGlobalStats())
            .then( stats => {
                this.globalStats = stats;
                this._deferred.stats.resolve({success:true});
            })
            .catch(err => {
                this._deferred.features.reject(err)
                this._deferred.stats.reject(err)
            })

        this.storeTimeout = args.storeTimeout || 3000;
    },

    _getRefSeqStore() {
        return new Promise((resolve,reject) => {
            this.browser.getStore('refseqs',resolve,reject)
        })
    },

    // used by the CRAM backend to fetch a region of the underlying reference
    // sequence.  needed for some of its calculations
    async _seqFetch(seqId, start, end) {
        start -= 1 // convert from 1-based closed to interbase

        const refSeqStore = await this._getRefSeqStore()
        if (!refSeqStore) return undefined
        const refName = this._refIdToName(seqId)
        if (!refName) return undefined

        const seqChunks = await new Promise((resolve,reject) => {
            let features = []
            refSeqStore.getFeatures(
                {ref: refName, start: start-1, end},
                f => features.push(f),
                () => resolve(features),
                reject
            )
        })

        const trimmed = []
        seqChunks
        .sort((a,b) => a.get('start') - b.get('start'))
        .forEach( (chunk,i) => {
            let chunkStart = chunk.get('start')
            let chunkEnd = chunk.get('end')
            let trimStart = Math.max(start - chunkStart, 0)
            let trimEnd = Math.min(end - chunkStart, chunkEnd-chunkStart)
            let trimLength = trimEnd - trimStart
            let chunkSeq = chunk.get('seq') || chunk.get('residues')
            trimmed.push(chunkSeq.substr(trimStart,trimLength))
        })

        const sequence = trimmed.join('')
        if (sequence.length !== (end-start))
            throw new Error('sequence fetch sanity check failed')
        return sequence
    },

    _refNameToId(refName) {
        return this.browser.getRefSeqNumber(refName)
    },

    _refIdToName(refId) {
        let ref = this.browser.getRefSeqById(refId)
        return ref ? ref.name : undefined
    },

    /**
     * Interrogate whether a store has data for a given reference
     * sequence.  Calls the given callback with either true or false.
     *
     * Implemented as a binary interrogation because some stores are
     * smart enough to regularize reference sequence names, while
     * others are not.
     */
    hasRefSeq: function( seqName, callback, errorCallback ) {
        seqName = this.browser.regularizeReferenceName( seqName );
        const refSeqNumber = this._refNameToId(seqName)
        if (refSeqNumber === undefined) callback(false)

        this._deferred.stats
        .then(() => this.cram.hasDataForReferenceSequence(refSeqNumber))
        .then(callback, errorCallback)
    },

    // called by getFeatures from the DeferredFeaturesMixin
    _getFeatures: function( query, featCallback, endCallback, errorCallback ) {
        //this.bam.fetch( query.ref ? query.ref : this.refSeq.name, query.start, query.end, featCallback, endCallback, errorCallback );
        const seqName = query.ref || this.refSeq.name
        const refSeqNumber = this._refNameToId(seqName)
        if (refSeqNumber === undefined) {
            endCallback()
            return
        }

        this.cram.getRecordsForRange(refSeqNumber, query.start + 1, query.end)
            .then(records => {
                for (let i = 0; i < records.length; i+= 1) {
                    featCallback(this._cramRecordToFeature(records[i]))
                }

                endCallback()
            })
            .catch(errorCallback)
    },

    _cramRecordToFeature(record) {
        const data = {
            name: record.readName,
            start: record.alignmentStart-1,
            end: record.alignmentStart+record.lengthOnRef-1,
            cram_read_features: record.readFeatures,
            type: 'match',
            MQ: record.mappingQuality,
            flags: `0x${record.flags.toString(16)}`,
            cramFlags: `0x${record.cramFlags.toString(16)}`,
            strand: record.isReverseComplemented() ? -1 : 1,
            qual: (record.qualityScores || []).map(q => q+33).join(' '),
            seq: record.readBases,
            seq_id: this._refIdToName(record.sequenceId),

            qc_failed: record.isFailedQc(),
            secondary_alignment: record.isSecondary(),
            supplementary_alignment: record.isSupplementary(),
            multi_segment_template: record.isPaired(),
            multi_segment_all_correctly_aligned: record.isProperlyPaired(),
            multi_segment_next_segment_unmapped: record.isMateUnmapped(),
            multi_segment_first: record.isRead1(),
            multi_segment_last: record.isRead2(),
            multi_segment_next_segment_reversed: record.isMateReverseComplemented(),
            unmapped: record.isSegmentUnmapped(),
            next_seq_id: record.mate ? this._refIdToName(record.mate.sequenceId) : undefined,
            next_segment_position: record.mate
                ? ( this._refIdToName(record.mate.sequenceId)+':'+record.mate.alignmentStart) : undefined,
        }
        Object.assign(data,record.tags || {})
        return new SimpleFeature({
            data,
            id: record.uniqueId
        })
    },

    saveStore: function() {
        return {
            urlTemplate: this.config.bam.url,
            baiUrlTemplate: this.config.bai.url
        };
    }

});
});
<commit_msg>use info in the CRAM file's SAM header to figure out sequence IDs<commit_after>const { IndexedCramFile, CraiIndex } = cjsRequire('@gmod/cram/src')
const { Buffer } = cjsRequire('buffer')

define( [
            'dojo/_base/declare',
            'JBrowse/Store/SeqFeature',
            'JBrowse/Store/DeferredStatsMixin',
            'JBrowse/Store/DeferredFeaturesMixin',
            'JBrowse/Store/SeqFeature/GlobalStatsEstimationMixin',
            'JBrowse/Model/XHRBlob',
            'JBrowse/Model/SimpleFeature',
        ],
        function(
            declare,
            SeqFeatureStore,
            DeferredStatsMixin,
            DeferredFeaturesMixin,
            GlobalStatsEstimationMixin,
            XHRBlob,
            SimpleFeature,
        ) {

// wrapper class to make old JBrowse *Blob data access classes work with
// the new-style filehandle API expected by the cram code

class BlobWrapper {
    constructor(oldStyleBlob) {
        this.blob = oldStyleBlob
    }

    read(buffer, offset = 0, length, position) {
        return new Promise((resolve,reject) => {
            this.blob.read(
                position,
                length,
                dataArrayBuffer => {
                    const data = Buffer.from(dataArrayBuffer)
                    data.copy(buffer, offset)
                    resolve()
                },
                reject,
            )
        })
    }

    readFile() {
        return new Promise((resolve, reject) => {
            this.blob.fetch( dataArrayBuffer => {
                resolve(Buffer.from(dataArrayBuffer))
            }, reject)
        })
    }

    stat() {
        return new Promise((resolve, reject) => {
            this.blob.stat(resolve, reject)
        })
    }
}

return declare( [ SeqFeatureStore, DeferredStatsMixin, DeferredFeaturesMixin, GlobalStatsEstimationMixin ],

/**
 * @lends JBrowse.Store.SeqFeature.CRAM
 */
{
    /**
     * Data backend for reading feature data directly from a
     * web-accessible CRAM file.
     *
     * @constructs
     */
    constructor: function( args ) {
        const cramArgs = {}

        let dataBlob
        if (args.cram)
            dataBlob = args.cram
        else if (args.urlTemplate)
            dataBlob = new XHRBlob(this.resolveUrl(args.urlTemplate || 'data.cram'))
        else throw new Error('must provide either `cram` or `urlTemplate`')
        cramArgs.cramFilehandle = new BlobWrapper(dataBlob)

        if (args.crai)
            cramArgs.index = new CraiIndex({ filehandle: new BlobWrapper(args.crai)})
        else if (args.craiUrlTemplate)
            cramArgs.index = new CraiIndex({filehandle: new BlobWrapper(new XHRBlob(this.resolveUrl(args.craiUrlTemplate)))})
        else if (args.urlTemplate)
            cramArgs.index = new CraiIndex({filehandle: new BlobWrapper(new XHRBlob(this.resolveUrl(args.urlTemplate+'.crai')))})
        else throw new Error('no index provided, must provide a CRAM index')
        // TODO: need to add .csi index support

        cramArgs.seqFetch = this._seqFetch.bind(this)
        cramArgs.checkSequenceMD5 = false

        this.cram = new IndexedCramFile(cramArgs)

        this.source = ( dataBlob.url  ? dataBlob.url.match( /\/([^/\#\?]+)($|[\#\?])/ )[1] :
                        dataBlob.blob ? dataBlob.blob.name : undefined ) || undefined;

        // pre-download the index before running the statistics estimation so that the stats
        // estimation doesn't time out
        this.cram.hasDataForReferenceSequence(0)
            .then(() => this.cram.cram.getSamHeader())
            .then(samHeader => {
                this._setSamHeader(samHeader)
            })
            .then(() => {
                this._deferred.features.resolve({success:true});
            })
            .then(() => this._estimateGlobalStats())
            .then(stats => {
                this.globalStats = stats;
                this._deferred.stats.resolve({success:true});
            })
            .catch(err => {
                this._deferred.features.reject(err)
                this._deferred.stats.reject(err)
            })

        this.storeTimeout = args.storeTimeout || 3000;
    },

    // process the parsed SAM header from the cram file
    _setSamHeader(samHeader) {
        this._samHeader = {}

        // use the @SQ lines in the header to figure out the
        // mapping between ref seq ID numbers and names
        const refSeqIdToName = []
        const refSeqNameToId = {}
        const sqLines = samHeader.filter(l => l.tag === 'SQ')
        sqLines.forEach((sqLine, seqId) => {
            sqLine.data.forEach(item => {
                if (item.tag === 'SN') {
                    // this is the seq name
                    const seqName = item.value
                    refSeqNameToId[seqName] = seqId
                    refSeqIdToName[seqId] = seqName
                }
            })
        })
        if (refSeqIdToName.length) {
            this._samHeader.refSeqIdToName = refSeqIdToName
            this._samHeader.refSeqNameToId = refSeqNameToId
        }
    },

    _refNameToId(refName) {
        // use info from the SAM header if possible, but fall back to using
        // the ref seq order from when the browser's refseqs were loaded
        if (this._samHeader.refSeqNameToId)
            return this._samHeader.refSeqNameToId[refName]
        else
            return this.browser.getRefSeqNumber(refName)
    },

    _refIdToName(refId) {
        // use info from the SAM header if possible, but fall back to using
        // the ref seq order from when the browser's refseqs were loaded
        if (this._samHeader.refSeqIdToName) {
            return this._samHeader.refSeqIdToName[refId]
        } else {
            let ref = this.browser.getRefSeqById(refId)
            return ref ? ref.name : undefined
        }
    },

    _getRefSeqStore() {
        return new Promise((resolve,reject) => {
            this.browser.getStore('refseqs',resolve,reject)
        })
    },

    // used by the CRAM backend to fetch a region of the underlying reference
    // sequence.  needed for some of its calculations
    async _seqFetch(seqId, start, end) {
        start -= 1 // convert from 1-based closed to interbase

        const refSeqStore = await this._getRefSeqStore()
        if (!refSeqStore) return undefined
        const refName = this._refIdToName(seqId)
        if (!refName) return undefined

        const seqChunks = await new Promise((resolve,reject) => {
            let features = []
            refSeqStore.getFeatures(
                {ref: refName, start: start-1, end},
                f => features.push(f),
                () => resolve(features),
                reject
            )
        })

        const trimmed = []
        seqChunks
        .sort((a,b) => a.get('start') - b.get('start'))
        .forEach( (chunk,i) => {
            let chunkStart = chunk.get('start')
            let chunkEnd = chunk.get('end')
            let trimStart = Math.max(start - chunkStart, 0)
            let trimEnd = Math.min(end - chunkStart, chunkEnd-chunkStart)
            let trimLength = trimEnd - trimStart
            let chunkSeq = chunk.get('seq') || chunk.get('residues')
            trimmed.push(chunkSeq.substr(trimStart,trimLength))
        })

        const sequence = trimmed.join('')
        if (sequence.length !== (end-start))
            throw new Error('sequence fetch sanity check failed')
        return sequence
    },

    /**
     * Interrogate whether a store has data for a given reference
     * sequence.  Calls the given callback with either true or false.
     *
     * Implemented as a binary interrogation because some stores are
     * smart enough to regularize reference sequence names, while
     * others are not.
     */
    hasRefSeq: function( seqName, callback, errorCallback ) {
        seqName = this.browser.regularizeReferenceName( seqName );
        const refSeqNumber = this._refNameToId(seqName)
        if (refSeqNumber === undefined) callback(false)

        this._deferred.stats
        .then(() => this.cram.hasDataForReferenceSequence(refSeqNumber))
        .then(callback, errorCallback)
    },

    // called by getFeatures from the DeferredFeaturesMixin
    _getFeatures: function( query, featCallback, endCallback, errorCallback ) {
        //this.bam.fetch( query.ref ? query.ref : this.refSeq.name, query.start, query.end, featCallback, endCallback, errorCallback );
        const seqName = query.ref || this.refSeq.name
        const refSeqNumber = this._refNameToId(seqName)
        if (refSeqNumber === undefined) {
            endCallback()
            return
        }

        this.cram.getRecordsForRange(refSeqNumber, query.start + 1, query.end)
            .then(records => {
                for (let i = 0; i < records.length; i+= 1) {
                    featCallback(this._cramRecordToFeature(records[i]))
                }

                endCallback()
            })
            .catch(errorCallback)
    },

    _cramRecordToFeature(record) {
        const data = {
            name: record.readName,
            start: record.alignmentStart-1,
            end: record.alignmentStart+record.lengthOnRef-1,
            cram_read_features: record.readFeatures,
            type: 'match',
            MQ: record.mappingQuality,
            flags: `0x${record.flags.toString(16)}`,
            cramFlags: `0x${record.cramFlags.toString(16)}`,
            strand: record.isReverseComplemented() ? -1 : 1,
            qual: (record.qualityScores || []).map(q => q+33).join(' '),
            seq: record.readBases,
            seq_id: this._refIdToName(record.sequenceId),

            qc_failed: record.isFailedQc(),
            secondary_alignment: record.isSecondary(),
            supplementary_alignment: record.isSupplementary(),
            multi_segment_template: record.isPaired(),
            multi_segment_all_correctly_aligned: record.isProperlyPaired(),
            multi_segment_next_segment_unmapped: record.isMateUnmapped(),
            multi_segment_first: record.isRead1(),
            multi_segment_last: record.isRead2(),
            multi_segment_next_segment_reversed: record.isMateReverseComplemented(),
            unmapped: record.isSegmentUnmapped(),
            next_seq_id: record.mate ? this._refIdToName(record.mate.sequenceId) : undefined,
            next_segment_position: record.mate
                ? ( this._refIdToName(record.mate.sequenceId)+':'+record.mate.alignmentStart) : undefined,
        }
        Object.assign(data,record.tags || {})
        return new SimpleFeature({
            data,
            id: record.uniqueId
        })
    },

    saveStore: function() {
        return {
            urlTemplate: this.config.bam.url,
            baiUrlTemplate: this.config.bai.url
        };
    }

});
});
<|endoftext|>


<commit_before>var page = require('webpage').create();
page.viewportSize = page.clipRect = { width: 1024, height: 768 };
page.clipRect.left = page.clipRect.top = 0;
page.open('http://github.com/', function() {
    page.render('github.clip.png');
    phantom.exit();
});<commit_msg>easy viewport awesomeness<commit_after>var page = require('webpage').create();

page.viewportSize = page.clipRect = {
    width: 1024, height: 768
};

page.open('http://github.com/', function() {
    page.render('github.clip.png');
    phantom.exit();
});<|endoftext|>