diff --git a/.idea/angel_route.iml b/.idea/angel_route.iml index b4a2e37b..78e503ef 100644 --- a/.idea/angel_route.iml +++ b/.idea/angel_route.iml @@ -6,6 +6,7 @@ + diff --git a/README.md b/README.md index 95329b93..69a570af 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ # angel_route + +![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev+7-red.svg) +![build status](https://travis-ci.org/angel-dart/route.svg) + A powerful, isomorphic routing library for Dart. This API is a huge improvement over the original [Angel](https://github.com/angel-dart/angel) diff --git a/lib/src/route.dart b/lib/src/route.dart index ef04ab70..4f29330c 100644 --- a/lib/src/route.dart +++ b/lib/src/route.dart @@ -177,7 +177,7 @@ class Route { Route result; if (path is RegExp) { - result = new Route(path, debug: debug); + result = new Route(path, debug: debug, handlers: handlers, method: method, name: name); } else { final segments = path .toString() @@ -302,7 +302,7 @@ class Route { /// Generates a URI to this route with the given parameters. String makeUri([Map params]) { - String result = _pathified; + String result = _pathify(path); if (params != null) { for (String key in (params.keys)) { result = result.replaceAll( @@ -323,8 +323,14 @@ class Route { Iterable values = _parseParameters(requestPath.replaceAll(_straySlashes, '')); + + _printDebug('Searched request path $requestPath and found these values: $values'); + + final pathString = _pathify(path).replaceAll(new RegExp('\/'), r'\/'); Iterable matches = - _param.allMatches(_pathified.replaceAll(new RegExp('\/'), r'\/')); + _param.allMatches(pathString); + _printDebug('All param names parsed in $pathString: ${matches.map((m) => m.group(0))}'); + for (int i = 0; i < matches.length && i < values.length; i++) { Match match = matches.elementAt(i); String paramName = match.group(1); diff --git a/lib/src/router.dart b/lib/src/router.dart index 367b03bf..f4cd181d 100644 --- a/lib/src/router.dart +++ b/lib/src/router.dart @@ -8,7 +8,8 @@ part 'route.dart'; final RegExp _param = new RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?'); final RegExp _rgxEnd = new RegExp(r'\$+$'); final RegExp _rgxStart = new RegExp(r'^\^+'); -final RegExp _rgxStraySlashes = new RegExp(r'(^((\\/)|(/))+)|(((\\/)|(/))+$)'); +final RegExp _rgxStraySlashes = + new RegExp(r'(^((\\+/)|(/))+)|(((\\+/)|(/))+$)'); final RegExp _slashDollar = new RegExp(r'/+\$'); final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); @@ -28,8 +29,7 @@ class Router extends Extensible { /// Provide a `root` to make this Router revolve around a pre-defined route. /// Not recommended. Router({this.debug: false, Route root}) { - _root = (_root = root ?? new _RootRoute()) - ..debug = debug; + _root = (_root = root ?? new _RootRoute())..debug = debug; } void _printDebug(msg) { @@ -49,13 +49,12 @@ class Router extends Extensible { if (path is RegExp) { return root.child(path, debug: debug, handlers: handlers, method: method); - } else if (path - .toString() - .replaceAll(_straySlashes, '') - .isEmpty) { + } else { + // if (path.toString().replaceAll(_straySlashes, '').isEmpty || true) { return root.child(path.toString(), debug: debug, handlers: handlers, method: method); - } else { + } + /* else { var segments = path .toString() .split('/') @@ -80,7 +79,7 @@ class Router extends Extensible { do { existing = result.resolve(segments[0], filter: (route) => - route.method == method || route.method == '*'); + route.method == method || route.method == '*'); if (existing != null) { result = existing; @@ -111,19 +110,21 @@ class Router extends Extensible { } return result..debug = debug; - } + } */ } /// Creates a visual representation of the route hierarchy and /// passes it to a callback. If none is provided, `print` is called. void dumpTree( - {callback(String tree), header: 'Dumping route tree:', tab: ' '}) { + {callback(String tree), + header: 'Dumping route tree:', + tab: ' ', + showMatchers: false}) { var tabs = 0; final buf = new StringBuffer(); void dumpRoute(Route route, {Pattern replace: null}) { - for (var i = 0; i < tabs; i++) - buf.write(tab); + for (var i = 0; i < tabs; i++) buf.write(tab); if (route == root) buf.writeln('(root)'); @@ -131,7 +132,7 @@ class Router extends Extensible { buf.write('- ${route.method} '); var p = - replace != null ? route.path.replaceAll(replace, '') : route.path; + replace != null ? route.path.replaceAll(replace, '') : route.path; p = p.replaceAll(_straySlashes, ''); if (p.isEmpty) @@ -139,6 +140,10 @@ class Router extends Extensible { else buf.write("'${p.replaceAll(_straySlashes, '')}'"); + if (showMatchers) { + buf.write(' (matcher: ${route.matcher.pattern})'); + } + if (route.handlers.isNotEmpty) buf.writeln(' => ${route.handlers.length} handler(s)'); else @@ -168,7 +173,7 @@ class Router extends Extensible { String name: null, String namespace: null}) { final route = - root.child(path, handlers: middleware, method: method, name: name); + root.child(path, handlers: middleware, method: method, name: name); final router = new Router(root: route); callback(router); @@ -178,7 +183,7 @@ class Router extends Extensible { Map copiedMiddleware = new Map.from(router.requestMiddleware); for (String middlewareName in copiedMiddleware.keys) { requestMiddleware["$middlewarePrefix$middlewareName"] = - copiedMiddleware[middlewareName]; + copiedMiddleware[middlewareName]; } return route; @@ -211,8 +216,7 @@ class Router extends Extensible { _resolve(Route ref, String fullPath, String method, String head, Iterable tail) { - _printDebug( - '$method on $ref: path: $fullPath, head: $head, tail: ${tail.join( + _printDebug('$method $fullPath on $ref: head: $head, tail: ${tail.join( '/')}'); // Does the index route match? @@ -240,19 +244,9 @@ class Router extends Extensible { return index; } } else { - // Try to match children by full path - for (Route child in ref.children) { - if (child.matcher.hasMatch(fullPath)) { - final resolved = _resolve(child, fullPath, method, head, tail); - - if (resolved != null) { - return resolved; - } - } - } - // Now, let's check if any route's head matches the // given head. If so, we try to resolve with that + // given head. If so, we try to resolve with that // route, using a head corresponding to the one we // matched. for (Route child in ref.children) { @@ -277,8 +271,21 @@ class Router extends Extensible { } } else if (child._head != null) { _printDebug( - 'Head ${child._head - .pattern} on $child failed to match $fullPath'); + 'Head ${child._head.pattern} on $child failed to match $fullPath'); + } + } + + // Try to match children by full path + for (Route child in ref.children) { + if (child.matcher.hasMatch(fullPath)) { + final resolved = _resolve(child, fullPath, method, head, tail); + + if (resolved != null) { + return resolved; + } + } else { + _printDebug( + 'Could not match full path $fullPath to matcher ${child.matcher.pattern}.'); } } } @@ -291,12 +298,12 @@ class Router extends Extensible { } } - /// Returns a new Router in which the route tree has been - /// flattened into a linear list. - Router flatten() { - final router = new Router(); + /// Flattens the route tree into a linear list. + void flatten() { + final router = new Router(debug: debug); + normalize(); - _flatten(Route route) { + _flatten(Route parent, Route route) { // if (route.children.isNotEmpty && route.method == '*') return; final r = new Route._base(); @@ -306,15 +313,34 @@ class Router extends Extensible { .._head = route._head .._matcher = route.matcher .._method = route.method - .._parent = router.root - .._path = route.path; + .._name = route.name + .._parent = route.parent // router.root + .._path = route + .path; //'${parent.path}/${route.path}'.replaceAll(_straySlashes, ''); + + // New matcher + final part1 = parent.matcher.pattern + .replaceAll(_rgxStart, '') + .replaceAll(_rgxEnd, '') + .replaceAll(_rgxStraySlashes, '') + .replaceAll(_straySlashes, ''); + final part2 = route.matcher.pattern + .replaceAll(_rgxStart, '') + .replaceAll(_rgxEnd, '') + .replaceAll(_rgxStraySlashes, '') + .replaceAll(_straySlashes, ''); + + final m = '$part1\\/$part2'.replaceAll(_rgxStraySlashes, ''); + + // r._matcher = new RegExp('^$m\$'); + _printDebug('Matcher of flattened route: ${r.matcher.pattern}'); router.root._children.add(r); - route.children.forEach(_flatten); + route.children.forEach((child) => _flatten(route, child)); } - root._children.forEach(_flatten); - return router..debug = debug; + root._children.forEach((child) => _flatten(root, child)); + _root = router.root; } /// Incorporates another [Router]'s routes into this one's. @@ -335,7 +361,7 @@ class Router extends Extensible { Map copiedMiddleware = new Map.from(router.requestMiddleware); for (String middlewareName in copiedMiddleware.keys) { requestMiddleware["$middlewarePrefix$middlewareName"] = - copiedMiddleware[middlewareName]; + copiedMiddleware[middlewareName]; } // final route = root.addChild(router.root, join: false); @@ -343,17 +369,36 @@ class Router extends Extensible { route.debug = debug; if (path is! RegExp) { + // Correct mounted path manually... + final clean = route.matcher.pattern + .replaceAll(_rgxStart, '') + .replaceAll(_rgxEnd, '') + .replaceAll(_rgxStraySlashes, ''); + route._matcher = new RegExp('^$clean\$'); + final _path = path.toString().replaceAll(_straySlashes, ''); _migrateRoute(Route r) { r._path = '$_path/${r.path}'.replaceAll(_straySlashes, ''); - var stripped = r.matcher.pattern + var m = r.matcher.pattern .replaceAll(_rgxStart, '') .replaceAll(_rgxEnd, '') .replaceAll(_rgxStraySlashes, '') .replaceAll(_straySlashes, ''); - stripped = '$_path/$stripped'.replaceAll(_straySlashes, ''); - r._matcher = new RegExp('^$stripped\$'); + + final m1 = _matcherify(_path) + .replaceAll(_rgxStart, '') + .replaceAll(_rgxEnd, '') + .replaceAll(_rgxStraySlashes, '') + .replaceAll(_straySlashes, ''); + + m = '$m1/$m' + .replaceAll(_rgxStraySlashes, '') + .replaceAll(_straySlashes, ''); + + r._matcher = new RegExp('^$m\$'); + _printDebug( + 'New matcher on route in mounted router: ${r.matcher.pattern}'); if (r._head != null) { final head = r._head.pattern @@ -362,7 +407,9 @@ class Router extends Extensible { .replaceAll(_rgxStraySlashes, '') .replaceAll('\\/', '/') .replaceAll(_straySlashes, ''); - r._head = new RegExp(_matcherify('$_path/$head').replaceAll(_rgxEnd, '')); + r._head = new RegExp(_matcherify('$_path/$head') + .replaceAll(_rgxEnd, '') + .replaceAll(_rgxStraySlashes, '')); _printDebug('Head of migrated route: ${r._head.pattern}'); } @@ -377,25 +424,30 @@ class Router extends Extensible { void normalize() { _printDebug('Normalizing route tree...'); - _normalize(Route route) { - route.children.forEach(_normalize); + _normalize(Route route, int index) { + var merge = route.path.replaceAll(_straySlashes, '').isEmpty && + route.children.isNotEmpty; + merge = merge || route.children.length == 1; - if (route.path - .replaceAll(_straySlashes, '') - .isEmpty && - route.children.isNotEmpty) { + if (merge) { _printDebug('Erasing this route: $route'); route.parent._handlers.addAll(route.handlers); for (Route child in route.children) { - route.parent._children.add(child.._parent = route.parent); + route.parent._children.insert(index, child.._parent = route.parent); } route.parent._children.remove(route); } + + for (int i = 0; i < route.children.length; i++) { + _normalize(route.children[i], i); + } } - root.children.forEach(_normalize); + for (int i = 0; i < root.children.length; i++) { + _normalize(root.children[i], i); + } } /// Adds a route that responds to any request matching the given path. diff --git a/pubspec.yaml b/pubspec.yaml index 835e0648..9e2aaab0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_route description: A powerful, isomorphic routing library for Dart. -version: 1.0.0-dev+6 +version: 1.0.0-dev+7 author: Tobe O homepage: https://github.com/angel-dart/angel_route dev_dependencies: