minor update and refactoring...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-12-18 21:33:37 +03:00
parent acade0db9b
commit 227f533be2
2 changed files with 83 additions and 143 deletions

View File

@ -14,18 +14,17 @@ var actions = module.actions = require('ig-actions')
/*********************************************************************/ /*********************************************************************/
var FeatureLinearizationError = // XXX use object.Error as base when ready...
var FeatureLinearizationError
module.FeatureLinearizationError = module.FeatureLinearizationError =
function(data){ object.Constructor('FeatureLinearizationError', Error, {
this.data = data get name(){
this.message = 'Failed to linearise.' return this.constructor.name },
this.toString = function(){ toString: function(){
return this.message return 'Failed to linearise' },
} __init__: function(data){
} this.data = data },
FeatureLinearizationError.prototype = Object.create(new Error) })
FeatureLinearizationError.prototype.constructor = FeatureLinearizationError
@ -156,15 +155,12 @@ object.Constructor('Feature', {
if(this.actions != null){ if(this.actions != null){
this.tag ? this.tag ?
actions.mixin(this.actions, {source_tag: this.tag}) actions.mixin(this.actions, {source_tag: this.tag})
: actions.mixin(this.actions) : actions.mixin(this.actions) }
}
// install handlers... // install handlers...
if(this.handlers != null){ if(this.handlers != null){
this.handlers.forEach(function(h){ this.handlers.forEach(function(h){
actions.on(h[0], that.tag, h[1]) actions.on(h[0], that.tag, h[1]) }) }
})
}
// merge config... // merge config...
// NOTE: this will merge the actual config in .config.__proto__ // NOTE: this will merge the actual config in .config.__proto__
@ -173,32 +169,29 @@ object.Constructor('Feature', {
|| (this.actions != null || (this.actions != null
&& this.actions.config != null)){ && this.actions.config != null)){
// sanity check -- warn of config shadowing... // sanity check -- warn of config shadowing...
// XXX make this optional...
if(this.config && this.actions && this.actions.config){ if(this.config && this.actions && this.actions.config){
console.warn('Feature config shadowed: ' console.warn('Feature config shadowed: '
+'both .config (used) and .actions.config (ignored) are defined for:', +'both .config (used) and .actions.config (ignored) are defined for:',
this.tag, this.tag,
this) this) }
}
var config = this.config = this.config || this.actions.config var config = this.config = this.config || this.actions.config
if(actions.config == null){ if(actions.config == null){
actions.config = Object.create({}) actions.config = Object.create({}) }
} Object.keys(config)
Object.keys(config).forEach(function(n){ .forEach(function(n){
// NOTE: this will overwrite existing values... // NOTE: this will overwrite existing values...
actions.config.__proto__[n] = config[n] actions.config.__proto__[n] = config[n] }) }
})
}
// custom setup... // custom setup...
// XXX is this the correct way??? // XXX is this the correct way???
if(this.hasOwnProperty('setup') && this.setup !== Feature.prototype.setup){ this.hasOwnProperty('setup')
this.setup(actions) && this.setup !== Feature.prototype.setup
} && this.setup(actions)
return this return this },
},
// XXX need to revise this... // XXX need to revise this...
// - .mixout(..) is available directly from the object while // - .mixout(..) is available directly from the object while
@ -206,25 +199,22 @@ object.Constructor('Feature', {
// - might be a good idea to add a specific lifecycle actions to // - might be a good idea to add a specific lifecycle actions to
// enable feautures to handle their removal correctly... // enable feautures to handle their removal correctly...
remove: function(actions){ remove: function(actions){
if(this.actions != null){ this.actions != null
actions.mixout(this.tag || this.actions) && actions.mixout(this.tag || this.actions)
}
if(this.handlers != null){ this.handlers != null
actions.off('*', this.tag) && actions.off('*', this.tag)
}
// XXX // XXX
if(this.hasOwnProperty('remove') && this.setup !== Feature.prototype.remove){ this.hasOwnProperty('remove')
this.remove(actions) && this.setup !== Feature.prototype.remove
} && this.remove(actions)
// remove feature DOM elements... // remove feature DOM elements...
// XXX // XXX
actions.ribbons.viewer.find('.' + this.tag).remove() actions.ribbons.viewer.find('.' + this.tag).remove()
return this return this },
},
// XXX EXPERIMENTAL: if called from a feature-set this will add self // XXX EXPERIMENTAL: if called from a feature-set this will add self
@ -246,8 +236,7 @@ object.Constructor('Feature', {
// Feature(<feature-set>, <obj>) // Feature(<feature-set>, <obj>)
} else { } else {
obj = tag obj = tag
tag = null tag = null }
}
// Feature(<obj>) // Feature(<obj>)
// NOTE: we need to account for context here -- inc length... // NOTE: we need to account for context here -- inc length...
@ -257,16 +246,15 @@ object.Constructor('Feature', {
// XXX EXPERIMENTAL... // XXX EXPERIMENTAL...
feature_set = context instanceof FeatureSet ? feature_set = context instanceof FeatureSet ?
context context
: (this.__featureset__ || Features) : (this.__featureset__ || Features) }
}
if(tag != null && obj.tag != null && obj.tag != tag){ 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... // action...
if(obj instanceof actions.Action){ if(obj instanceof actions.Action){
if(tag == null){ if(tag == null){
throw 'Error: need a tag to make a feature out of an action' } throw new Error('need a tag to make a feature out of an action') }
var f = { var f = {
tag: tag, tag: tag,
actions: obj, actions: obj,
@ -276,22 +264,18 @@ object.Constructor('Feature', {
// meta-feature... // meta-feature...
} else if(obj.constructor === Array){ } else if(obj.constructor === Array){
if(tag == null){ if(tag == null){
throw 'Error: need a tag to make a meta-feature' throw new Error('need a tag to make a meta-feature') }
}
var f = { var f = {
tag: tag, tag: tag,
suggested: obj, suggested: obj,
} }
obj = f obj = f }
}
// feature-set... // feature-set...
if(feature_set){ if(feature_set){
feature_set[obj.tag] = obj feature_set[obj.tag] = obj }
}
return obj return obj },
},
}) })
@ -369,8 +353,7 @@ object.Constructor('FeatureSet', {
} }
exclusive[e] = (exclusive[e] || []).concat([k]) exclusive[e] = (exclusive[e] || []).concat([k])
rev_exclusive[k] = (rev_exclusive[k] || []).concat([e]) }) }) rev_exclusive[k] = (rev_exclusive[k] || []).concat([e]) }) })
return exclusive return exclusive },
},
// Build list of features in load order... // Build list of features in load order...
// //
@ -491,8 +474,12 @@ object.Constructor('FeatureSet', {
// when needed... // when needed...
buildFeatureList: function(lst, isDisabled){ buildFeatureList: function(lst, isDisabled){
var all = this.features var all = this.features
lst = (lst == null || lst == '*') ? all : lst lst = (lst == null || lst == '*') ?
lst = lst.constructor !== Array ? [lst] : lst all
: lst
lst = lst instanceof Array ?
[lst]
: lst
//isDisabled = isDisabled || function(){ return false } //isDisabled = isDisabled || function(){ return false }
@ -519,8 +506,7 @@ object.Constructor('FeatureSet', {
// NOTE: Infinity - Infinity is NaN, so we need // NOTE: Infinity - Infinity is NaN, so we need
// to guard against it... // to guard against it...
return i - j || 0 }) return i - j || 0 })
.map(function(e){ return e[0] }) }) .map(function(e){ return e[0] }) }) }
}
// Expand feature references (recursive)... // Expand feature references (recursive)...
// //
@ -546,22 +532,17 @@ object.Constructor('FeatureSet', {
console.warn(`Disable loop detected at "${n}" in chain: ${_seen}`) console.warn(`Disable loop detected at "${n}" in chain: ${_seen}`)
var loop = _seen.slice(_seen.indexOf(n)).concat([n]) var loop = _seen.slice(_seen.indexOf(n)).concat([n])
data.disable_loops = (data.disable_loops || []).push(loop) data.disable_loops = (data.disable_loops || []).push(loop)
return false return false }
}
// XXX STUB -- need to resolve actual loops and // XXX STUB -- need to resolve actual loops and
// make the disable global... // make the disable global...
if(n in store){ 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) data.disabled.push(n)
return false return false }
}
// skip already disabled features... // skip already disabled features...
if(data.disabled.indexOf(n) >= 0){ if(data.disabled.indexOf(n) >= 0){
return false return false }
} return true })
return true
})
: lst : lst
// traverse the tree... // traverse the tree...
@ -572,19 +553,15 @@ object.Constructor('FeatureSet', {
// exclusive tags... // exclusive tags...
if(f == null && data.exclusive && n in data.exclusive){ if(f == null && data.exclusive && n in data.exclusive){
store[n] = null store[n] = null
return false return false }
}
// feature not defined or is not a feature... // feature not defined or is not a feature...
if(f == null){ if(f == null){
data.missing data.missing
&& data.missing.indexOf(n) < 0 && data.missing.indexOf(n) < 0
&& data.missing.push(n) && data.missing.push(n)
return false return false }
} return n })
return n
})
.filter(function(e){ return e }) .filter(function(e){ return e })
// traverse down... // traverse down...
.forEach(function(f){ .forEach(function(f){
// dependency loop detection... // dependency loop detection...
@ -592,13 +569,11 @@ object.Constructor('FeatureSet', {
var loop = _seen.slice(_seen.indexOf(f)).concat([f]) var loop = _seen.slice(_seen.indexOf(f)).concat([f])
data.loops data.loops
&& data.loops.push(loop) && data.loops.push(loop)
return return }
}
// skip already done features... // skip already done features...
if(f in store){ if(f in store){
return return }
}
//var feature = store[f] = that[f] //var feature = store[f] = that[f]
var feature = that[f] var feature = that[f]
@ -613,12 +588,9 @@ object.Constructor('FeatureSet', {
store[f] = _lst store[f] = _lst
// traverse down... // 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... // Expand feature dependencies and suggestions recursively...
// //
@ -692,14 +664,11 @@ object.Constructor('FeatureSet', {
// mix suggested into features... // mix suggested into features...
} else { } else {
features[f] = s[f] features[f] = s[f]
suggested[f] = (s[f] || []).slice() suggested[f] = (s[f] || []).slice() } })
}
})
sortExclusive(features) sortExclusive(features)
return features return features }
}
//--------------------- Globals: filtering / exclusive tags --- //--------------------- Globals: filtering / exclusive tags ---
@ -748,8 +717,7 @@ object.Constructor('FeatureSet', {
// link alias to existing feature... // link alias to existing feature...
} else { } else {
var target = candidates[0] var target = candidates[0] }
}
// remove the alias... // remove the alias...
// NOTE: exclusive tag can match a feature tag, thus // NOTE: exclusive tag can match a feature tag, thus
@ -766,8 +734,7 @@ object.Constructor('FeatureSet', {
&& features[e].splice(i, 1, target) && features[e].splice(i, 1, target)
}) })
f = target f = target
done.push(f) done.push(f) }
}
// exclusive feature... // exclusive feature...
if(f in rev_exclusive){ if(f in rev_exclusive){
@ -777,10 +744,7 @@ object.Constructor('FeatureSet', {
.filter(function(c){ return c in features }) .filter(function(c){ return c in features })
if(!(group in conflicts) && candidates.length > 1){ if(!(group in conflicts) && candidates.length > 1){
conflicts[group] = candidates conflicts[group] = candidates } } })
}
}
})
// cleanup... // cleanup...
to_remove.forEach(function(f){ delete features[f] }) to_remove.forEach(function(f){ delete features[f] })
// resolve any exclusivity conflicts found... // resolve any exclusivity conflicts found...
@ -818,9 +782,7 @@ object.Constructor('FeatureSet', {
&& features[f].indexOf(d) >= 0 && features[f].indexOf(d) >= 0
&& disabled.indexOf(f) < 0){ && disabled.indexOf(f) < 0){
expanded_disabled = true expanded_disabled = true
disabled.push(f) disabled.push(f) } })
}
})
// delete the feature itself... // delete the feature itself...
var s = suggests[d] || [] var s = suggests[d] || []
@ -838,10 +800,7 @@ object.Constructor('FeatureSet', {
.filter(n => n.indexOf(f) >= 0) .filter(n => n.indexOf(f) >= 0)
.length == 0){ .length == 0){
expanded_disabled = true expanded_disabled = true
disabled.push(f) disabled.push(f) } }) })
}
})
})
} while(expanded_disabled) } while(expanded_disabled)
// remove orphaned features... // remove orphaned features...
@ -858,8 +817,7 @@ object.Constructor('FeatureSet', {
.forEach(function(f){ .forEach(function(f){
console.log('ORPHANED:', f) console.log('ORPHANED:', f)
disabled.push(f) disabled.push(f)
delete features[f] delete features[f] })
})
//---------------------------------- Stage 2: sort features --- //---------------------------------- Stage 2: sort features ---
@ -872,8 +830,7 @@ object.Constructor('FeatureSet', {
var expanddeps = function(lst, cur, seen){ var expanddeps = function(lst, cur, seen){
seen = seen || [] seen = seen || []
if(features[cur] == null){ if(features[cur] == null){
return return }
}
// expand the dep list recursively... // expand the dep list recursively...
// NOTE: this will expand features[cur] in-place while // NOTE: this will expand features[cur] in-place while
// iterating over it... // iterating over it...
@ -886,11 +843,7 @@ object.Constructor('FeatureSet', {
features[cur].forEach(function(e){ features[cur].forEach(function(e){
lst.indexOf(e) < 0 lst.indexOf(e) < 0
&& lst.push(e) && lst.push(e) }) } } }
})
}
}
}
// do the actual expansion... // do the actual expansion...
var list = Object.keys(features) var list = Object.keys(features)
list.forEach(function(f){ expanddeps(list, f) }) list.forEach(function(f){ expanddeps(list, f) })
@ -921,8 +874,7 @@ object.Constructor('FeatureSet', {
do { do {
var moves = 0 var moves = 0
if(list.length == 0){ if(list.length == 0){
break break }
}
list list
.slice() .slice()
.forEach(function(e){ .forEach(function(e){
@ -941,9 +893,7 @@ object.Constructor('FeatureSet', {
// place after last dependency... // place after last dependency...
list.splice(to[1]+1, 0, e) list.splice(to[1]+1, 0, e)
list.splice(from, 1) list.splice(from, 1)
moves++ moves++ } })
}
})
loop_limit-- loop_limit--
} while(moves > 0 && loop_limit > 0) } while(moves > 0 && loop_limit > 0)
@ -1033,8 +983,7 @@ object.Constructor('FeatureSet', {
// no explicit object is given... // no explicit object is given...
if(lst == null){ if(lst == null){
lst = obj lst = obj
obj = null obj = null }
}
obj = obj || (this.__actions__ || actions.Actions)() obj = obj || (this.__actions__ || actions.Actions)()
lst = lst instanceof Array ? lst : [lst] lst = lst instanceof Array ? lst : [lst]
@ -1043,16 +992,13 @@ object.Constructor('FeatureSet', {
(function(n){ (function(n){
// if we already tested unapplicable, no need to test again... // if we already tested unapplicable, no need to test again...
if(unapplicable.indexOf(n) >= 0){ if(unapplicable.indexOf(n) >= 0){
return true return true }
}
var f = this[n] var f = this[n]
// check applicability if possible... // check applicability if possible...
if(f && f.isApplicable && !f.isApplicable.call(this, obj)){ if(f && f.isApplicable && !f.isApplicable.call(this, obj)){
unapplicable.push(n) unapplicable.push(n)
return true return true }
} return false }).bind(this))
return false
}).bind(this))
features.unapplicable = unapplicable features.unapplicable = unapplicable
// cleanup disabled -- filter out unapplicable and excluded features... // cleanup disabled -- filter out unapplicable and excluded features...
// NOTE: this is done mainly for cleaner and simpler reporting // NOTE: this is done mainly for cleaner and simpler reporting
@ -1081,8 +1027,7 @@ object.Constructor('FeatureSet', {
console.error('Exclusive "'+ group +'" conflict at:', error.conflicts[group]) }) console.error('Exclusive "'+ group +'" conflict at:', error.conflicts[group]) })
// report loop limit... // report loop limit...
error.sort_loop_overflow error.sort_loop_overflow
&& console.error('Hit loop limit while sorting dependencies!') && console.error('Hit loop limit while sorting dependencies!') }
}
features.FeatureSet = this features.FeatureSet = this
@ -1090,8 +1035,7 @@ object.Constructor('FeatureSet', {
// fatal error -- can't load... // fatal error -- can't load...
if(fatal){ if(fatal){
throw new FeatureLinearizationError(features) throw new FeatureLinearizationError(features) }
}
// do the setup... // do the setup...
var that = this var that = this
@ -1100,12 +1044,9 @@ object.Constructor('FeatureSet', {
// setup... // setup...
if(that[n] != null){ if(that[n] != null){
this.__verbose__ && console.log('Setting up feature:', n) this.__verbose__ && console.log('Setting up feature:', n)
setup.call(that[n], obj) setup.call(that[n], obj) } })
}
})
return obj return obj },
},
// XXX revise... // XXX revise...
// ...the main problem here is that .mixout(..) is accesible // ...the main problem here is that .mixout(..) is accesible
@ -1143,8 +1084,7 @@ object.Constructor('FeatureSet', {
}) })
graph += '}' graph += '}'
return graph return graph },
},
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "ig-features", "name": "ig-features",
"version": "3.4.2", "version": "3.4.3",
"description": "", "description": "",
"main": "features.js", "main": "features.js",
"scripts": { "scripts": {
@ -23,7 +23,7 @@
}, },
"homepage": "https://github.com/flynx/features.js#readme", "homepage": "https://github.com/flynx/features.js#readme",
"dependencies": { "dependencies": {
"ig-actions": "^3.24.11", "ig-actions": "^3.24.13",
"ig-object": "^5.0.2" "ig-object": "^5.4.11"
} }
} }