text: // Query String Utilities
  // Taken from Jack
  
  var DEFAULT_SEP = "&";
  var DEFAULT_EQ = "=";
  
  exports.unescape = function (str, decodeSpaces) {
      return decodeURIComponent(decodeSpaces ? str.replace(/\+/g, " ") : str);
  };
  
  exports.escape = function (str) {
      return encodeURIComponent(str);
  };
  
  exports.toQueryString = function (obj, sep, eq, name) {
      sep = sep || DEFAULT_SEP;
      eq = eq || DEFAULT_EQ;
  	if (isA(obj, null) || isA(obj, undefined)) {
  		return name ? encodeURIComponent(name) + eq : '';
  	}
  	if (isNumber(obj) || isString(obj)) {
  		return encodeURIComponent(name) + eq + encodeURIComponent(obj);
  	}
  	if (isA(obj, [])) {
  		var s = [];
  		name = name+'[]';
  		for (var i = 0, l = obj.length; i < l; i ++) {
  			s.push( exports.toQueryString(obj[i], sep, eq, name) );
  		}
  		return s.join(sep);
  	}
  	// now we know it's an object.
  	var s = [];
  	var begin = name ? name + '[' : '';
  	var end = name ? ']' : '';
  	for (var i in obj) if (obj.hasOwnProperty(i)) {
  		var n = begin + i + end;
  		s.push(exports.toQueryString(obj[i], sep, eq, n));
  	}
  	return s.join(sep);
  };
  
  exports.parseQuery = function(qs, sep, eq) {
  	return qs
  		.split(sep||DEFAULT_SEP)
  		.map(pieceParser(eq||DEFAULT_EQ))
  		.reduce(mergeParams);
  };
  
  // Parse a key=val string.
  // These can get pretty hairy
  // example flow:
  // parse(foo[bar][][bla]=baz)
  // return parse(foo[bar][][bla],"baz")
  // return parse(foo[bar][], {bla : "baz"})
  // return parse(foo[bar], [{bla:"baz"}])
  // return parse(foo, {bar:[{bla:"baz"}]})
  // return {foo:{bar:[{bla:"baz"}]}}
  var pieceParser = function (eq) {
      return function parsePiece (key, val) {
  		if (arguments.length !== 2) {
  			// key=val, called from the map/reduce
  		 	key = key.split(eq);
  			return parsePiece(
  			    exports.unescape(key.shift(), true), exports.unescape(key.join(eq), true)
  			);
  		}
  		key = key.replace(/^\s+|\s+$/g, '');
  	    if (isString(val)) val = val.replace(/^\s+|\s+$/g, '');
  		var sliced = /(.*)\[([^\]]*)\]$/.exec(key);
  		if (!sliced) {
  			var ret = {};
  			if (key) ret[key] = val;
  			return ret;
  		}
  		// ["foo[][bar][][baz]", "foo[][bar][]", "baz"]
  		var tail = sliced[2], head = sliced[1];
  
  		// array: key[]=val
  		if (!tail) return parsePiece(head, [val]);
  
  		// obj: key[subkey]=val
  		var ret = {};
  		ret[tail] = val;
  		return parsePiece(head, ret);
  	};
  };
  
  // the reducer function that merges each query piece together into one set of params
  function mergeParams (params, addition) {
  	return (
  		// if it's uncontested, then just return the addition.
  		(!params) ? addition
  		// if the existing value is an array, then concat it.
  		: (isA(params, [])) ? params.concat(addition)
  		// if the existing value is not an array, arrayify it.
  		: (!isA(params, {}) || !isA(addition, {})) ? [params].concat(addition)
  		// else merge them as objects, which is a little more complex
  		: mergeObjects(params, addition)
  	)
  };
  
  // Merge two *objects* together. If this is called, we've already ruled
  // out the simple cases, and need to do the for-in business.
  function mergeObjects (params, addition) {
  	for (var i in addition) if (i && addition.hasOwnProperty(i)) {
  		params[i] = mergeParams(params[i], addition[i]);
  	}
  	return params;
  };
  
  // duck typing
  function isA (thing, canon) {
  	return (
  		// truthiness. you can feel it in your gut.
  		(!thing === !canon)
  		// typeof is usually "object"
  		&& typeof(thing) === typeof(canon)
  		// check the constructor
  		&& Object.prototype.toString.call(thing) === Object.prototype.toString.call(canon)
  	);
  };
  function isNumber (thing) {
  	return typeof(thing) === "number" && isFinite(thing);
  };
  function isString (thing) {
  	return typeof(thing) === "string";
  };