wildcard patch

This commit is contained in:
Tobe O 2017-11-28 16:07:14 -05:00
parent 08c22da8e5
commit 1c5e572688
8 changed files with 94 additions and 65 deletions

View file

@ -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])});
});

View file

@ -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]);

View file

@ -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) {

View file

@ -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;

View file

@ -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:

View file

@ -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);

View file

@ -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
View 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']);
});
}