2.0.3
This commit is contained in:
parent
685205adde
commit
b08bca3d6f
9 changed files with 297 additions and 635 deletions
19
README.md
19
README.md
|
@ -66,25 +66,6 @@ needs a lot of flexibility with which to handle requests.
|
||||||
|
|
||||||
## Hierarchy
|
## Hierarchy
|
||||||
|
|
||||||
```dart
|
|
||||||
main() {
|
|
||||||
final foo = new Route('/');
|
|
||||||
final bar = foo.child('bar');
|
|
||||||
final baz = foo.child('baz');
|
|
||||||
|
|
||||||
final a = bar.child('a');
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Relative paths:
|
|
||||||
* a.resolve('../baz') = baz;
|
|
||||||
* bar.resolve('a') = a;
|
|
||||||
*
|
|
||||||
* Absolute paths:
|
|
||||||
* a.resolve('/bar/a') = a;
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
main() {
|
main() {
|
||||||
final router = new Router();
|
final router = new Router();
|
||||||
|
|
165
lib/src/grammar.dart
Normal file
165
lib/src/grammar.dart
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
part of angel_route.src.router;
|
||||||
|
|
||||||
|
class _RouteGrammar {
|
||||||
|
static final Parser<String> notSlash =
|
||||||
|
match(new RegExp(r'[^/]+')).value((r) => r.span.text);
|
||||||
|
static final Parser<RegExp> regExp = new _RegExpParser();
|
||||||
|
static final Parser<String> parameterName =
|
||||||
|
match(new RegExp(r':([A-Za-z0-9_]+)'))
|
||||||
|
.value((r) => r.span.text.substring(1));
|
||||||
|
|
||||||
|
static final Parser<_ParameterSegment> parameterSegment = chain([
|
||||||
|
parameterName,
|
||||||
|
match('?').value((r) => true).opt(),
|
||||||
|
regExp.opt(),
|
||||||
|
]).map((r) {
|
||||||
|
var s = new _ParameterSegment(r.value[0], r.value[2]);
|
||||||
|
return r.value[1] == true ? new _OptionalSegment(s) : s;
|
||||||
|
});
|
||||||
|
|
||||||
|
static final Parser<_WildcardSegment> wildcardSegment =
|
||||||
|
match('*').value((r) => new _WildcardSegment());
|
||||||
|
|
||||||
|
static final Parser<_ConstantSegment> constantSegment =
|
||||||
|
notSlash.map((r) => new _ConstantSegment(r.value));
|
||||||
|
|
||||||
|
static final Parser<_RouteSegment> routeSegment =
|
||||||
|
any([parameterSegment, wildcardSegment, constantSegment]);
|
||||||
|
|
||||||
|
static final Parser<_RouteDefinition> routeDefinition = routeSegment
|
||||||
|
.separatedBy(match('/'))
|
||||||
|
.map((r) => new _RouteDefinition(r.value ?? []))
|
||||||
|
.surroundedBy(match('/').star().opt());
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RegExpParser extends Parser<RegExp> {
|
||||||
|
static final RegExp rgx = new RegExp(r'\((.+)\)');
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<RegExp> parse(SpanScanner scanner, [int depth = 1]) {
|
||||||
|
if (!scanner.matches(rgx)) return new ParseResult(this, false, []);
|
||||||
|
return new ParseResult(this, true, [],
|
||||||
|
span: scanner.lastSpan, value: new RegExp(scanner.lastMatch[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RouteDefinition {
|
||||||
|
final List<_RouteSegment> segments;
|
||||||
|
|
||||||
|
_RouteDefinition(this.segments);
|
||||||
|
|
||||||
|
Parser<Map<String, String>> compile() {
|
||||||
|
Parser<Map<String, String>> out;
|
||||||
|
|
||||||
|
for (var s in segments) {
|
||||||
|
if (out == null)
|
||||||
|
out = s.compile();
|
||||||
|
else
|
||||||
|
out = s.compileNext(out.then(match('/')).index(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _RouteSegment {
|
||||||
|
Parser<Map<String, String>> compile();
|
||||||
|
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConstantSegment extends _RouteSegment {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
_ConstantSegment(this.text);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Constant: $text';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Parser<Map<String, String>> compile() {
|
||||||
|
return match(text).value((r) => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
||||||
|
return p.then(compile()).index(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WildcardSegment extends _RouteSegment {
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Wildcard segment';
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser<Map<String, String>> _compile() {
|
||||||
|
return match(new RegExp(r'[^/]*'));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Parser<Map<String, String>> compile() {
|
||||||
|
return _compile().map((r) => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
||||||
|
return p.then(_compile()).index(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OptionalSegment extends _ParameterSegment {
|
||||||
|
final _ParameterSegment parameter;
|
||||||
|
|
||||||
|
_OptionalSegment(this.parameter) : super(parameter.name, parameter.regExp);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Optional: $parameter';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Parser<Map<String, String>> compile() {
|
||||||
|
return super.compile().opt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
||||||
|
return p.then(_compile().opt()).map((r) {
|
||||||
|
if (r.value[1] == null) return r.value[0];
|
||||||
|
return r.value[0]..addAll({name: Uri.decodeComponent(r.value[1])});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ParameterSegment extends _RouteSegment {
|
||||||
|
final String name;
|
||||||
|
final RegExp regExp;
|
||||||
|
|
||||||
|
_ParameterSegment(this.name, this.regExp);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
if (regExp != null) return 'Param: $name (${regExp.pattern})';
|
||||||
|
return 'Param: $name';
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser<Map<String, String>> _compile() {
|
||||||
|
return regExp != null
|
||||||
|
? match(regExp).value((r) => r.span.text)
|
||||||
|
: _RouteGrammar.notSlash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Parser<Map<String, String>> compile() {
|
||||||
|
return _compile().map((r) => {name: Uri.decodeComponent(r.span.text)});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
||||||
|
return p.then(_compile()).map((r) {
|
||||||
|
return r.value[0]..addAll({name: Uri.decodeComponent(r.value[1])});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,529 +1,64 @@
|
||||||
part of angel_route.src.router;
|
part of angel_route.src.router;
|
||||||
|
|
||||||
String _matcherify(String path, {bool expand: true}) {
|
|
||||||
var p = path.replaceAll(new RegExp(r'/\*$'), "*").replaceAll('/', r'\/');
|
|
||||||
|
|
||||||
if (expand) {
|
|
||||||
var match = _param.firstMatch(p);
|
|
||||||
|
|
||||||
while (match != null) {
|
|
||||||
if (match.group(3) == null)
|
|
||||||
p = p.replaceAll(match[0], '([^\/]+)');
|
|
||||||
else
|
|
||||||
p = p.replaceAll(match[0], '(${match[3]})');
|
|
||||||
match = _param.firstMatch(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p = p.replaceAll(new RegExp('\\*'), '.*');
|
|
||||||
|
|
||||||
p = '^$p\$';
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _pathify(String path) {
|
|
||||||
var p = path.replaceAll(_straySlashes, '');
|
|
||||||
|
|
||||||
Map<String, String> replace = {};
|
|
||||||
|
|
||||||
for (Match match in _param.allMatches(p)) {
|
|
||||||
if (match[3] != null) replace[match[0]] = ':${match[1]}';
|
|
||||||
}
|
|
||||||
|
|
||||||
replace.forEach((k, v) {
|
|
||||||
p = p.replaceAll(k, v);
|
|
||||||
});
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a virtual location within an application.
|
/// Represents a virtual location within an application.
|
||||||
class Route {
|
class Route {
|
||||||
final Map<String, Match> _cache = {};
|
final String method;
|
||||||
final List<Route> _children = [];
|
final String path;
|
||||||
final List _handlers = [];
|
final List handlers;
|
||||||
RegExp _head;
|
final Map<String, Map<String, String>> _cache = {};
|
||||||
RegExp _matcher;
|
final _RouteDefinition _routeDefinition;
|
||||||
String _method;
|
String name;
|
||||||
String _name;
|
Parser<Map<String, String>> _parser;
|
||||||
Route _parent;
|
|
||||||
RegExp _parentResolver;
|
|
||||||
String _path;
|
|
||||||
String _pathified;
|
|
||||||
RegExp _resolver;
|
|
||||||
RegExp _stub;
|
|
||||||
|
|
||||||
/// Set to `true` to print verbose debug output when interacting with this route.
|
Route(this.path, {@required this.method, @required this.handlers})
|
||||||
bool debug;
|
: _routeDefinition = _RouteGrammar.routeDefinition
|
||||||
|
.parse(new SpanScanner(path.replaceAll(_straySlashes, '')))
|
||||||
/// Contains any child routes attached to this one.
|
.value {
|
||||||
List<Route> get children => new List.unmodifiable(_children);
|
if (_routeDefinition.segments.isEmpty) _parser = match('').value((r) => {});
|
||||||
|
|
||||||
/// A `List` of arbitrary objects chosen to respond to this request.
|
|
||||||
List get handlers => new List.unmodifiable(_handlers);
|
|
||||||
|
|
||||||
/// A `RegExp` that matches requests to this route.
|
|
||||||
RegExp get matcher => _matcher;
|
|
||||||
|
|
||||||
/// The HTTP method this route is designated for.
|
|
||||||
String get method => _method;
|
|
||||||
|
|
||||||
/// The name of this route, if any.
|
|
||||||
String get name => _name;
|
|
||||||
|
|
||||||
/// The hierarchical parent of this route.
|
|
||||||
Route get parent => _parent;
|
|
||||||
|
|
||||||
/// The virtual path on which this route is mounted.
|
|
||||||
String get path => _path;
|
|
||||||
|
|
||||||
/// The [Route] at the top of the hierarchy this route is found in.
|
|
||||||
Route get absoluteParent {
|
|
||||||
Route result = this;
|
|
||||||
|
|
||||||
while (result.parent != null) result = result.parent;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [Route] instances that will respond to requests
|
factory Route.join(Route a, Route b) {
|
||||||
/// to the index of this instance's path.
|
var start = a.path.replaceAll(_straySlashes, '');
|
||||||
///
|
var end = b.path.replaceAll(_straySlashes, '');
|
||||||
/// May return `this`.
|
return new Route('$start/$end'.replaceAll(_straySlashes, ''),
|
||||||
Iterable<Route> get allIndices {
|
method: b.method, handlers: b.handlers);
|
||||||
return children.where((r) => r.path.replaceAll(path, '').isEmpty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Backtracks up the hierarchy, and builds
|
Parser<Map<String, String>> get parser =>
|
||||||
/// a sequential list of all handlers from both
|
_parser ??= _routeDefinition.compile();
|
||||||
/// this route, and every found parent route.
|
|
||||||
///
|
|
||||||
/// The resulting list puts handlers from routes
|
|
||||||
/// higher in the tree at lower indices. Thus,
|
|
||||||
/// this can be used in a routing-enabled application
|
|
||||||
/// to evaluate multiple middleware on a single route,
|
|
||||||
/// and apply them to all children.
|
|
||||||
List get handlerSequence {
|
|
||||||
final result = [];
|
|
||||||
var r = this;
|
|
||||||
|
|
||||||
while (r != null) {
|
|
||||||
result.insertAll(0, r.handlers);
|
|
||||||
r = r.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
@override
|
||||||
}
|
String toString() {
|
||||||
|
return '$method $path => $handlers';
|
||||||
/// Returns the [Route] instance that will respond to requests
|
|
||||||
/// to the index of this instance's path.
|
|
||||||
///
|
|
||||||
/// May return `this`.
|
|
||||||
Route get indexRoute {
|
|
||||||
return children.firstWhere((r) => r.path.replaceAll(path, '').isEmpty,
|
|
||||||
orElse: () => this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Route._base();
|
|
||||||
|
|
||||||
Route(Pattern path,
|
|
||||||
{Iterable<Route> children: const [],
|
|
||||||
this.debug: false,
|
|
||||||
Iterable handlers: const [],
|
|
||||||
method: "GET",
|
|
||||||
String name: null}) {
|
|
||||||
if (children != null) _children.addAll(children);
|
|
||||||
if (handlers != null) _handlers.addAll(handlers);
|
|
||||||
_method = method;
|
|
||||||
_name = name;
|
|
||||||
|
|
||||||
if (path is RegExp) {
|
|
||||||
_matcher = path;
|
|
||||||
_path = path.pattern;
|
|
||||||
} else {
|
|
||||||
_matcher = new RegExp(
|
|
||||||
_matcherify(path.toString().replaceAll(_straySlashes, '')));
|
|
||||||
_path = _pathified = _pathify(path.toString());
|
|
||||||
_resolver = new RegExp(_matcherify(
|
|
||||||
path.toString().replaceAll(_straySlashes, ''),
|
|
||||||
expand: false));
|
|
||||||
}
|
|
||||||
|
|
||||||
_parentResolver = new RegExp(_matcher.pattern.replaceAll(_rgxEnd, ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Splits a route path into a list of segments, and then
|
|
||||||
/// builds a hierarchy of off that.
|
|
||||||
///
|
|
||||||
/// This should generally be used instead of the original
|
|
||||||
/// Route constructor.
|
|
||||||
///
|
|
||||||
/// All children and handlers, as well as the method, will be
|
|
||||||
/// assigned to the last child route created.
|
|
||||||
///
|
|
||||||
/// The final child route is returned.
|
|
||||||
factory Route.build(Pattern path,
|
|
||||||
{Iterable<Route> children: const [],
|
|
||||||
bool debug: false,
|
|
||||||
Iterable handlers: const [],
|
|
||||||
method: "GET",
|
|
||||||
String name: null}) {
|
|
||||||
Route result;
|
|
||||||
|
|
||||||
if (path is RegExp) {
|
|
||||||
result = new Route(path,
|
|
||||||
debug: debug, handlers: handlers, method: method, name: name);
|
|
||||||
} else {
|
|
||||||
final segments = path
|
|
||||||
.toString()
|
|
||||||
.split('/')
|
|
||||||
.where((str) => str.isNotEmpty)
|
|
||||||
.toList(growable: false);
|
|
||||||
|
|
||||||
if (segments.isEmpty) {
|
|
||||||
return new Route('/',
|
|
||||||
children: children,
|
|
||||||
debug: debug,
|
|
||||||
handlers: handlers,
|
|
||||||
method: method,
|
|
||||||
name: name);
|
|
||||||
}
|
|
||||||
|
|
||||||
var head = '';
|
|
||||||
|
|
||||||
for (int i = 0; i < segments.length; i++) {
|
|
||||||
final segment = segments[i];
|
|
||||||
head = (head + '/$segment').replaceAll(_straySlashes, '');
|
|
||||||
|
|
||||||
if (i == segments.length - 1) {
|
|
||||||
if (result == null) {
|
|
||||||
result = new Route(segment, debug: debug);
|
|
||||||
} else {
|
|
||||||
result = result.child(segment, debug: debug);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (result == null) {
|
|
||||||
result = new Route(segment, debug: debug, method: "*");
|
|
||||||
} else {
|
|
||||||
result = result.child(segment, debug: debug, method: "*");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result._head = new RegExp(_matcherify(head).replaceAll(_rgxEnd, ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result._children.addAll(children);
|
|
||||||
result._handlers.addAll(handlers);
|
|
||||||
result._method = method;
|
|
||||||
result._name = name;
|
|
||||||
|
|
||||||
return result..debug = debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Combines the paths and matchers of two [Route] instances, and creates a new instance.
|
|
||||||
factory Route.join(Route parent, Route child, {bool debug: false}) {
|
|
||||||
final String path1 = parent.path
|
|
||||||
.replaceAll(_rgxStart, '')
|
|
||||||
.replaceAll(_rgxEnd, '')
|
|
||||||
.replaceAll(_straySlashes, '');
|
|
||||||
final String path2 = child.path
|
|
||||||
.replaceAll(_rgxStart, '')
|
|
||||||
.replaceAll(_rgxEnd, '')
|
|
||||||
.replaceAll(_straySlashes, '');
|
|
||||||
final String pattern1 = parent.matcher.pattern
|
|
||||||
.replaceAll(_rgxEnd, '')
|
|
||||||
.replaceAll(_rgxStraySlashes, '');
|
|
||||||
final String pattern2 = child.matcher.pattern
|
|
||||||
.replaceAll(_rgxStart, '')
|
|
||||||
.replaceAll(_rgxStraySlashes, '');
|
|
||||||
|
|
||||||
final route = new Route('$path1/$path2',
|
|
||||||
children: child.children,
|
|
||||||
handlers: child.handlers,
|
|
||||||
method: child.method,
|
|
||||||
name: child.name);
|
|
||||||
|
|
||||||
String separator = (pattern1.isEmpty || pattern1 == '^' || pattern2 == r'$') ? '' : '\\/';
|
|
||||||
|
|
||||||
parent._children.add(route
|
|
||||||
.._matcher = new RegExp('$pattern1$separator$pattern2')
|
|
||||||
.._head = new RegExp(
|
|
||||||
_matcherify('$path1/$path2'.replaceAll(_straySlashes, ''))
|
|
||||||
.replaceAll(_rgxEnd, ''))
|
|
||||||
.._parent = parent
|
|
||||||
.._stub = child.matcher);
|
|
||||||
|
|
||||||
/*parent._printDebug(
|
|
||||||
"Joined '/$path1' and '/$path2', created head: ${route._head.pattern} and stub: ${route._stub.pattern}");*/
|
|
||||||
|
|
||||||
return route..debug = parent.debug || child.debug || debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls [addChild] on all given routes.
|
|
||||||
List<Route> addAll(Iterable<Route> routes, {bool join: true}) {
|
|
||||||
return routes.map((route) => addChild(route, join: join)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds the given route as a hierarchical child of this one.
|
|
||||||
Route addChild(Route route, {bool join: true}) {
|
|
||||||
Route created;
|
|
||||||
|
|
||||||
if (join) {
|
|
||||||
created = new Route.join(this, route);
|
|
||||||
} else {
|
|
||||||
_children.add(created = route.._parent = this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return created..debug = debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assigns a name to this route.
|
|
||||||
Route as(String name) => this.._name = name;
|
|
||||||
|
|
||||||
/// Creates a hierarchical child of this route with the given path.
|
|
||||||
Route child(Pattern path,
|
|
||||||
{Iterable<Route> children: const [],
|
|
||||||
bool debug: false,
|
|
||||||
Iterable handlers: const [],
|
|
||||||
String method: "GET",
|
|
||||||
String name: null}) {
|
|
||||||
final route = new Route.build(path,
|
|
||||||
children: children,
|
|
||||||
debug: debug,
|
|
||||||
handlers: handlers,
|
|
||||||
method: method,
|
|
||||||
name: name);
|
|
||||||
return addChild(route);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Route clone() {
|
Route clone() {
|
||||||
final Route route = new Route('', debug: debug);
|
return new Route(path, method: method, handlers: handlers)
|
||||||
|
.._cache.addAll(_cache);
|
||||||
return route
|
|
||||||
.._children.addAll(children)
|
|
||||||
.._handlers.addAll(handlers)
|
|
||||||
.._head = _head
|
|
||||||
.._matcher = _matcher
|
|
||||||
.._method = _method
|
|
||||||
.._name = name
|
|
||||||
.._parent = _parent
|
|
||||||
.._parentResolver = _parentResolver
|
|
||||||
.._path = _path
|
|
||||||
.._pathified = _pathified
|
|
||||||
.._resolver = _resolver
|
|
||||||
.._stub = _stub;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a URI to this route with the given parameters.
|
/// Use the setter instead.
|
||||||
String makeUri([Map<String, dynamic> params]) {
|
@deprecated
|
||||||
String result = _pathify(path);
|
void as(String n) {
|
||||||
if (params != null) {
|
name = n;
|
||||||
for (String key in (params.keys)) {
|
}
|
||||||
result = result.replaceAll(
|
|
||||||
new RegExp(":$key" + r"\??"), params[key].toString());
|
String makeUri(Map<String, dynamic> params) {
|
||||||
|
var b = new StringBuffer();
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
for (var seg in _routeDefinition.segments) {
|
||||||
|
if (i++ > 0) b.write('/');
|
||||||
|
if (seg is _ConstantSegment)
|
||||||
|
b.write(seg.text);
|
||||||
|
else if (seg is _ParameterSegment) {
|
||||||
|
if (!params.containsKey(seg.name))
|
||||||
|
throw new ArgumentError('Missing parameter "${seg.name}".');
|
||||||
|
b.write(params[seg.name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.replaceAll("*", "");
|
return b.toString();
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to match a path against this route.
|
|
||||||
Match match(String path) =>
|
|
||||||
_cache.putIfAbsent(path, () => matcher.firstMatch(path.replaceAll(_straySlashes, '')));
|
|
||||||
|
|
||||||
/// Extracts route parameters from a given path.
|
|
||||||
Map<String, String> parseParameters(String requestPath) {
|
|
||||||
Map<String, String> result = {};
|
|
||||||
|
|
||||||
Iterable<String> values =
|
|
||||||
_parseParameters(requestPath.replaceAll(_straySlashes, ''));
|
|
||||||
|
|
||||||
// _printDebug(
|
|
||||||
// 'Searched request path $requestPath and found these values: $values');
|
|
||||||
// _printDebug('This route\'s path is "$path".');
|
|
||||||
|
|
||||||
final pathString = _pathify(path).replaceAll(new RegExp('\/'), r'\/');
|
|
||||||
Iterable<Match> matches = _param.allMatches(pathString);
|
|
||||||
// _printDebug(
|
|
||||||
// 'All param names parsed in "$pathString": ${matches.map((m) => m[1])}');
|
|
||||||
|
|
||||||
for (int i = 0; i < matches.length && i < values.length; i++) {
|
|
||||||
Match match = matches.elementAt(i);
|
|
||||||
String paramName = match.group(1);
|
|
||||||
String value = Uri.decodeComponent(values.elementAt(i));
|
|
||||||
// _printDebug('Setting param "$paramName" to "$value"...');
|
|
||||||
result[paramName] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
_parseParameters(String requestPath) sync* {
|
|
||||||
Match routeMatch = matcher.firstMatch(requestPath);
|
|
||||||
|
|
||||||
if (routeMatch != null)
|
|
||||||
for (int i = 1; i <= routeMatch.groupCount; i++)
|
|
||||||
yield routeMatch.group(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds the first route available within this hierarchy that can respond to the given path.
|
|
||||||
///
|
|
||||||
/// Can be used to navigate a route hierarchy like a file system.
|
|
||||||
Route resolve(String path, {bool filter(Route route), String fullPath}) {
|
|
||||||
// _printDebug(
|
|
||||||
// 'Path to resolve: "/${path.replaceAll(_straySlashes, '')}", our matcher: ${matcher.pattern}');
|
|
||||||
bool _filter(route) {
|
|
||||||
if (filter == null) {
|
|
||||||
// _printDebug('No filter provided, returning true for $route');
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// _printDebug('Running filter on $route');
|
|
||||||
final result = filter(route);
|
|
||||||
// _printDebug('Filter result: $result');
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final _fullPath = fullPath ?? path;
|
|
||||||
|
|
||||||
if ((path.isEmpty || path == '.') && _filter(indexRoute)) {
|
|
||||||
// Try to find index
|
|
||||||
// _printDebug('Empty path, resolving with indexRoute: $indexRoute');
|
|
||||||
return indexRoute;
|
|
||||||
} else if (path == '/') {
|
|
||||||
return absoluteParent.resolve('');
|
|
||||||
} else if (path.replaceAll(_straySlashes, '').isEmpty) {
|
|
||||||
for (Route route in children) {
|
|
||||||
final stub = route.path.replaceAll(this.path, '');
|
|
||||||
|
|
||||||
if ((stub == '/' || stub.isEmpty) && _filter(route))
|
|
||||||
return route.resolve('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_filter(indexRoute)) {
|
|
||||||
// _printDebug(
|
|
||||||
// 'Path "/$path" is technically empty, sending to indexRoute: $indexRoute');
|
|
||||||
return indexRoute;
|
|
||||||
} else
|
|
||||||
return null;
|
|
||||||
} else if (path == '..') {
|
|
||||||
if (parent != null)
|
|
||||||
return parent;
|
|
||||||
else
|
|
||||||
throw new RoutingException.orphan();
|
|
||||||
} else if (path.startsWith('/') &&
|
|
||||||
path.length > 1 &&
|
|
||||||
path[1] != '/' &&
|
|
||||||
absoluteParent != null) {
|
|
||||||
return absoluteParent.resolve(path.substring(1),
|
|
||||||
filter: _filter, fullPath: _fullPath);
|
|
||||||
} else if (matcher.hasMatch(path.replaceAll(_straySlashes, '')) ||
|
|
||||||
_resolver.hasMatch(path.replaceAll(_straySlashes, ''))) {
|
|
||||||
// _printDebug(
|
|
||||||
// 'Path "/$path" matched our matcher, sending to indexRoute: $indexRoute');
|
|
||||||
return indexRoute;
|
|
||||||
} else {
|
|
||||||
final segments = path.split('/').where((str) => str.isNotEmpty).toList();
|
|
||||||
// _printDebug('Segments: $segments on "/${this.path}"');
|
|
||||||
|
|
||||||
if (segments.isEmpty) {
|
|
||||||
// _printDebug('Empty segments, sending to indexRoute: $indexRoute');
|
|
||||||
return indexRoute;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segments[0] == '..') {
|
|
||||||
if (parent != null)
|
|
||||||
return parent.resolve(segments.skip(1).join('/'),
|
|
||||||
filter: _filter, fullPath: _fullPath);
|
|
||||||
else
|
|
||||||
throw new RoutingException.orphan();
|
|
||||||
} else if (segments[0] == '.') {
|
|
||||||
return resolve(segments.skip(1).join('/'),
|
|
||||||
filter: _filter, fullPath: _fullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Route route in children) {
|
|
||||||
final subPath = '${this.path}/${segments[0]}';
|
|
||||||
// _printDebug(
|
|
||||||
// 'seg0: ${segments[0]}, stub: ${route._stub?.pattern}, path: $path, route.path: ${route.path}, route.matcher: ${route.matcher.pattern}, this.matcher: ${matcher.pattern}');
|
|
||||||
|
|
||||||
if (route.match(subPath) != null ||
|
|
||||||
route._resolver.firstMatch(subPath) != null) {
|
|
||||||
if (segments.length == 1 && _filter(route))
|
|
||||||
return route.resolve('');
|
|
||||||
else {
|
|
||||||
return route.resolve(segments.skip(1).join('/'),
|
|
||||||
filter: _filter,
|
|
||||||
fullPath: this.path.replaceAll(_straySlashes, '') +
|
|
||||||
'/' +
|
|
||||||
_fullPath.replaceAll(_straySlashes, ''));
|
|
||||||
}
|
|
||||||
} else if (route._stub != null && route._stub.hasMatch(segments[0])) {
|
|
||||||
if (segments.length == 1) {
|
|
||||||
// _printDebug('Stub perhaps matches');
|
|
||||||
return route.resolve('');
|
|
||||||
} else {
|
|
||||||
// _printDebug(
|
|
||||||
// 'Maybe stub matches. Sending remaining segments to $route');
|
|
||||||
return route.resolve(segments.skip(1).join('/'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to match "subdirectory"
|
|
||||||
for (Route route in children) {
|
|
||||||
// _printDebug(
|
|
||||||
// 'Trying to match subdir for $path; child ${route.path} on ${this.path}');
|
|
||||||
final match = route._parentResolver.firstMatch(path);
|
|
||||||
|
|
||||||
if (match != null) {
|
|
||||||
final subPath =
|
|
||||||
path.replaceFirst(match[0], '').replaceAll(_straySlashes, '');
|
|
||||||
// _printDebug("Subdir path: $subPath");
|
|
||||||
|
|
||||||
for (Route child in route.children) {
|
|
||||||
final testPath = child.path
|
|
||||||
.replaceFirst(route.path, '')
|
|
||||||
.replaceAll(_straySlashes, '');
|
|
||||||
|
|
||||||
if (subPath == testPath &&
|
|
||||||
(child.match(_fullPath) != null ||
|
|
||||||
child._resolver.firstMatch(_fullPath) != null) &&
|
|
||||||
_filter(child)) {
|
|
||||||
return child.resolve('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to match the whole route, if nothing else works
|
|
||||||
for (Route route in children) {
|
|
||||||
// _printDebug(
|
|
||||||
// 'Trying to match full $_fullPath for ${route.path} on ${this.path}');
|
|
||||||
if ((route.match(_fullPath) != null ||
|
|
||||||
route._resolver.firstMatch(_fullPath) != null) &&
|
|
||||||
_filter(route)) {
|
|
||||||
// _printDebug('Matched full path!');
|
|
||||||
return route.resolve('');
|
|
||||||
} else if ((route.match('/$_fullPath') != null ||
|
|
||||||
route._resolver.firstMatch('/$_fullPath') != null) &&
|
|
||||||
_filter(route)) {
|
|
||||||
// _printDebug('Matched full path (with a leading slash!)');
|
|
||||||
return route.resolve('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lastly, check to see if we have an index route to resolve with
|
|
||||||
if (indexRoute != this) {
|
|
||||||
// _printDebug('Forwarding "/$path" to indexRoute');
|
|
||||||
return indexRoute.resolve(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => "$method '$path' => ${handlers.length} handler(s)";
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
library angel_route.src.router;
|
library angel_route.src.router;
|
||||||
|
|
||||||
|
import 'package:combinator/combinator.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
import 'routing_exception.dart';
|
import 'routing_exception.dart';
|
||||||
import '../string_util.dart';
|
import '../string_util.dart';
|
||||||
|
part 'grammar.dart';
|
||||||
part 'symlink_route.dart';
|
part 'symlink_route.dart';
|
||||||
part 'route.dart';
|
part 'route.dart';
|
||||||
part 'routing_result.dart';
|
part 'routing_result.dart';
|
||||||
|
@ -63,7 +67,7 @@ class Router {
|
||||||
/// Adds a route that responds to the given path
|
/// Adds a route that responds to the given path
|
||||||
/// for requests with the given method (case-insensitive).
|
/// for requests with the given method (case-insensitive).
|
||||||
/// Provide '*' as the method to respond to all methods.
|
/// Provide '*' as the method to respond to all methods.
|
||||||
Route addRoute(String method, Pattern path, Object handler,
|
Route addRoute(String method, String path, Object handler,
|
||||||
{List middleware: const []}) {
|
{List middleware: const []}) {
|
||||||
if (_useCache == true)
|
if (_useCache == true)
|
||||||
throw new StateError('Cannot add routes after caching is enabled.');
|
throw new StateError('Cannot add routes after caching is enabled.');
|
||||||
|
@ -73,10 +77,9 @@ class Router {
|
||||||
|
|
||||||
if (middleware != null) handlers.insertAll(0, middleware);
|
if (middleware != null) handlers.insertAll(0, middleware);
|
||||||
|
|
||||||
final route =
|
final route = new Route(path, method: method, handlers: handlers);
|
||||||
new Route(path, debug: debug, method: method, handlers: handlers);
|
|
||||||
_routes.add(route);
|
_routes.add(route);
|
||||||
return route.._path = _pathify(path);
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepends the given middleware to any routes created
|
/// Prepends the given middleware to any routes created
|
||||||
|
@ -87,7 +90,7 @@ class Router {
|
||||||
/// The resulting router can be chained, too.
|
/// The resulting router can be chained, too.
|
||||||
_ChainedRouter chain(middleware) {
|
_ChainedRouter chain(middleware) {
|
||||||
var piped = new _ChainedRouter(this, middleware);
|
var piped = new _ChainedRouter(this, middleware);
|
||||||
var route = new SymlinkRoute('/', '/', piped);
|
var route = new SymlinkRoute('/', piped);
|
||||||
_routes.add(route);
|
_routes.add(route);
|
||||||
return piped;
|
return piped;
|
||||||
}
|
}
|
||||||
|
@ -103,8 +106,7 @@ class Router {
|
||||||
} else if (route is SymlinkRoute) {
|
} else if (route is SymlinkRoute) {
|
||||||
final newRouter = route.router.clone();
|
final newRouter = route.router.clone();
|
||||||
newMounted[route.path] = newRouter;
|
newMounted[route.path] = newRouter;
|
||||||
final symlink = new SymlinkRoute(route.path, route.pattern, newRouter)
|
final symlink = new SymlinkRoute(route.path, newRouter);
|
||||||
.._head = route._head;
|
|
||||||
router._routes.add(symlink);
|
router._routes.add(symlink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,8 +119,7 @@ class Router {
|
||||||
void dumpTree(
|
void dumpTree(
|
||||||
{callback(String tree),
|
{callback(String tree),
|
||||||
String header: 'Dumping route tree:',
|
String header: 'Dumping route tree:',
|
||||||
String tab: ' ',
|
String tab: ' '}) {
|
||||||
bool showMatchers: false}) {
|
|
||||||
final buf = new StringBuffer();
|
final buf = new StringBuffer();
|
||||||
int tabs = 0;
|
int tabs = 0;
|
||||||
|
|
||||||
|
@ -138,14 +139,14 @@ class Router {
|
||||||
|
|
||||||
for (Route route in router.routes) {
|
for (Route route in router.routes) {
|
||||||
indent();
|
indent();
|
||||||
buf.write('- ${route.path.isNotEmpty ? route.path : '/'}');
|
buf.write('- ');
|
||||||
|
if (route is! SymlinkRoute) buf.write('${route.method} ');
|
||||||
|
buf.write('${route.path.isNotEmpty ? route.path : '/'}');
|
||||||
|
|
||||||
if (route is SymlinkRoute) {
|
if (route is SymlinkRoute) {
|
||||||
buf.writeln();
|
buf.writeln();
|
||||||
dumpRouter(route.router);
|
dumpRouter(route.router);
|
||||||
} else {
|
} else {
|
||||||
if (showMatchers) buf.write(' (${route.matcher.pattern})');
|
|
||||||
|
|
||||||
buf.writeln(' => ${route.handlers.length} handler(s)');
|
buf.writeln(' => ${route.handlers.length} handler(s)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,13 +164,13 @@ class Router {
|
||||||
///
|
///
|
||||||
/// Returns the created route.
|
/// Returns the created route.
|
||||||
/// You can also register middleware within the router.
|
/// You can also register middleware within the router.
|
||||||
SymlinkRoute group(Pattern path, void callback(Router router),
|
SymlinkRoute group(String path, void callback(Router router),
|
||||||
{Iterable middleware: const [],
|
{Iterable middleware: const [],
|
||||||
String name: null,
|
String name: null,
|
||||||
String namespace: null}) {
|
String namespace: null}) {
|
||||||
final router = new Router().._middleware.addAll(middleware);
|
final router = new Router().._middleware.addAll(middleware);
|
||||||
callback(router..debug = debug);
|
callback(router..debug = debug);
|
||||||
return mount(path, router, namespace: namespace).._name = name;
|
return mount(path, router, namespace: namespace)..name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a URI string based on the given input.
|
/// Generates a URI string based on the given input.
|
||||||
|
@ -224,8 +225,13 @@ class Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search by path
|
// Search by path
|
||||||
|
if (!resolved) {
|
||||||
|
var scanner = new SpanScanner(param.replaceAll(_straySlashes, ''));
|
||||||
for (Route route in search.routes) {
|
for (Route route in search.routes) {
|
||||||
if (route.match(param) != null) {
|
int pos = scanner.position;
|
||||||
|
if (route.parser
|
||||||
|
.parse(scanner)
|
||||||
|
.successful && scanner.isDone) {
|
||||||
segments.add(route.path.replaceAll(_straySlashes, ''));
|
segments.add(route.path.replaceAll(_straySlashes, ''));
|
||||||
lastRoute = route;
|
lastRoute = route;
|
||||||
|
|
||||||
|
@ -235,6 +241,8 @@ class Router {
|
||||||
|
|
||||||
resolved = true;
|
resolved = true;
|
||||||
break;
|
break;
|
||||||
|
} else
|
||||||
|
scanner.position = pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,74 +280,46 @@ class Router {
|
||||||
/// with the given method.
|
/// with the given method.
|
||||||
bool resolve(String absolute, String relative, List<RoutingResult> out,
|
bool resolve(String absolute, String relative, List<RoutingResult> out,
|
||||||
{String method: 'GET', bool strip: true}) {
|
{String method: 'GET', bool strip: true}) {
|
||||||
//final cleanAbsolute =
|
|
||||||
// strip == false ? absolute : stripStraySlashes(absolute);
|
|
||||||
final cleanRelative =
|
final cleanRelative =
|
||||||
strip == false ? relative : stripStraySlashes(relative);
|
strip == false ? relative : stripStraySlashes(relative);
|
||||||
//final segments = cleanRelative.split('/').where((str) => str.isNotEmpty);
|
var scanner = new SpanScanner(cleanRelative);
|
||||||
|
|
||||||
|
bool crawl(Router r) {
|
||||||
bool success = false;
|
bool success = false;
|
||||||
//print(
|
|
||||||
// 'Now resolving $method "/$cleanRelative", absolute: $cleanAbsolute');
|
|
||||||
//print('Path segments: ${segments.toList()}');
|
|
||||||
|
|
||||||
for (Route route in routes) {
|
for (Route route in r.routes) {
|
||||||
/* // No longer necessary
|
int pos = scanner.position;
|
||||||
if (route is SymlinkRoute && route._head != null && segments.isNotEmpty) {
|
|
||||||
final s = [];
|
|
||||||
|
|
||||||
for (String seg in segments) {
|
if (route is SymlinkRoute) {
|
||||||
s.add(seg);
|
if (route.parser.parse(scanner).successful) {
|
||||||
final match = route._head.firstMatch(s.join('/'));
|
var s = crawl(route.router);
|
||||||
|
if (s) success = true;
|
||||||
if (match != null) {
|
|
||||||
final cleaned = s.join('/').replaceFirst(match[0], '');
|
|
||||||
var tail = cleanRelative.replaceAll(route._head, '');
|
|
||||||
tail = stripStraySlashes(tail);
|
|
||||||
|
|
||||||
if (cleaned.isEmpty) {
|
|
||||||
//print(
|
|
||||||
// 'Matched relative "$cleanRelative" to head ${route._head
|
|
||||||
// .pattern} on $route. Tail: "$tail"');
|
|
||||||
route.router.debug = route.router.debug || debug;
|
|
||||||
var nested = <RoutingResult>[];
|
|
||||||
route.router.resolve(cleanAbsolute, tail, nested,
|
|
||||||
method: method, strip: false);
|
|
||||||
var result = _dumpResult(
|
|
||||||
cleanRelative,
|
|
||||||
new RoutingResult(
|
|
||||||
match: match,
|
|
||||||
nested: nested,
|
|
||||||
params: route.parseParameters(match[0]),
|
|
||||||
shallowRoute: route,
|
|
||||||
shallowRouter: this,
|
|
||||||
tail: tail));
|
|
||||||
out.add(result);
|
|
||||||
success = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (route.method == '*' || route.method == method) {
|
scanner.position = pos;
|
||||||
final match = route.match(cleanRelative);
|
} else if (route.method == '*' || route.method == method) {
|
||||||
|
var parseResult = route.parser.parse(scanner);
|
||||||
|
|
||||||
if (match != null) {
|
if (parseResult.successful && scanner.isDone) {
|
||||||
var result = new RoutingResult(
|
var result = new RoutingResult(
|
||||||
match: match,
|
parseResult: parseResult,
|
||||||
params: route.parseParameters(cleanRelative),
|
params: parseResult.value,
|
||||||
shallowRoute: route,
|
shallowRoute: route,
|
||||||
shallowRouter: this);
|
shallowRouter: this);
|
||||||
out.add(result);
|
out.add(result);
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scanner.position = pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//print('Could not resolve path "/$cleanRelative".');
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return crawl(this);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the result of [resolve] with [path] passed as
|
/// Returns the result of [resolve] with [path] passed as
|
||||||
/// both `absolute` and `relative`.
|
/// both `absolute` and `relative`.
|
||||||
Iterable<RoutingResult> resolveAbsolute(String path,
|
Iterable<RoutingResult> resolveAbsolute(String path,
|
||||||
|
@ -378,7 +358,7 @@ class Router {
|
||||||
/// For example, if the [Router] has a middleware 'y', and the `namespace`
|
/// For example, if the [Router] has a middleware 'y', and the `namespace`
|
||||||
/// is 'x', then that middleware will be available as 'x.y' in the main router.
|
/// is 'x', then that middleware will be available as 'x.y' in the main router.
|
||||||
/// These namespaces can be nested.
|
/// These namespaces can be nested.
|
||||||
SymlinkRoute mount(Pattern path, Router router,
|
SymlinkRoute mount(String path, Router router,
|
||||||
{bool hooked: true, String namespace: null}) {
|
{bool hooked: true, String namespace: null}) {
|
||||||
// Let's copy middleware, heeding the optional middleware namespace.
|
// Let's copy middleware, heeding the optional middleware namespace.
|
||||||
String middlewarePrefix = namespace != null ? "$namespace." : "";
|
String middlewarePrefix = namespace != null ? "$namespace." : "";
|
||||||
|
@ -389,52 +369,51 @@ class Router {
|
||||||
copiedMiddleware[middlewareName];
|
copiedMiddleware[middlewareName];
|
||||||
}
|
}
|
||||||
|
|
||||||
final route =
|
final route = new SymlinkRoute(path, router);
|
||||||
new SymlinkRoute(path, path, router..debug = debug || router.debug);
|
|
||||||
_mounted[route.path] = router;
|
_mounted[route.path] = router;
|
||||||
_routes.add(route);
|
_routes.add(route);
|
||||||
route._head = new RegExp(route.matcher.pattern.replaceAll(_rgxEnd, ''));
|
//route._head = new RegExp(route.matcher.pattern.replaceAll(_rgxEnd, ''));
|
||||||
|
|
||||||
return route.._name = namespace;
|
return route..name = namespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to any request matching the given path.
|
/// Adds a route that responds to any request matching the given path.
|
||||||
Route all(Pattern path, Object handler, {List middleware}) {
|
Route all(String path, Object handler, {List middleware}) {
|
||||||
return addRoute('*', path, handler, middleware: middleware);
|
return addRoute('*', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a DELETE request.
|
/// Adds a route that responds to a DELETE request.
|
||||||
Route delete(Pattern path, Object handler, {List middleware}) {
|
Route delete(String path, Object handler, {List middleware}) {
|
||||||
return addRoute('DELETE', path, handler, middleware: middleware);
|
return addRoute('DELETE', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a GET request.
|
/// Adds a route that responds to a GET request.
|
||||||
Route get(Pattern path, Object handler, {List middleware}) {
|
Route get(String path, Object handler, {List middleware}) {
|
||||||
return addRoute('GET', path, handler, middleware: middleware);
|
return addRoute('GET', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a HEAD request.
|
/// Adds a route that responds to a HEAD request.
|
||||||
Route head(Pattern path, Object handler, {List middleware}) {
|
Route head(String path, Object handler, {List middleware}) {
|
||||||
return addRoute('HEAD', path, handler, middleware: middleware);
|
return addRoute('HEAD', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a OPTIONS request.
|
/// Adds a route that responds to a OPTIONS request.
|
||||||
Route options(Pattern path, Object handler, {List middleware}) {
|
Route options(String path, Object handler, {List middleware}) {
|
||||||
return addRoute('OPTIONS', path, handler, middleware: middleware);
|
return addRoute('OPTIONS', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a POST request.
|
/// Adds a route that responds to a POST request.
|
||||||
Route post(Pattern path, Object handler, {List middleware}) {
|
Route post(String path, Object handler, {List middleware}) {
|
||||||
return addRoute('POST', path, handler, middleware: middleware);
|
return addRoute('POST', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a PATCH request.
|
/// Adds a route that responds to a PATCH request.
|
||||||
Route patch(Pattern path, Object handler, {List middleware}) {
|
Route patch(String path, Object handler, {List middleware}) {
|
||||||
return addRoute('PATCH', path, handler, middleware: middleware);
|
return addRoute('PATCH', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a route that responds to a PUT request.
|
/// Adds a route that responds to a PUT request.
|
||||||
Route put(Pattern path, Object handler, {List middleware}) {
|
Route put(String path, Object handler, {List middleware}) {
|
||||||
return addRoute('PUT', path, handler, middleware: middleware);
|
return addRoute('PUT', path, handler, middleware: middleware);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,7 +430,7 @@ class _ChainedRouter extends Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Route addRoute(String method, Pattern path, handler,
|
Route addRoute(String method, String path, handler,
|
||||||
{List middleware: const []}) {
|
{List middleware: const []}) {
|
||||||
var route = super.addRoute(method, path, handler,
|
var route = super.addRoute(method, path, handler,
|
||||||
middleware: []..addAll(_handlers)..addAll(middleware ?? []));
|
middleware: []..addAll(_handlers)..addAll(middleware ?? []));
|
||||||
|
@ -459,18 +438,18 @@ class _ChainedRouter extends Router {
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
SymlinkRoute group(Pattern path, void callback(Router router),
|
SymlinkRoute group(String path, void callback(Router router),
|
||||||
{Iterable middleware: const [],
|
{Iterable middleware: const [],
|
||||||
String name: null,
|
String name: null,
|
||||||
String namespace: null}) {
|
String namespace: null}) {
|
||||||
final router =
|
final router =
|
||||||
new _ChainedRouter(_root, []..addAll(_handlers)..addAll(middleware));
|
new _ChainedRouter(_root, []..addAll(_handlers)..addAll(middleware));
|
||||||
callback(router..debug = debug);
|
callback(router..debug = debug);
|
||||||
return mount(path, router, namespace: namespace).._name = name;
|
return mount(path, router, namespace: namespace)..name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SymlinkRoute mount(Pattern path, Router router,
|
SymlinkRoute mount(String path, Router router,
|
||||||
{bool hooked: true, String namespace: null}) {
|
{bool hooked: true, String namespace: null}) {
|
||||||
final route =
|
final route =
|
||||||
super.mount(path, router, hooked: hooked, namespace: namespace);
|
super.mount(path, router, hooked: hooked, namespace: namespace);
|
||||||
|
@ -485,7 +464,7 @@ class _ChainedRouter extends Router {
|
||||||
piped._handlers.addAll([]
|
piped._handlers.addAll([]
|
||||||
..addAll(_handlers)
|
..addAll(_handlers)
|
||||||
..addAll(middleware is Iterable ? middleware : [middleware]));
|
..addAll(middleware is Iterable ? middleware : [middleware]));
|
||||||
var route = new SymlinkRoute('/', '/', piped);
|
var route = new SymlinkRoute('/', piped);
|
||||||
_routes.add(route);
|
_routes.add(route);
|
||||||
return piped;
|
return piped;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ part of angel_route.src.router;
|
||||||
|
|
||||||
/// Represents a complex result of navigating to a path.
|
/// Represents a complex result of navigating to a path.
|
||||||
class RoutingResult {
|
class RoutingResult {
|
||||||
/// The Regex match that matched the given sub-path.
|
/// The parse result that matched the given sub-path.
|
||||||
final Match match;
|
final ParseResult<Map<String, String>> parseResult;
|
||||||
|
|
||||||
/// A nested instance, if a sub-path was matched.
|
/// A nested instance, if a sub-path was matched.
|
||||||
final Iterable<RoutingResult> nested;
|
final Iterable<RoutingResult> nested;
|
||||||
|
@ -80,7 +80,7 @@ class RoutingResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
RoutingResult(
|
RoutingResult(
|
||||||
{this.match,
|
{this.parseResult,
|
||||||
Map<String, dynamic> params: const {},
|
Map<String, dynamic> params: const {},
|
||||||
this.nested,
|
this.nested,
|
||||||
this.shallowRoute,
|
this.shallowRoute,
|
||||||
|
|
|
@ -3,8 +3,6 @@ part of angel_route.src.router;
|
||||||
/// Placeholder [Route] to serve as a symbolic link
|
/// Placeholder [Route] to serve as a symbolic link
|
||||||
/// to a mounted [Router].
|
/// to a mounted [Router].
|
||||||
class SymlinkRoute extends Route {
|
class SymlinkRoute extends Route {
|
||||||
final Pattern pattern;
|
|
||||||
final Router router;
|
final Router router;
|
||||||
|
SymlinkRoute(String path, this.router) : super(path, method: null, handlers: null);
|
||||||
SymlinkRoute(Pattern path, this.pattern, this.router) : super(path);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
name: angel_route
|
name: angel_route
|
||||||
description: A powerful, isomorphic routing library for Dart.
|
description: A powerful, isomorphic routing library for Dart.
|
||||||
version: 2.0.2
|
version: 2.0.3
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/angel_route
|
homepage: https://github.com/angel-dart/angel_route
|
||||||
|
environment:
|
||||||
|
sdk: ">=1.19.0"
|
||||||
|
dependencies:
|
||||||
|
combinator: ^1.0.0-beta
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
browser: ">=0.10.0 < 0.11.0"
|
browser: ">=0.10.0 < 0.11.0"
|
||||||
http: ">=0.11.3 <0.12.0"
|
http: ">=0.11.3 <0.12.0"
|
||||||
|
|
|
@ -4,9 +4,9 @@ import 'package:test/test.dart';
|
||||||
main() {
|
main() {
|
||||||
final router = new Router();
|
final router = new Router();
|
||||||
|
|
||||||
router.get('/', 'GET').as('root');
|
router.get('/', 'GET').name = 'root';
|
||||||
router.get('/user/:id', 'GET');
|
router.get('/user/:id', 'GET');
|
||||||
router.get('/first/:first/last/:last', 'GET').as('full_name');
|
router.get('/first/:first/last/:last', 'GET').name = 'full_name';
|
||||||
|
|
||||||
navigate(params) {
|
navigate(params) {
|
||||||
final uri = router.navigate(params);
|
final uri = router.navigate(params);
|
||||||
|
@ -14,7 +14,7 @@ main() {
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.dumpTree(showMatchers: true);
|
router.dumpTree();
|
||||||
|
|
||||||
group('top-level', () {
|
group('top-level', () {
|
||||||
test('named', () {
|
test('named', () {
|
||||||
|
|
|
@ -25,8 +25,8 @@ basic(BrowserRouter router) {
|
||||||
router.get('a', 'a handler');
|
router.get('a', 'a handler');
|
||||||
|
|
||||||
router.group('b', (router) {
|
router.group('b', (router) {
|
||||||
router.get('a', 'b/a handler').as('b/a');
|
router.get('a', 'b/a handler').name = 'b/a';
|
||||||
router.get('b', 'b/b handler', middleware: ['b/b middleware']).as('b/b');
|
router.get('b', 'b/b handler', middleware: ['b/b middleware']).name = 'b/b';
|
||||||
}, middleware: ['b middleware']);
|
}, middleware: ['b middleware']);
|
||||||
|
|
||||||
router.get('c', 'c handler');
|
router.get('c', 'c handler');
|
||||||
|
|
Loading…
Reference in a new issue