Skip to content
This repository has been archived by the owner on May 4, 2021. It is now read-only.

Commit

Permalink
Added optional parameters and query string support to url patterns us…
Browse files Browse the repository at this point in the history
…ing a :: syntax for optional values.
  • Loading branch information
skibblenybbles committed Jan 8, 2015
1 parent e7483b5 commit f1b3d51
Showing 1 changed file with 74 additions and 51 deletions.
125 changes: 74 additions & 51 deletions lib/urls.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ module.exports = function(option, path) {

q = require('rw-mercenary/promises'),
lang = require('rw-mercenary/lang'),
partial = lang.partial,
invoke = lang.invoke,
compose = lang.compose,
destructure = lang.destructure,
destructured = lang.destructured,
each = lang.each,
len = lang.len,
has = lang.has,
push = lang.push,
concat = lang.concat,
replace = lang.replace,
split = lang.split,
join = lang.join,
match = lang.match,
isArray = lang.is.array,
isDefined = lang.is.def,
isString = lang.is.string,

//----------------------
Expand All @@ -26,45 +32,77 @@ module.exports = function(option, path) {
// The registry of named URL patterns.
registry = {},

// Parse the URL pattern parameters.
rxParams = /:([^/]+)/g,
params = function(pattern) {
var next = function() {
var matched = match(rxParams, pattern);

return matched && matched[1];
},
unique = {},
params = [],
param = next();

while (param) {
if (has(unique, param)) {
throw new Error(
'Parameter names are not unique in the URL pattern: ' +
pattern);
}
unique[param] = true;
push(params, param);
param = next();
}
return params;
// Create a URL resolver for the URL data. In the URL's pattern, required parameters are
// specified with a :name syntax in the path or query string. Optional parameters
// are specified with a ::name syntax in the path or query string, e.g.
// '/articles/:topic/?ordering=::ordering'. Any missing optional parameters will be
// excluded from the path or query string by the resolver.
resolver = function(url) {
var // Resolve an individual term with respect to the given parameters. If the term
// is not a parameter, returns it. If it is a parameter, returns its resolved value
// or undefined if the parameter is missing and not required.
resolve = function(term, values) {
var matched = match(/^\:(\:)?(.+)$/, term),
name = matched && matched[2],
required = matched && !matched[1];

if (matched) {
if (required && !has(values, name)) {
throw new Error(
'The required parameter "' +
name +
'" for the URL pattern "' +
url.name +
'" is missing');
}
return values[name];
} else {
return term;
}
};

return function(values) {
return destructure(split(url.pattern, '?'), function(pathPattern, queryPattern) {
var path = [],
query = [];

each(split(pathPattern || '', '/'), function(term) {
var resolved = resolve(term, values);

if (isDefined(resolved)) {
push(path, resolved);
}
});
each(split(queryPattern || '', '&'), function(assignment) {
if (assignment) {
destructure(split(assignment, '='), function(name, term) {
var resolved = resolve(term, values);

if (isDefined(resolved)) {
push(query, name + '=' + encodeURIComponent(resolved));
}
});
}
});
return (
join(path, '/') +
(len(query) ?
('?' + join(query, '&')) :
''));
});
};
},

// Extend a URL pattern with URL patterns from another module.
include = function(name) {
return (
// Resolve the top-level URL patterns promise.
(path.js(name)
.then(function(urls) {
return q.when(require(urls));
}))
path.js(name)
.then(compose(q.when, require))
// Resolve the nested URL patterns.
.then(q.all)
// Flatten the nested URL patterns.
.then(function(entries) {
return invoke(concat, entries);
})
.then(partial(invoke, concat))
// Resolve the URL patterns.
.then(q.all));
},
Expand All @@ -75,7 +113,7 @@ module.exports = function(option, path) {
q.all([pattern, viewOrUrls, name])
.then(destructured(function(pattern, viewOrUrls, name) {
if (isArray(viewOrUrls)) {
if (isString(name)) {
if (name || isString(name)) {
throw new Error(
'The URL pattern "' +
pattern +
Expand All @@ -84,7 +122,6 @@ module.exports = function(option, path) {
// Update the patterns.
each(viewOrUrls, function(url) {
url.pattern = pattern + url.pattern;
url.params = params(url.pattern);
});
return viewOrUrls;
} else {
Expand All @@ -104,38 +141,24 @@ module.exports = function(option, path) {
registry[name] = {
name: name,
pattern: pattern,
params: params(pattern),
view: viewOrUrls
};
return [registry[name]];
}
})));
},

// Resolve a URL from a named URL pattern and an object of parameters.
resolve = function(name, params) {
params = params || {};
// Resolve a URL from a named URL pattern and an object of parameter values.
resolve = function(name, values) {
if (!has(registry, name)) {
throw new Error(
'Unknown URL pattern "' +
name +
'"');
}
return (
replace(
registry[name].pattern,
rxParams,
function(matched, param) {
if (!has(params, param)) {
throw new Error(
'The parameter "' +
param +
'" was not specified for the URL pattern "' +
registry[name].name +
'"');
}
return params[param];
}));
resolver(registry[name])(
values || {}));
};

//------------------
Expand Down

0 comments on commit f1b3d51

Please sign in to comment.