Skip to content

Commit

Permalink
Added client-side support. Closes tj#33
Browse files Browse the repository at this point in the history
  • Loading branch information
tj committed Feb 29, 2012
1 parent 587d699 commit 35ddd4c
Show file tree
Hide file tree
Showing 12 changed files with 14,205 additions and 13 deletions.
4 changes: 3 additions & 1 deletion Makefile
@@ -1,7 +1,9 @@

querystring.js: lib/head.js lib/querystring.js lib/tail.js
cat $^ > $@

test:
@./node_modules/.bin/mocha \
--require should \
--ui bdd

.PHONY: test
1 change: 1 addition & 0 deletions lib/head.js
@@ -0,0 +1 @@
(function(exports){
1 change: 1 addition & 0 deletions lib/tail.js
@@ -0,0 +1 @@
})('undefined' == typeof exports ? qs = {} : exports);
265 changes: 265 additions & 0 deletions querystring.js
@@ -0,0 +1,265 @@
(function(exports){
/*!
* querystring
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/

/**
* Library version.
*/

exports.version = '0.4.2';

/**
* Object#toString() ref for stringify().
*/

var toString = Object.prototype.toString;

/**
* Cache non-integer test regexp.
*/

var isint = /^[0-9]+$/;

function promote(parent, key) {
if (parent[key].length == 0) return parent[key] = {};
var t = {};
for (var i in parent[key]) t[i] = parent[key][i];
parent[key] = t;
return t;
}

function parse(parts, parent, key, val) {
var part = parts.shift();
// end
if (!part) {
if (Array.isArray(parent[key])) {
parent[key].push(val);
} else if ('object' == typeof parent[key]) {
parent[key] = val;
} else if ('undefined' == typeof parent[key]) {
parent[key] = val;
} else {
parent[key] = [parent[key], val];
}
// array
} else {
var obj = parent[key] = parent[key] || [];
if (']' == part) {
if (Array.isArray(obj)) {
if ('' != val) obj.push(val);
} else if ('object' == typeof obj) {
obj[Object.keys(obj).length] = val;
} else {
obj = parent[key] = [parent[key], val];
}
// prop
} else if (~part.indexOf(']')) {
part = part.substr(0, part.length - 1);
if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
parse(parts, obj, part, val);
// key
} else {
if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
parse(parts, obj, part, val);
}
}
}

/**
* Merge parent key/val pair.
*/

function merge(parent, key, val){
if (~key.indexOf(']')) {
var parts = key.split('[')
, len = parts.length
, last = len - 1;
parse(parts, parent, 'base', val);
// optimize
} else {
if (!isint.test(key) && Array.isArray(parent.base)) {
var t = {};
for (var k in parent.base) t[k] = parent.base[k];
parent.base = t;
}
set(parent.base, key, val);
}

return parent;
}

/**
* Parse the given obj.
*/

function parseObject(obj){
var ret = { base: {} };
Object.keys(obj).forEach(function(name){
merge(ret, name, obj[name]);
});
return ret.base;
}

/**
* Parse the given str.
*/

function parseString(str){
return String(str)
.split('&')
.reduce(function(ret, pair){
try{
pair = decodeURIComponent(pair.replace(/\+/g, ' '));
} catch(e) {
// ignore
}

var eql = pair.indexOf('=')
, brace = lastBraceInKey(pair)
, key = pair.substr(0, brace || eql)
, val = pair.substr(brace || eql, pair.length)
, val = val.substr(val.indexOf('=') + 1, val.length);

// ?foo
if ('' == key) key = pair, val = '';

return merge(ret, key, val);
}, { base: {} }).base;
}

/**
* Parse the given query `str` or `obj`, returning an object.
*
* @param {String} str | {Object} obj
* @return {Object}
* @api public
*/

exports.parse = function(str){
if (null == str || '' == str) return {};
return 'object' == typeof str
? parseObject(str)
: parseString(str);
};

/**
* Turn the given `obj` into a query string
*
* @param {Object} obj
* @return {String}
* @api public
*/

var stringify = exports.stringify = function(obj, prefix) {
if (Array.isArray(obj)) {
return stringifyArray(obj, prefix);
} else if ('[object Object]' == toString.call(obj)) {
return stringifyObject(obj, prefix);
} else if ('string' == typeof obj) {
return stringifyString(obj, prefix);
} else {
return prefix + '=' + obj;
}
};

/**
* Stringify the given `str`.
*
* @param {String} str
* @param {String} prefix
* @return {String}
* @api private
*/

function stringifyString(str, prefix) {
if (!prefix) throw new TypeError('stringify expects an object');
return prefix + '=' + encodeURIComponent(str);
}

/**
* Stringify the given `arr`.
*
* @param {Array} arr
* @param {String} prefix
* @return {String}
* @api private
*/

function stringifyArray(arr, prefix) {
var ret = [];
if (!prefix) throw new TypeError('stringify expects an object');
for (var i = 0; i < arr.length; i++) {
ret.push(stringify(arr[i], prefix + '['+i+']'));
}
return ret.join('&');
}

/**
* Stringify the given `obj`.
*
* @param {Object} obj
* @param {String} prefix
* @return {String}
* @api private
*/

function stringifyObject(obj, prefix) {
var ret = []
, keys = Object.keys(obj)
, key;

for (var i = 0, len = keys.length; i < len; ++i) {
key = keys[i];
ret.push(stringify(obj[key], prefix
? prefix + '[' + encodeURIComponent(key) + ']'
: encodeURIComponent(key)));
}

return ret.join('&');
}

/**
* Set `obj`'s `key` to `val` respecting
* the weird and wonderful syntax of a qs,
* where "foo=bar&foo=baz" becomes an array.
*
* @param {Object} obj
* @param {String} key
* @param {String} val
* @api private
*/

function set(obj, key, val) {
var v = obj[key];
if (undefined === v) {
obj[key] = val;
} else if (Array.isArray(v)) {
v.push(val);
} else {
obj[key] = [v, val];
}
}

/**
* Locate last brace in `str` within the key.
*
* @param {String} str
* @return {Number}
* @api private
*/

function lastBraceInKey(str) {
var len = str.length
, brace
, c;
for (var i = 0; i < len; ++i) {
c = str[i];
if (']' == c) brace = false;
if ('[' == c) brace = true;
if ('=' == c && !brace) return i;
}
}
})('undefined' == typeof exports ? qs = {} : exports);

0 comments on commit 35ddd4c

Please sign in to comment.