cleanup and some refactoring...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-04-23 20:50:21 +03:00
parent 677260e50b
commit b97550d36a
2 changed files with 41 additions and 340 deletions

View File

@ -11,334 +11,10 @@ var object = require('ig-object')
/*********************************************************************/
// Return value wrapper...
//
// Wrapping a value in this and returning it from an action will force
// the action to return the value as-is...
// This is mainly usefull for specially handled values.
var ASIS =
module.ASIS =
object.Constructor('ASIS', {
__init__: function(obj){ this.value = obj } })
// undefined wrapper...
var UNDEFINED =
module.UNDEFINED = ASIS(undefined)
/*********************************************************************/
// XXX should we maintain two sets of docs, here and in README.md???
//
// Actions
//
// Actions are an extension to the JavaScript object model tailored for
// a set of specific tasks.
//
// Goals:
// - provide a unified mechanism to define and manage user API's for
// use in UI-hooks, keyboard mappings, scripting, ... etc.
// - a means to generate configuration UI's
// - a means to generate documentation
//
//
// The main entities:
//
// Action set
// - an object containing a number of actions,
// - optionally, directly or indirectly inherited from MetaActions
// and/or other action sets,
// - the action handlers are bound relative to it (._action_handlers)
//
// Action
//
// + pre + pre + + post + post +
// Action event handler: o-------x o-------x
// v ^
// Actions o-------x o-------x
// v ^
// Root Action o---|---x
//
// - a method, created by Action(..),
// - calls all the shadowed/overloaded actions in the inheritance
// chain in sequence implicitly,
// NOTE: there is no way to prevent an action in the chain from
// running, this is by design, i.e. no way to fully shadow.
// - actions that do not shadow anything are called root actions.
// - returns the action set by default (for call chaining),
// - the base/root action can return any value.
// NOTE: if undefined is returned, it will be replaced by the
// action context/action set.
// NOTE: there is no distinction between root and other actions
// other than that root action's return values are not
// ignored.
// - if the root action returns a Promise, the post phase is run
// when that promise is resolved or rejected.
// This can be disabled by setting the 'await' action attribute
// to false (default: true).
// - can consist of two parts: the first is called before the
// shadowed action (pre-callback) and the second after (post-callback).
// - post-callback has access to the return value and can modify it
// but not replace it.
// - can be bound to, a-la an event, calling the handlers when it is
// called,
//
// Action (event) handler
// - a function,
// - can be bound to run before and/or after the action itself,
// - is local to an action set it was bound via,
// - when an action is triggered from an action set, all the pre
// handlers in its inheritance chain will be called before the
// respective actions they are bound to and all the post handlers
// are called directly after.
// - pre handlers are passed the same arguments the original actions
// got when it was called.
// - post action handlers will get the root action result as first
// argument succeeded by the action arguments.
//
// Alias
// - an action created by Alias(..),
// - identical to an action with one key difference: instead of a
// function Alias(..) expects a string/code,
// - code syntax is configurable, defaulting to the defined by
// parseActionCall(..)
// - aliases are designed to be defined and managed in runtime while
// actions are mainly load-time entities.
//
//
// The action system main protocols:
//
// 1) Documentation generation and introspection (MetaActions)
//
// <action>.toString()
// -> code of original action function
//
// <action-set>.getDoc()
// <action-set>.getDoc(<action-name>[, ..])
// -> dict of action-name, doc
//
// <action-set>.a.getHandlerDocStr(<action-name>)
// -> formated string of action handlers
//
// <action-set>.actions
// -> list of action names
//
// <action-set>.length
// -> number of actions
//
//
// 2) Event-like callbacks for actions (MetaActions, Action)
//
// <action-set>.on('action', function(){ ... })
// <action-set>.on('action.post', function(){ ... })
//
// <action-set>.on('action.pre', function(){ ... })
//
//
// 3) A mechanism to define and extend already defined actions
// This replaces / complements the standard JavaScript overloading
// mechanisms (Action, Actions)
//
// // Actions...
// var X = Actions({
// m: [function(){ console.log('m') }]
// })
// var O = Actions(X, {
// m: [function(){
// console.log('pre')
// return function(res){
// console.log('post')
// }
// }]
// })
//
// NOTE: what is done here is similar to calling O.__proto__.m.call(..)
// but is implicit, and not dependant on the original containing
// object name/reference ('O'), thus enabling an action to be
// referenced and called from any object and still chain correctly.
// NOTE: if a normal method is encountered in the inheritance chain, it
// will shadow all the actions beyond it.
//
//
//
// Secondary action protocols:
//
// 1) A mechanism to manually call the pre/post stages of an action
//
// Pre phase...
// <action>.pre(<context>)
// <action>.pre(<context>, [<arg>, ..])
// -> <call-data>
//
// Post phase...
// <action>.post(<context>, <call-data>)
// -> <result>
//
// This is internally used to implement the action call as well as the
// chaining callbacks (see below).
//
// All action protocol details apply.
//
// NOTE: there is not reliable way to call the post phase without first
// calling the pre phase due to how the pre phase is defined (i.e.
// pre phase functions can return post phase functions).
//
//
// 2) A mechanism to chain/wrap actions or an action and a function.
// This enables us to call a callback or another action (inner) between
// the root action's (outer) pre and post stages.
//
// Outer action o-------x o-------x
// v ^
// Inner action/callback o---|---x
//
// A trivial example:
//
// actionSet.someAction.chainApply(actionsSet,
// function(){
// // this gets run between someAction's pre and post
// // stages...
// },
// args)
//
// This is intended to implement protocols where a single action is
// intended to act as a hook point (outer) and multiple different
// implementations (inner) within a single action set can be used as
// entry points.
//
// // Protocol root action (outer) definition...
// protocolAction: [function(){}],
//
// // Implementation actions (inner)...
// implementationAction1: [function(){
// return this.protocolAction.chainApply(this, function(){
// // ...
// }, ..)
// }]
//
// implementationAction2: [function(){
// return this.protocolAction.chainApply(this, function(){
// // ...
// }, ..)
// }]
//
// Now calling any of the 'implementation' actions will execute code
// in the following order:
// 1) pre phase of protocol action (outer)
// 2) implementation action (inner)
// 3) post phase of protocol action (outer)
//
// Differences form the base action protocol:
// - returning undefined from inner will keep the outer return value.
// ...this is different from the base action protocol where returning
// undefined will get replaces by the context (this), here to
// guarantee returning the context, it should be returned explicitly,
// otherwise the responsibility is shifted to the outer action.
// - returning anything else will override the outer return
//
// NOTE: this will not affect to protocol/signature of the outer action
// in any way other than the ability to override the return value.
// NOTE: both the inner and outer actions will get passed the same
// arguments.
// NOTE: another use-case is testing/debugging actions.
// NOTE: this is effectively the inside-out of normal action overloading.
// NOTE: there is intentionally no shorthand for this feature, to avoid
// confusion and to discourage the use of this feature unless
// really necessary.
//
//
// 3) .__actioncall__ action / handler
// This action if defined is called for every action called. It behaves
// 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 second arguments, and as normal a result on the post phase.
//
// NOTE: it is not necessary to define the actual action, binding to a
// handler will also work.
// NOTE: one should not call actions directly from within a __actioncall__
// handler as that will result in infinite recursion.
// XXX need a way to prevent this...
// NOTE: one should use this with extreme care as this will introduce
// an overhead on all the actions if not done carefully.
//
//
// 4) Action attributes
// XXX
//
//
// 5) After action calls...
// This enables the action code to schedule a call after the current
// action level or the root action is done.
//
// .afterAction(func)
// .afterAction('top', func)
// -> this
//
// .afterAction('local', func)
// -> this
//
// Example:
// someAction: [
// function(){
// ...
//
// // the function passed will get called after the root action
// // and all it's handlers are done.
// this.afterAction(function(){ ... })
//
// ...
// }],
//
// NOTE: the functions are executed in order of registration.
// XXX EXPERIMENTAL (handler cache)...
//
//
//
// Alias protocols:
//
// 1) Defining aliases in runtime (MetaActions)
//
// <action-set>.alias('alias', 'action: args')
// <action-set>.alias('alias', .., 'action: args')
// -> <action-set>
//
// To enable extending in runtime .alias(..) itself is implemented as
// an action, thus all action protocols also apply.
//
// NOTE: .alias(..) is signature compatible to Action(..) / Alias(..),
// supporting all the documentation and attribute definition.
//
//
// 2) Deleting aliases in runtime (MetaActions)
//
// <action-set>.alias('alias', null)
// <action-set>.alias('alias', false)
// -> <action-set>
//
// NOTE: only own aliases can be deleted via .alias(.., null|false)
//
//
// 3) Documentation generation and introspection (MetaActions, Alias)
//
// Alias code...
// <alias>.alias
// <alias>.toString()
// -> <code>
//
// List own aliases...
// <action-set>.aliases
// -> <action-set>
//
//
//
/*********************************************************************/ /*********************************************************************/
// helpers... // helpers...
var normalizeIndent = object.normalizeIndent // XXX doc...
var doWithRootAction = var doWithRootAction =
module.doWithRootAction = module.doWithRootAction =
function(func){ function(func){
@ -347,12 +23,19 @@ function(func){
var handlers = (this.getHandlerList var handlers = (this.getHandlerList
|| MetaActions.getHandlerList) || MetaActions.getHandlerList)
.apply(this, args) .apply(this, args)
return func.apply(this, [handlers.pop()].concat(args)) } } return func.apply(this, [handlers.pop()].concat(args)) } }
//---------------------------------------------------------------------
// String action parser/runner... // String action parser/runner...
//
// Examples:
// 'actionName'
// 'actionName: attr 123 "string" -- comment...'
// 'actionName: ...'
// //
//
// Syntax: // Syntax:
// ALIAS ::= // ALIAS ::=
// <action-name> // <action-name>
@ -387,6 +70,7 @@ function(func){
// code: txt, // code: txt,
// } // }
// //
//
// NOTE: identifiers are resolved as attributes of the context... // NOTE: identifiers are resolved as attributes of the context...
// NOTE: this is a stateless object... // 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
@ -533,6 +217,22 @@ module.isStringAction =
/*********************************************************************/ /*********************************************************************/
// Action...
// Return value wrapper...
//
// Wrapping a value in this and returning it from an action will force
// the action to return the value as-is...
// This is mainly usefull for specially handled values.
var ASIS =
module.ASIS =
object.Constructor('ASIS', {
__init__: function(obj){ this.value = obj } })
// undefined wrapper...
var UNDEFINED =
module.UNDEFINED = ASIS(undefined)
// Construct an action object... // Construct an action object...
// //
@ -997,7 +697,7 @@ object.Constructor('Action', {
// make introspection be a bit better... // make introspection be a bit better...
meth.toString = function(){ meth.toString = function(){
return normalizeIndent(func.toString()) } return object.normalizeIndent(func.toString()) }
// setup attrs... // setup attrs...
Object.assign(meth, attrs) Object.assign(meth, attrs)
@ -1561,6 +1261,7 @@ module.MetaActions = {
// NOTE: the object result must be compatible with Action.pre(..) // NOTE: the object result must be compatible with Action.pre(..)
// return value... // return value...
// NOTE: this is mostly a stub, here for documentation reasons... // NOTE: this is mostly a stub, here for documentation reasons...
// XXX doc / revise...
//preActionHandler: doWithRootAction( //preActionHandler: doWithRootAction(
// function(action, name, handlers, args){ return null }), // function(action, name, handlers, args){ return null }),
@ -2183,19 +1884,19 @@ module.MetaActions = {
var getTags = function(handler, p){ var getTags = function(handler, p){
return (handler.event_tag ? return (handler.event_tag ?
normalizeIndent('// Event tag: ' + handler.event_tag) + p object.normalizeIndent('// Event tag: ' + handler.event_tag) + p
: '') : '')
+ (handler.source_tag ? + (handler.source_tag ?
normalizeIndent('// Source tag: ' + handler.source_tag) + p object.normalizeIndent('// Source tag: ' + handler.source_tag) + p
: '') } : '') }
var getDoc = function(cur, p){ var getDoc = function(cur, p){
return (cur.doc ? return (cur.doc ?
'// --- .doc ---'+p '// --- .doc ---'+p
+'// '+ normalizeIndent(cur.doc).replace(/\n/g, p+'// ') +p +'// '+ object.normalizeIndent(cur.doc).replace(/\n/g, p+'// ') +p
: '') : '')
+ (cur.long_doc ? + (cur.long_doc ?
'// --- .long_doc ---'+p '// --- .long_doc ---'+p
+'// '+ normalizeIndent(cur.long_doc).replace(/\n/g, p+'// ') + p +'// '+ object.normalizeIndent(cur.long_doc).replace(/\n/g, p+'// ') + p
: '') } : '') }
var handler = function(p){ var handler = function(p){
@ -2214,7 +1915,7 @@ module.MetaActions = {
+ getTags(cur.pre, p) + getTags(cur.pre, p)
+ getDoc(cur, p) + getDoc(cur, p)
// code... // code...
+ normalizeIndent(cur.pre.toString()).replace(/\n/g, p) + object.normalizeIndent(cur.pre.toString()).replace(/\n/g, p)
+ p + p
} }
@ -2227,7 +1928,7 @@ module.MetaActions = {
+ getTags(cur.post, p) + getTags(cur.post, p)
+ getDoc(cur, p) + getDoc(cur, p)
// code... // code...
+ normalizeIndent(cur.post.toString()).replace(/\n/g, p) + object.normalizeIndent(cur.post.toString()).replace(/\n/g, p)
} }
} }
@ -2252,11 +1953,11 @@ module.MetaActions = {
p.append($('<pre>').html( p.append($('<pre>').html(
// meta... // meta...
(cur.pre.event_tag ? (cur.pre.event_tag ?
normalizeIndent('// Event tag: ' + cur.pre.event_tag) + p : '') object.normalizeIndent('// Event tag: ' + cur.pre.event_tag) + p : '')
+ (cur.pre.source_tag ? + (cur.pre.source_tag ?
normalizeIndent('// Source tag: ' + cur.pre.source_tag) + p : '') object.normalizeIndent('// Source tag: ' + cur.pre.source_tag) + p : '')
// code... // code...
+ normalizeIndent(cur.pre.toString()) + object.normalizeIndent(cur.pre.toString())
.replace(/return/g, '<b>return</b>'))) .replace(/return/g, '<b>return</b>')))
} }
@ -2266,11 +1967,11 @@ module.MetaActions = {
p.append($('<pre>').html( p.append($('<pre>').html(
// meta... // meta...
(cur.post.event_tag ? (cur.post.event_tag ?
normalizeIndent('// Event source tag: ' + cur.post.event_tag) + p : '') object.normalizeIndent('// Event source tag: ' + cur.post.event_tag) + p : '')
+ (cur.post.source_tag ? + (cur.post.source_tag ?
normalizeIndent('// Source tag: ' + cur.post.source_tag) + p : '') object.normalizeIndent('// Source tag: ' + cur.post.source_tag) + p : '')
// code... // code...
+ normalizeIndent(cur.post.toString()))) + object.normalizeIndent(cur.post.toString())))
} }
} }

View File

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