-
Notifications
You must be signed in to change notification settings - Fork 29.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add URL and QueryString modules, and tests for each.
Also, make a slight change from original on url-module to put the spacePattern into the function. On closer inspection, it turns out that the nonlocal-var cost is higher than the compiling-a-regexp cost. Also, documentation.
- Loading branch information
Showing
5 changed files
with
1,189 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1531,6 +1531,99 @@ require("path").exists("/etc/passwd", function (exists) { | |
------------------------------------ | ||
|
||
|
||
=== URL Module | ||
|
||
This module has utilities for URL resolution and parsing. | ||
|
||
Parsed URL objects have some or all of the following fields, depending on whether or not | ||
they exist in the URL string. Any parts that are not in the URL string will not be in the | ||
parsed object. Examples are shown for the URL +"http://user:[email protected]:8080/p/a/t/h?query=string#hash"+ | ||
|
||
+href+:: | ||
The full URL that was originally parsed. Example: +"http://user:[email protected]:8080/p/a/t/h?query=string#hash"+ | ||
|
||
+protocol+:: | ||
The request protocol. Example: +"http:"+ | ||
|
||
+host+:: | ||
The full host portion of the URL, including port and authentication information. Example: | ||
+"user:[email protected]:8080"+ | ||
|
||
+auth+:: | ||
The authentication information portion of a URL. Example: +"user:pass"+ | ||
|
||
+hostname+:: | ||
Just the hostname portion of the host. Example: +"host.com"+ | ||
|
||
+port+:: | ||
The port number portion of the host. Example: +"8080"+ | ||
|
||
+pathname+:: | ||
The path section of the URL, that comes after the host and before the query, including the | ||
initial slash if present. Example: +"/p/a/t/h"+ | ||
|
||
+search+:: | ||
The "query string" portion of the URL, including the leading question mark. Example: | ||
+"?query=string"+ | ||
|
||
+query+:: | ||
Either the "params" portion of the query string, or a querystring-parsed object. Example: | ||
+"query=string"+ or +{"query":"string"}+ | ||
|
||
+hash+:: | ||
The portion of the URL after the pound-sign. Example: +"#hash"+ | ||
|
||
The following methods are provided by the URL module: | ||
|
||
+url.parse(urlStr, parseQueryString=false)+:: | ||
Take a URL string, and return an object. Pass +true+ as the second argument to also parse | ||
the query string using the +querystring+ module. | ||
|
||
+url.format(urlObj)+:: | ||
Take a parsed URL object, and return a formatted URL string. | ||
|
||
+url.resolve(from, to)+:: | ||
Take a base URL, and a href URL, and resolve them as a browser would for an anchor tag. | ||
|
||
|
||
=== Query String Module | ||
|
||
This module provides utilities for dealing with query strings. It provides the following methods: | ||
|
||
+querystring.stringify(obj, sep="&", eq="=")+:: | ||
Serialize an object to a query string. Optionally override the default separator and assignment characters. | ||
Example: | ||
+ | ||
------------------------------------ | ||
node> require("querystring").stringify({foo:"bar", baz : {quux:"asdf", oof : "rab"}, boo:[1,2,3]}) | ||
"foo=bar&baz%5Bquux%5D=asdf&baz%5Boof%5D=rab&boo%5B%5D=1&boo%5B%5D=2&boo%5B%5D=3" | ||
------------------------------------ | ||
+ | ||
|
||
+querystring.parse(str, sep="&", eq="=")+:: | ||
Deserialize a query string to an object. Optionally override the default separator and assignment characters. | ||
+ | ||
------------------------------------ | ||
node> require("querystring").parse("foo=bar&baz%5Bquux%5D=asdf&baz%5Boof%5D=rab&boo%5B%5D=1") | ||
{ | ||
"foo": "bar", | ||
"baz": { | ||
"quux": "asdf", | ||
"oof": "rab" | ||
}, | ||
"boo": [ | ||
1 | ||
] | ||
} | ||
------------------------------------ | ||
+ | ||
|
||
+querystring.escape+ | ||
The escape function used by +querystring.stringify+, provided so that it could be overridden if necessary. | ||
|
||
+querystring.unescape+ | ||
The unescape function used by +querystring.parse+, provided so that it could be overridden if necessary. | ||
|
||
== REPL | ||
|
||
A Read-Eval-Print-Loop is available both as a standalone program and easily | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
// Query String Utilities | ||
|
||
var QueryString = exports; | ||
|
||
QueryString.unescape = function (str, decodeSpaces) { | ||
return decodeURIComponent(decodeSpaces ? str.replace(/\+/g, " ") : str); | ||
}; | ||
|
||
QueryString.escape = function (str) { | ||
return encodeURIComponent(str); | ||
}; | ||
|
||
|
||
var stack = []; | ||
/** | ||
* <p>Converts an arbitrary value to a Query String representation.</p> | ||
* | ||
* <p>Objects with cyclical references will trigger an exception.</p> | ||
* | ||
* @method stringify | ||
* @param obj {Variant} any arbitrary value to convert to query string | ||
* @param sep {String} (optional) Character that should join param k=v pairs together. Default: "&" | ||
* @param eq {String} (optional) Character that should join keys to their values. Default: "=" | ||
* @param name {String} (optional) Name of the current key, for handling children recursively. | ||
* @static | ||
*/ | ||
QueryString.stringify = function (obj, sep, eq, name) { | ||
sep = sep || "&"; | ||
eq = eq || "="; | ||
if (isA(obj, null) || isA(obj, undefined) || typeof(obj) === 'function') { | ||
return name ? encodeURIComponent(name) + eq : ''; | ||
} | ||
|
||
if (isBool(obj)) obj = +obj; | ||
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( QueryString.stringify(obj[i], sep, eq, name) ); | ||
} | ||
return s.join(sep); | ||
} | ||
// now we know it's an object. | ||
|
||
// Check for cyclical references in nested objects | ||
for (var i = stack.length - 1; i >= 0; --i) if (stack[i] === obj) { | ||
throw new Error("querystring.stringify. Cyclical reference"); | ||
} | ||
|
||
stack.push(obj); | ||
|
||
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(QueryString.stringify(obj[i], sep, eq, n)); | ||
} | ||
|
||
stack.pop(); | ||
|
||
s = s.join(sep); | ||
if (!s && name) return name + "="; | ||
return s; | ||
}; | ||
|
||
QueryString.parseQuery = QueryString.parse = function (qs, sep, eq) { | ||
return qs | ||
.split(sep||"&") | ||
.map(pieceParser(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 trimmerPattern = /^\s+|\s+$/g, | ||
slicerPattern = /(.*)\[([^\]]*)\]$/; | ||
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( | ||
QueryString.unescape(key.shift(), true), | ||
QueryString.unescape(key.join(eq), true) | ||
); | ||
} | ||
key = key.replace(trimmerPattern, ''); | ||
if (isString(val)) { | ||
val = val.replace(trimmerPattern, ''); | ||
// convert numerals to numbers | ||
if (!isNaN(val)) { | ||
var numVal = +val; | ||
if (val === numVal.toString(10)) val = numVal; | ||
} | ||
} | ||
var sliced = slicerPattern.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, and either are not objects, 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 isBool (thing) { | ||
return ( | ||
typeof(thing) === "boolean" | ||
|| isA(thing, new Boolean(thing)) | ||
); | ||
}; | ||
function isNumber (thing) { | ||
return ( | ||
typeof(thing) === "number" | ||
|| isA(thing, new Number(thing)) | ||
) && isFinite(thing); | ||
}; | ||
function isString (thing) { | ||
return ( | ||
typeof(thing) === "string" | ||
|| isA(thing, new String(thing)) | ||
); | ||
}; |
Oops, something went wrong.