refactoring, minor tweaks and fixes...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-04-23 17:59:13 +03:00
parent e4a1139be3
commit 8f259d21fe
2 changed files with 415 additions and 380 deletions

View File

@ -249,7 +249,7 @@ module.UNDEFINED = ASIS(undefined)
// really necessary. // really necessary.
// //
// //
// 3) .__call__ action / handler // 3) .__actioncall__ action / handler
// This action if defined is called for every action called. It behaves // This action if defined is called for every action called. It behaves
// like any other action but with a fixed signature, it always receives // like any other action but with a fixed signature, it always receives
// the action name as first argument and a list of action arguments as // the action name as first argument and a list of action arguments as
@ -257,7 +257,7 @@ module.UNDEFINED = ASIS(undefined)
// //
// NOTE: it is not necessary to define the actual action, binding to a // NOTE: it is not necessary to define the actual action, binding to a
// handler will also work. // handler will also work.
// NOTE: one should not call actions directly from within a __call__ // NOTE: one should not call actions directly from within a __actioncall__
// handler as that will result in infinite recursion. // handler as that will result in infinite recursion.
// XXX need a way to prevent this... // XXX need a way to prevent this...
// NOTE: one should use this with extreme care as this will introduce // NOTE: one should use this with extreme care as this will introduce
@ -374,6 +374,7 @@ function(func){
return func.apply(this, [handlers.pop()].concat(args)) } } return func.apply(this, [handlers.pop()].concat(args)) } }
// String action parser/runner...
// //
// Syntax: // Syntax:
// ALIAS ::= // ALIAS ::=
@ -410,6 +411,7 @@ function(func){
// } // }
// //
// NOTE: identifiers are resolved as attributes of the context... // 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 // XXX this is the same as ImageGrid's keyboard.parseActionCall(..), reuse
// in a logical manner... // in a logical manner...
@ -482,8 +484,10 @@ Object.assign(
stop_propagation: false, stop_propagation: false,
code: txt, code: txt,
} } },
},{
// API and utils...
{
// atoms... // atoms...
Atom: (__Atom = object.Constructor('Atom', { Atom: (__Atom = object.Constructor('Atom', {
__init__: function(value){ __init__: function(value){
@ -501,7 +505,7 @@ Object.assign(
resolveArgs: function(context, action_args, call_args){ resolveArgs: function(context, action_args, call_args){
var that = this var that = this
var rest var rest
var args = [].slice.call(action_args) var args = [...action_args]
// merge args... // merge args...
.map(function(arg, i){ .map(function(arg, i){
return arg instanceof that.Argument ? return arg instanceof that.Argument ?
@ -660,100 +664,78 @@ module.isStringAction =
// XXX might be a good idea to add an option to return the full results... // XXX might be a good idea to add an option to return the full results...
var Action = var Action =
module.Action = module.Action =
function Action(name, doc, ldoc, attrs, func){ object.Constructor('Action', {
// we got called without a 'new'... __proto__: Function,
if(this == null || this.constructor !== Action){
// XXX using something like .apply(.., arguemnts) would be more
// generel but have no time to figure out how to pass it
// to new without the later complaining...
return new Action(name, doc, ldoc, attrs, func)
}
// prevent action overloading... // Control how an action handles returned promises...
if(this[name] != null){ //
throw 'action "'+name+'" already exists.' // Possible values:
} // true - if an action returns a promise then trigger the post phase
// after that promise is resolved / rejected... (default)
// false - handle promises like any other returned value.
//
//
// NOTE: .await is only checked in the root action, thus it can not be
// overloaded by extending actions.
// This is done intentionally, as the action actually returning a
// value (and defining the signature) is the only one responsible
// for controlling how it's handled.
//
// For implmentation see: Action.prototype.chainApply(..)
//
// XXX should we be able to set this in the context???
// XXX can we use 'await'???
await: true,
// create the actual instance we will be returning...
var meth = function(){
return meth.chainApply(this, null, arguments) }
meth.__proto__ = this.__proto__
// precess args... // pre/post stage runners...
var args = doc instanceof Array ? //
doc // .pre(context, args)
: [].slice.call(arguments) // -> data
.filter(function(e){ return e !== undefined }) //
.slice(1) // .post(context, data)
func = args.pop() // -> result
last = args[args.length-1] //
attrs = last != null && typeof(last) != typeof('str') ? args.pop() : {} //
doc = typeof(args[0]) == typeof('str') ? args.shift() // Call data format:
: func.doc ? func.doc // {
: null // arguments: args,
ldoc = typeof(args[0]) == typeof('str') ? args.shift() //
: func.long_doc ? func.long_doc // wrapper: call_wrapper,
: null // handlers: handlers,
//
// populate the action attributes... // result: res,
//meth.name = name // }
Object.defineProperty(meth, 'name', { //
value: name, //
}) // External methods (required):
func.doc = meth.doc = doc // .getHandlers(..) resolved from: context, MetaActions
func.long_doc = meth.long_doc = ldoc //
//
meth.func = func // External methods (optoinal):
// .__actioncall__(..) resolved from: context
if(func.name == '<action-name>'){ // .preActionHandler(..) resolved from: context, MetaActions
Object.defineProperty(func, 'name', { //
value: name, //
}) // Special cases:
} // - An action is referenced via a different name than is in its .name
// this can occur if:
// make introspection be a bit better... // 1) an action is renamed but its .name is not
meth.toString = func.toString.bind(func) // 2) an action is created and stored with a different name
// var f = new Action('m', function(){ ... })
// setup attrs... //
Object.assign(meth, attrs) //
Object.assign(func, attrs) // NOTE: All the defaults should be handled by the pre stage, post will
// process data assuming that it is correct.
return meth // NOTE: .post(..) will not wait for returned promises to resolve, use
} // .chainApply(..) / ,chainCall(..) instead, or handle .result
// this will make action instances behave like real functions... // manually...
Action.prototype.__proto__ = Function // (see: Action.prototype.chainApply(..))
// XXX revise the structure....
// The pre/post stage runners... // ...is it a better idea to define action methods in an object
// // and assign that???
// .pre(context, args) pre: function(context, args){
// -> data var that = this
//
// .post(context, data)
// -> result
//
//
// Call data format:
// {
// arguments: args,
//
// wrapper: call_wrapper,
// handlers: handlers,
//
// result: res,
// }
//
//
// NOTE: All the defaults should be handled by the pre stage, post will
// process data assuming that it is correct.
// NOTE: .post(..) will not wait for returned promises to resolve, use
// .chainApply(..) / ,chainCall(..) instead, or handle .result
// manually...
// (see: Action.prototype.chainApply(..))
//
// XXX revise the structure....
// ...is it a better idea to define action methods in an object
// and assign that???
Action.prototype.pre = function(context, args){
args = args || [] args = args || []
// prepare for after calls... // prepare for after calls...
@ -771,21 +753,36 @@ Action.prototype.pre = function(context, args){
var outer = this.name var outer = this.name
// get the handler list... // get the handler list...
var getHandlers = context.getHandlers || MetaActions.getHandlers var getHandlers = context.getHandlers
|| MetaActions.getHandlers
var handlers = getHandlers.call(context, outer) var handlers = getHandlers.call(context, outer)
// special case: see if we need to handle the call without handlers... // handle cases where .func is not in handlers...
var preActionHandler = context.preActionHandler || MetaActions.preActionHandler //
if(preActionHandler){ // NOTE: see Special cases in method doc above...
// XXX signature needs work... if(handlers.length == 0
var res = preActionHandler.call(context, outer, handlers, args) || handlers.filter(function(h){
if(res !== undefined){ return h.pre === that.func }).length == 0){
return res var cur = {
pre: this.func,
} }
this.doc
&& (cur.doc = this.doc)
this.long_doc
&& (cur.long_doc = this.long_doc)
handlers.unshift(cur)
} }
var call_wrapper = outer != '__call__' ? // special case: see if we need to handle the call without handlers...
getHandlers.call(context, '__call__') var preActionHandler = context.preActionHandler
|| MetaActions.preActionHandler
if(preActionHandler){
var res = preActionHandler.call(context, outer, handlers, args)
if(res !== undefined){
return res } }
var call_wrapper = outer != '__actioncall__' ?
getHandlers.call(context, '__actioncall__')
: [] : []
try { try {
@ -803,8 +800,7 @@ Action.prototype.pre = function(context, args){
a.post = res a.post = res
} }
} }
return a return a })
})
// handlers: pre phase... // handlers: pre phase...
handlers handlers
@ -828,8 +824,7 @@ Action.prototype.pre = function(context, args){
res = context res = context
} }
} }
return a return a })
})
// XXX EXPERIMENTAL (after calls)... // XXX EXPERIMENTAL (after calls)...
} catch(error){ } catch(error){
@ -856,8 +851,8 @@ Action.prototype.pre = function(context, args){
result: res, result: res,
} }
} },
Action.prototype.post = function(context, data){ post: function(context, data){
var res = data.result var res = data.result
var args = data.arguments || [] var args = data.arguments || []
@ -912,35 +907,14 @@ Action.prototype.post = function(context, data){
} }
return res return res
} },
// Control how an action handles returned promises... // chaining...
// //
// Possible values: // For docs see: MetaActions.chainApply(..) and the base module doc.
// true - if an action returns a promise then trigger the post phase chainApply: function(context, inner, args){
// after that promise is resolved / rejected... (default) args = [...(args || [])]
// false - handle promises like any other returned value.
//
//
// NOTE: .await is only checked in the root action, thus it can not be
// overloaded by extending actions.
// This is done intentionally, as the action actually returning a
// value (and defining the signature) is the only one responsible
// for controlling how it's handled.
//
// For implmentation see: Action.prototype.chainApply(..)
//
// XXX should we be able to set this in the context???
// XXX can we use 'await'???
Action.prototype.await = true
// Chaining...
//
// For docs see: MetaActions.chainApply(..) and the base module doc.
Action.prototype.chainApply = function(context, inner, args){
args = [].slice.call(args || [])
var outer = this.name var outer = this.name
var data = this.pre(context, args) var data = this.pre(context, args)
@ -972,7 +946,7 @@ Action.prototype.chainApply = function(context, inner, args){
// returned promise -> await for resolve/error... // returned promise -> await for resolve/error...
// XXX should we be able to set this in the context??? // XXX should we be able to set this in the context???
if(data.result instanceof Promise if(data.result instanceof Promise
&& (context.getRootActionAttr || ActionSet.getRootActionAttr) && (context.getRootActionAttr || MetaActions.getRootActionAttr)
.call(context, this.name, 'await') ){ .call(context, this.name, 'await') ){
var that = this var that = this
return data.result return data.result
@ -983,10 +957,77 @@ Action.prototype.chainApply = function(context, inner, args){
} }
return this.post(context, data) return this.post(context, data)
} },
Action.prototype.chainCall = function(context, inner){ chainCall: function(context, inner){
return this.chainApply(context, inner, [...arguments].slice(2)) return this.chainApply(context, inner, [...arguments].slice(2)) },
}
// constructor...
//
// Action(<name>, <function>)
// Action(<name>[, <doc>[, <long-doc>]][, <attrs>,] <function>)
// Action(<name>, [ [<doc>[, <long-doc>]][, <attrs>,] <function> ])
// -> <action>
//
__new__: function Action(context, name, doc, ldoc, attrs, func){
// prevent action overloading...
// XXX do we need this???
//if(context != null && context[name] != null){
// throw 'action "'+name+'" already exists.' }
// create the actual instance we will be returning...
var meth = function(){
return meth.chainApply(this, null, arguments) }
meth.__proto__ = this.__proto__
// precess args...
var args = doc instanceof Array ?
doc
: [...arguments]
.filter(function(e){ return e !== undefined })
.slice(1)
func = args.pop()
last = args[args.length-1]
attrs = (last != null && typeof(last) != typeof('str')) ?
args.pop()
: {}
doc = typeof(args[0]) == typeof('str') ?
args.shift()
: func.doc ?
func.doc
: null
ldoc = typeof(args[0]) == typeof('str') ?
args.shift()
: func.long_doc ?
func.long_doc
: null
// populate the action attributes...
//meth.name = name
Object.defineProperty(meth, 'name', {
value: name,
})
func.doc = meth.doc = doc
func.long_doc = meth.long_doc = ldoc
meth.func = func
if(func.name == '<action-name>'){
Object.defineProperty(func, 'name', {
value: name,
})
}
// make introspection be a bit better...
meth.toString = func.toString.bind(func)
// setup attrs...
Object.assign(meth, attrs)
Object.assign(func, attrs)
return meth
},
})
@ -1010,19 +1051,14 @@ Action.prototype.chainCall = function(context, inner){
// XXX should an alias return a value??? // XXX should an alias return a value???
var Alias = var Alias =
module.Alias = module.Alias =
function Alias(alias, doc, ldoc, attrs, target){ object.Constructor('Alias', {
// we got called without a 'new'... __proto__: Action.prototype,
if(this == null || this.constructor !== Alias){
// XXX using something like .apply(.., arguemnts) would be more
// generel but have no time to figure out how to pass it
// to new without the later complaining...
return new Alias(alias, doc, ldoc, attrs, target)
}
__new__: function Alias(context, alias, doc, ldoc, attrs, target){
// precess args... // precess args...
var args = doc instanceof Array ? var args = doc instanceof Array ?
doc doc
: [].slice.call(arguments) : [...arguments]
.filter(function(e){ return e !== undefined }) .filter(function(e){ return e !== undefined })
.slice(1) .slice(1)
target = args.pop() target = args.pop()
@ -1045,14 +1081,15 @@ function Alias(alias, doc, ldoc, attrs, target){
return return
} }
var that = this var that = this
var in_args = [].slice.call(arguments) var in_args = [...arguments]
var p = parsed || this.parseStringAction(target) var p = parsed
|| (this.parseStringAction || parseStringAction)(target)
// XXX should an alias return a value??? // XXX should an alias return a value???
//p.action in this ? //p.action in this ?
return p.action in this ? return p.action in this ?
this.parseStringAction.callAction(this, p, ...in_args) (this.parseStringAction || parseStringAction).callAction(this, p, ...in_args)
// error... // error...
: console.error(`${alias}: Unknown alias target action: ${p.action}`) : console.error(`${alias}: Unknown alias target action: ${p.action}`)
} }
@ -1060,15 +1097,15 @@ function Alias(alias, doc, ldoc, attrs, target){
return meth.alias.code || meth.alias } return meth.alias.code || meth.alias }
// make the action... // make the action...
var meth = Action(alias, doc, ldoc, attrs, func) // XXX not sure this is the correct way to go...
var meth = this.__proto__.__new__.call(this, context, alias, doc, ldoc, attrs, func)
meth.__proto__ = this.__proto__ meth.__proto__ = this.__proto__
meth.func.alias = target meth.func.alias = target
return meth return meth
} },
// inherit from Action... })
Alias.prototype.__proto__ = Action.prototype
@ -1180,7 +1217,7 @@ module.MetaActions = {
// - Local action function (.func) // - Local action function (.func)
// - if an alias look in the target... // - if an alias look in the target...
// - repeat for .__proto__ (until top of MRO) // - repeat for .__proto__ (until top of MRO)
// - repeat for '__call__' special action (XXX EXPERIMENTAL) // - repeat for '__actioncall__' special action (XXX EXPERIMENTAL)
// //
// //
// NOTE: this will get attribute set both on the action object and // NOTE: this will get attribute set both on the action object and
@ -1201,7 +1238,7 @@ module.MetaActions = {
// modifyAction(function(){ ... })], // modifyAction(function(){ ... })],
// //
// XXX document... // XXX document...
// XXX add option to to enable/disable look in .__call__... // XXX add option to to enable/disable look in .__actioncall__...
getActionAttr: function(action, attr){ getActionAttr: function(action, attr){
var cur = this var cur = this
@ -1230,9 +1267,9 @@ module.MetaActions = {
cur = cur.__proto__ cur = cur.__proto__
} }
// search .__call__ action... // search .__actioncall__ action...
if(cur[action] != null && action != '__call__'){ if(cur[action] != null && action != '__actioncall__'){
return this.getActionAttr('__call__', attr) return this.getActionAttr('__actioncall__', attr)
} }
}, },
@ -1535,9 +1572,7 @@ module.MetaActions = {
a.long_doc a.long_doc
&& (res.long_doc = a.long_doc) && (res.long_doc = a.long_doc)
return res return res }) },
})
},
// Handler for cases when we need to avoid the pre/post handlers... // Handler for cases when we need to avoid the pre/post handlers...
// //
@ -2283,7 +2318,7 @@ object.Constructor('ActionSet', MetaActions)
// An action set... // An action set constructor...
// //
// Actions(<object>) // Actions(<object>)
// Actions(<prototype>, <object>) // Actions(<prototype>, <object>)
@ -2387,7 +2422,7 @@ function Actions(a, b){
var mix = var mix =
module.mix = module.mix =
function(){ function(){
var args = [].slice.call(arguments) var args = [...arguments]
var res = {} var res = {}
// special case: if MetaActions is in the args then inherit the root // special case: if MetaActions is in the args then inherit the root

View File

@ -1,6 +1,6 @@
{ {
"name": "ig-actions", "name": "ig-actions",
"version": "3.24.4", "version": "3.24.5",
"description": "", "description": "",
"main": "actions.js", "main": "actions.js",
"scripts": { "scripts": {