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