Improve code quality and write tests
This commit is contained in:
+443
@@ -0,0 +1,443 @@
|
||||
var urlParse = require('url').parse;
|
||||
var querystring = require('querystring');
|
||||
var isBrowser = (typeof window !== "undefined");
|
||||
|
||||
var getDefaults = function(){
|
||||
var defaultUrl = "http://localhost:80";
|
||||
if (isBrowser){
|
||||
defaultUrl = window.location.href.toString();
|
||||
}
|
||||
var defaults = urlParse(defaultUrl);
|
||||
return defaults;
|
||||
};
|
||||
|
||||
if (!Array.isArray) {
|
||||
Array.isArray = function (arg) {
|
||||
return Object.prototype.toString.call(arg) === '[object Array]';
|
||||
};
|
||||
}
|
||||
|
||||
var objectEach = function(obj, cb){
|
||||
for (var k in obj){
|
||||
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
||||
cb(obj[k], k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var argsArray = function(obj){
|
||||
if (!obj) { return []; }
|
||||
if (Array.isArray(obj)) { return obj.slice() ; }
|
||||
var args = [];
|
||||
objectEach(obj, function(v, k){
|
||||
args[k] = v;
|
||||
});
|
||||
return args;
|
||||
};
|
||||
|
||||
var arrLast = function(arr){
|
||||
return arr[arr.length-1];
|
||||
};
|
||||
|
||||
var arrFlatten = function(input, output) {
|
||||
if (!output) { output = []; }
|
||||
for (var i = 0; i < input.length; i++){
|
||||
var value = input[i];
|
||||
if (Array.isArray(value)) {
|
||||
arrFlatten(value, output);
|
||||
} else {
|
||||
output.push(value);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
var UrlGrey = function(url){
|
||||
if (!url && isBrowser){
|
||||
url = window.location.href.toString();
|
||||
}
|
||||
this.url = url;
|
||||
this._parsed = null;
|
||||
};
|
||||
|
||||
UrlGrey.prototype.parsed = function(){
|
||||
if (!this._parsed){
|
||||
this._parsed = urlParse(this.url);
|
||||
var defaults = getDefaults();
|
||||
var p = this._parsed;
|
||||
p.protocol = p.protocol || defaults.protocol;
|
||||
p.protocol = p.protocol.slice(0,-1);
|
||||
if (p.hash){
|
||||
p.hash = p.hash.substring(1);
|
||||
}
|
||||
p.username = '';
|
||||
p.password = '';
|
||||
if (p.protocol !== 'file'){
|
||||
p.port = parseInt(p.port, 10);
|
||||
if (p.auth){
|
||||
var auth = p.auth.split(':');
|
||||
p.username = auth[0];
|
||||
p.password = auth[1];
|
||||
}
|
||||
}
|
||||
if (isBrowser){
|
||||
p.hostname = p.hostname || defaults.hostname;
|
||||
}
|
||||
}
|
||||
|
||||
// enforce only returning these properties
|
||||
this._parsed = {
|
||||
protocol : this._parsed.protocol,
|
||||
auth: this._parsed.auth,
|
||||
host: this._parsed.host,
|
||||
port: this._parsed.port,
|
||||
hostname: this._parsed.hostname,
|
||||
hash: this._parsed.hash,
|
||||
search: this._parsed.search,
|
||||
query: this._parsed.query,
|
||||
pathname: this._parsed.pathname,
|
||||
path: this._parsed.path,
|
||||
href: this._parsed.href,
|
||||
username: this._parsed.username,
|
||||
password: this._parsed.password
|
||||
};
|
||||
|
||||
|
||||
return this._parsed;
|
||||
};
|
||||
|
||||
|
||||
UrlGrey.prototype.extendedPath = function(url){
|
||||
if (url){
|
||||
var p = urlParse(url);
|
||||
var obj = new UrlGrey(this.toString());
|
||||
if (p.hash){
|
||||
p.hash = p.hash.substring(1);
|
||||
}
|
||||
obj.parsed().hash = p.hash;
|
||||
obj.parsed().query = p.query;
|
||||
obj = obj.path(p.pathname);
|
||||
return obj;
|
||||
} else {
|
||||
var href = this.path();
|
||||
href += this.queryString() ? '?' + this.queryString() : '';
|
||||
href += this.hash() ? '#' + this.hash() : '';
|
||||
return href;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
UrlGrey.prototype.port = function(num){
|
||||
var hostname = this.parsed().hostname;
|
||||
|
||||
// setter
|
||||
if (num){
|
||||
if (this.protocol() === 'file'){
|
||||
throw new Error("file urls don't have ports");
|
||||
}
|
||||
var obj = new UrlGrey(this.toString());
|
||||
obj.parsed().port = parseInt(num, 10);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// getter
|
||||
var output = this._parsed.port;
|
||||
if (!output){
|
||||
switch(this.protocol()){
|
||||
case 'http' : return 80;
|
||||
case 'https' : return 443;
|
||||
default : return null;
|
||||
}
|
||||
}
|
||||
return parseInt(output, 10);
|
||||
};
|
||||
|
||||
UrlGrey.prototype.query = function(mergeObject){
|
||||
if (arguments.length === 0) {
|
||||
return querystring.parse(this.queryString());
|
||||
} else if (mergeObject === null || mergeObject === false){
|
||||
return this.queryString('');
|
||||
} else {
|
||||
// read the object out
|
||||
var oldQuery = querystring.parse(this.queryString());
|
||||
objectEach(mergeObject, function(v, k){
|
||||
if (v === null || v === false){
|
||||
delete oldQuery[k];
|
||||
} else {
|
||||
oldQuery[k] = v;
|
||||
}
|
||||
});
|
||||
var newString = querystring.stringify(oldQuery);
|
||||
var ret = this.queryString(newString);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
UrlGrey.prototype.rawQuery = function(mergeObject){
|
||||
if (arguments.length === 0) {
|
||||
if (this.queryString().length === 0) { return {}; }
|
||||
|
||||
return this.queryString().split("&").reduce(function(obj, pair) {
|
||||
pair = pair.split("=");
|
||||
var key = pair[0];
|
||||
var val = pair[1];
|
||||
obj[key] = val;
|
||||
return obj;
|
||||
}, {});
|
||||
} else if (mergeObject === null || mergeObject === false){
|
||||
return this.queryString('');
|
||||
} else {
|
||||
// read the object out
|
||||
var oldQuery = querystring.parse(this.queryString());
|
||||
objectEach(mergeObject, function(v, k){
|
||||
if (v === null){
|
||||
delete oldQuery[k];
|
||||
} else {
|
||||
oldQuery[k] = v;
|
||||
}
|
||||
});
|
||||
var pairs = [];
|
||||
objectEach(oldQuery, function(v, k){
|
||||
pairs.push(k + '=' + v);
|
||||
});
|
||||
var newString = pairs.join('&');
|
||||
|
||||
return this.queryString(newString);
|
||||
}
|
||||
};
|
||||
|
||||
addPropertyGetterSetter('protocol');
|
||||
addPropertyGetterSetter('username');
|
||||
addPropertyGetterSetter('password');
|
||||
addPropertyGetterSetter('hostname');
|
||||
addPropertyGetterSetter('hash');
|
||||
// add a method called queryString that manipulates 'query'
|
||||
addPropertyGetterSetter('query', 'queryString');
|
||||
addPropertyGetterSetter('pathname', 'path');
|
||||
|
||||
UrlGrey.prototype.path = function(){
|
||||
var args = arrFlatten(argsArray(arguments));
|
||||
if (args.length !== 0){
|
||||
var obj = new UrlGrey(this.toString());
|
||||
var str = args.join('/');
|
||||
str = str.replace(/\/+/g, '/'); // remove double slashes
|
||||
str = str.replace(/\/$/, ''); // remove all trailing slashes
|
||||
args = str.split('/');
|
||||
for(var i = 0; i < args.length; i++){
|
||||
args[i] = this.encode(args[i]);
|
||||
}
|
||||
str = args.join('/');
|
||||
if (str[0] !== '/'){ str = '/' + str; }
|
||||
obj.parsed().pathname = str;
|
||||
return obj;
|
||||
}
|
||||
return this.parsed().pathname;
|
||||
};
|
||||
|
||||
UrlGrey.prototype.rawPath = function(){
|
||||
var args = arrFlatten(argsArray(arguments));
|
||||
if (args.length !== 0){
|
||||
var obj = new UrlGrey(this.toString());
|
||||
var str = args.join('/');
|
||||
str = str.replace(/\/+/g, '/'); // remove double slashes
|
||||
str = str.replace(/\/$/, ''); // remove all trailing slashes
|
||||
if (str[0] !== '/'){ str = '/' + str; }
|
||||
obj.parsed().pathname = str;
|
||||
return obj;
|
||||
}
|
||||
return this.parsed().pathname;
|
||||
};
|
||||
|
||||
UrlGrey.prototype.encode = function(str){
|
||||
try {
|
||||
return encodeURIComponent(str);
|
||||
} catch (ex) {
|
||||
return querystring.escape(str);
|
||||
}
|
||||
};
|
||||
|
||||
UrlGrey.prototype.decode = function(str){
|
||||
return decode(str);
|
||||
};
|
||||
|
||||
UrlGrey.prototype.parent = function(){
|
||||
// read-only. (can't SET parent)
|
||||
var pieces = this.path().split("/");
|
||||
var popped = pieces.pop();
|
||||
if (popped === ''){ // ignore trailing slash
|
||||
popped = pieces.pop();
|
||||
}
|
||||
if (!popped){
|
||||
throw new Error("The current path has no parent path");
|
||||
}
|
||||
return this.query(false).hash('').path(pieces.join("/"));
|
||||
};
|
||||
|
||||
UrlGrey.prototype.rawChild = function(suffixes){
|
||||
if (suffixes){
|
||||
suffixes = argsArray(arguments);
|
||||
return this.query(false).hash('').rawPath(this.path(), suffixes);
|
||||
} else {
|
||||
// if no suffix, return the child
|
||||
var pieces = this.path().split("/");
|
||||
var last = arrLast(pieces);
|
||||
if ((pieces.length > 1) && (last === '')){
|
||||
// ignore trailing slashes
|
||||
pieces.pop();
|
||||
last = arrLast(pieces);
|
||||
}
|
||||
return last;
|
||||
}
|
||||
};
|
||||
|
||||
UrlGrey.prototype.child = function(suffixes){
|
||||
suffixes = argsArray(arguments);
|
||||
if (suffixes.length > 0){
|
||||
return this.query(false).hash('').path(this.path(), suffixes);
|
||||
}
|
||||
|
||||
// if no suffix, return the child
|
||||
var pieces = pathPieces(this.path());
|
||||
var last = arrLast(pieces);
|
||||
if ((pieces.length > 1) && (last === '')){
|
||||
// ignore trailing slashes
|
||||
pieces.pop();
|
||||
last = arrLast(pieces);
|
||||
}
|
||||
return last;
|
||||
};
|
||||
|
||||
UrlGrey.prototype.toJSON = function(){
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
UrlGrey.prototype.toString = function(){
|
||||
var p = this.parsed();
|
||||
var retval = this.protocol() + '://';
|
||||
if (this.protocol() !== 'file'){
|
||||
var userinfo = p.username + ':' + p.password;
|
||||
if (userinfo !== ':'){
|
||||
retval += userinfo + '@';
|
||||
}
|
||||
retval += p.hostname;
|
||||
var port = portString(this);
|
||||
if (port !== ''){
|
||||
retval += ':' + port;
|
||||
}
|
||||
}
|
||||
retval += this.path() === '/' ? '' : this.path();
|
||||
var qs = this.queryString();
|
||||
if (qs){
|
||||
retval += '?' + qs;
|
||||
}
|
||||
if (p.hash){
|
||||
retval += '#' + p.hash;
|
||||
}
|
||||
return retval;
|
||||
};
|
||||
|
||||
var pathPieces = function(path){
|
||||
var pieces = path.split('/');
|
||||
for(var i = 0; i < pieces.length; i++){
|
||||
pieces[i] = decode(pieces[i]);
|
||||
}
|
||||
return pieces;
|
||||
};
|
||||
|
||||
var decode = function(str){
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch (ex) {
|
||||
return querystring.unescape(str);
|
||||
}
|
||||
};
|
||||
|
||||
var portString = function(o){
|
||||
if (o.protocol() === 'https'){
|
||||
if (o.port() === 443){
|
||||
return '';
|
||||
}
|
||||
}
|
||||
if (o.protocol() === 'http'){
|
||||
if (o.port() === 80){
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return '' + o.port();
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
UrlGrey.prototype.absolute = function(path){
|
||||
if (path[0] == '/'){
|
||||
path = path.substring(1);
|
||||
}
|
||||
var parsed = urlParse(path);
|
||||
if (!!parsed.protocol){ // if it's already absolute, just return it
|
||||
return path;
|
||||
}
|
||||
return this._protocol + "://" + this._host + '/' + path;
|
||||
};
|
||||
|
||||
// TODO make this interpolate vars into the url. both sinatra style and url-tempates
|
||||
// TODO name this:
|
||||
UrlGrey.prototype.get = function(nameOrPath, varDict){
|
||||
if (!!nameOrPath){
|
||||
if (!!varDict){
|
||||
return this.absolute(this._router.getUrl(nameOrPath, varDict));
|
||||
}
|
||||
return this.absolute(this._router.getUrl(nameOrPath));
|
||||
}
|
||||
return this.url;
|
||||
};*/
|
||||
|
||||
/*
|
||||
// TODO needs to take a template as an input
|
||||
UrlGrey.prototype.param = function(key, defaultValue){
|
||||
var value = this.params()[key];
|
||||
if (!!value) {
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
// TODO extract params, given a template?
|
||||
// TODO needs to take a template as an input
|
||||
UrlGrey.prototype.params = function(inUrl){
|
||||
if (!!inUrl){
|
||||
return this._router.pathVariables(inUrl);
|
||||
}
|
||||
if (!!this._params){
|
||||
return this._params;
|
||||
}
|
||||
return this._router.pathVariables(this.url);
|
||||
};
|
||||
*/
|
||||
|
||||
// TODO relative() // takes an absolutepath and returns a relative one
|
||||
// TODO absolute() // takes a relative path and returns an absolute one.
|
||||
|
||||
|
||||
|
||||
module.exports = function(url){ return new UrlGrey(url); };
|
||||
|
||||
function addPropertyGetterSetter(propertyName, methodName){
|
||||
if (!methodName){
|
||||
methodName = propertyName;
|
||||
}
|
||||
UrlGrey.prototype[methodName] = function(str){
|
||||
if (!str && str !== "") {
|
||||
return this.parsed()[propertyName];
|
||||
} else {
|
||||
var obj = new UrlGrey(this.toString());
|
||||
obj.parsed()[propertyName] = str;
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user