diff --git a/Makefile b/Makefile index c7a02ea..08ead97 100755 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ + bootstrap.js: scripts/bootstrap.js node $< @@ -11,6 +12,16 @@ bootstrap.js: scripts/bootstrap.js bootstrap: bootstrap.js +node_modules: + npm install + + +dev: node_modules + cp $ func +// +// doc(doc, long_doc, func) +// -> func +// +// +// XXX is this a good idea to combine a documenter and doc formatter??? +// ...can't think of a good enough set of names to separate them... +var doc = +module.doc = +function(doc, action){ + // template string processor... + if(doc instanceof Array){ + return object.doc(...arguments) } + // document function... + var args = [...arguments] + action = args.pop() + var [doc, long_doc] = args + return object.mixinFlat( + action, + { + doc, + long_doc, + }) } + + // XXX doc... var doWithRootAction = module.doWithRootAction = @@ -26,6 +58,46 @@ function(func){ return func.apply(this, [handlers.pop()].concat(args)) } } +//--------------------------------------------------------------------- +// pre-call tests... + +// Debounce action call... +// +// debounce() +// debounce(timeout[, postcall]) +// -> this +// -> false +// +// +// XXX would be good to add a version of this that would debounce taking +// into acoount args... +// XXX EXPERIMENTAL (precall)... +var debounce = +module.debounce = +function(timeout=200, postcall=true){ + var debounced = false + var last_args + + return function(action, ...args){ + // call... + if(!debounced){ + debounced = setTimeout( + function(){ + debounced = false + // post call... + postcall + && last_args !== undefined + && action.call(this, ...last_args) }.bind(this), + timeout) + // cleanup... + last_args = undefined + return false + // skip... + } else { + last_args = args + return this } } } + + //--------------------------------------------------------------------- // String action parser/runner... @@ -71,6 +143,7 @@ function(func){ // } // // +// NOTE: both actionName and IDENTIFIER can contain '.' delimiting paths... // NOTE: identifiers are resolved as attributes of the context... // NOTE: this is a stateless object... // XXX this is the same as ImageGrid's keyboard.parseActionCall(..), reuse @@ -116,7 +189,7 @@ Object.assign( '\\d+\\.\\d+|\\d+', // identifiers... - '[a-zA-Z$@#_][a-zA-Z0-9$@#_]*', + '[a-zA-Z$@#_][a-zA-Z0-9$@#_\\-\\.]*', // rest args... '\\.\\.\\.', @@ -133,8 +206,9 @@ Object.assign( new parseStringAction.Argument(e.slice(1)) // idetifiers... // NOTE: keep this last as it is the most general... - : /^[a-zA-Z$@#_][a-zA-Z0-9$@#_]*$/.test(e) ? + : /^[a-zA-Z$@#_][a-zA-Z0-9$@#_\-\.]*$/.test(e) ? new parseStringAction.Identifier(e) + // other values... : JSON.parse(e) }) return { @@ -163,6 +237,7 @@ Object.assign( ALLARGS: new __Argument('...'), // general API... + // XXX should this use .resolvePath(..) to get args??? resolveArgs: function(context, action_args, call_args){ var that = this var rest @@ -178,26 +253,43 @@ Object.assign( : call_args[parseInt(arg.value)]) // resolve idents... : arg instanceof that.Identifier ? - context[arg.value] + that.resolvePathValue(context, arg.value) + //context[arg.value] : arg }) rest != null && args.splice(rest, 1, ...call_args) return args }, - + resolvePath: function(context, path){ + var path = path.split(/\./g) + return { + name: path.pop(), + context: path + .reduce(function(res, n){ + return res != null ? + res[n] + : res + }, context), + } }, + resolvePathValue: function(context, path){ + var {context, name} = this.resolvePath(...arguments) + return context[name] }, + isPathReachable: function(context, path){ + var {context, name} = this.resolvePath(context, path) + return context + && name in context }, // XXX should this break if action does not exist??? callAction: function(context, action, ...args){ action = typeof(action) == typeof('str') ? this(action) : action - // XXX should this break if action does not exist??? - return context[action.action] instanceof Function ? - context[action.action] - .apply(context, this.resolveArgs(context, action.arguments, args)) - // action not found or is not callable... (XXX) + // NOTE: we use the root context to resolve the args... + var root = context + var {context, name} = this.resolvePath(context, action.action) + return (context && context[name] instanceof Function) ? + context[name](...this.resolveArgs(root, action.arguments, args)) + // action not found or is not callable... + // XXX should this break if action does not exist??? : undefined }, - applyAction: function(context, action, args){ - return this.callAction(context, action, ...args) }, - // XXX make this stricter... isStringAction: function(txt){ try{ @@ -303,9 +395,9 @@ module.UNDEFINED = ASIS(undefined) // XXX is this correct?? // NOTE: by default an action will return 'this', i.e. the action set // object the action was called from. -// NOTE: if func.nmae is set to '' it will be reset to the -// action name by Action(..). This is a means for extending functions -// to get the specific action name. +// NOTE: if func.nmae is set to '' or it is '' (anonymous +// function) it will be reset to the action name by Action(..). This +// is a means for extending functions to get the specific action name. // Example: // var getActionName = function(func){ // var f = function(...args){ @@ -341,9 +433,7 @@ module.UNDEFINED = ASIS(undefined) // XXX might be a good idea to add an option to return the full results... var Action = module.Action = -object.Constructor('Action', { - __proto__: Function, - +object.Constructor('Action', Function, { // Control how an action handles returned promises... // // Possible values: @@ -434,6 +524,29 @@ object.Constructor('Action', { || MetaActions.getHandlers var handlers = getHandlers.call(context, outer) + // precall test... + // NOTE: we are calling only the top-most precall method, the + // rest are ignored... + // XXX EXPERIMENTAL (precall)... + var precall = handlers + .map(function(h){ + return (h.pre || {}).precall ? + [h.pre.precall] + : [] }) + .flat() + .pop() + if(typeof(precall) == 'function'){ + // XXX revise args... + precall = precall.call(context, this, ...args) + if(precall){ + return { + rejected: true, + // XXX revise how default value is returned... + result: precall instanceof ASIS ? + precall.value + : precall, + } } } + // handle cases where .func is not in handlers... // // NOTE: see Special cases in method doc above... @@ -447,8 +560,7 @@ object.Constructor('Action', { && (cur.doc = this.doc) this.long_doc && (cur.long_doc = this.long_doc) - handlers.unshift(cur) - } + handlers.unshift(cur) } // special case: see if we need to handle the call without handlers... var preActionHandler = context.preActionHandler @@ -474,9 +586,7 @@ object.Constructor('Action', { if(res && res !== context && res instanceof Function){ - a.post = res - } - } + a.post = res } } return a }) // handlers: pre phase... @@ -498,26 +608,20 @@ object.Constructor('Action', { // reset the result... // NOTE: this is the only difference between this // and wrapper stages... - res = context - } - } + res = context } } return a }) // XXX EXPERIMENTAL (after calls)... } catch(error){ // XXX should we unwind this??? delete context.__action_after_running - throw error - } + throw error } // return context if nothing specific is returned... - res = res === undefined ? context - : res instanceof ASIS ? res.value - // XXX returning an explicit [undefined]... - //: res instanceof Array - // && res.length == 1 - // && res.indexOf(undefined) == 0 ? - // undefined + res = res === undefined ? + context + : res instanceof ASIS ? + res.value : res return { @@ -544,8 +648,7 @@ object.Constructor('Action', { .reverse() .forEach(function(a){ a.post - && a.post.apply(context, args) - }) + && a.post.apply(context, args) }) // wrapper handlers: post phase... data.wrapper && data.wrapper @@ -553,15 +656,13 @@ object.Constructor('Action', { .reverse() .forEach(function(a){ a.post - && a.post.call(context, res, outer, args.slice(1)) - }) + && a.post.call(context, res, outer, args.slice(1)) }) // XXX EXPERIMENTAL (after calls)... } catch(error){ // should we unwind this??? delete context.__action_after_running - throw error - } + throw error } // handle after calls... // XXX EXPERIMENTAL (after calls)... @@ -578,9 +679,7 @@ object.Constructor('Action', { delete context.__action_after_running // back to prev level... } else { - context.__action_after_running = context.__action_after_running[0] - } - } + context.__action_after_running = context.__action_after_running[0] } } return res }, @@ -588,12 +687,17 @@ object.Constructor('Action', { // chaining... // // For docs see: MetaActions.chainApply(..) and the base module doc. - chainApply: function(context, inner, args){ - args = [...(args || [])] + chainCall: function(context, inner, ...args){ + args = args || [] var outer = this.name var data = this.pre(context, args) + // precall test... + // XXX EXPERIMENTAL (precall)... + if(data.rejected == true){ + return data.result } + // call the inner action/function if preset.... // NOTE: this is slightly different (see docs) to what happens in // .pre(..)/.post(..), thus we are doing this separately and @@ -602,9 +706,9 @@ object.Constructor('Action', { var res = inner instanceof Function ? inner.apply(context, args) : inner instanceof Array && inner.length > 0 ? - context[inner.pop()].chainApply(context, inner, args) + context[inner.pop()].chainCall(context, inner, ...args) : typeof(inner) == typeof('str') ? - context[inner].chainApply(context, null, args) + context[inner].chainCall(context, null, ...args) : undefined // call the resulting function... @@ -614,9 +718,7 @@ object.Constructor('Action', { // push the inner result into the chain... } else if(res !== undefined){ - data.result = res - } - } + data.result = res } } // returned promise -> await for resolve/error... // XXX should we be able to set this in the context??? @@ -628,12 +730,11 @@ object.Constructor('Action', { .then(function(){ return that.post(context, data) }) .catch(function(){ - return that.post(context, data) }) - } + return that.post(context, data) }) } return this.post(context, data) }, - chainCall: function(context, inner){ - return this.chainApply(context, inner, [...arguments].slice(2)) }, + chainApply: function(context, inner, args){ + return this.chainCall(context, inner, ...args) }, // constructor... @@ -651,7 +752,7 @@ object.Constructor('Action', { // create the actual instance we will be returning... var meth = function(){ - return meth.chainApply(this, null, arguments) } + return meth.chainCall(this, null, ...arguments) } meth.__proto__ = this.__proto__ // precess args... @@ -661,7 +762,7 @@ object.Constructor('Action', { .slice(2) .filter(function(e){ return e !== undefined }) func = args.pop() - last = args[args.length-1] + var last = args[args.length-1] attrs = (last != null && typeof(last) != typeof('str')) ? args.pop() : {} @@ -686,11 +787,8 @@ object.Constructor('Action', { meth.func = func - if(func.name == ''){ - Object.defineProperty(func, 'name', { - value: name, - }) - } + ;(func.name == '' || func.name == '') + && Object.defineProperty(func, 'name', { value: name }) // make introspection be a bit better... meth.toString = function(){ @@ -725,9 +823,7 @@ object.Constructor('Action', { // XXX should an alias return a value??? var Alias = module.Alias = -object.Constructor('Alias', { - __proto__: Action.prototype, - +object.Constructor('Alias', Action, { __new__: function(context, alias, doc, ldoc, attrs, target){ // precess args... var args = doc instanceof Array ? @@ -736,7 +832,7 @@ object.Constructor('Alias', { .slice(2) .filter(function(e){ return e !== undefined }) target = args.pop() - last = args[args.length-1] + var last = args[args.length-1] attrs = (last != null && typeof(last) != typeof('str')) ? args.pop() : {} @@ -764,11 +860,14 @@ object.Constructor('Alias', { if(target == ''){ return } + var parser = + this.parseStringAction + || parseStringAction var p = parsed - || (this.parseStringAction || parseStringAction)(target) + || parser(target) - return p.action in this ? - (this.parseStringAction || parseStringAction).callAction(this, p, ...arguments) + return parser.isPathReachable(this, p.action) ? + parser.callAction(this, p, ...arguments) // error... : console.error(`${alias}: Unknown alias target action: ${p.action}`) } func.toString = function(){ @@ -776,7 +875,6 @@ object.Constructor('Alias', { // make the action... var meth = object.parentCall(Alias.prototype.__new__, this, context, alias, doc, ldoc, attrs, func) - //meth.__proto__ = this.__proto__ meth.func.alias = target @@ -805,19 +903,15 @@ module.MetaActions = { var prop = Object.getOwnPropertyDescriptor(cur, k) while(!prop && cur.__proto__ != null){ var cur = cur.__proto__ - var prop = Object.getOwnPropertyDescriptor(cur, k) - } + var prop = Object.getOwnPropertyDescriptor(cur, k) } if(prop.get != null){ - continue - } + continue } //if(k == 'actions' || k == 'length'){ // continue //} // get only actions... if(this[k] instanceof Action){ - res.push(k) - } - } + res.push(k) } } return res }, @@ -841,12 +935,23 @@ module.MetaActions = { // XXX EXPERIMENTAL... call: function(action, ...args){ return action instanceof Function ? - action.apply(this, args) + action.call(this, ...args) : this[action] ? - this[action].apply(this, args) - : this.parseStringAction.applyAction(this, action, args) }, - apply: function(action, args){ - return this.call(action, ...args)}, + this[action](...args) + : this.parseStringAction.callAction(this, action, ...args) }, + // XXX EXPERIMENTAL -- symantics of this are not final... + // XXX need str syntax for this... + // XXX need to be able to both chain and parallel actions, a-la + // Promise.all(..)... + chain: function(actions, ...args){ + var that = this + return actions + .reduce(function(res, action){ + return res instanceof Promise ? + res + .then(function(res){ + return that.call(action, res, ...args) }) + : that.call(action, res, ...args) }, that) }, // Set/remove action alias... @@ -880,8 +985,7 @@ module.MetaActions = { // set alias... } else { - this[alias] = Alias(...arguments) - } }), + this[alias] = Alias(...arguments) } }), // Get action attribute... @@ -915,6 +1019,9 @@ module.MetaActions = { // XXX add option to to enable/disable look in .__actioncall__... getActionAttr: function(action, attr){ var cur = this + action = typeof(action) == 'function' ? + action.name + : action // go up the proto chain... while(cur.__proto__ != null){ @@ -934,18 +1041,35 @@ module.MetaActions = { this.parseStringAction(cur[action].alias).action, attr) if(res !== undefined){ - return res - } - } - } - cur = cur.__proto__ - } + return res } } } + cur = cur.__proto__ } // search .__actioncall__ action... if(cur[action] != null && action != '__actioncall__'){ - return this.getActionAttr('__actioncall__', attr) - } - }, + return this.getActionAttr('__actioncall__', attr) } }, + + // Get action attribute with alias support... + // + // An aliased attribute is one containing a string name of another + // action. + // + // To avoid things changing when actions are added/removed this does + // not support string attrubute values. + getActionAttrAliased: function(action, attr){ + var value = action + var seen = new Set() + do { + // recursive alias... + if(seen.has(value)){ + throw new Error( + 'getActionAttrAliased: recursive alias for "'+ attr +'": ' + // XXX should we split seen at value??? + + [...seen, value].join(' -> ')) } + seen.add(value) + // next value... + value = this.getActionAttr(value, attr) + } while(typeof(value) == 'string') + return value }, // Get root action attribute value... // @@ -956,14 +1080,15 @@ module.MetaActions = { // base Action object is checked (Action.prototype.await)... getRootActionAttr: function(action, attr){ var cur = this + action = typeof(action) == 'function' ? + action.name + : action // go up the proto chain... while(cur.__proto__ != null){ if(cur[action] != null){ - var target = cur - } - cur = cur.__proto__ - } + var target = cur } + cur = cur.__proto__ } // attribute of action... if(target[action][attr] !== undefined){ @@ -972,9 +1097,7 @@ module.MetaActions = { // attribute of action function... } else if(target[action].func && target[action].func[attr] !== undefined){ - return target[action].func[attr] - } - }, + return target[action].func[attr] } }, // Get action documentation... // @@ -992,49 +1115,57 @@ module.MetaActions = { getDoc: function(actions){ var res = {} var that = this - actions = actions == null ? this.actions - : arguments.length > 1 ? [...arguments] - : typeof(actions) == typeof('str') ? [actions] + actions = actions == null ? + this.actions + : arguments.length > 1 ? + [...arguments] + : (typeof(actions) == typeof('str') + || typeof(actions) == 'function') ? + [actions] : actions // get the first defined set of docs in the inheritance chain... actions.forEach(function(n){ var cur = that + n = typeof(n) == 'function' ? + n.name + : n res[n] = [] // go up the proto chain... while(cur.__proto__ != null){ if(cur[n] != null && cur[n].doc != null){ res[n] = [ cur[n].doc, cur[n].long_doc, cur[n].name ] - break - } - cur = cur.__proto__ - } - }) + break } + cur = cur.__proto__ } }) return res }, getPath: function(actions){ var res = {} var that = this - actions = actions == null ? this.actions - : arguments.length > 1 ? [...arguments] - : typeof(actions) == typeof('str') ? [actions] + actions = actions == null ? + this.actions + : arguments.length > 1 ? + [...arguments] + : (typeof(actions) == typeof('str') + || typeof(actions) == 'function') ? + [actions] : actions // get the first defined set of docs in the inheritance chain... actions.forEach(function(n){ var cur = that + n = typeof(n) == 'function' ? + n.name + : n // go up the proto chain... while(cur.__proto__ != null){ if(cur[n] != null && cur[n].doc != null){ var doc = cur[n].doc var long_doc = cur[n].long_doc - break - } - cur = cur.__proto__ - } + break } + cur = cur.__proto__ } - res[(doc && doc.replace(/[\\\/]$/, '/'+n)) || n] = [n, doc, long_doc] - }) + res[(doc && doc.replace(/[\\\/]$/, '/'+n)) || n] = [n, doc, long_doc] }) return res }, @@ -1077,8 +1208,7 @@ module.MetaActions = { return this.__handler_cache ? 'on' : 'off' } else if(to == '??'){ - return ['on', 'off'] - } + return ['on', 'off'] } to = (to === true || to == 'on') ? true : (to === false || to == 'off') ? false @@ -1092,19 +1222,16 @@ module.MetaActions = { var parent = this.__handler_cache var cache = this.__handler_cache = {} for(var a in parent){ - cache[a] = parent[a] - } + cache[a] = parent[a] } // local cache only... } else { - this.__handler_cache = this.__handler_cache || {} - } + this.__handler_cache = this.__handler_cache || {} } } else { // NOTE: we do not delete here so as to shadow the parent's // cache... - this.__handler_cache = false - } + this.__handler_cache = false } // XXX this is not the handler protocol... return this }, @@ -1136,13 +1263,9 @@ module.MetaActions = { var parent = this.__handler_cache var cache = this.__handler_cache = {} for(var a in parent){ - cache[a] = parent[a] - } - } + cache[a] = parent[a] } } - delete cache[name] - } - } + delete cache[name] } } return this }, // Get action handlers from the inheritance chain... @@ -1160,19 +1283,17 @@ module.MetaActions = { // XXX EXPERIMENTAL (handler cache)... var cache = this.__handler_cache if(cache && cache[name]){ - return cache[name].slice() - } + return cache[name].slice() } // get the handlers... var handlers = [] + var actions = [] var cur = this - while(cur.__proto__ != null){ + while(cur != null){ // get action "event" handlers... - if(cur.hasOwnProperty('_action_handlers') - && name in cur._action_handlers){ - handlers.splice.apply(handlers, - [handlers.length, 0].concat(cur._action_handlers[name])) - } + if(cur.hasOwnProperty('__action_handlers') + && name in cur.__action_handlers){ + handlers.push(cur.__action_handlers[name]) } // get the overloading action... // NOTE: this will get all the handlers including the root @@ -1182,23 +1303,25 @@ module.MetaActions = { if(cur.hasOwnProperty(name)){ // action -> collect... if(cur[name] instanceof Action){ - handlers.push(cur[name].func) + actions.push(cur[name].func) // function -> terminate chain... } else if(cur[name] instanceof Function){ - handlers.push(cur[name]) - break - } - } + actions.push(cur[name]) + break } } - cur = cur.__proto__ - } + cur = cur.__proto__ } + + // NOTE: we call all the handlers before the actions... (???) + handlers = [ + ...handlers.flat(), + ...actions, + ] // handler cache... // XXX EXPERIMENTAL (handler cache)... if(cache){ - cache[name] = handlers - } + cache[name] = handlers } return handlers }, @@ -1233,8 +1356,7 @@ module.MetaActions = { res.pre = a } else { - res.post = a.post_handler - } + res.post = a.post_handler } a.doc && (res.doc = a.doc) @@ -1288,14 +1410,32 @@ module.MetaActions = { // NOTE: 'post' mode is the default. // // XXX should we have multiple tags per handler??? + //__action_handlers: null, on: function(actions, b, c){ - var handler = typeof(c) == 'function' ? c : b - var tag = typeof(c) == 'function' ? b : c - - // XXX make this split by whitespace... - actions = typeof(actions) == 'string' ? actions.split(/ +/) : actions - var that = this + var _handler = arguments.length == 3 ? c : b + var tag = arguments.length == 3 ? b : c + + // alias handler... + // NOTE: we cache the parsed handler... + var parsed + var handler = typeof(_handler) == 'function' ? + _handler + // alias handler... + : function(...args){ + var parser = this.parseStringAction || parseStringAction + parsed = parsed || parser(_handler) + return parser.isPathReachable(this, parsed.action) ? + parser.callAction(this, parsed, ...arguments) + // error... + : console.error( + `.on(..): Unknown handler target action: ${parsed.action}`) } + + // actions as a string/array... + actions = typeof(actions) == 'string' ? + actions.split(/\s+/) + : actions + actions.forEach(function(action){ // prepare the handler... var mode = action.split('.') @@ -1321,24 +1461,19 @@ module.MetaActions = { // not pre mode... } else if(mode != 'pre') { // XXX - throw 'Unknown action mode: '+action+'.'+mode - } + throw new Error('Unknown action mode: '+action+'.'+mode) } a_handler.event_tag = tag // register handlers locally only... - if(!that.hasOwnProperty('_action_handlers')){ - that._action_handlers = {} - } - if(!(action in that._action_handlers)){ - that._action_handlers[action] = [] - } + if(!that.hasOwnProperty('__action_handlers')){ + that.__action_handlers = {} } + if(!(action in that.__action_handlers)){ + that.__action_handlers[action] = [] } // register a handler only once... - if(that._action_handlers[action].indexOf(a_handler) < 0){ + if(that.__action_handlers[action].indexOf(a_handler) < 0){ // NOTE: last registered is first... - that._action_handlers[action].splice(0, 0, a_handler) - } - }) + that.__action_handlers[action].splice(0, 0, a_handler) } }) return this }, @@ -1361,9 +1496,9 @@ module.MetaActions = { // NOTE: the handler passed to .off(..) for removal must be the same // as the handler passed to .on(..) / .one(..) off: function(actions, handler){ - if(this.hasOwnProperty('_action_handlers')){ + if(this.hasOwnProperty('__action_handlers')){ - actions = actions == '*' ? Object.keys(this._action_handlers) + actions = actions == '*' ? Object.keys(this.__action_handlers) : typeof(actions) == 'string' ? actions.split(' ') : actions @@ -1376,7 +1511,7 @@ module.MetaActions = { that.resetHandlerCache(action) // get the handlers... - var h = that._action_handlers[action] || [] + var h = that.__action_handlers[action] || [] // remove explicit handler... if(typeof(handler) == 'function'){ @@ -1386,18 +1521,14 @@ module.MetaActions = { h.forEach(function(e, j){ // NOTE: we will only get the first match... if(e.orig_handler === handler && i == -1){ - i = j - } - }) + i = j } }) } else if(mode == 'pre'){ - i = h.indexOf(handler) - } + i = h.indexOf(handler) } // NOTE: unknown modes are skipped... if(i >= 0){ - h.splice(i, 1) - } + h.splice(i, 1) } // remove all handlers... } else if(handler == null || handler == 'all' || handler == '*'){ @@ -1409,10 +1540,7 @@ module.MetaActions = { h.splice.apply(h, [0, h.length] .concat(h.filter(function(e){ - return e.event_tag != handler }))) - } - }) - } + return e.event_tag != handler }))) } }) } return this }, @@ -1439,11 +1567,9 @@ module.MetaActions = { that.off(action, handler.orig_handler) // call the actual supplied handler function... - return handler.orig_handler.apply(this, arguments) - } + return handler.orig_handler.apply(this, arguments) } handler.orig_handler = _handler - that.on(action, tag, handler) - }) + that.on(action, tag, handler) }) return this }, @@ -1466,8 +1592,7 @@ module.MetaActions = { mode = mode || 'top' if(!this.__action_after_running){ - throw new Error('afterAction: no action is running.') - } + throw new Error('afterAction: no action is running.') } ;(mode == 'top' ? this.__action_after_running[1] @@ -1480,14 +1605,13 @@ module.MetaActions = { // Apply/call a function/action "inside" an action... // - // .chainApply(outer, inner) - // .chainApply(outer, inner, arguments) - // -> result - // // .chainCall(outer, inner) // .chainCall(outer, inner, ..) // -> result // + // .chainApply(outer, inner) + // .chainApply(outer, inner, arguments) + // -> result // // The inner action call is completely nested as base of the outer // action. @@ -1513,10 +1637,10 @@ module.MetaActions = { // NOTE: these call the action's .chainApply(..) and .chainCall(..) // methods, thus is not compatible with non-action methods... // NOTE: .chainCall('action', ..) is equivalent to .action.chainCall(..) - chainApply: function(outer, inner, args){ - return this[outer].chainApply(this, inner, args) }, - chainCall: function(outer, inner){ - return this[outer].chainApply(this, inner, [...arguments].slice(2)) }, + chainCall: function(outer, inner, ...args){ + return this[outer].chainCall(this, inner, ...args) }, + chainApply: function(outer, inner, ...args){ + return this[outer].chainCall(this, inner, args) }, // Call action handlers serted by .sortedActionPriority... @@ -1602,8 +1726,7 @@ module.MetaActions = { cur : [cur.__mixin_tag, cur]) // go to next item in chain... - cur = cur.__proto__ - } + cur = cur.__proto__ } return res }, // Get mixin object in inheritance chain... @@ -1629,13 +1752,15 @@ module.MetaActions = { // otherwise only mixin local actions... // NOTE: this will override existing own attributes. // + // XXX should this also mixin .__action_handlers??? // XXX should we include functions by default???? // XXX should .source_tag be set here or in Actions(..)??? inlineMixin: function(from, options){ // defaults... options = options || {} - var descriptors = options.descriptors || true - var all_attr_types = options.all_attr_types || false + var descriptors = options.descriptors == null ? true : false + var all_attr_types = !!options.all_attr_types + var action_handlers = !!options.action_handlers var source_tag = options.source_tag resetHandlerCache = (this.resetHandlerCache || MetaActions.resetHandlerCache) @@ -1644,11 +1769,9 @@ module.MetaActions = { if(options.all){ var keys = [] for(var k in from){ - keys.push(k) - } + keys.push(k) } } else { - var keys = Object.keys(from) - } + var keys = Object.keys(from) } var that = this keys.forEach(function(k){ @@ -1674,8 +1797,14 @@ module.MetaActions = { if(all_attr_types || attr instanceof Function || attr instanceof Action){ - that[k] = attr - } + that[k] = attr } + + // copy the action handlers... + if(action_handlers && k == '__action_handlers' && attr){ + var h = that[k] = {} + Object.entries(attr) + .forEach(function([k, v]){ + h[k] = v.slice() }) } // source tag actions... // XXX should this set action and method .source_tag or only action??? @@ -1690,21 +1819,16 @@ module.MetaActions = { // XXX not sure if this is the right way to go... } else if(that[k].source_tag || (that[k].func || {}).source_tag){ - console.warn('Aactions: about to overwrite source tag...\n' + console.warn('Actions: about to overwrite source tag...\n' +' from: "' +(that[k].source_tag || (that[k].func || {}).source_tag)+'"\n' +' to: "'+source_tag+'"\n' - +' on:', that[k]) - } + +' on:', that[k]) } if(that[k].func){ - that[k].func.source_tag = source_tag - } - that[k].source_tag = source_tag - } - } - }) + that[k].func.source_tag = source_tag } + that[k].source_tag = source_tag } } }) return this }, @@ -1725,8 +1849,7 @@ module.MetaActions = { // add source tag to proto... if(options && options.source_tag){ - proto.__mixin_tag = options.source_tag - } + proto.__mixin_tag = options.source_tag } this.__proto__ = proto @@ -1753,6 +1876,8 @@ module.MetaActions = { // not be affected... // NOTE: this will not affect event handlers, they should be removed // manually if needed... + // + // XXX do .__action_handlers??? inlineMixout: function(from, options){ // defaults... options = options || {} @@ -1767,8 +1892,7 @@ module.MetaActions = { keys.push(k) } } else { - var keys = Object.keys(from) - } + var keys = Object.keys(from) } var locals = Object.keys(this) var that = this @@ -1778,8 +1902,7 @@ module.MetaActions = { // descriptor... if(descriptors && prop.get != null){ if(prop.get === Object.getOwnPropertyDescriptor(that, k).get){ - delete that[k] - } + delete that[k] } // actions and other attrs... } else { @@ -1787,10 +1910,7 @@ module.MetaActions = { if((all_attr_types || attr instanceof Action) // remove only local attrs... && locals.indexOf(k) >= 0){ - delete that[k] - } - } - }) + delete that[k] } } }) return this }, @@ -1806,8 +1926,7 @@ module.MetaActions = { if(o != null){ target = o.__proto__ o.__proto__ = o.__proto__.__proto__ - this.resetHandlerCache() - } + this.resetHandlerCache() } return target }, // Remove a set of local mixed in actions from object... @@ -1823,15 +1942,14 @@ module.MetaActions = { // // XXX is this correct??? // XXX should this be an action??? + // XXX should this handle .__handler_cache ??? clone: function(full){ var o = Object.create(this) if(this.config){ if(full){ o.config = JSON.parse(JSON.stringify(this.config)) } else { - o.config = Object.create(this.config) - } - } + o.config = Object.create(this.config) } } return o }, getHandlerSourceTags: function(name){ @@ -1839,8 +1957,7 @@ module.MetaActions = { .map(function(a){ return a.pre ? (a.pre.source_tag || a.pre.event_tag) : a.post ? (a.post.source_tag || a.post.event_tag) - : null - }) + : null }) .unique() }, @@ -1890,8 +2007,7 @@ module.MetaActions = { var handler = function(p){ if(lst.length == 0){ //str += p + '---' - return - } + return } // indicate root action... p = lst.length == 1 ? p+'| ' : p+' ' @@ -1904,8 +2020,7 @@ module.MetaActions = { + getDoc(cur, p) // code... + object.normalizeIndent(cur.pre.toString()).replace(/\n/g, p) - + p - } + + p } handler(p + ' |') @@ -1916,9 +2031,7 @@ module.MetaActions = { + getTags(cur.post, p) + getDoc(cur, p) // code... - + object.normalizeIndent(cur.post.toString()).replace(/\n/g, p) - } - } + + object.normalizeIndent(cur.post.toString()).replace(/\n/g, p) } } handler('\n|') @@ -1945,8 +2058,7 @@ module.MetaActions = { object.normalizeIndent('// Source tag: ' + cur.pre.source_tag) + p : '') // code... + object.normalizeIndent(cur.pre.toString()) - .replace(/return/g, 'return'))) - } + .replace(/return/g, 'return'))) } handler(p) @@ -1958,9 +2070,7 @@ module.MetaActions = { + (cur.post.source_tag ? object.normalizeIndent('// Source tag: ' + cur.post.source_tag) + p : '') // code... - + object.normalizeIndent(cur.post.toString()))) - } - } + + object.normalizeIndent(cur.post.toString()))) } } handler(res) @@ -2041,9 +2151,7 @@ function Actions(a, b){ // XXX is this the right way to go??? if(obj.config != null && proto.config != null){ - obj.config.__proto__ = proto.config - } - } + obj.config.__proto__ = proto.config } } // NOTE: this is intentionally done only for own attributes... Object.keys(obj).forEach(function(k){ @@ -2063,9 +2171,7 @@ function Actions(a, b){ || (obj.isStringAction || isStringAction)(arg[arg.length-1])))) ){ obj[k] = arg[arg.length-1] instanceof Function ? (new Action(k, arg)) - : (new Alias(k, arg)) - } - }) + : (new Alias(k, arg)) } }) return obj } @@ -2093,8 +2199,7 @@ function(){ // object from it... if(args.indexOf(MetaActions) >= 0){ args.splice(args.indexOf(MetaActions), 1) - res.__proto__ = MetaActions - } + res.__proto__ = MetaActions } var mixin = MetaActions.inlineMixin @@ -2106,10 +2211,8 @@ function(){ var config = res.config = res.config || Object.create({}) Object.keys(p.config).forEach(function(k){ - res.config.__proto__[k] = JSON.parse(JSON.stringify(p.config[k])) - }) - } - }) + res.config.__proto__[k] = + JSON.parse(JSON.stringify(p.config[k])) }) } }) return res } @@ -2132,16 +2235,13 @@ function test(){ function(){ console.log(' test 1!') return function(){ - console.log(' test 2!') - } - }], + console.log(' test 2!') } }], testActionGen2: ['baisc 2\'nd gen test action...', // no extra info... function(){ console.log(' test gen 2!') - this.testActionGen1() - }], + this.testActionGen1() }], }) var TestActions2 = @@ -2154,14 +2254,11 @@ function test(){ function(){ console.log(' pre callback!') return function(){ - console.log(' post callback!') - } - }], + console.log(' post callback!') } }], testAction2: ['this is an action', function(){ - console.log('testAction2 args:', arguments) - }], + console.log('testAction2 args:', arguments) }], }) @@ -2222,5 +2319,5 @@ function test(){ /********************************************************************** -* vim:set ts=4 sw=4 : */ +* vim:set ts=4 sw=4 nowrap : */ return module }) diff --git a/lib/features.js b/lib/features.js index bd41924..1f6aec1 100755 --- a/lib/features.js +++ b/lib/features.js @@ -11,21 +11,19 @@ var object = require('ig-object') var actions = module.actions = require('ig-actions') - /*********************************************************************/ -var FeatureLinearizationError = +// XXX use object.Error as base when ready... +var FeatureLinearizationError module.FeatureLinearizationError = -function(data){ - this.data = data - this.message = 'Failed to linearise.' - this.toString = function(){ - return this.message - } -} -FeatureLinearizationError.prototype = Object.create(new Error) -FeatureLinearizationError.prototype.constructor = FeatureLinearizationError - +object.Constructor('FeatureLinearizationError', Error, { + get name(){ + return this.constructor.name }, + toString: function(){ + return 'Failed to linearise' }, + __init__: function(data){ + this.data = data }, +}) @@ -113,6 +111,13 @@ object.Constructor('Feature', { //__featureset__: Features, __featureset__: null, + __verbose: null, + get __verbose__(){ + return this.__verbose == null + && (this.__featureset__ || {}).__verbose__ }, + set __verbose__(value){ + this.__verbose = value }, + // Attributes... tag: null, @@ -145,26 +150,36 @@ object.Constructor('Feature', { : res) : res }, - // XXX this could install the handlers in two locations: + // XXX HANDLERS this could install the handlers in two locations: + // - the actions object... // - mixin if available... // - base object (currently implemented) - // should the first be done? + // ...the handlers should theoreticly be stored neither in the + // instance nor in the mixin but rather in the action-set itself + // on feature creation... (???) + // ...feels like user handlers and feature handlers should be + // isolated... + // XXX setting handlers on the .__proto__ breaks... setup: function(actions){ var that = this // mixin actions... + // NOTE: this will only mixin functions and actions... if(this.actions != null){ this.tag ? - actions.mixin(this.actions, {source_tag: this.tag}) - : actions.mixin(this.actions) - } + // XXX HANDLERS + actions.mixin(this.actions, {source_tag: this.tag, action_handlers: true}) + : actions.mixin(this.actions, {action_handlers: true}) } + //actions.mixin(this.actions, {source_tag: this.tag}) + //: actions.mixin(this.actions) } + /*/ XXX HANDLERS this is not needed if handlers are local to actions... // install handlers... if(this.handlers != null){ - this.handlers.forEach(function(h){ - actions.on(h[0], that.tag, h[1]) - }) - } + this.handlers.forEach(function([a, h]){ + //actions.__proto__.on(a, that.tag, h) }) } + actions.on(a, that.tag, h) }) } + //*/ // merge config... // NOTE: this will merge the actual config in .config.__proto__ @@ -173,32 +188,30 @@ object.Constructor('Feature', { || (this.actions != null && this.actions.config != null)){ // sanity check -- warn of config shadowing... - if(this.config && this.actions && this.actions.config){ + // XXX do we need this??? + if(this.__verbose__ + && this.config && (this.actions || {}).config){ console.warn('Feature config shadowed: ' +'both .config (used) and .actions.config (ignored) are defined for:', this.tag, - this) - } + this) } var config = this.config = this.config || this.actions.config if(actions.config == null){ - actions.config = Object.create({}) - } - Object.keys(config).forEach(function(n){ - // NOTE: this will overwrite existing values... - actions.config.__proto__[n] = config[n] - }) - } + actions.config = Object.create({}) } + Object.keys(config) + .forEach(function(n){ + // NOTE: this will overwrite existing values... + actions.config.__proto__[n] = config[n] }) } // custom setup... // XXX is this the correct way??? - if(this.hasOwnProperty('setup') && this.setup !== Feature.prototype.setup){ - this.setup(actions) - } + this.hasOwnProperty('setup') + && this.setup !== Feature.prototype.setup + && this.setup(actions) - return this - }, + return this }, // XXX need to revise this... // - .mixout(..) is available directly from the object while @@ -206,25 +219,20 @@ object.Constructor('Feature', { // - might be a good idea to add a specific lifecycle actions to // enable feautures to handle their removal correctly... remove: function(actions){ - if(this.actions != null){ - actions.mixout(this.tag || this.actions) - } + this.actions != null + && actions.mixout(this.tag || this.actions) - if(this.handlers != null){ - actions.off('*', this.tag) - } + /*/ XXX HANDLERS do we need this if .handlers if local to action... + this.handlers != null + && actions.off('*', this.tag) + //*/ - // XXX - if(this.hasOwnProperty('remove') && this.setup !== Feature.prototype.remove){ - this.remove(actions) - } + // XXX revise naming... + this.hasOwnProperty('remove') + && this.setup !== Feature.prototype.remove + && this.remove(actions) - // remove feature DOM elements... - // XXX - actions.ribbons.viewer.find('.' + this.tag).remove() - - return this - }, + return this }, // XXX EXPERIMENTAL: if called from a feature-set this will add self @@ -246,8 +254,7 @@ object.Constructor('Feature', { // Feature(, ) } else { obj = tag - tag = null - } + tag = null } // Feature() // NOTE: we need to account for context here -- inc length... @@ -257,41 +264,45 @@ object.Constructor('Feature', { // XXX EXPERIMENTAL... feature_set = context instanceof FeatureSet ? context - : (this.__featureset__ || Features) - } + : (this.__featureset__ || Features) } if(tag != null && obj.tag != null && obj.tag != tag){ - throw 'Error: tag and obj.tag mismatch, either use one or both must match.' } + throw new Error('tag and obj.tag mismatch, either use one or both must match.') } - // action... + // actions... if(obj instanceof actions.Action){ if(tag == null){ - throw 'Error: need a tag to make a feature out of an action' } - var f = { + throw new Error('need a tag to make a feature out of an action') } + obj = { tag: tag, actions: obj, } - obj = f // meta-feature... } else if(obj.constructor === Array){ if(tag == null){ - throw 'Error: need a tag to make a meta-feature' - } - var f = { + throw new Error('need a tag to make a meta-feature') } + obj = { tag: tag, suggested: obj, - } - obj = f - } + } } + + // XXX HANDLERS setup .handlers... + if(obj.handlers){ + obj.actions = obj.actions || {} + // NOTE: obj.actions does not have to be an action so w cheat \ + // a bit here, then copy the mindings... + var tmp = Object.create(actions.MetaActions) + obj.handlers + .forEach(function([a, h]){ + tmp.on(a, obj.tag, h) }) + Object.assign(obj.actions, tmp) } // feature-set... if(feature_set){ - feature_set[obj.tag] = obj - } + feature_set[obj.tag] = obj } - return obj - }, + return obj }, }) @@ -369,8 +380,7 @@ object.Constructor('FeatureSet', { } exclusive[e] = (exclusive[e] || []).concat([k]) rev_exclusive[k] = (rev_exclusive[k] || []).concat([e]) }) }) - return exclusive - }, + return exclusive }, // Build list of features in load order... // @@ -491,8 +501,12 @@ object.Constructor('FeatureSet', { // when needed... buildFeatureList: function(lst, isDisabled){ var all = this.features - lst = (lst == null || lst == '*') ? all : lst - lst = lst.constructor !== Array ? [lst] : lst + lst = (lst == null || lst == '*') ? + all + : lst + lst = lst instanceof Array ? + lst + : [lst] //isDisabled = isDisabled || function(){ return false } @@ -519,8 +533,7 @@ object.Constructor('FeatureSet', { // NOTE: Infinity - Infinity is NaN, so we need // to guard against it... return i - j || 0 }) - .map(function(e){ return e[0] }) }) - } + .map(function(e){ return e[0] }) }) } // Expand feature references (recursive)... // @@ -546,22 +559,17 @@ object.Constructor('FeatureSet', { console.warn(`Disable loop detected at "${n}" in chain: ${_seen}`) var loop = _seen.slice(_seen.indexOf(n)).concat([n]) data.disable_loops = (data.disable_loops || []).push(loop) - return false - } + return false } // XXX STUB -- need to resolve actual loops and // make the disable global... if(n in store){ - console.warn('Disabling a feature after it is loaded:', n, _seen) - } + console.warn('Disabling a feature after it is loaded:', n, _seen) } data.disabled.push(n) - return false - } + return false } // skip already disabled features... if(data.disabled.indexOf(n) >= 0){ - return false - } - return true - }) + return false } + return true }) : lst // traverse the tree... @@ -572,19 +580,15 @@ object.Constructor('FeatureSet', { // exclusive tags... if(f == null && data.exclusive && n in data.exclusive){ store[n] = null - return false - } + return false } // feature not defined or is not a feature... if(f == null){ data.missing && data.missing.indexOf(n) < 0 && data.missing.push(n) - return false - } - return n - }) + return false } + return n }) .filter(function(e){ return e }) - // traverse down... .forEach(function(f){ // dependency loop detection... @@ -592,13 +596,11 @@ object.Constructor('FeatureSet', { var loop = _seen.slice(_seen.indexOf(f)).concat([f]) data.loops && data.loops.push(loop) - return - } + return } // skip already done features... if(f in store){ - return - } + return } //var feature = store[f] = that[f] var feature = that[f] @@ -613,12 +615,9 @@ object.Constructor('FeatureSet', { store[f] = _lst // traverse down... - expand(target, _lst, store, data, _seen.concat([f])) - } - }) + expand(target, _lst, store, data, _seen.concat([f])) } }) - return store - } + return store } // Expand feature dependencies and suggestions recursively... // @@ -692,14 +691,11 @@ object.Constructor('FeatureSet', { // mix suggested into features... } else { features[f] = s[f] - suggested[f] = (s[f] || []).slice() - } - }) + suggested[f] = (s[f] || []).slice() } }) sortExclusive(features) - return features - } + return features } //--------------------- Globals: filtering / exclusive tags --- @@ -748,8 +744,7 @@ object.Constructor('FeatureSet', { // link alias to existing feature... } else { - var target = candidates[0] - } + var target = candidates[0] } // remove the alias... // NOTE: exclusive tag can match a feature tag, thus @@ -766,8 +761,7 @@ object.Constructor('FeatureSet', { && features[e].splice(i, 1, target) }) f = target - done.push(f) - } + done.push(f) } // exclusive feature... if(f in rev_exclusive){ @@ -777,10 +771,7 @@ object.Constructor('FeatureSet', { .filter(function(c){ return c in features }) if(!(group in conflicts) && candidates.length > 1){ - conflicts[group] = candidates - } - } - }) + conflicts[group] = candidates } } }) // cleanup... to_remove.forEach(function(f){ delete features[f] }) // resolve any exclusivity conflicts found... @@ -818,9 +809,7 @@ object.Constructor('FeatureSet', { && features[f].indexOf(d) >= 0 && disabled.indexOf(f) < 0){ expanded_disabled = true - disabled.push(f) - } - }) + disabled.push(f) } }) // delete the feature itself... var s = suggests[d] || [] @@ -838,10 +827,7 @@ object.Constructor('FeatureSet', { .filter(n => n.indexOf(f) >= 0) .length == 0){ expanded_disabled = true - disabled.push(f) - } - }) - }) + disabled.push(f) } }) }) } while(expanded_disabled) // remove orphaned features... @@ -858,8 +844,7 @@ object.Constructor('FeatureSet', { .forEach(function(f){ console.log('ORPHANED:', f) disabled.push(f) - delete features[f] - }) + delete features[f] }) //---------------------------------- Stage 2: sort features --- @@ -872,8 +857,7 @@ object.Constructor('FeatureSet', { var expanddeps = function(lst, cur, seen){ seen = seen || [] if(features[cur] == null){ - return - } + return } // expand the dep list recursively... // NOTE: this will expand features[cur] in-place while // iterating over it... @@ -886,11 +870,7 @@ object.Constructor('FeatureSet', { features[cur].forEach(function(e){ lst.indexOf(e) < 0 - && lst.push(e) - }) - } - } - } + && lst.push(e) }) } } } // do the actual expansion... var list = Object.keys(features) list.forEach(function(f){ expanddeps(list, f) }) @@ -921,8 +901,7 @@ object.Constructor('FeatureSet', { do { var moves = 0 if(list.length == 0){ - break - } + break } list .slice() .forEach(function(e){ @@ -941,9 +920,7 @@ object.Constructor('FeatureSet', { // place after last dependency... list.splice(to[1]+1, 0, e) list.splice(from, 1) - moves++ - } - }) + moves++ } }) loop_limit-- } while(moves > 0 && loop_limit > 0) @@ -1033,8 +1010,7 @@ object.Constructor('FeatureSet', { // no explicit object is given... if(lst == null){ lst = obj - obj = null - } + obj = null } obj = obj || (this.__actions__ || actions.Actions)() lst = lst instanceof Array ? lst : [lst] @@ -1043,16 +1019,13 @@ object.Constructor('FeatureSet', { (function(n){ // if we already tested unapplicable, no need to test again... if(unapplicable.indexOf(n) >= 0){ - return true - } + return true } var f = this[n] // check applicability if possible... if(f && f.isApplicable && !f.isApplicable.call(this, obj)){ unapplicable.push(n) - return true - } - return false - }).bind(this)) + return true } + return false }).bind(this)) features.unapplicable = unapplicable // cleanup disabled -- filter out unapplicable and excluded features... // NOTE: this is done mainly for cleaner and simpler reporting @@ -1081,8 +1054,7 @@ object.Constructor('FeatureSet', { console.error('Exclusive "'+ group +'" conflict at:', error.conflicts[group]) }) // report loop limit... error.sort_loop_overflow - && console.error('Hit loop limit while sorting dependencies!') - } + && console.error('Hit loop limit while sorting dependencies!') } features.FeatureSet = this @@ -1090,8 +1062,13 @@ object.Constructor('FeatureSet', { // fatal error -- can't load... if(fatal){ - throw new FeatureLinearizationError(features) - } + throw new FeatureLinearizationError(features) } + + // mixout everything... + this.remove(obj, + obj.mro('tag') + .filter(function(e){ + return !!e })) // do the setup... var that = this @@ -1099,13 +1076,11 @@ object.Constructor('FeatureSet', { features.features.forEach(function(n){ // setup... if(that[n] != null){ - this.__verbose__ && console.log('Setting up feature:', n) - setup.call(that[n], obj) - } - }) + this.__verbose__ + && console.log('Setting up feature:', n) + setup.call(that[n], obj) } }) - return obj - }, + return obj }, // XXX revise... // ...the main problem here is that .mixout(..) is accesible @@ -1118,7 +1093,8 @@ object.Constructor('FeatureSet', { var that = this lst.forEach(function(n){ if(that[n] != null){ - console.log('Removing feature:', n) + this.__verbose__ + && console.log('Removing feature:', n) that[n].remove(obj) } }) }, @@ -1143,8 +1119,7 @@ object.Constructor('FeatureSet', { }) graph += '}' - return graph - }, + return graph }, }) diff --git a/lib/object.js b/lib/object.js index f73b2d5..70e6cde 100755 --- a/lib/object.js +++ b/lib/object.js @@ -1,568 +1,1438 @@ -/********************************************************************** -* -* object.js -* -* Repo and docs: -* https://github.com/flynx/object.js -* -* -* XXX should this extend Object??? -* ...if yes then it would also be logical to move Object.run(..) -* here... -* -**********************************************************************/ -((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) -(function(require){ var module={} // make module AMD/node compatible... -/*********************************************************************/ -// Helpers... - -var TAB_SIZE = -module.TAB_SIZE = 4 - - -// Normalize indent... -// -// normalizeIndent(text) -// -> text -// -// -// This will remove common indent from each like of text, this is useful -// for printing function code of functions that were defined at deep -// levels of indent. -// -// NOTE: this will trim out both leading and trailing white-space. -// -// XXX is this the right place for this??? -// ...when moving take care that ImageGrid's core.doc uses this... -var normalizeIndent = -module.normalizeIndent = -function(text, tab_size){ - tab_size = tab_size || TAB_SIZE - text = tab_size > 0 ? - text.replace(/\t/g, ' '.repeat(tab_size)) - : text - var lines = text.split(/\n/) - var l = lines - .reduce(function(l, e, i){ - var indent = e.length - e.trimLeft().length - return e.trim().length == 0 - // ignore 0 indent of first line... - || (i == 0 && indent == 0) ? l - : l < 0 ? - indent - : Math.min(l, indent) - }, -1) - return lines - .map(function(line, i){ - return i == 0 ? - line - : line.slice(l) }) - .join('\n') - .trim() } - - - -//--------------------------------------------------------------------- -// Prototype chain content access... - -// Get a list of source objects for a prop/attr name... -// -// sources(obj, name) -// sources(obj, name, callback) -// -> list -// -> [] -// -// callback(obj) -// -> true | 'stop' -// -> .. -// -// -// The callback(..) is called with each matching object. -// -// The callback(..) can be used to break/stop the search, returning -// a partial list og matcges up untill and including the object -// triggering the stop. -// -// -// NOTE: this go up the prototype chain, not caring about any role ( -// instance/class or instance/prototype) bounderies and depends -// only on the object given as the starting point. -// It is possible to start the search from this, thus checking -// for any overloading in the instance, though this approach is -// not very reusable.... -// NOTE: this will not trigger any props... -var sources = -module.sources = -function(obj, name, callback){ - var stop - var res = [] - do { - if(obj.hasOwnProperty(name)){ - res.push(obj) - // handle callback... - stop = callback - && callback(obj) - // stop requested by callback... - if(stop === true || stop == 'stop'){ - return res } - } - obj = obj.__proto__ - } while(obj !== null) - return res } - - -// Find the next parent attribute in the prototype chain. -// -// Get parent attribute value... -// parent(proto, name) -// -> value -// -> undefined -// -// Get parent method... -// parent(method, this) -// -> meth -// -> undefined -// -// -// The two forms differ in: -// - in parent(method, ..) a method's .name attr is used for name. -// - in parent(method, ..) the containing prototype is inferred. -// -// NOTE: there are cases where method.name is not set (e.g. anonymous -// function), so there a name should be passed explicitly... -// NOTE: when passing a method it is recommended to pass an explicit -// reference to it relative to the constructor, i.e.: -// Constructor.prototype.method -// this will avoid relative resolution loops, for example: -// this.method -// deep in a chain will resolve to the first .method value visible -// from 'this', i.e. the top most value and not the value visible -// from that particular level... -// -// -// Example: -// var X = object.Constructor('X', { -// __proto__: Y.prototype, -// -// attr: 123, -// -// method: function(){ -// // get attribute... -// var a = object.parent(X.prototype, 'attr') -// -// // get method... -// var ret = object.parent(X.prototype.method, this) -// .call(this, ...arguments) -// -// // ... -// } -// }) -// -// -// NOTE: in the general case this will get the value of the returned -// property/attribute, the rest of the way passive to props. -// The method case will get the value of every method from 'this' -// and to the method after the match. -// NOTE: this is super(..) replacement, usable in any context without -// restriction -- super(..) is restricted to class methods only... -var parent = -module.parent = -function(proto, name){ - // special case: method... - if(typeof(name) != typeof('str')){ - that = name - name = proto.name - // get first matching source... - proto = sources(that, name, - function(obj){ return obj[name] === proto }) - .pop() } - // get first source... - var res = sources(proto, name, - function(obj){ return 'stop' }) - .pop() - return res ? - // get next value... - res.__proto__[name] - : undefined } - - -// Find the next parent property descriptor in the prototype chain... -// -// parentProperty(proto, name) -// -> prop-descriptor -// -// -// This is like parent(..) but will get a property descriptor... -// -var parentProperty = -module.parentProperty = -function(proto, name){ - // get second source... - var c = 0 - var res = sources(proto, name, - function(obj){ return c++ == 1 }) - .pop() - return res ? - // get next value... - Object.getOwnPropertyDescriptor(res, name) - : undefined } - - -// Find the next parent method and call it... -// -// parentCall(proto, name, this, ...) -// parentCall(meth, this, ...) -// -> res -// -> undefined -// -// -// This also gracefully handles the case when no higher level definition -// is found, i.e. the corresponding parent(..) call will return undefined -// or a non-callable. -// -// NOTE: this is just like parent(..) but will call the retrieved method, -// essentially this is a shorthand to: -// parent(proto, name).call(this, ...) -// or: -// parent(method, this).call(this, ...) -// NOTE: for more docs see parent(..) -var parentCall = -module.parentCall = -function(proto, name, that, ...args){ - var meth = parent(proto, name) - return meth instanceof Function ? - meth.call(...( typeof(name) == typeof('str') ? - [...arguments].slice(2) - : [...arguments].slice(1) )) - : undefined } - - - -//--------------------------------------------------------------------- -// Mixin utils... -// XXX should we add mixout(..) and friends ??? - -// Mix a set of methods/props/attrs into an object... -// -// mixinFlat(root, object, ...) -// -> root -// -// -// NOTE: essentially this is just like Object.assign(..) but copies -// properties directly rather than copying property values... -var mixinFlat = -module.mixinFlat = -function(root, ...objects){ - return objects - .reduce(function(root, cur){ - Object.keys(cur) - .map(function(k){ - Object.defineProperty(root, k, - Object.getOwnPropertyDescriptor(cur, k)) }) - return root }, root) } - - -// Mix sets of methods/props/attrs into an object as prototypes... -// -// mixin(root, object, ...) -// -> root -// -// -// This will create a new object per set of methods given and -// mixinFlat(..) the method set into this object leaving the -// original objects intact. -// -// root <-- object1_copy <-- .. <-- objectN_copy -// -var mixin = -module.mixin = -function(root, ...objects){ - return objects - .reduce(function(res, cur){ - return module.mixinFlat(Object.create(res), cur) }, root) } - - - -//--------------------------------------------------------------------- -// Constructor... - -// Make an uninitialized instance object... -// -// makeRawInstance(context, constructor, ...) -// -> instance -// -// -// This will: -// - construct an object -// - if .__new__(..) is defined -// -> call and use its return value -// - if prototype is a function or if .__call__(..) is defined -// -> use a wrapper function -// - else -// -> use {} -// - link the object into the prototype chain -// -// -// This will not call .__init__(..) -// -// -// NOTE: context is only used when passeding to .__new__(..) if defined, -// and is ignored otherwise... -// NOTE: as this is simply an extension to the base JavaScript protocol this -// can be used to construct any object... -// Example: -// var O = function(){} -// // new is optional... -// var o = new makeRawInstance(null, O) -// NOTE: .__new__(..) is intentionaly an instance method (contary to -// Python) this is done because there are no classes in JS and -// adding and instance constructor as a class method would create -// unneccessary restrictions both on the "class" object and on the -// instance... -// -// XXX Q: should the context (this) in .__new__(..) be _constructor or -// .prototype??? -// ... .prototype seems to be needed more often but through it we -// can't reach the actual constructor... but on the other hand we -// can (should?) always explicitly use it -- .__new__(..) is usually -// in the same scope + this makes it more reusable for chaining -// .__new__(..) calls... -// ...currently it's .prototype... -var makeRawInstance = -module.makeRawInstance = -function(context, constructor, ...args){ - var _mirror_doc = function(func, target){ - Object.defineProperty(func, 'toString', { - value: function(...args){ - return target.toString(...args) }, - enumerable: false, - }) - return func } - - var obj = - // prototype defines .__new__(..)... - constructor.prototype.__new__ instanceof Function ? - constructor.prototype.__new__(context, ...args) - // callable instance -- prototype is a function... - // NOTE: we need to isolate the .prototype from instances... - : constructor.prototype instanceof Function ? - _mirror_doc( - function(){ - return constructor.prototype - .call(obj, this, ...arguments) }, - constructor.prototype) - // callable instance -- prototype defines .__call__(..)... - // NOTE: we need to isolate the .__call__ from instances... - : constructor.prototype.__call__ instanceof Function ? - _mirror_doc( - function(){ - return constructor.prototype.__call__ - .call(obj, this, ...arguments) }, - constructor.prototype.__call__) - // default object base... - : {} - - // link to prototype chain... - obj.__proto__ = constructor.prototype - Object.defineProperty(obj, 'constructor', { - value: constructor, - enumerable: false, - }) - - return obj } - - -// Make an object constructor function... -// -// Make a constructor with an object prototype... -// Constructor(name, proto) -// -> constructor -// -// Make a constructor with a prototype (object/function) and a class -// prototype... -// Constructor(name, class-proto, proto) -// -> constructor -// NOTE: the defines a set of class methods and -// attributes. -// -// -// The resulting constructor can produce objects in one of these ways: -// -// Create instance... -// constructor(..) -// new constructor -// new constructor(..) -// -> instance -// -// Create raw/uninitialized instance... -// constructor.__rawinstance__(..) -// makeRawInstance(null, constructor, ..) -// -> raw-instance -// -// -// All produced objects are instances of the constructor -// instance instanceof constructor -// -> true -// -// -// -// Create and initialization protocol: -// 1) raw instance is created: -// a) constructor.__rawinstance__(..) / makeRawInstance(..) called: -// - call .__new__(..) if defined and get return value as -// instance, or -// - if .__call__(..) defined or prototype is a function, wrap -// it and use the wrapper function as instance, or -// - create an empty object -// b) instance linked to prototype chain -// set .__proto__ to constructor.prototype -// 2) instance is initialized: -// call .__init__(..) if defined -// -// -// -// Special methods (constructor): -// -// Handle uninitialized instance construction -// .__rawinstance__(context, ...) -// -> instance -// NOTE: This is a shorthand to makeRawInstance(..) see it for -// details. -// -// -// Special methods (.prototype): -// -// Create new instance object... -// .__new__(context, ..) -// -> object -// -// Handle instance call... -// .__call__(context, ..) -// -> .. -// -// Initialize instance object... -// .__init__(..) -// -> .. -// -// -// NOTE: raw instance creation is defined by makeRawInstance(..) so see -// it for more info. -// NOTE: raw instance creation can be completely overloaded by defining -// .__rawinstance__(..) on the constructor. -// -// -// -// Inheritance: -// A simple way to build C -> B -> A chain would be: -// -// // NOTE: new is optional... -// var A = new Constructor('A') -// -// // NOTE: in a prototype chain the prototypes are "inherited" -// // NOTE: JS has no classes and the prototype is just another -// // object, the only difference is that it's used by the -// // constructor to link other objects i.e. "instances" to... -// var B = Constructor('B', {__proto__: A.prototype}) -// -// var C = Constructor('C', Objec.create(B.prototype)) -// -// var c = C() -// -// c instanceof C // -> true -// c instanceof B // -> true -// c instanceof A // -> true -// -// A.prototype.x = 123 -// -// c.x // -> 123 -// -// -// -// Motivation: -// The general motivation here is to standardise the constructor -// protocol and make a single simple way to go with minimal variation. -// This is due to the JavaScript base protocol though quite simple, -// being too flexible making it very involved to produce objects in a -// consistent manner by hand, especially in long running projects, -// in turn spreading all the refactoring over multiple sites and styles. -// -// This removes part of the flexibility and in return gives us: -// - single, well defined protocol -// - one single spot where all the "magic" happens -// - full support for existing JavaScript ways of doing things -// - easy refactoring without touching the client code -// -// -// NOTE: this sets the proto's .constructor attribute, thus rendering it -// not reusable, to use the same prototype for multiple objects -// clone it via. Object.create(..) or copy it... -// NOTE: to disable .__rawinstance__(..) handling set it to false in the -// class prototype... -var Constructor = -module.Constructor = -// shorthand... -module.C = -function Constructor(name, a, b){ - var proto = b == null ? a : b - var cls_proto = b == null ? b : a - proto = proto || {} - - // the actual constructor... - var _constructor = function Constructor(){ - // create raw instance... - var obj = _constructor.__rawinstance__ ? - _constructor.__rawinstance__(this, ...arguments) - : makeRawInstance(this, _constructor, ...arguments) - // initialize... - obj.__init__ instanceof Function - && obj.__init__(...arguments) - return obj } - - _constructor.name = name - // just in case the browser refuses to change the name, we'll make - // it a different offer ;) - _constructor.name == 'Constructor' - // NOTE: this eval(..) should not be a risk as its inputs are - // static and never infuenced by external inputs... - && eval('_constructor = '+ _constructor - .toString() - .replace(/Constructor/g, name)) - // set .toString(..)... - // NOTE: do this only if .toString(..) is not defined by user... - ;((cls_proto || {}).toString() == ({}).toString()) - && Object.defineProperty(_constructor, 'toString', { - value: function(){ - var args = proto.__init__ ? - proto.__init__ - .toString() - .split(/\n/)[0] - .replace(/function\(([^)]*)\){.*/, '$1') - : '' - var code = proto.__init__ ? - proto.__init__ - .toString() - .replace(/[^{]*{/, '{') - : '{ .. }' - return `${this.name}(${args})${normalizeIndent(code)}` }, - enumerable: false, - }) - _constructor.__proto__ = cls_proto - _constructor.prototype = proto - // generic raw instance constructor... - _constructor.__rawinstance__ instanceof Function - || (_constructor.__rawinstance__ = - function(context, ...args){ - return makeRawInstance(context, this, ...args) }) - - // set .prototype.constructor - Object.defineProperty(_constructor.prototype, 'constructor', { - value: _constructor, - enumerable: false, - }) - - return _constructor } - - - -/********************************************************************** -* vim:set ts=4 sw=4 : */ return module }) +/********************************************************************** +* +* object.js +* +* This is a set of tools and abstractions to create and manage +* constructors, objects and prototype chains in idiomatic JavaScript. +* +* Motivation: +* This package was originally written to unify low level object +* definitios within a large project and from there evolved to be a +* full functional alternative to the ES6 class notation with all of +* its inconsistencies, hoops, "the same but slightly different" ways +* to do things and "magic" (hidden) functionality. +* +* Repo and docs: +* https://github.com/flynx/object.js +* +* +***********************************************/ /* c8 ignore next 2 */ +((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) +(function(require){ var module={} // make module AMD/node compatible... +/*********************************************************************/ + + +// Function methods to link into a constructor producing a callable +// defined via .__call__(..) +// +// These are needed to support the expected popular function API in a +// callable potentially not related to a function. +// +// see: Constructor(..) for details. +module.LINK_FUNCTION_METHODS = [ + 'call', + 'apply', + 'bind', +] + + + + +//--------------------------------------------------------------------- +// Helpers... + +// Bootstrapping utility... +// +// Since we can face chicken-egg issues here, this should keep things +// both consistent in terms of doc flow and in terms of actual logical +// consistency... +// +var BOOTSTRAP = +function(func){ + var b = BOOTSTRAP.__delayed = BOOTSTRAP.__delayed || [] + func ? + b.push(func) + : b.map(function(f){ f() }) } + + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +module.TAB_SIZE = 4 + +module.LEADING_TABS = 1 + + +// Normalize code indent... +// +// normalizeIndent(text) +// -> text +// +// +// This will remove common indent from each line of text, this is useful +// for printing function code of functions that were defined at deep +// levels of indent. +// +// This will ignore the indent of the first line. +// +// If the last line is indented higher or equal to the rest of the text +// we will use leading_tabs (defaults to LEADING_TABS) to indent the +// rest of the text. +// This will indent the following styles correctnly: +// +// |function(a, b){ |function(a, b){ +// | return a + b } | return a + b +// | |} +// +// +// NOTE: this will trim out both leading and trailing white-space. +// NOTE: this is generally code-agnostic with one sigificant +// exception -- normalizeIndent(..) will break code written +// in Whitespace. +// +// XXX BUG? +// `a `a `a +// | b -> |b expected? | b +// | c` | c` | c` +// while: +// `a `a +// | b -> | b as expected. +// | c` | c` +// this leads to functions like the following to get messed up: +// |function(a){ +// | return a +// | || 'moo' } +// +// XXX is this the right place for this??? +// ...when moving take care that ImageGrid's core.doc uses this... +var normalizeIndent = +module.normalizeIndent = +function(text, {tab_size=module.TAB_SIZE, leading_tabs=module.LEADING_TABS, pad_tabs=0}={}){ + leading_tabs *= tab_size + var padding = ' '.repeat(pad_tabs*tab_size) + // prepare text... + var tab = ' '.repeat(tab_size || 0) + text = tab != '' ? + text.replace(/\t/g, tab) + : text + // trim the tail and remove leading blank lines... + var lines = text.trimEnd().split(/\n/) + while(lines.length > 0 + && lines[0].trim() == ''){ + // XXX we have two options here: + // - indent everyline including the first non-blank + // - do not indent anything (current) + // ...not sure which is best... + leading_tabs = 0 + lines.shift() } + // count common indent... + var l = lines + .reduce(function(l, e, i){ + var indent = e.length - e.trimLeft().length + return e.trim().length == 0 + // ignore 0 indent of first line... + || (i == 0 && indent == 0) ? + l + // last line... + : i == lines.length-1 + && indent >= l ? + // XXX feels a bit overcomplicated... + (l < 0 ? + // last of two with 0 indent on first -> indent... + Math.max(indent - leading_tabs, 0) + // ignore leading_tabs if lower indent... + : Math.min(l, Math.max(indent - leading_tabs, 0))) + // initial state... + : l < 0 ? + indent + // min... + : Math.min(l, indent) }, -1) || 0 + // normalize... + return padding + +lines + .map(function(line, i){ + return i == 0 ? + line + : line.slice(l) }) + .join('\n'+ padding) + .trim() } + + +// shorthand more suted for text... +var normalizeTextIndent = +module.normalizeTextIndent = +function(text, opts={leading_tabs: 0}){ + return module.normalizeIndent(text, opts) } + + +// template string tag versions of the above... +var doc = +module.doc = +function(strings, ...values){ + return normalizeIndent(strings + .map(function(s, i){ return s + (values[i] || '') }) + .join('')) } + +var text = +module.text = +function(strings, ...values){ + return normalizeTextIndent(strings + .map(function(s, i){ return s + (values[i] || '') }) + .join('')) } + + +// Get keys from prototype chain... +// +// deepKeys(obj) +// deepKeys(obj, stop) +// -> keys +// +// +// NOTE: this is like Object.keys(..) but will get keys for all levels +// till stop if given... +var deepKeys = +module.deepKeys = +function(obj, stop){ + var res = [] + while(obj != null){ + res.push(Object.keys(obj)) + if(obj === stop){ + break } + obj = obj.__proto__ } + return [...(new Set(res.flat()))] } + + +// Match two objects... +// +// match(a, b) +// -> bool +// +// +// This will match objects iff: +// - if they are identical or +// - attr count is the same and, +// - attr names are the same and, +// - attr values are identical. +// +// +// Non-strict match... +// match(a, b, true) +// +// This is similar to the default case but uses equality rather than +// identity to match values. +// +// +// NOTE: this will do a shallow test using Object.keys(..) thus .__proto__ +// attributes are ignored... +var match = +module.match = +function(base, obj, non_strict){ + // identity... + if(base === obj){ + return true } + // typeof -- sanity check... + if(typeof(base) != typeof(obj)){ + return false } + // attr count... + var o = Object.keys(Object.getOwnPropertyDescriptors(obj)) + if(Object.keys(Object.getOwnPropertyDescriptors(base)).length != o.length){ + return false } + // names and values... + o = o.map(function(k){ + return [k, obj[k]] }) + while(o.length > 0){ + var [k, v] = o.pop() + if(!base.hasOwnProperty(k) + || (non_strict ? + base[k] != v + : base[k] !== v)){ + return false } } + return true } + + +// Like .match(..) but will test if obj is a non-strict subset of base... +// +// NOTE: this will only check direct attributes of both base and obj. +var matchPartial = +module.matchPartial = +function(base, obj, non_strict){ + return base === obj + || Object.entries(obj) + .filter(function([n, v]){ + return !base.hasOwnProperty(n) + || (non_strict ? + base[n] != v + : base[n] !== v) }) + .length == 0 } + + +// like Object.create(..) but also handles callable objects correctly... +// +// create(obj) +// -> obj +// +// create(func) +// create(name, func) +// -> func +// +// +// XXX would be nice to re-use RawInstance... or use create(..) from +// RawInstance... +// XXX should we autogenerate a descriptive name??? +// XXX revise .toString(..) creation... +var create = +module.create = +function(obj){ + // name given... + var name = '' + if(typeof(obj) == 'string' && arguments.length > 1){ + ;[name, obj] = arguments + // sanity check... + if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name.trim())){ + throw new Error(`create(..): invalid name: "${name}"`) } } + // calable... + if(typeof(obj) == 'function'){ + /* c8 ignore next 9 */ + var func = function(){ + return '__call__' in func ? + func.__call__(this, ...arguments) + : 'call' in obj ? + obj.call(func, ...arguments) + // NOTE: if obj does not inherit from Function .call(..) + // might not be available directly so it is saver to + // use Reflect.apply(..)... + : Reflect.apply(obj, func, [...arguments]) } + // rename... + // NOTE: we just created func(..) so no need to sanitize it, the + // only potential vector of atack (AFAIK) here is name and + // that is checked above... + func.name = name + func.name != name + && (func = eval('('+ + func + .toString() + .replace(/function\(/, `function ${name}(`) +')')) + func.__proto__ = obj + __toStringProxy(func) + return func } + // normal object... + return Object.create(obj) } + + + +//--------------------------------------------------------------------- +// Helper objects/constructors... + +BOOTSTRAP(function(){ + + // Error with some JS quirks fixed... + // + // XXX EXPERIMENTAL + module.Error = + Constructor('Error', Error, { + get name(){ + return this.constructor.name }, + + // XXX BUG? is this an error that with this everything seems to work + // while without this instances of this work fine while instances + // of "sub-classes" do not set the .stack correctly??? + // ...is this a JS quirk or am I missing something??? + __new__: function(context, ...args){ + return Reflect.construct(module.Error.__proto__, args, this.constructor) }, + //return Reflect.construct(Error, args, this.constructor) }, + }) + +}) + + +//--------------------------------------------------------------------- +// Prototype chain content access... + +BOOTSTRAP(function(){ + + // Value trigger iteration stop and to carry results... + // + module.STOP = + Constructor('STOP', { + doc: 'stop iteration.', + __init__: function(value){ + this.value = value }, + }) + +}) + + + +// Get a list of source objects for a prop/attr name... +// +// sources(obj, name) +// sources(obj, name, callback) +// -> list +// -> [] +// +// Get callables or objects defining .__call__ (special-case) +// sources(obj, '__call__') +// sources(obj, '__call__', callback) +// -> list +// -> [] +// +// Get full chain... +// sources(obj) +// sources(obj, callback) +// -> list +// +// +// callback(obj, i) +// -> STOP +// -> STOP(value) +// -> .. +// +// +// The callback(..) is called with each matching object. +// +// callback(..) return values: +// - STOP - stop the search and return the match list terminated +// with the object triggering the stop. +// - STOP(value) - stop the search and return the match list terminated +// with the value passed to STOP(..) +// - undefined - return the triggering object as-is +// NOTE: this is the same as returning [obj] +// - array - merge array content into the result insteaad of +// the triggering value. +// NOTE: an ampty array will effectively omit the +// triggering object from the results. +// - other - return a value instead of the triggering object. +// +// +// NOTE: this gos up the prototype chain, not caring about any role ( +// instance/class or instance/prototype) bounderies and depends +// only on the object given as the starting point. +// It is possible to start the search from this, thus checking +// for any overloading in the instance, though this approach is +// not very reusable.... +// NOTE: this will not trigger any props... +var sources = +module.sources = +function(obj, name, callback){ + // get full chain... + if(typeof(name) == 'function'){ + callback = name + name = undefined + } + var i = 0 + var o + var res = [] + while(obj != null){ + //if(obj.hasOwnProperty(name)){ + if(name === undefined + || obj.hasOwnProperty(name) + || (name == '__call__' && typeof(obj) == 'function')){ + // handle callback... + o = callback + && callback(obj, i++) + // manage results... + res.push( + (o === undefined || o === module.STOP) ? + [obj] + : o instanceof module.STOP ? + o.value + : o ) + // stop... + if(o === module.STOP + || o instanceof module.STOP){ + return res.flat() } } + obj = obj.__proto__ } + return res.flat() } + + +// Get a list of values/props set in source objects for a prop/attr name... +// +// Get values... +// values(obj, name) +// values(obj, name, callback) +// -> list +// -> [] +// +// Get propery descriptors... +// values(obj, name, true) +// values(obj, name, callback, true) +// -> list +// -> [] +// +// callback(value/prop, obj) +// -> STOP +// -> STOP(value) +// -> .. +// +// +// Special case: name is given as '__call__' +// This will return either the value the object if it is callable +// or the value of .__call__ attribute... +// +// +// NOTE: for more docs on the callback(..) see sources(..) +var values = +module.values = +function(obj, name, callback, props){ + props = callback === true ? + callback + : props + var _get = function(obj, name){ + return props ? + Object.getOwnPropertyDescriptor(obj, name) + // handle callable instance... + : !(name in obj) + && name == '__call__' + && typeof(obj) == 'function' ? + obj + // normal attr... + : obj[name] } + // wrap the callback if given... + var c = typeof(callback) == 'function' + && function(obj, i){ + var val = _get(obj, name) + var res = callback(val, obj, i) + return res === module.STOP ? + // wrap the expected stop result if the user did not do it... + module.STOP(val) + : res } + return c ? + // NOTE: we do not need to handle the callback return values as + // this is fully done by c(..) in sources(..) + sources(obj, name, c) + : sources(obj, name) + .map(function(obj){ + return _get(obj, name) }) } + + +// Find the next parent attribute in the prototype chain. +// +// Get parent attribute value... +// parent(proto, name) +// -> value +// -> undefined +// +// Get parent callable or .__call__ value (special-case) +// parent(proto, '__call__') +// -> value +// -> undefined +// +// Get parent method... +// parent(method, this) +// -> meth +// -> undefined +// +// Get parent object... +// parent(this) +// -> parent +// +// +// The two forms differ in: +// - in parent(method, ..) a method's .name attr is used for name. +// - in parent(method, ..) the containing prototype is inferred. +// +// NOTE: there are cases where method.name is not set (e.g. anonymous +// function), so there a name should be passed explicitly... +// NOTE: when passing a method it is recommended to pass an explicit +// reference to it relative to the constructor, i.e.: +// Constructor.prototype.method +// this will avoid relative resolution loops, for example: +// this.method +// deep in a chain will resolve to the first .method value visible +// from 'this', i.e. the top most value and not the value visible +// from that particular level... +// +// +// Example: +// var X = object.Constructor('X', { +// __proto__: Y.prototype, +// +// attr: 123, +// +// method: function(){ +// // get attribute... +// var a = object.parent(X.prototype, 'attr') +// +// // get method... +// var ret = object.parent(X.prototype.method, this) +// .call(this, ...arguments) +// +// // ... +// } +// }) +// +// +// NOTE: in the general case this will get the value of the returned +// property/attribute, the rest of the way passive to props. +// The method case will get the value of every method from 'this' +// and to the method after the match. +// NOTE: this is super(..) replacement, usable in any context without +// restriction -- super(..) is restricted to class methods only... +// NOTE: contrary to sources(..) in the .__call__ case, this will skip +// the base callable instance, this will make both the following +// cases identical: +// parent(C.prototype.__call__, obj) +// and: +// parent(C.prototype, '__call__') +var parent = +module.parent = +function(proto, name){ + // special case: get parent... + if(arguments.length == 1){ + return proto.__proto__ } + // special case: get method... + if(typeof(name) != typeof('str')){ + var that = name + name = proto.name + // sanity check... + if(name == ''){ + throw new Error('parent(..): need a method with non-empty .name') } + // get first matching source... + proto = sources(that, name, + function(obj, i){ + // NOTE: the .hasOwnProperty(..) test is here so as + // to skip the base callable when searching for + // .__call__ that is returned as a special case + // by sourcei(..) and this should have no effect + // or other cases... + // NOTE: this will only skip the root callable... + return (i > 0 || obj.hasOwnProperty(name)) + && obj[name] === proto + && module.STOP }) + .pop() } + // get first source... + var res = sources(proto, name, + function(obj, i){ + return i == 1 + && module.STOP }) + .pop() + return !res ? + undefined + :(!(name in res) && typeof(res) == 'function') ? + res + : res[name] } + + +// Find the next parent property descriptor in the prototype chain... +// +// parentProperty(proto, name) +// -> prop-descriptor +// +// +// This is like parent(..) but will get a property descriptor... +var parentProperty = +module.parentProperty = +function(proto, name){ + // get second source... + var res = sources(proto, name, + function(obj, i){ + return i == 1 + && module.STOP }) + .pop() + return res ? + // get next value... + Object.getOwnPropertyDescriptor(res, name) + : undefined } + + +// Find the next parent method and call it... +// +// parentCall(proto, name, this, ..) +// parentCall(meth, this, ..) +// -> res +// -> undefined +// +// +// This also gracefully handles the case when no higher level definition +// is found, i.e. the corresponding parent(..) call will return undefined +// or a non-callable. +// +// NOTE: this is just like parent(..) but will call the retrieved method, +// essentially this is a shorthand to: +// parent(proto, name).call(this, ...) +// or: +// parent(method, this).call(this, ...) +// NOTE: for more docs see parent(..) +var parentCall = +module.parentCall = +function(proto, name, that, ...args){ + var meth = parent(proto, name) + return typeof(meth) == 'function' ? + meth.call(...( typeof(name) == typeof('str') ? + [...arguments].slice(2) + : [...arguments].slice(1) )) + : undefined } + + +// Test if child is related to parent... +// +// parentOf(parent, child) +// -> bool +// +// +// NOTE: this is like a instanceof b but within the prototype chain +var parentOf = +module.parentOf = +function(parent, child){ + return new Set(sources(child)).has(parent) } + +// Reverse of parentOf(..) +var childOf = +module.childOf = +function(child, parent){ + return parentOf(parent, child) } + +var related = +module.related = +function(a, b){ + return parentOf(a, b) + || parentOf(b, a) } + + + +//--------------------------------------------------------------------- +// Constructor... + +// Create a .toString(..) proxy +// +// This is needed to show the user code instead of the library code that +// callas it... +var __toStringProxy = +//module.__toStringProxy = +function(func){ + Object.defineProperty(func, 'toString', { + value: function toString(...args){ + var f = ( + // explicitly defined .toString(..) + this.__proto__.toString !== Function.prototype.toString + && this.__proto__.toString !== Object.prototype.toString ? + this.__proto__ + // use .__call__... + : '__call__' in this ? + this.__call__ + : this.__proto__) + return module.normalizeIndent(f.toString(...args)) }, + enumerable: false, + }) + return func } + +// Make an uninitialized instance from a constructor... +// +// RawInstance(context, constructor, ...) +// -> instance +// +// +// This will: +// - construct an object +// - if .__new__(..) is defined +// -> call and use its return value +// - if prototype is a function or if .__call__(..) is defined +// -> use a wrapper function +// - if construct.__proto__ has .__rawinstance__(..) +// -> use it to create an instance +// - if constructor.__proto__ is a constructor +// -> use it to create an instance +// - else +// -> use {} +// - link the object into the prototype chain +// +// +// This will not call .__init__(..), hence the "uninitialized". +// +// +// NOTE: "context" is only used when passeding to .__new__(..) if defined, +// and is ignored otherwise... +// NOTE: as this is simply an extension to the base JavaScript protocol this +// can be used to construct any object... +// Example: +// // new is optional... +// var l = new RawInstance(null, Array, 'a', 'b', 'c') +// NOTE: the following are not the same in structure but functionally +// are identical: +// var C = Constructor('C', function(){ .. }) +// and +// var C2 = Constructor('C2', { __call__: function(){ .. } }) +// the difference is in C.prototype vs. C2.prototype, the first +// being a function while the second is an object with a call +// method... +// NOTE: essentially this is an extended version of Reflect.construct(..) +// +// XXX need a way to set .name of a callable instance... +// XXX need to auto-generate .name for callable instances... +var RawInstance = +module.RawInstance = +function(context, constructor, ...args){ + var obj = + // prototype defines .__new__(..)... + constructor.prototype.__new__ instanceof Function ? + constructor.prototype.__new__(context, ...args) + // native constructor... + : /\[native code\]/.test(constructor.toString()) ? + Reflect.construct(constructor, args) + // callable instance... + // NOTE: we need to isolate the callable from instances, thus we + // reference 'constructor' directly rather than using + // 'this.constructor'... + // XXX autogenerate/set .name ... + : (typeof(constructor.prototype) == 'function' + || constructor.prototype.__call__ instanceof Function) ? + __toStringProxy( + function(){ + return ( + // .prototype is a function... + typeof(constructor.prototype) == 'function' ? + // NOTE: we are not using .call(..) here as it + // may not be accesible through the prototype + // chain, this can occur when creating a + // callable instance from a non-callable + // parent... + Reflect.apply( + constructor.prototype, obj, [this, ...arguments]) + // .__call__(..) or fail semi-gracefully... + : constructor.prototype.__call__ + .call(obj, this, ...arguments)) }) + // recursively call .__rawinstance__(..) + : constructor.__proto__.__rawinstance__ ? + constructor.__proto__.__rawinstance__(context, ...args) + // use parent's constructor... + : (typeof(constructor.__proto__) == 'function' + // XXX for some reason if using (function(){}).__proto__ + // instead of Function.prototype below, coverage is + // not counted for the condition.... + && constructor.__proto__ !== Function.prototype) ? + Reflect.construct(constructor.__proto__, args, constructor) + // default object base... + : Reflect.construct(Object, [], constructor) + + // link to prototype chain, if not done already... + obj.__proto__ !== constructor.prototype + && (obj.__proto__ = constructor.prototype) + + return obj } + + +// Make an object constructor function... +// +// Make a constructor with an object prototype... +// Constructor(name, proto) +// -> constructor +// +// Make a constructor with a prototype and a constructor prototype... +// Constructor(name, constructor-mixin, proto) +// -> constructor +// +// Make a constructor with prototype extending parent-constructor... +// Constructor(name, parent-constructor, proto) +// Constructor(name, parent-constructor, constructor-mixin, proto) +// -> constructor +// +// +// The resulting constructor can produce objects in one of these ways: +// +// Create instance... +// constructor(..) +// new constructor +// new constructor(..) +// -> instance +// +// Create raw/uninitialized instance... +// constructor.__rawinstance__(..) +// RawInstance(null, constructor, ..) +// -> raw-instance +// +// +// All produced objects are instances of the constructor +// instance instanceof constructor +// -> true +// +// +// +// Create and initialization protocol: +// 1) raw instance is created: +// a) constructor.__rawinstance__(..) / RawInstance(..) called: +// - call .__new__(..) if defined and get return value as +// instance, or +// - if .__call__(..) defined or prototype is a function, wrap +// it and use the wrapper function as instance, or +// - create an empty object +// b) instance linked to prototype chain +// set .__proto__ to constructor.prototype +// 2) instance is initialized: +// call .__init__(..) if defined +// +// +// Special attributes: +// +// Sets parent constructor +// .__extends__ = constructor +// NOTE: this can be set on either constructor-mixin or proto but +// not on both... +// NOTE: if .__proto__ is not set in the proto, then it will be +// set to .__extends__.prototype by default. +// NOTE: setting this and proto.__proto__ to can be used to link the +// constructor and instance object to different prototype chains +// NOTE: this attr is only used if explicitly defined, inherited +// values are ignored. +// XXX this may get removed in future versions. +// +// If true do not link function methods if .__call__(..) is defined +// .__skip_call_attrs__ = bool +// +// +// Special methods (constructor): +// +// Handle uninitialized instance construction +// .__rawinstance__(context, ...) +// -> instance +// NOTE: This is a shorthand to RawInstance(..) see it for +// details. +// +// +// Special methods (.prototype): +// +// Create new instance object... +// .__new__(context, ..) +// -> object +// +// Handle instance call... +// .__call__(context, ..) +// -> .. +// +// Initialize instance object... +// .__init__(..) +// -> .. +// +// +// NOTE: raw instance creation is defined by RawInstance(..) so see +// it for more info. +// NOTE: raw instance creation can be completely overloaded by defining +// .__rawinstance__(..) on the constructor. +// NOTE: if constructor-mixin's .__proto__ is set it will also be copied +// to the created constructor... +// +// +// +// Inheritance: +// A simple way to build C -> B -> A chain would be: +// +// // NOTE: new is optional... +// var A = new Constructor('A') +// +// var B = Constructor('B', A, {}) +// +// var C = Constructor('C', B, {}) +// +// var c = C() +// +// c instanceof C // -> true +// c instanceof B // -> true +// c instanceof A // -> true +// +// A.prototype.x = 123 +// +// c.x // -> 123 +// +// +// +// NOTE: this sets the proto's .constructor attribute, thus rendering it +// not reusable, to use the same prototype for multiple objects +// clone it via. Object.create(..) or copy it... +// NOTE: to disable .__rawinstance__(..) handling set it to false in the +// class prototype... +// NOTE: it is currently not possible to mix native unrelated types, for +// example a callable array constructor will produce inconsistent +// instance objects that in general will not work as expected... +// Reflect.construct(Array, [], Function) +// or +// Reflect.construct(Function, [], Array) +// will either initialize internal/hidden state for either one or +// the other producing a semi-broken instance. +// It is however possible to mix related types as we are doing for +// callable instances (Function + Object -- a function is an object). +// See README.md for more info. +// NOTE: making an object callable does not guarantee that it will pass +// the instanceof Function test, for that the prototype chain needs +// to be rooted in Function. +// though the typeof(..) == 'function' will always work. +// NOTE: this will fail with non-identifier names... +// XXX is this a bug or a feature??? =) +var Constructor = +module.Constructor = +// shorthand... +module.C = +function Constructor(name, a, b, c){ + var args = [...arguments].slice(1, 4) + + // sanity check... + if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name.trim())){ + throw new Error(`Constructor(..): invalid name: "${name}"`) } + + // parse args... + // Constructor(name[[, constructor[, mixin]], proto]) + var proto = args.pop() || {} + var constructor_proto = typeof(args[0]) == 'function' ? + args.shift() + : undefined + var constructor_mixin = args.pop() + + // handle: + // Constructor(name, constructor, ..) + // + // NOTE: this is a bit too functional in style by an if-tree would + // be more bulky and less readable... + constructor_proto + // XXX need a better test -- need to test if .__proto__ was set + // manually and not mess it up... + && (proto.__proto__ === Object.prototype + || proto.__proto__ === Function.prototype) + && (proto.__proto__ = constructor_proto.prototype) + // restore func .toString(..) that was replaced to object's .toString(..) + // in the previous op but only if it was not set by user... + && (typeof(proto) == 'function' + && proto.toString === Object.prototype.toString) + // XXX should we wrap this in normalizeIndent(..) ??? + && (proto.toString = Function.prototype.toString) + + // handle: .__extends__ + if(!constructor_proto){ + // handle .__extends__ + a = Object.hasOwnProperty.call(proto, '__extends__') + && proto.__extends__ + b = constructor_mixin != null + && Object.hasOwnProperty.call(constructor_mixin, '__extends__') + && constructor_mixin.__extends__ + // sanity check... + if(!!a && !!b){ + throw new Error('Constructor(..): ' + +'only one of prototype.__extends__ or constructor.__extends__ ' + +'can exist.') } + constructor_proto = !!a ? a : b + // cleanup... + if(!!b){ + constructor_mixin = mixinFlat({}, constructor_mixin) + delete constructor_mixin.__extends__ } + !!constructor_proto + && (proto.__proto__ = constructor_proto.prototype) } + + // the constructor base... + /* c8 ignore next 9 */ + var _constructor = function Constructor(){ + // create raw instance... + var obj = _constructor.__rawinstance__ ? + _constructor.__rawinstance__(this, ...arguments) + : RawInstance(this, _constructor, ...arguments) + // initialize... + obj.__init__ instanceof Function + && obj.__init__(...arguments) + return obj } + + // constructor naming... + // NOTE: we are not using: + // Object.defineProperty(_constructor, 'name', { value: name }) + // because this does not affect the name displayed by the Chrome + // DevTools. FF does not seem to care about either version of code... + _constructor.name = name + // just in case the browser/node refuses to change the name, we'll make + // them a different offer ;) + // NOTE: it is not possible to abstract this eval(..) into something + // like renameFunction(..) as reconstructing the function will + // lose it's closure that we depend on here... + // NOTE: this eval(..) should not be a risk as its inputs are + // static and never infuenced by external inputs... + _constructor.name != name + && (_constructor = eval('('+ + _constructor + .toString() + .replace(/Constructor/g, name) +')')) + // set .toString(..)... + // NOTE: this test is here to enable mixinFlat(..) to overwrite + // .toString(..) below... + // XXX not sure if this is the correct approach... + // XXX might be a good idea to create a common base class and + // keep this there... + ;((constructor_mixin || {}).toString === Function.prototype.toString + || (constructor_mixin || {}).toString === Object.prototype.toString) + && Object.defineProperty(_constructor, 'toString', { + value: function toString(){ + var args = proto.__init__ ? + proto.__init__ + .toString() + .split(/\n/)[0] + .replace(/function\(([^)]*)\){.*/, '$1') + : '' + var code = proto.__init__ ? + proto.__init__ + .toString() + .replace(/[^{]*{/, '{') + : '{ .. }' + return `${this.name}(${args})${module.normalizeIndent(code)}` }, + enumerable: false, + }) + // set generic raw instance constructor... + _constructor.__rawinstance__ instanceof Function + || Object.defineProperty(_constructor, '__rawinstance__', { + value: function __rawinstance__(context, ...args){ + return RawInstance(context, this, ...args) }, + enumerable: false, + }) + !!constructor_proto + && (_constructor.__proto__ = constructor_proto) + _constructor.prototype = proto + _constructor.prototype.constructor = _constructor + + // NOTE: this is intentionally last, this enables the user to override + // any of the system methods... + // NOTE: place the non-overridable definitions after this... + !!constructor_mixin + && mixinFlat( + _constructor, + constructor_mixin) + // also transfer non-default constructor_mixin.__proto__ + && constructor_mixin.__proto__ !== Object.prototype + && (_constructor.__proto__ = constructor_mixin.__proto__) + + // link function stuff for convenience... + proto.__call__ && !(proto instanceof Function) + && _constructor.__skip_call_attrs__ !== true + && module.LINK_FUNCTION_METHODS + .forEach(function(n){ + proto[n] + || Object.defineProperty(proto, n, + Object.assign( + Object.getOwnPropertyDescriptor(Function.prototype, n), + // NOTE: we can't use Function[n] directly because + // they in general test this for relation to + // function which will fail here... + { value: function(){ + return this.__call__[n](this, ...arguments) }, })) }) + + return _constructor } + + +// Complete the constructor... +// +// NOTE: currently this is a complement to the top level functions. +Object.assign(Constructor, { + sources, + values, + + parent, + parentProperty, + parentCall, + + parentOf, + childOf, + related, + + match, + matchPartial, + + deepKeys, + + create, +}) + + + +//--------------------------------------------------------------------- +// Mixin utils... + +// Mix a set of methods/props/attrs into an object... +// +// Mix objects into base... +// mixinFlat(base, object, ...) +// -> base +// +// Soft mix objects into base... +// mixinFlat('soft', base, object, ...) +// -> base +// +// +// 'soft' mode only mixies in props if base does not define them already +// +// +// NOTE: essentially this is just like Object.assign(..) but copies +// properties directly rather than copying property values... +// NOTE: this will not transfer several the special variables not listed +// by Object.keys(..). +// This includes things like .__proto__ +// NOTE: this can and will overwrite attributes... +var mixinFlat = +module.mixinFlat = +function(base, ...objects){ + var soft = base === 'soft' + if(soft){ + base = objects.shift() + objects = objects + .slice() + .reverse() } + return objects + .reduce(function(base, cur){ + Object.keys(cur) + .map(function(k){ + ;(!soft || !base.hasOwnProperty(k)) + && Object.defineProperty(base, k, + Object.getOwnPropertyDescriptor(cur, k)) }) + return base }, base) } + + +// Mix sets of methods/props/attrs into an object as prototypes... +// +// mixin(base, object, ..) +// -> base +// +// +// This will create a new object per set of methods given and +// mixinFlat(..) the method set into this object leaving the +// original objects intact. +// +// base <-- object1_copy <-- .. <-- objectN_copy <- base.__proto__ +// +// +// NOTE: this will only mix in non-empty objects... +// NOTE: mixing into a constructor will break object creation via new... +// Example: +// class A {} +// class B extends A {} +// +// mixin(B, {x: 123}) +// +// var b = new B() // will break... +// +// This does not affect object.Constructor(..) chains... +// NOTE: mixin(Object.prototype, ..) will fail because Object.prototype.__proto__ +// is imutable... +var mixin = +module.mixin = +function(base, ...objects){ + base.__proto__ = objects + .reduce(function(res, cur){ + return Object.keys(cur).length > 0 ? + module.mixinFlat(Object.create(res), cur) + : res }, base.__proto__) + return base } + + +// Get matching mixins... +// +// mixins(base, object[, callback]) +// mixins(base, list[, callback]) +// -> list +// +// +// callback(base, obj, parent) +// -> STOP +// -> undefined +// +// +// NOTE: this will also match base... +// NOTE: if base matches directly callback(..) will get undefined as parent +// NOTE: for more docs on the callback(..) see sources(..) +var mixins = +module.mixins = +function(base, object, callback){ + object = object instanceof Array ? + object + : [object] + var res = [] + var o + var parent + while(base != null){ + // match each object... + for(var obj of object){ + if(match(base, obj)){ + o = callback + && callback(base, obj, parent) + // manage results... + res.push( + (o === undefined || o === module.STOP) ? + [base] + : o instanceof module.STOP ? + o.value + : o ) + if(o === module.STOP + || o instanceof module.STOP){ + return res.flat() } + // match found, no need to test further... + break } } + parent = base + base = base.__proto__ } + return res.flat() } + + +// Check of base has mixin... +// +// hasMixin(base, mixin) +// -> bool +// +// +// NOTE: to test for a flat mixin directly use .matchPartial(base, object) +var hasMixin = +module.hasMixin = +function(base, object){ + return ( + // normal mixin... + mixins(base, object, function(){ return module.STOP }) + .length > 0 + // flat mixin search... + || sources(base, function(p){ + return matchPartial(p, object) ? + module.STOP + : [] }) + .length > 0 )} + + +// Mix-out sets of methods/props/attrs out of an object prototype chain... +// +// Mix-out first occurrence of each matching object... +// mixout(base, object, ..) +// mixout(base, 'first', object, ..) +// -> base +// +// Mix-out all occurrences of each matching object... +// mixout(base, 'all', object, ..) +// -> base +// +// +// NOTE: this is the opposite to mixin(..) +// NOTE: this used mixins(..) / match(..) to find the relevant mixins, +// see those for more info... +var mixout = +module.mixout = +function(base, ...objects){ + var all = objects[0] == 'all' ? + !!objects.shift() + : objects[0] == 'first' ? + !objects.shift() + : false + var remove = [] + mixins(base, objects, function(match, obj, parent){ + parent && remove.push(parent) + // when removing the first occurrence, don't check for obj again... + all || objects.splice(objects.indexOf(obj), 1) }) + // NOTE: we are removing on a separate stage so as not to mess with + // mixins(..) iterating... + remove + // XXX not sure why this is needed, needs thought... + .reverse() + .forEach(function(p){ + p.__proto__ = p.__proto__.__proto__ }) + return base } + + +// Mixin wrapper/object... +// +// Create a new mixin... +// Mixin(name, data, ..) +// -> mixin +// +// Create a new mixin setting the default mode... +// Mixin(name, mode, data, ..) +// -> mixin +// +// +// Apply mixin in the prototype chain (default)... +// mixin(obj) +// mixin('proto', obj) +// -> obj +// +// Copy date from mixin into obj directly... +// mixin('flat', obj) +// -> obj +// +// +// +// Example: +// +// var BasicMixin = Mixin('BasicMixin', { +// ... +// }) +// +// ... +// +// var o = { +// ... +// } +// +// BasicMixin(o) +// +// +// NOTE: the constructor will allways create a new .data object but will +// not do a deep copy into it -- see mixin(..) / mixinFlat(..) +// +// XXX should this move to its own modue??? +var Mixin = +module.Mixin = +Constructor('Mixin', { + // static methods... + // + // NOTE: currently this is a complement to the top level functions. + mixin, + mixinFlat, + mixout, + + mixins, + hasMixin, + +}, { + name: null, + + // mixin data... + data: null, + + // data "copy" mode... + // + // This can be: + // 'proto' - mix data into prototype chain (default) + // 'flat' - use mixinFlat(..) to copy data + // 'soft' - like 'flat' but uses mixinFlat('soft', ..) + mode: 'proto', + + // base API... + // + isMixed: function(target){ + return this.constructor.hasMixin(target, this.data) }, + mixout: function(target){ + return this.constructor.mixout(target, this.data) }, + + // mix into target... + __call__: function(_, target, mode=this.mode){ + typeof(target) == typeof('str') + && ([_, mode, target] = arguments) + return mode == 'flat' ? + this.constructor.mixinFlat(target, this.data) + : mode == 'soft' ? + this.constructor.mixinFlat('soft', target, this.data) + : this.constructor.mixin(target, this.data) }, + + __init__: function(name, ...data){ + // Mixin(name, mode, ...) -- handle default mode... + typeof(data[0]) == typeof('str') + && (this.mode = data.shift()) + // name... + // NOTE: .defineProperty(..) is used because this is a function + // and function's .name is not too configurable... + // NOTE: we do not need to configure this any more, .defineProperty(..) + // merges the descriptor into the original keeping any values not + // explicitly overwritten... + Object.defineProperty(this, 'name', { value: name }) + // create/merge .data... + this.data = this.constructor.mixinFlat({}, + ...data.map(function(e){ + // handle bare objects and mixins differently... + return e instanceof Mixin ? + e.data + : e })) }, +}) + + + +//--------------------------------------------------------------------- + +BOOTSTRAP() + + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */ return module }) diff --git a/package.json b/package.json index 25a5951..7f90260 100755 --- a/package.json +++ b/package.json @@ -4,20 +4,22 @@ "author": "Alex A. Naanou ", "license": "BSD", "dependencies": { - "glob": "^7.1.4", - "ig-actions": "^1.9.0", - "ig-features": "^2.2.6", - "ig-object": "^2.0.0", - "jszip": "^2.6.1", - "peer": "^0.2.10", - "pouch-replicate-webrtc": "0.0.9", - "pouchdb": "^7.1.1", - "requirejs": "^2.3.6", - "showdown": "^1.9.0", - "xss": "^0.2.13" + "glob": "*", + "ig-actions": "*", + "ig-features": "*", + "ig-object": "*", + "jszip": "*", + "requirejs": "*" + }, + "disabled-dependencies": { + "peer": "*", + "pouch-replicate-webrtc": "*", + "pouchdb": "*", + "showdown": "*", + "xss": "*" }, "devDependencies": {}, "scripts": { - "bootstrap": "node make_bootstrap.js" + "bootstrap": "node scripts/bootstrap.js" } } diff --git a/pwiki.js b/pwiki.js index 387478a..45e8dda 100755 --- a/pwiki.js +++ b/pwiki.js @@ -14,7 +14,6 @@ var features = require('lib/features') var macro = require('macro') - /*********************************************************************/ // Split path into a list and handle special path elements... @@ -42,12 +41,15 @@ var macro = require('macro') // NOTE: '>>' has no effect when at last position (XXX ???) var path2list = module.path2list = function(path){ - return (path instanceof Array ? path : path.split(/[\\\/]+/g)) + return (path instanceof Array ? + path + : path.split(/[\\\/]+/g)) // handle '..' (lookahead) and trim path elements... // NOTE: this will not touch the leading '.' or '..' .map(function(p, i, l){ // remove '..' and '.' out at positions > 0... - return (i > 0 && (p.trim() == '..' || p.trim() == '.') + return (i > 0 + && (p.trim() == '..' || p.trim() == '.') // remove items followed by '..'... || (l[i+1] || '').trim() == '..' // remove items preceded by '>>'... @@ -64,7 +66,8 @@ module.path2list = function(path){ // // This is the same as path2list(..) but also joins the path with '/' var normalizePath = -module.normalizePath = function(path){ return path2list(path).join('/') } +module.normalizePath = function(path){ + return path2list(path).join('/') } var path2re = diff --git a/wiki.js b/wiki.js index a733ede..0b42b50 100755 --- a/wiki.js +++ b/wiki.js @@ -16,17 +16,21 @@ RegExp.quoteRegExp = .replace(/([\.\\\/\(\)\[\]\$\*\+\-\{\}\@\^\&\?\<\>])/g, '\\$1') } var path2lst = function(path){ - return (path instanceof Array ? path : path.split(/[\\\/]+/g)) + return (path instanceof Array ? + path + : path.split(/[\\\/]+/g)) // handle '..' (lookahead) and trim path elements... // NOTE: this will not touch the leading '.' or '..' .map(function(p, i, l){ - return (i > 0 && (p.trim() == '..' || p.trim() == '.') + return (i > 0 + && (p.trim() == '..' || p.trim() == '.') || (l[i+1] || '').trim() == '..') ? null : p.trim() }) // cleanup and clear '.'... .filter(function(p){ - return p != null && p != '' })} + return p != null + && p != '' })} var normalizePath = function(path){ return path2lst(path).join('/') }