director is a fast routing library for D, modeled after the routing
semantics of Sinatra and Ruby on Rails.
dub --build=unittest to unittest.
DMD 2.064 and LDC on the merge-2.064 branch are supported.
Router, from the director.router module, is the main type exposed
by the director library. Pushing routes onto the router is done
through the Router#push(string, callback) method, where callback can be one of four types:
void function()void function(Params)void delegate()void delegate(Params)
where Params is a hash of parameters matched in the route.
Routes can contain variables, denoted by beginning with a :, that
are then passed to the handler if it accepts a Params argument.
For instance, the route /users/:name will match /users/dymk, and
pass the handler a Params object containing ["name": "dymk"].
Optional variables are also supported, by appending a ? to the end
of the variable name. For instance, the route /users/:name? will match both
/users and /users/dymk, passing an empty hash to the handler in the first case.
Route#push(string, callback) pushes a new route to match on. The method takes a
string route pattern, and a callback to invoke when matched. The
method can be chained.
Example:
import director.router;
void main()
{
auto router = Router();
/**
* Passing functions as handlers
*/
router.get("/foo/bar", {
writeln("/foo/bar was called");
});
/**
* Passing functions as handlers, which take a
* string[string] containing the params hash
*/
router.get("/job/:name/:occupation", (params) {
writefln("%s is a %s", params.name, params.occupation);
});
/**
* Optional route parts
* This will match on /user, or /user/bob
*/
router.get("/user/:name?", (params) {
if(params.has("name"))
{
writeln("You didn't supply a name!");
}
else
{
writeln("Hello, ", params.name);
}
});
/**
* Delegate support
*/
int i = 0;
router.get("/callme", {
i++;
writefln("Been called %d times", i);
});
/**
* Delegate with param support
*/
string[] seen_names;
router.get("/addname/:name", (params) {
seen_names ~= params["name"];
writeln("Seen names: ", seen_names);
});
/**
* Push method chaining
*/
router
.get("/chained/a", () { writeln("Matched a"); })
.get("/chained/b", () { writeln("Matched b"); })
.get("/chained/:o", (params) {
writeln("Matched other: ", params.o);
});Matching on routes is simple, and accessed through the Route#match
method. Route#match(string) returns true if a match was found; else, false:
void main()
{
auto router = Router();
router.push("/users/:name", (params) {
writeln("Hello, ", params.name);
});
router.match("/foo/Dave"); // Prints `Hello, Dave` to stdout
assert(router.match("/foo/bar") == true);
assert(rotuer.match("/baz") == false);
}Params responds to opDispatch, and makes accessing variables matched
in the route easy.
bool Params#has(string name): Returns true ifnameis in the params hashinoperator: Returns a pointer to the value in params (identical tostring in string[string])opDispatch(string name)(): Returns the parameter variable for a given name, or raises if it's not found.opIndex(string name): Identical tostring[string]'sopIndex; returns the parameter for that name.
void main()
{
auto r = Router();
r.push("/:first/:opt", (params) {
if(params.has("opt"))
{
writeln("First was: ", params.first);
writeln("Opt was: ", params.opt);
}
else
{
writeln("First was: ", params["first"]);
}
// 'in' operator
if("opt" in params)
{
writeln("Opt in params");
}
});
}There are two implementations for determining if a route matches or not:
a SplitterRoute, and a RegexRoute class, both of which implement
the Route interface. SplitterRoute is approximatly twice as fast
for matching for all tests than RegexRoute, but it cannot handle
optional parameters.
Router will determine which Route implementation to use, prefering
SplitterRoute if the route contains no optional parameters.