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;
|
part of angel_route.src.router;
|
||||||
|
|
||||||
class _RouteGrammar {
|
class RouteGrammar {
|
||||||
static final Parser<String> notSlash =
|
static final Parser<String> notSlash =
|
||||||
match(new RegExp(r'[^/]+')).value((r) => r.span.text);
|
match(new RegExp(r'[^/]+')).value((r) => r.span.text);
|
||||||
static final Parser<RegExp> regExp = new _RegExpParser();
|
static final Parser<RegExp> regExp = new _RegExpParser();
|
||||||
|
@ -8,27 +8,27 @@ class _RouteGrammar {
|
||||||
match(new RegExp(r':([A-Za-z0-9_]+)'))
|
match(new RegExp(r':([A-Za-z0-9_]+)'))
|
||||||
.value((r) => r.span.text.substring(1));
|
.value((r) => r.span.text.substring(1));
|
||||||
|
|
||||||
static final Parser<_ParameterSegment> parameterSegment = chain([
|
static final Parser<ParameterSegment> parameterSegment = chain([
|
||||||
parameterName,
|
parameterName,
|
||||||
match('?').value((r) => true).opt(),
|
match('?').value((r) => true).opt(),
|
||||||
regExp.opt(),
|
regExp.opt(),
|
||||||
]).map((r) {
|
]).map((r) {
|
||||||
var s = new _ParameterSegment(r.value[0], r.value[2]);
|
var s = new ParameterSegment(r.value[0], r.value[2]);
|
||||||
return r.value[1] == true ? new _OptionalSegment(s) : s;
|
return r.value[1] == true ? new OptionalSegment(s) : s;
|
||||||
});
|
});
|
||||||
|
|
||||||
static final Parser<_WildcardSegment> wildcardSegment =
|
static final Parser<WildcardSegment> wildcardSegment =
|
||||||
match('*').value((r) => new _WildcardSegment());
|
match('*').value((r) => new WildcardSegment());
|
||||||
|
|
||||||
static final Parser<_ConstantSegment> constantSegment =
|
static final Parser<ConstantSegment> constantSegment =
|
||||||
notSlash.map((r) => new _ConstantSegment(r.value));
|
notSlash.map((r) => new ConstantSegment(r.value));
|
||||||
|
|
||||||
static final Parser<_RouteSegment> routeSegment =
|
static final Parser<RouteSegment> routeSegment =
|
||||||
any([parameterSegment, wildcardSegment, constantSegment]);
|
any([parameterSegment, wildcardSegment, constantSegment]);
|
||||||
|
|
||||||
static final Parser<_RouteDefinition> routeDefinition = routeSegment
|
static final Parser<RouteDefinition> routeDefinition = routeSegment
|
||||||
.separatedBy(match('/'))
|
.separatedBy(match('/'))
|
||||||
.map((r) => new _RouteDefinition(r.value ?? []))
|
.map((r) => new RouteDefinition(r.value ?? []))
|
||||||
.surroundedBy(match('/').star().opt());
|
.surroundedBy(match('/').star().opt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,34 +43,37 @@ class _RegExpParser extends Parser<RegExp> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RouteDefinition {
|
class RouteDefinition {
|
||||||
final List<_RouteSegment> segments;
|
final List<RouteSegment> segments;
|
||||||
|
|
||||||
_RouteDefinition(this.segments);
|
RouteDefinition(this.segments);
|
||||||
|
|
||||||
Parser<Map<String, String>> compile() {
|
Parser<Map<String, dynamic>> compile() {
|
||||||
Parser<Map<String, String>> out;
|
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)
|
if (out == null)
|
||||||
out = s.compile();
|
out = s.compile(isLast);
|
||||||
else
|
else
|
||||||
out = s.compileNext(out.then(match('/')).index(0));
|
out = s.compileNext(out.then(match('/')).index(0), isLast);
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _RouteSegment {
|
abstract class RouteSegment {
|
||||||
Parser<Map<String, String>> compile();
|
Parser<Map<String, dynamic>> compile(bool isLast);
|
||||||
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p);
|
Parser<Map<String, dynamic>> compileNext(
|
||||||
|
Parser<Map<String, dynamic>> p, bool isLast);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ConstantSegment extends _RouteSegment {
|
class ConstantSegment extends RouteSegment {
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
_ConstantSegment(this.text);
|
ConstantSegment(this.text);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -78,41 +81,44 @@ class _ConstantSegment extends _RouteSegment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Parser<Map<String, String>> compile() {
|
Parser<Map<String, dynamic>> compile(bool isLast) {
|
||||||
return match(text).value((r) => {});
|
return match(text).value((r) => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
Parser<Map<String, dynamic>> compileNext(
|
||||||
return p.then(compile()).index(0);
|
Parser<Map<String, dynamic>> p, bool isLast) {
|
||||||
|
return p.then(compile(isLast)).index(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _WildcardSegment extends _RouteSegment {
|
class WildcardSegment extends RouteSegment {
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Wildcard segment';
|
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'[^/]*'));
|
return match(new RegExp(r'[^/]*'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Parser<Map<String, String>> compile() {
|
Parser<Map<String, dynamic>> compile(bool isLast) {
|
||||||
return _compile().map((r) => {});
|
return _compile(isLast).map((r) => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Parser<Map<String, String>> compileNext(Parser<Map<String, String>> p) {
|
Parser<Map<String, dynamic>> compileNext(
|
||||||
return p.then(_compile()).index(0);
|
Parser<Map<String, dynamic>> p, bool isLast) {
|
||||||
|
return p.then(_compile(isLast)).index(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OptionalSegment extends _ParameterSegment {
|
class OptionalSegment extends ParameterSegment {
|
||||||
final _ParameterSegment parameter;
|
final ParameterSegment parameter;
|
||||||
|
|
||||||
_OptionalSegment(this.parameter) : super(parameter.name, parameter.regExp);
|
OptionalSegment(this.parameter) : super(parameter.name, parameter.regExp);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -120,12 +126,13 @@ class _OptionalSegment extends _ParameterSegment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Parser<Map<String, String>> compile() {
|
Parser<Map<String, dynamic>> compile(bool isLast) {
|
||||||
return super.compile().opt();
|
return super.compile(isLast).opt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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) {
|
return p.then(_compile().opt()).map((r) {
|
||||||
if (r.value[1] == null) return r.value[0];
|
if (r.value[1] == null) return r.value[0];
|
||||||
return r.value[0]..addAll({name: Uri.decodeComponent(r.value[1])});
|
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 String name;
|
||||||
final RegExp regExp;
|
final RegExp regExp;
|
||||||
|
|
||||||
_ParameterSegment(this.name, this.regExp);
|
ParameterSegment(this.name, this.regExp);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -145,19 +152,20 @@ class _ParameterSegment extends _RouteSegment {
|
||||||
return 'Param: $name';
|
return 'Param: $name';
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser<Map<String, String>> _compile() {
|
Parser<Map<String, dynamic>> _compile() {
|
||||||
return regExp != null
|
return regExp != null
|
||||||
? match(regExp).value((r) => r.span.text)
|
? match(regExp).value((r) => r.span.text)
|
||||||
: _RouteGrammar.notSlash;
|
: RouteGrammar.notSlash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Parser<Map<String, String>> compile() {
|
Parser<Map<String, dynamic>> compile(bool isLast) {
|
||||||
return _compile().map((r) => {name: Uri.decodeComponent(r.span.text)});
|
return _compile().map((r) => {name: Uri.decodeComponent(r.span.text)});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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 p.then(_compile()).map((r) {
|
||||||
return r.value[0]..addAll({name: Uri.decodeComponent(r.value[1])});
|
return r.value[0]..addAll({name: Uri.decodeComponent(r.value[1])});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,13 +5,13 @@ class Route {
|
||||||
final String method;
|
final String method;
|
||||||
final String path;
|
final String path;
|
||||||
final List handlers;
|
final List handlers;
|
||||||
final Map<String, Map<String, String>> _cache = {};
|
final Map<String, Map<String, dynamic>> _cache = {};
|
||||||
final _RouteDefinition _routeDefinition;
|
final RouteDefinition _routeDefinition;
|
||||||
String name;
|
String name;
|
||||||
Parser<Map<String, String>> _parser;
|
Parser<Map<String, dynamic>> _parser;
|
||||||
|
|
||||||
Route(this.path, {@required this.method, @required this.handlers})
|
Route(this.path, {@required this.method, @required this.handlers})
|
||||||
: _routeDefinition = _RouteGrammar.routeDefinition
|
: _routeDefinition = RouteGrammar.routeDefinition
|
||||||
.parse(new SpanScanner(path.replaceAll(_straySlashes, '')))
|
.parse(new SpanScanner(path.replaceAll(_straySlashes, '')))
|
||||||
.value {
|
.value {
|
||||||
if (_routeDefinition.segments.isEmpty) _parser = match('').value((r) => {});
|
if (_routeDefinition.segments.isEmpty) _parser = match('').value((r) => {});
|
||||||
|
@ -24,7 +24,7 @@ class Route {
|
||||||
method: b.method, handlers: b.handlers);
|
method: b.method, handlers: b.handlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser<Map<String, String>> get parser =>
|
Parser<Map<String, dynamic>> get parser =>
|
||||||
_parser ??= _routeDefinition.compile();
|
_parser ??= _routeDefinition.compile();
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,9 +50,9 @@ class Route {
|
||||||
|
|
||||||
for (var seg in _routeDefinition.segments) {
|
for (var seg in _routeDefinition.segments) {
|
||||||
if (i++ > 0) b.write('/');
|
if (i++ > 0) b.write('/');
|
||||||
if (seg is _ConstantSegment)
|
if (seg is ConstantSegment)
|
||||||
b.write(seg.text);
|
b.write(seg.text);
|
||||||
else if (seg is _ParameterSegment) {
|
else if (seg is ParameterSegment) {
|
||||||
if (!params.containsKey(seg.name))
|
if (!params.containsKey(seg.name))
|
||||||
throw new ArgumentError('Missing parameter "${seg.name}".');
|
throw new ArgumentError('Missing parameter "${seg.name}".');
|
||||||
b.write(params[seg.name]);
|
b.write(params[seg.name]);
|
||||||
|
|
|
@ -27,9 +27,6 @@ class Router {
|
||||||
final List<Route> _routes = [];
|
final List<Route> _routes = [];
|
||||||
bool _useCache = false;
|
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);
|
List get middleware => new List.unmodifiable(_middleware);
|
||||||
|
|
||||||
Map<Pattern, Router> get mounted =>
|
Map<Pattern, Router> get mounted =>
|
||||||
|
@ -57,7 +54,7 @@ class Router {
|
||||||
|
|
||||||
/// Provide a `root` to make this Router revolve around a pre-defined route.
|
/// Provide a `root` to make this Router revolve around a pre-defined route.
|
||||||
/// Not recommended.
|
/// Not recommended.
|
||||||
Router({this.debug: false});
|
Router();
|
||||||
|
|
||||||
/// Enables the use of a cache to eliminate the overhead of consecutive resolutions of the same path.
|
/// Enables the use of a cache to eliminate the overhead of consecutive resolutions of the same path.
|
||||||
void enableCache() {
|
void enableCache() {
|
||||||
|
@ -97,7 +94,7 @@ class Router {
|
||||||
|
|
||||||
/// Returns a [Router] with a duplicated version of this tree.
|
/// Returns a [Router] with a duplicated version of this tree.
|
||||||
Router clone() {
|
Router clone() {
|
||||||
final router = new Router(debug: debug);
|
final router = new Router();
|
||||||
final newMounted = new Map<Pattern, Router>.from(mounted);
|
final newMounted = new Map<Pattern, Router>.from(mounted);
|
||||||
|
|
||||||
for (Route route in routes) {
|
for (Route route in routes) {
|
||||||
|
@ -169,7 +166,7 @@ class Router {
|
||||||
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);
|
||||||
return mount(path, router, namespace: namespace)..name = name;
|
return mount(path, router, namespace: namespace)..name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,7 +439,7 @@ class _ChainedRouter extends Router {
|
||||||
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);
|
||||||
return mount(path, router, namespace: namespace)..name = name;
|
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.
|
/// Optimizes a router by condensing all its routes into one level.
|
||||||
Router flatten(Router router) {
|
Router flatten(Router router) {
|
||||||
var flattened = new Router(debug: router.debug == true)
|
var flattened = new Router()
|
||||||
..requestMiddleware.addAll(router.requestMiddleware);
|
..requestMiddleware.addAll(router.requestMiddleware);
|
||||||
|
|
||||||
for (var route in router.routes) {
|
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.
|
/// Represents a complex result of navigating to a path.
|
||||||
class RoutingResult {
|
class RoutingResult {
|
||||||
/// The parse result that matched the given sub-path.
|
/// 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.
|
/// A nested instance, if a sub-path was matched.
|
||||||
final Iterable<RoutingResult> nested;
|
final Iterable<RoutingResult> nested;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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.3+2
|
version: 2.0.4
|
||||||
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:
|
environment:
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:angel_route/angel_route.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
final router = new Router(debug: true)
|
final router = new Router()
|
||||||
..get('/hello', '')
|
..get('/hello', '')
|
||||||
..get('/user/:id', '');
|
..get('/user/:id', '');
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ main() {
|
||||||
router.get('/readers/:readerId', '');
|
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);
|
setUp(router.dumpTree);
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const List<Map<String, String>> people = const [
|
||||||
main() {
|
main() {
|
||||||
http.Client client;
|
http.Client client;
|
||||||
|
|
||||||
final Router router = new Router(debug: true);
|
final Router router = new Router();
|
||||||
HttpServer server;
|
HttpServer server;
|
||||||
String url;
|
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