wildcard patch
This commit is contained in:
parent
08c22da8e5
commit
1c5e572688
8 changed files with 94 additions and 65 deletions
|
@ -1,6 +1,6 @@
|
|||
part of angel_route.src.router;
|
||||
|
||||
class _RouteGrammar {
|
||||
class RouteGrammar {
|
||||
static final Parser<String> notSlash =
|
||||
match(new RegExp(r'[^/]+')).value((r) => r.span.text);
|
||||
static final Parser<RegExp> regExp = new _RegExpParser();
|
||||
|
@ -8,27 +8,27 @@ class _RouteGrammar {
|
|||
match(new RegExp(r':([A-Za-z0-9_]+)'))
|
||||
.value((r) => r.span.text.substring(1));
|
||||
|
||||
static final Parser<_ParameterSegment> parameterSegment = chain([
|
||||
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;
|
||||
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<WildcardSegment> wildcardSegment =
|
||||
match('*').value((r) => new WildcardSegment());
|
||||
|
||||
static final Parser<_ConstantSegment> constantSegment =
|
||||
notSlash.map((r) => new _ConstantSegment(r.value));
|
||||
static final Parser<ConstantSegment> constantSegment =
|
||||
notSlash.map((r) => new ConstantSegment(r.value));
|
||||
|
||||
static final Parser<_RouteSegment> routeSegment =
|
||||
static final Parser<RouteSegment> routeSegment =
|
||||
any([parameterSegment, wildcardSegment, constantSegment]);
|
||||
|
||||
static final Parser<_RouteDefinition> routeDefinition = routeSegment
|
||||
static final Parser<RouteDefinition> routeDefinition = routeSegment
|
||||
.separatedBy(match('/'))
|
||||
.map((r) => new _RouteDefinition(r.value ?? []))
|
||||
.map((r) => new RouteDefinition(r.value ?? []))
|
||||
.surroundedBy(match('/').star().opt());
|
||||
}
|
||||
|
||||
|
@ -43,34 +43,37 @@ class _RegExpParser extends Parser<RegExp> {
|
|||
}
|
||||
}
|
||||
|
||||
class _RouteDefinition {
|
||||
final List<_RouteSegment> segments;
|
||||
class RouteDefinition {
|
||||
final List<RouteSegment> segments;
|
||||
|
||||
_RouteDefinition(this.segments);
|
||||
RouteDefinition(this.segments);
|
||||
|
||||
Parser<Map<String, String>> compile() {
|
||||
Parser<Map<String, String>> out;
|
||||
Parser<Map<String, dynamic>> compile() {
|
||||
Parser<Map<String, dynamic>> out;
|
||||
|
||||
for (var s in segments) {
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
var s = segments[i];
|
||||
bool isLast = i == segments.length - 1;
|
||||
if (out == null)
|
||||
out = s.compile();
|
||||
out = s.compile(isLast);
|
||||
else
|
||||
out = s.compileNext(out.then(match('/')).index(0));
|
||||
out = s.compileNext(out.then(match('/')).index(0), isLast);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _RouteSegment {
|
||||
Parser<Map<String, String>> compile();
|
||||
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p);
|
||||
abstract class RouteSegment {
|
||||
Parser<Map<String, dynamic>> compile(bool isLast);
|
||||
Parser<Map<String, dynamic>> compileNext(
|
||||
Parser<Map<String, dynamic>> p, bool isLast);
|
||||
}
|
||||
|
||||
class _ConstantSegment extends _RouteSegment {
|
||||
class ConstantSegment extends RouteSegment {
|
||||
final String text;
|
||||
|
||||
_ConstantSegment(this.text);
|
||||
ConstantSegment(this.text);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
@ -78,41 +81,44 @@ class _ConstantSegment extends _RouteSegment {
|
|||
}
|
||||
|
||||
@override
|
||||
Parser<Map<String, String>> compile() {
|
||||
Parser<Map<String, dynamic>> compile(bool isLast) {
|
||||
return match(text).value((r) => {});
|
||||
}
|
||||
|
||||
@override
|
||||
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
||||
return p.then(compile()).index(0);
|
||||
Parser<Map<String, dynamic>> compileNext(
|
||||
Parser<Map<String, dynamic>> p, bool isLast) {
|
||||
return p.then(compile(isLast)).index(0);
|
||||
}
|
||||
}
|
||||
|
||||
class _WildcardSegment extends _RouteSegment {
|
||||
class WildcardSegment extends RouteSegment {
|
||||
@override
|
||||
String toString() {
|
||||
return 'Wildcard segment';
|
||||
}
|
||||
|
||||
Parser<Map<String, String>> _compile() {
|
||||
Parser<Map<String, dynamic>> _compile(bool isLast) {
|
||||
if (isLast) return match(new RegExp(r'.*'));
|
||||
return match(new RegExp(r'[^/]*'));
|
||||
}
|
||||
|
||||
@override
|
||||
Parser<Map<String, String>> compile() {
|
||||
return _compile().map((r) => {});
|
||||
Parser<Map<String, dynamic>> compile(bool isLast) {
|
||||
return _compile(isLast).map((r) => {});
|
||||
}
|
||||
|
||||
@override
|
||||
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
||||
return p.then(_compile()).index(0);
|
||||
Parser<Map<String, dynamic>> compileNext(
|
||||
Parser<Map<String, dynamic>> p, bool isLast) {
|
||||
return p.then(_compile(isLast)).index(0);
|
||||
}
|
||||
}
|
||||
|
||||
class _OptionalSegment extends _ParameterSegment {
|
||||
final _ParameterSegment parameter;
|
||||
class OptionalSegment extends ParameterSegment {
|
||||
final ParameterSegment parameter;
|
||||
|
||||
_OptionalSegment(this.parameter) : super(parameter.name, parameter.regExp);
|
||||
OptionalSegment(this.parameter) : super(parameter.name, parameter.regExp);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
@ -120,12 +126,13 @@ class _OptionalSegment extends _ParameterSegment {
|
|||
}
|
||||
|
||||
@override
|
||||
Parser<Map<String, String>> compile() {
|
||||
return super.compile().opt();
|
||||
Parser<Map<String, dynamic>> compile(bool isLast) {
|
||||
return super.compile(isLast).opt();
|
||||
}
|
||||
|
||||
@override
|
||||
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
||||
Parser<Map<String, dynamic>> compileNext(
|
||||
Parser<Map<String, dynamic>> p, bool isLast) {
|
||||
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])});
|
||||
|
@ -133,11 +140,11 @@ class _OptionalSegment extends _ParameterSegment {
|
|||
}
|
||||
}
|
||||
|
||||
class _ParameterSegment extends _RouteSegment {
|
||||
class ParameterSegment extends RouteSegment {
|
||||
final String name;
|
||||
final RegExp regExp;
|
||||
|
||||
_ParameterSegment(this.name, this.regExp);
|
||||
ParameterSegment(this.name, this.regExp);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
@ -145,19 +152,20 @@ class _ParameterSegment extends _RouteSegment {
|
|||
return 'Param: $name';
|
||||
}
|
||||
|
||||
Parser<Map<String, String>> _compile() {
|
||||
Parser<Map<String, dynamic>> _compile() {
|
||||
return regExp != null
|
||||
? match(regExp).value((r) => r.span.text)
|
||||
: _RouteGrammar.notSlash;
|
||||
: RouteGrammar.notSlash;
|
||||
}
|
||||
|
||||
@override
|
||||
Parser<Map<String, String>> compile() {
|
||||
Parser<Map<String, dynamic>> compile(bool isLast) {
|
||||
return _compile().map((r) => {name: Uri.decodeComponent(r.span.text)});
|
||||
}
|
||||
|
||||
@override
|
||||
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
||||
Parser<Map<String, dynamic>> compileNext(
|
||||
Parser<Map<String, dynamic>> p, bool isLast) {
|
||||
return p.then(_compile()).map((r) {
|
||||
return r.value[0]..addAll({name: Uri.decodeComponent(r.value[1])});
|
||||
});
|
||||
|
|
|
@ -5,13 +5,13 @@ class Route {
|
|||
final String method;
|
||||
final String path;
|
||||
final List handlers;
|
||||
final Map<String, Map<String, String>> _cache = {};
|
||||
final _RouteDefinition _routeDefinition;
|
||||
final Map<String, Map<String, dynamic>> _cache = {};
|
||||
final RouteDefinition _routeDefinition;
|
||||
String name;
|
||||
Parser<Map<String, String>> _parser;
|
||||
Parser<Map<String, dynamic>> _parser;
|
||||
|
||||
Route(this.path, {@required this.method, @required this.handlers})
|
||||
: _routeDefinition = _RouteGrammar.routeDefinition
|
||||
: _routeDefinition = RouteGrammar.routeDefinition
|
||||
.parse(new SpanScanner(path.replaceAll(_straySlashes, '')))
|
||||
.value {
|
||||
if (_routeDefinition.segments.isEmpty) _parser = match('').value((r) => {});
|
||||
|
@ -24,7 +24,7 @@ class Route {
|
|||
method: b.method, handlers: b.handlers);
|
||||
}
|
||||
|
||||
Parser<Map<String, String>> get parser =>
|
||||
Parser<Map<String, dynamic>> get parser =>
|
||||
_parser ??= _routeDefinition.compile();
|
||||
|
||||
|
||||
|
@ -50,9 +50,9 @@ class Route {
|
|||
|
||||
for (var seg in _routeDefinition.segments) {
|
||||
if (i++ > 0) b.write('/');
|
||||
if (seg is _ConstantSegment)
|
||||
if (seg is ConstantSegment)
|
||||
b.write(seg.text);
|
||||
else if (seg is _ParameterSegment) {
|
||||
else if (seg is ParameterSegment) {
|
||||
if (!params.containsKey(seg.name))
|
||||
throw new ArgumentError('Missing parameter "${seg.name}".');
|
||||
b.write(params[seg.name]);
|
||||
|
|
|
@ -27,9 +27,6 @@ class Router {
|
|||
final List<Route> _routes = [];
|
||||
bool _useCache = false;
|
||||
|
||||
/// Set to `true` to print verbose debug output when interacting with this route.
|
||||
bool debug = false;
|
||||
|
||||
List get middleware => new List.unmodifiable(_middleware);
|
||||
|
||||
Map<Pattern, Router> get mounted =>
|
||||
|
@ -57,7 +54,7 @@ class Router {
|
|||
|
||||
/// Provide a `root` to make this Router revolve around a pre-defined route.
|
||||
/// Not recommended.
|
||||
Router({this.debug: false});
|
||||
Router();
|
||||
|
||||
/// Enables the use of a cache to eliminate the overhead of consecutive resolutions of the same path.
|
||||
void enableCache() {
|
||||
|
@ -97,7 +94,7 @@ class Router {
|
|||
|
||||
/// Returns a [Router] with a duplicated version of this tree.
|
||||
Router clone() {
|
||||
final router = new Router(debug: debug);
|
||||
final router = new Router();
|
||||
final newMounted = new Map<Pattern, Router>.from(mounted);
|
||||
|
||||
for (Route route in routes) {
|
||||
|
@ -169,7 +166,7 @@ class Router {
|
|||
String name: null,
|
||||
String namespace: null}) {
|
||||
final router = new Router().._middleware.addAll(middleware);
|
||||
callback(router..debug = debug);
|
||||
callback(router);
|
||||
return mount(path, router, namespace: namespace)..name = name;
|
||||
}
|
||||
|
||||
|
@ -442,7 +439,7 @@ class _ChainedRouter extends Router {
|
|||
String namespace: null}) {
|
||||
final router =
|
||||
new _ChainedRouter(_root, []..addAll(_handlers)..addAll(middleware));
|
||||
callback(router..debug = debug);
|
||||
callback(router);
|
||||
return mount(path, router, namespace: namespace)..name = name;
|
||||
}
|
||||
|
||||
|
@ -470,7 +467,7 @@ class _ChainedRouter extends Router {
|
|||
|
||||
/// Optimizes a router by condensing all its routes into one level.
|
||||
Router flatten(Router router) {
|
||||
var flattened = new Router(debug: router.debug == true)
|
||||
var flattened = new Router()
|
||||
..requestMiddleware.addAll(router.requestMiddleware);
|
||||
|
||||
for (var route in router.routes) {
|
||||
|
|
|
@ -3,7 +3,7 @@ part of angel_route.src.router;
|
|||
/// Represents a complex result of navigating to a path.
|
||||
class RoutingResult {
|
||||
/// The parse result that matched the given sub-path.
|
||||
final ParseResult<Map<String, String>> parseResult;
|
||||
final ParseResult<Map<String, dynamic>> parseResult;
|
||||
|
||||
/// A nested instance, if a sub-path was matched.
|
||||
final Iterable<RoutingResult> nested;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name: angel_route
|
||||
description: A powerful, isomorphic routing library for Dart.
|
||||
version: 2.0.3+2
|
||||
version: 2.0.4
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/angel_route
|
||||
environment:
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:angel_route/angel_route.dart';
|
|||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
final router = new Router(debug: true)
|
||||
final router = new Router()
|
||||
..get('/hello', '')
|
||||
..get('/user/:id', '');
|
||||
|
||||
|
@ -11,7 +11,7 @@ main() {
|
|||
router.get('/readers/:readerId', '');
|
||||
});
|
||||
|
||||
router.mount('/color', new Router(debug: true)..get('/:name/shades', ''));
|
||||
router.mount('/color', new Router()..get('/:name/shades', ''));
|
||||
|
||||
setUp(router.dumpTree);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ const List<Map<String, String>> people = const [
|
|||
main() {
|
||||
http.Client client;
|
||||
|
||||
final Router router = new Router(debug: true);
|
||||
final Router router = new Router();
|
||||
HttpServer server;
|
||||
String url;
|
||||
|
||||
|
|
24
test/wildcard_test.dart
Normal file
24
test/wildcard_test.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
import 'package:angel_route/angel_route.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
var router = new Router();
|
||||
router.get('/songs/*/key', 'of life');
|
||||
router.get('/isnt/she/*', 'lovely');
|
||||
router.all('*', 'stevie');
|
||||
|
||||
test('match until end if * is last', () {
|
||||
var result = router.resolveAbsolute('/wonder').first;
|
||||
expect(result.handlers, ['stevie']);
|
||||
});
|
||||
|
||||
test('match if not last', () {
|
||||
var result = router.resolveAbsolute('/songs/what/key').first;
|
||||
expect(result.handlers, ['of life']);
|
||||
});
|
||||
|
||||
test('match if segments before', () {
|
||||
var result = router.resolveAbsolute('/isnt/she/fierce%20harmonica%solo').first;
|
||||
expect(result.handlers, ['lovely']);
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue