/** * * Dromeo * Simple and Flexible Pattern Routing Framework for PHP, JavaScript, Python * @version: 1.3.0 * * https://github.com/foo123/Dromeo * **/ !function(root, name, factory) { "use strict"; var m; if (('undefined'!==typeof Components)&&('object'===typeof Components.classes)&&('object'===typeof Components.classesByID)&&Components.utils&&('function'===typeof Components.utils['import'])) /* XPCOM */ (root.EXPORTED_SYMBOLS = [name]) && (root[name] = factory.call(root)); else if (('object'===typeof module)&&module.exports) /* CommonJS */ module.exports = factory.call(root); else if (('function'===typeof(define))&&define.amd&&('function'===typeof(require))&&('function'===typeof(require.specified))&&require.specified(name)) /* AMD */ define(name,['require','exports','module'],function() {return factory.call(root);}); else if (!(name in root)) /* Browser/WebWorker/.. */ (root[name] = (m=factory.call(root)))&&('function'===typeof(define))&&define.amd&&define(function() {return m;} ); }( /* current root */ 'undefined' !== typeof self ? self : this, /* module name */ "Dromeo", /* module factory */ function ModuleFactory__Dromeo(undef) { "use strict"; var __version__ = "1.3.0", PROTO = 'prototype', OP = Object[PROTO], AP = Array[PROTO], FP = Function[PROTO], toString = OP.toString, HAS = OP.hasOwnProperty, _patternOr = /^([^|]+(\|[^|]+)+)$/, _nested = /\[([^\]]*?)\]$/, _group = /\((\d+)\)$/, trim_re = /^\s+|\s+$/g, re_escape = /([*+\[\]\(\)?^$\/\\:.])/g, // auxilliaries trim = String[PROTO].trim ? function(s) {return String(s).trim();} : function(s) {return String(s).replace(trim_re, '');} ; function length(s) { return s.length > 0; } function esc_regex(s) { return s.replace(re_escape, '\\$1'); } function is_array(o) { return '[object Array]' === toString.call(o); } function is_obj(o) { return ('[object Object]' === toString.call(o)) && ('function' === typeof o.constructor) && ('Object' === o.constructor.name); } function is_string(o) { return ('string' === typeof o) || ('[object String]' === toString.call(o)); } function is_number(o) { return ('number' === typeof o) || ('[object Number]' === toString.call(o)); } function is_callable(o) { return "function" === typeof o; } function array_keys(o) { if ('function' === typeof Object.keys) return Object.keys(o); var v, k, l; if (is_array(o)) { v = new Array(l=o.length); for (k=0; k -1) key = key.slice(0, j); if (key && ('[' !== key.charAt(0))) { keys = []; postLeftBracketPos = 0; for (j=0; j ct && p.match(/^\d+$/g)) { ct = +p; } } } key = ct + 1;*/ key = true; } } if (true === key) { lastObj.push(value); } else { if (key == +key) possibleLists.push({key:prevkey, obj:prevobj}); lastObj[key] = value; } } } for (i=possibleLists.length-1; i>=0; --i) { // safe to pass multiple times same obj, it is possible obj = possibleLists[i].key ? possibleLists[i].obj[possibleLists[i].key] : possibleLists[i].obj; if (is_numeric_array(obj)) { obj = array_values(obj); if (possibleLists[i].key) possibleLists[i].obj[possibleLists[i].key] = obj; else array = obj; } } return array; } function flatten(input, output, prefix) { if (is_obj(input) || is_array(input)) { for (var k=array_keys(input),i=0,n=k.length; i 1) parts.push(part[1]); } return parts; } } function offset(i) { return function(m) { return i; }; } function matched(i) { return function(m) { return m[i] ? m[i].length : 0; }; } function index(offsets) { return function(m) { return offsets.reduce(function(i, offset) { return i + offset(m); }, 0); }; } function makePattern(_delims, _patterns, pattern) { var i, l, isPattern, p, m, numGroups = 0, offsets, types = {}, tpl, tplPattern, pat; pattern = split(pattern, _delims[2], _delims[3]); p = []; tpl = []; offsets = []; tplPattern = null; l = pattern.length; isPattern = false; for (i=0; i route.indexOf(_delims[0])) { // literal route return [route, prefix && prefix.length ? prefix+route : route, {}, method, true, [route]]; } parts = split(route, _delims[0], _delims[1]); l = parts.length; isPattern = false; pattern = ''; currOffset = 0; offsets = []; numGroups = 0; captures = {}; tpl = []; if (prefix && prefix.length) { pattern += esc_regex(prefix); currOffset = prefix.length; } for (i=0; i 1) { captureName = trim(p[1]); isOptional = (captureName.length && '?' === captureName.charAt(0)); if (isOptional) captureName = captureName.slice(1); if ((m = captureName.match(_group))) { captureName = captureName.slice(0, -m[0].length); captureIndex = parseInt(m[1], 10); patternTypecaster = HAS.call(capturePattern[2], captureIndex) ? capturePattern[2][captureIndex] : null; if (captureIndex > 0 && captureIndex < capturePattern[1]) { done = false; offsetCapture = capturePattern[5].reduce(function(offsetCapture, o) { if (is_array(o)) { if (o[0] >= captureIndex) { done = true; } } if (!done) { offsetCapture.push(is_array(o) ? matched(o[0]+numGroups+1) : offset(o)); } return offsetCapture; }, []); captureIndex += numGroups + 1; } else { captureIndex = numGroups + 1; } } else { patternTypecaster = capturePattern[2][0] ? capturePattern[2][0] : null; captureIndex = numGroups + 1; } isCaptured = (captureName.length > 0); } pattern += capturePattern[0]; if (isOptional) pattern += '?'; if (isCaptured) captures[captureName] = [captureIndex, patternTypecaster, index(offsets.concat(offsetCapture))]; if (isCaptured) tpl.push({ name : captureName, optional : isOptional, re : new RegExp('^' + capturePattern[4] + '$'), tpl : capturePattern[3] }); currOffset = 0; offsets.push(matched(numGroups + 1)); numGroups += capturePattern[1]; isPattern = false; } else { pattern += esc_regex(part); currOffset += part.length; tpl.push(part); offsets.push(offset(currOffset)); isPattern = true; } } return [route, new RegExp('^' + pattern + '$'), captures, method, false, tpl]; } function to_key(route, method) { return method.join(',') + '->' + route; } function to_method(method) { method = method ? (method.map ? method.map(function(x){return x.toLowerCase()}) : [String(method).toLowerCase()]) : ['*']; if (in_array('*', method)) method = ['*']; method.sort(); return method; } function insertRoute(self, route, oneOff) { if ( route && is_string(route.route) /*&& route.route.length*/ && route.handler && is_callable(route.handler) ) { oneOff = (true === oneOff); var handler = route.handler, defaults = route.defaults || {}, types = route.types || null, name = route.name || null, method = to_method(route.method), h, r, i, l, key; route = self.key + route.route; key = to_key(route, method); r = null; for (i=0,l=self._routes.length; i=0; --i) { if (key === self._routes[i].key) { route = self._routes[i]; self._routes.splice(i, 1); self._delNamedRoute(route); route.dispose(); } } } function DromeoException(message) { Error.call(this, message); this.message = message; this.name = 'DromeoException'; } DromeoException[PROTO] = Object.create(Error[PROTO]); DromeoException[PROTO].constructor = DromeoException; function Route(delims, patterns, route, method, name, prefix) { var self = this; self.__args__ = [delims, patterns]; self.isParsed = false; // lazy init self.handlers = []; self.route = null != route ? String(route) : ''; self.prefix = null != prefix ? String(prefix) : ''; self.method = method; self.pattern = null; self.captures = null; self.literal = false; self.namespace = null; self.tpl = null; self.name = null != name ? String(name) : null; self.key = to_key(self.route, self.method); } Route.to_key = to_key; Route[PROTO] = { constructor: Route, __args__: null, isParsed: false, handlers: null, route: null, prefix: null, pattern: null, captures: null, tpl: null, method: null, literal: null, namespace: null, name: null, key: null, dispose: function() { var self = this; self.__args__ = null; self.isParsed = null; self.handlers = null; self.route = null; self.prefix = null; self.pattern = null; self.captures = null; self.tpl = null; self.method = null; self.literal = null; self.namespace = null; self.name = null; self.key = null; return self; }, parse: function() { var self = this; if (self.isParsed) return self; var r = makeRoute(self.__args__[0], self.__args__[1], self.route, self.method, self.prefix); self.pattern = r[1]; self.captures = r[2]; self.tpl = r[5]; self.literal = true === r[4]; self.__args__ = null; self.isParsed = true; return self; }, match: function(route, method) { var self = this; method = method || '*'; if (!in_array(method, self.method) && ('*' !== self.method[0])) return null; if (!self.isParsed) self.parse(); // lazy init route = String(route); return self.literal ? (route === self.pattern ? [] : null) : route.match(self.pattern); }, make: function(params, strict) { var self = this, out = '', i, l, j, k, p, param, part, tpl; params = params || {}; strict = true === strict; if (!self.isParsed) self.parse(); // lazy init tpl = self.tpl; for (i=0,l=tpl.length; i 0 && delims[0]) _delims[0] = delims[0]; if (l > 1 && delims[1]) _delims[1] = delims[1]; if (l > 2 && delims[2]) _delims[2] = delims[2]; if (l > 3 && delims[3]) _delims[3] = delims[3]; if (l > 4 && delims[4]) _delims[4] = delims[4]; } return self; }, definePattern: function(className, subPattern, typecaster) { var self = this; if ( typecaster && is_string(typecaster) && typecaster.length && HAS.call(Dromeo.TYPES, typecaster) ) typecaster = Dromeo.TYPES[typecaster]; if (!typecaster || !is_callable(typecaster)) typecaster = null; self._patterns[className] = [subPattern, typecaster]; return self; }, dropPattern: function(className) { var self = this, patterns = self._patterns; if (HAS.call(patterns, className)) delete patterns[className]; return self; }, defineType: function(type, caster) { Dromeo.defType(type, caster); return this; }, // build/glue together a uri component from a params object glue: function(params) { return Dromeo.glue_params(params); }, // unglue/extract params object from uri component unglue: function(s) { return Dromeo.unglue_params(s); }, // parse and extract uri components and optional query/fragment params parse: function(s, query_p, fragment_p) { return Dromeo.parse_components(s, query_p, fragment_p); }, // build a url from baseUrl plus query/hash params build: function(baseUrl, query, hash, q, h) { return Dromeo.build_components(baseUrl, query, hash, q, h); }, onGroup: function(groupRoute, handler) { var self = this, groupRouter; groupRoute = String(groupRoute); if (groupRoute.length && is_callable(handler)) { groupRouter = self.clone(groupRoute); self._routes.push(groupRouter); handler(groupRouter); } return self; }, on: function(/* var args here .. */) { var self = this, args = arguments, args_len = args.length, routes, i, n ; if (1 === args_len) { routes = is_array(args[0]) ? args[0] : [args[0]]; } else if (2 === args_len && is_string(args[0]) && is_callable(args[1])) { routes = [{ route: args[0], handler: args[1], method: '*', defaults: {}, types: null }]; } else { routes = args; } for (i=0,n=routes.length; i=0; --i) { if (handler === r.handlers[i][0]) r.handlers.splice(i, 1); } if (!r.handlers.length) clearRoute(self, key); } else { clearRoute(self, key); } } else if (is_string(route) && route.length) { route = String(route); key = to_key(route, to_method(method)); r = null; for (i=0,l=routes.length; i=0; --i) { if (handler === r.handlers[i][0]) r.handlers.splice(i, 1); } if (!r.handlers.length) clearRoute(self, key); } else { clearRoute(self, key); } } } return self; }, fallback: function(handler) { var self = this; if (1 > arguments.length) handler = false; if (false === handler || null === handler || is_callable(handler)) self._fallback = handler; return self; }, make: function(named_route, params, strict) { var routes = this._named_routes; return HAS.call(routes, named_route) ? routes[named_route].make(params, strict) : null; }, route: function(r, method, breakOnFirstMatch, getter) { var self = this, proceed, prefix, routes, route, params, defaults, type, to_remove, i, l, lh, h, match, handlers, handler, found; ; if (!self.isTop() && !self._routes.length) return false; proceed = true; found = false; r = null != r ? String(r) : ''; prefix = self._prefix + self.key; if (prefix.length) { proceed = (prefix === r.slice(0, prefix.length)); } if (proceed) { breakOnFirstMatch = false !== breakOnFirstMatch; method = null != method ? String(method).toLowerCase() : '*'; routes = self._routes.slice(); // copy, avoid mutation l = routes.length; for (i=0; i