1 | (function (global, factory) {
|
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
3 | typeof define === 'function' && define.amd ? define(factory) :
|
4 | (global = global || self, global.AbstractNestedRouter = factory());
|
5 | }(this, function () { 'use strict';
|
6 |
|
7 | var defaultExport = (function (Error) {
|
8 | function defaultExport(route, path) {
|
9 | var message = "Unreachable '" + route + "', segment '" + path + "' is not defined";
|
10 | Error.call(this, message);
|
11 | this.message = message;
|
12 | }
|
13 |
|
14 | if ( Error ) defaultExport.__proto__ = Error;
|
15 | defaultExport.prototype = Object.create( Error && Error.prototype );
|
16 | defaultExport.prototype.constructor = defaultExport;
|
17 |
|
18 | return defaultExport;
|
19 | }(Error));
|
20 |
|
21 | function buildMatcher(path, parent) {
|
22 | var regex;
|
23 |
|
24 | var _isSplat;
|
25 |
|
26 | var _priority = -100;
|
27 |
|
28 | var keys = [];
|
29 | regex = path.replace(/[-$.]/g, '\\$&').replace(/\(/g, '(?:').replace(/\)/g, ')?').replace(/([:*]\w+)(?:<([^<>]+?)>)?/g, function (_, key, expr) {
|
30 | keys.push(key.substr(1));
|
31 |
|
32 | if (key.charAt() === ':') {
|
33 | _priority += 100;
|
34 | return ("((?!#)" + (expr || '[^#/]+?') + ")");
|
35 | }
|
36 |
|
37 | _isSplat = true;
|
38 | _priority += 500;
|
39 | return ("((?!#)" + (expr || '[^#]+?') + ")");
|
40 | });
|
41 |
|
42 | try {
|
43 | regex = new RegExp(("^" + regex + "$"));
|
44 | } catch (e) {
|
45 | throw new TypeError(("Invalid route expression, given '" + parent + "'"));
|
46 | }
|
47 |
|
48 | var _hashed = path.includes('#') ? 0.5 : 1;
|
49 |
|
50 | var _depth = path.length * _priority * _hashed;
|
51 |
|
52 | return {
|
53 | keys: keys,
|
54 | regex: regex,
|
55 | _depth: _depth,
|
56 | _isSplat: _isSplat
|
57 | };
|
58 | }
|
59 | var PathMatcher = function PathMatcher(path, parent) {
|
60 | var ref = buildMatcher(path, parent);
|
61 | var keys = ref.keys;
|
62 | var regex = ref.regex;
|
63 | var _depth = ref._depth;
|
64 | var _isSplat = ref._isSplat;
|
65 | return {
|
66 | _isSplat: _isSplat,
|
67 | _depth: _depth,
|
68 | match: function (value) {
|
69 | var matches = value.match(regex);
|
70 |
|
71 | if (matches) {
|
72 | return keys.reduce(function (prev, cur, i) {
|
73 | prev[cur] = typeof matches[i + 1] === 'string' ? decodeURIComponent(matches[i + 1]) : null;
|
74 | return prev;
|
75 | }, {});
|
76 | }
|
77 | }
|
78 | };
|
79 | };
|
80 |
|
81 | PathMatcher.push = function push (key, prev, leaf, parent) {
|
82 | var root = prev[key] || (prev[key] = {});
|
83 |
|
84 | if (!root.pattern) {
|
85 | root.pattern = new PathMatcher(key, parent);
|
86 | root.route = (leaf || '').replace(/\/$/, '') || '/';
|
87 | }
|
88 |
|
89 | prev.keys = prev.keys || [];
|
90 |
|
91 | if (!prev.keys.includes(key)) {
|
92 | prev.keys.push(key);
|
93 | PathMatcher.sort(prev);
|
94 | }
|
95 |
|
96 | return root;
|
97 | };
|
98 |
|
99 | PathMatcher.sort = function sort (root) {
|
100 | root.keys.sort(function (a, b) {
|
101 | return root[a].pattern._depth - root[b].pattern._depth;
|
102 | });
|
103 | };
|
104 |
|
105 | function merge(path, parent) {
|
106 | return ("" + (parent && parent !== '/' ? parent : '') + (path || ''));
|
107 | }
|
108 | function walk(path, cb) {
|
109 | var matches = path.match(/<[^<>]*\/[^<>]*>/);
|
110 |
|
111 | if (matches) {
|
112 | throw new TypeError(("RegExp cannot contain slashes, given '" + matches + "'"));
|
113 | }
|
114 |
|
115 | var parts = path !== '/' ? path.split('/') : [''];
|
116 | var root = [];
|
117 | parts.some(function (x, i) {
|
118 | var parent = root.concat(x).join('/') || null;
|
119 | var segment = parts.slice(i + 1).join('/') || null;
|
120 | var retval = cb(("/" + x), parent, segment ? ((x ? ("/" + x) : '') + "/" + segment) : null);
|
121 | root.push(x);
|
122 | return retval;
|
123 | });
|
124 | }
|
125 | function reduce(key, root, _seen) {
|
126 | var params = {};
|
127 | var out = [];
|
128 | var splat;
|
129 | walk(key, function (x, leaf, extra) {
|
130 | var found;
|
131 |
|
132 | if (!root.keys) {
|
133 | throw new defaultExport(key, x);
|
134 | }
|
135 |
|
136 | root.keys.some(function (k) {
|
137 | if (_seen.includes(k)) { return false; }
|
138 | var ref = root[k].pattern;
|
139 | var match = ref.match;
|
140 | var _isSplat = ref._isSplat;
|
141 | var matches = match(_isSplat ? extra || x : x);
|
142 |
|
143 | if (matches) {
|
144 | Object.assign(params, matches);
|
145 |
|
146 | if (root[k].route) {
|
147 | var routeInfo = Object.assign({}, root[k].info);
|
148 |
|
149 | var hasMatch = false;
|
150 |
|
151 | if (routeInfo.exact) {
|
152 | hasMatch = extra === null;
|
153 | } else {
|
154 | hasMatch = x && leaf === null || x === leaf || _isSplat || !extra;
|
155 | }
|
156 |
|
157 | routeInfo.matches = hasMatch;
|
158 | routeInfo.params = Object.assign({}, params);
|
159 | routeInfo.route = root[k].route;
|
160 | routeInfo.path = _isSplat ? extra : leaf || x;
|
161 | out.push(routeInfo);
|
162 | }
|
163 |
|
164 | if (extra === null && !root[k].keys) {
|
165 | return true;
|
166 | }
|
167 |
|
168 | if (k !== '/') { _seen.push(k); }
|
169 | splat = _isSplat;
|
170 | root = root[k];
|
171 | found = true;
|
172 | return true;
|
173 | }
|
174 |
|
175 | return false;
|
176 | });
|
177 |
|
178 | if (!(found || root.keys.some(function (k) { return root[k].pattern.match(x); }))) {
|
179 | throw new defaultExport(key, x);
|
180 | }
|
181 |
|
182 | return splat || !found;
|
183 | });
|
184 | return out;
|
185 | }
|
186 | function find(path, routes, retries) {
|
187 | var get = reduce.bind(null, path, routes);
|
188 | var set = [];
|
189 |
|
190 | while (retries > 0) {
|
191 | retries -= 1;
|
192 |
|
193 | try {
|
194 | return get(set);
|
195 | } catch (e) {
|
196 | if (retries > 0) {
|
197 | return get(set);
|
198 | }
|
199 |
|
200 | throw e;
|
201 | }
|
202 | }
|
203 | }
|
204 | function add(path, routes, parent, routeInfo) {
|
205 | var fullpath = merge(path, parent);
|
206 | var root = routes;
|
207 | var key;
|
208 |
|
209 | if (routeInfo && routeInfo.nested !== true) {
|
210 | key = routeInfo.key;
|
211 | delete routeInfo.key;
|
212 | }
|
213 |
|
214 | walk(fullpath, function (x, leaf) {
|
215 | root = PathMatcher.push(x, root, leaf, fullpath);
|
216 |
|
217 | if (x !== '/') {
|
218 | root.info = root.info || Object.assign({}, routeInfo);
|
219 | }
|
220 | });
|
221 | root.info = root.info || Object.assign({}, routeInfo);
|
222 |
|
223 | if (key) {
|
224 | root.info.key = key;
|
225 | }
|
226 |
|
227 | return fullpath;
|
228 | }
|
229 | function rm(path, routes, parent) {
|
230 | var fullpath = merge(path, parent);
|
231 | var root = routes;
|
232 | var leaf = null;
|
233 | var key = null;
|
234 | walk(fullpath, function (x) {
|
235 | if (!root) {
|
236 | leaf = null;
|
237 | return true;
|
238 | }
|
239 |
|
240 | key = x;
|
241 | leaf = x === '/' ? routes['/'] : root;
|
242 |
|
243 | if (!leaf.keys) {
|
244 | throw new defaultExport(path, x);
|
245 | }
|
246 |
|
247 | root = root[x];
|
248 | });
|
249 |
|
250 | if (!(leaf && key)) {
|
251 | throw new defaultExport(path, key);
|
252 | }
|
253 |
|
254 | delete leaf[key];
|
255 |
|
256 | if (key === '/') {
|
257 | delete leaf.info;
|
258 | delete leaf.route;
|
259 | }
|
260 |
|
261 | var offset = leaf.keys.indexOf(key);
|
262 |
|
263 | if (offset !== -1) {
|
264 | leaf.keys.splice(leaf.keys.indexOf(key), 1);
|
265 | PathMatcher.sort(leaf);
|
266 | }
|
267 | }
|
268 |
|
269 | var Router = function Router() {
|
270 | var routes = {};
|
271 | var stack = [];
|
272 | return {
|
273 | resolve: function (path, cb) {
|
274 | var ref = path.split(/(?=[#?])/);
|
275 | var uri = ref[0];
|
276 | var hash = ref[1];
|
277 | var query = ref[2];
|
278 | var segments = uri.substr(1).split('/');
|
279 | var prefix = [];
|
280 | var seen = [];
|
281 | segments.some(function (key) {
|
282 | var sub = prefix.concat(("/" + key)).join('');
|
283 | if (key.length) { prefix.push(("/" + key)); }
|
284 |
|
285 | try {
|
286 | var next = find(sub, routes, 1);
|
287 | cb(null, next.filter(function (x) {
|
288 | if (!seen.includes(x.route)) {
|
289 | seen.push(x.route);
|
290 | return true;
|
291 | }
|
292 |
|
293 | return false;
|
294 | }));
|
295 | } catch (e) {
|
296 | cb(e, []);
|
297 | return true;
|
298 | }
|
299 |
|
300 | return false;
|
301 | });
|
302 |
|
303 | if (hash) {
|
304 | cb(null, find(("" + uri + hash), routes, 1));
|
305 | }
|
306 | },
|
307 | mount: function (path, cb) {
|
308 | if (path !== '/') {
|
309 | stack.push(path);
|
310 | }
|
311 |
|
312 | cb();
|
313 | stack.pop();
|
314 | },
|
315 | find: function (path, retries) { return find(path, routes, retries === true ? 2 : retries || 1); },
|
316 | add: function (path, routeInfo) { return add(path, routes, stack.join(''), routeInfo); },
|
317 | rm: function (path) { return rm(path, routes, stack.join('')); }
|
318 | };
|
319 | };
|
320 |
|
321 | return Router;
|
322 |
|
323 | }));
|