cleanup + tweaking docs...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2021-06-02 11:52:42 +03:00
parent a37e9f8cb0
commit 8a44d91c83
2 changed files with 1192 additions and 1183 deletions

View File

@ -1,8 +1,8 @@
# Features # Features
Features is a module that helps build _features_ out of sets of actions `features.js` organizes sets of [actions](https://github.com/flynx/actions.js)
apply them to objects and manage sets of features via external criteria or _objects_ into features, apply them to objects, manage sets of features via
and feature-to-feature dependencies. inter-feature dependencies and external criteria.
### The main entities: ### The main entities:
@ -31,57 +31,57 @@ XXX
**Feature** **Feature**
```javascript ```javascript
feature_set.Feature({ feature_set.Feature({
tag: 'minimal_feature_example', tag: 'minimal_feature_example',
}) })
feature_set.Feature({ feature_set.Feature({
// documentation (optional)... // documentation (optional)...
title: 'Example Feature', title: 'Example Feature',
doc: 'A feature to demo the base API...', doc: 'A feature to demo the base API...',
// feature unique identifier (required)... // feature unique identifier (required)...
tag: 'feature_example', tag: 'feature_example',
// applicability test (optional) // applicability test (optional)
isApplicable: function(){ /* ... */ }, isApplicable: function(){ /* ... */ },
// feature load priority (optional) // feature load priority (optional)
priority: 'medium', priority: 'medium',
// list of feature tags to load if available (optional) // list of feature tags to load if available (optional)
suggested: [], suggested: [],
// list of feature tags required to load before this feature (optional) // list of feature tags required to load before this feature (optional)
depends: [], depends: [],
// Exclusive tag (optional) // Exclusive tag (optional)
// NOTE: a feature can be a member of more than one exclusive group, // NOTE: a feature can be a member of more than one exclusive group,
// to list more than one use an Array... // to list more than one use an Array...
exclusive: 'Example', exclusive: 'Example',
// feature configuration (optional) // feature configuration (optional)
// NOTE: if not present here this will be taken from .actions.config // NOTE: if not present here this will be taken from .actions.config
// NOTE: this takes priority over .actions.config, it is not recommended // NOTE: this takes priority over .actions.config, it is not recommended
// to define both. // to define both.
config: {
option: 'value',
// ...
},
// actions (optional)
actions: Actions({
// alternative configuration location...
config: { config: {
// ... option: 'value',
} // ...
// ... },
})
// action handlers (optional) // actions (optional)
handlers: [ actions: Actions({
['action.pre', function(){ /* ... */ }], // alternative configuration location...
// ... config: {
] // ...
},
// ...
}),
// action handlers (optional)
handlers: [
['action.pre', function(){ /* ... */ }],
// ...
],
}) })
``` ```
@ -93,9 +93,9 @@ XXX
```javascript ```javascript
// meta-feature... // meta-feature...
feature_set.Feature('meta-feature-tag', [ feature_set.Feature('meta-feature-tag', [
'suggested-feature-tag', 'suggested-feature-tag',
'other-suggested-feature-tag', 'other-suggested-feature-tag',
// ... // ...
]) ])
``` ```

View File

@ -30,79 +30,79 @@ object.Constructor('FeatureLinearizationError', Error, {
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// Base feature... // Base feature...
// //
// Feature(obj) // Feature(obj)
// -> feature // -> feature
// //
// Feature(feature-set, obj) // Feature(feature-set, obj)
// -> feature // -> feature
// //
// Feature(tag, obj) // Feature(tag, obj)
// -> feature // -> feature
// //
// Feature(tag, suggested) // Feature(tag, suggested)
// -> feature // -> feature
// //
// Feature(tag, actions) // Feature(tag, actions)
// -> feature // -> feature
// //
// Feature(feature-set, tag, actions) // Feature(feature-set, tag, actions)
// -> feature // -> feature
// //
// //
// Feature attributes: // Feature attributes:
// .tag - feature tag (string) // .tag - feature tag (string)
// this is used to identify the feature, its event // this is used to identify the feature, its event
// handlers and DOM elements. // handlers and DOM elements.
// //
// .title - feature name (string | null) // .title - feature name (string | null)
// .doc - feature description (string | null) // .doc - feature description (string | null)
// //
// .priority - feature priority // .priority - feature priority
// can be: // can be:
// - 'high' (99) | 'medium' (0) | 'low' (-99) // - 'high' (99) | 'medium' (0) | 'low' (-99)
// - number // - number
// - null (0, default) // - null (0, default)
// features with higher priority will be setup first, // features with higher priority will be setup first,
// features with the same priority will be run in // features with the same priority will be run in
// order of occurrence. // order of occurrence.
// .suggested - list of optional suggested features, these are not // .suggested - list of optional suggested features, these are not
// required but setup if available. // required but setup if available.
// This is useful for defining meta features but // This is useful for defining meta features but
// without making each sub-feature a strict dependency. // without making each sub-feature a strict dependency.
// .depends - feature dependencies -- tags of features that must // .depends - feature dependencies -- tags of features that must
// setup before the feature (list | null) // setup before the feature (list | null)
// NOTE: a feature can depend on an exclusive tag, // NOTE: a feature can depend on an exclusive tag,
// this will remove the need to track which // this will remove the need to track which
// specific exclusive tagged feature is loaded... // specific exclusive tagged feature is loaded...
// .exclusive - feature exclusivity tags (list | null) // .exclusive - feature exclusivity tags (list | null)
// an exclusivity group enforces that only one feature // an exclusivity group enforces that only one feature
// in it will be run, i.e. the first / highest priority. // in it will be run, i.e. the first / highest priority.
// //
// .actions - action object containing feature actions (ActionSet | null) // .actions - action object containing feature actions (ActionSet | null)
// this will be mixed into the base object on .setup() // this will be mixed into the base object on .setup()
// and mixed out on .remove() // and mixed out on .remove()
// .config - feature configuration, will be merged with base // .config - feature configuration, will be merged with base
// object's .config // object's .config
// NOTE: the final .config is an empty object with // NOTE: the final .config is an empty object with
// .__proto__ set to the merged configuration // .__proto__ set to the merged configuration
// data... // data...
// .handlers - feature event handlers (list | null) // .handlers - feature event handlers (list | null)
// //
// //
// //
// .handlers format: // .handlers format:
// [ // [
// [ <event-spec>, <handler-function> ], // [ <event-spec>, <handler-function> ],
// ... // ...
// ] // ]
// //
// NOTE: both <event-spec> and <handler-function> must be compatible with // NOTE: both <event-spec> and <handler-function> must be compatible with
// Action.on(..) // Action.on(..)
// //
// //
// Feature applicability: // Feature applicability:
// If feature.isApplicable(..) returns false then the feature will not be // If feature.isApplicable(..) returns false then the feature will not be
// considered on setup... // considered on setup...
// //
// //
var Feature = var Feature =
@ -151,15 +151,15 @@ object.Constructor('Feature', {
: res }, : res },
// XXX HANDLERS this could install the handlers in two locations: // XXX HANDLERS this could install the handlers in two locations:
// - the actions object... // - the actions object...
// - mixin if available... // - mixin if available...
// - base object (currently implemented) // - base object (currently implemented)
// ...the handlers should theoreticly be stored neither in the // ...the handlers should theoreticly be stored neither in the
// instance nor in the mixin but rather in the action-set itself // instance nor in the mixin but rather in the action-set itself
// on feature creation... (???) // on feature creation... (???)
// ...feels like user handlers and feature handlers should be // ...feels like user handlers and feature handlers should be
// isolated... // isolated...
// XXX setting handlers on the .__proto__ breaks... // XXX setting handlers on the .__proto__ breaks...
setup: function(actions){ setup: function(actions){
var that = this var that = this
@ -183,7 +183,7 @@ object.Constructor('Feature', {
// merge config... // merge config...
// NOTE: this will merge the actual config in .config.__proto__ // NOTE: this will merge the actual config in .config.__proto__
// keeping the .config clean for the user to lay with... // keeping the .config clean for the user to lay with...
if(this.config != null if(this.config != null
|| (this.actions != null || (this.actions != null
&& this.actions.config != null)){ && this.actions.config != null)){
@ -214,10 +214,10 @@ object.Constructor('Feature', {
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
// .remove(..) is not... // .remove(..) is not...
// - 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){
this.actions != null this.actions != null
&& actions.mixout(this.tag || this.actions) && actions.mixout(this.tag || this.actions)
@ -236,7 +236,7 @@ object.Constructor('Feature', {
// XXX EXPERIMENTAL: if called from a feature-set this will add self // XXX EXPERIMENTAL: if called from a feature-set this will add self
// to that feature-set... // to that feature-set...
// XXX do we need this to be .__new__(..) and not .__init__(..) // XXX do we need this to be .__new__(..) and not .__init__(..)
__new__: function(context, feature_set, tag, obj){ __new__: function(context, feature_set, tag, obj){
// NOTE: we need to account for context here -- inc length... // NOTE: we need to account for context here -- inc length...
@ -291,7 +291,7 @@ object.Constructor('Feature', {
if(obj.handlers){ if(obj.handlers){
obj.actions = obj.actions || {} obj.actions = obj.actions || {}
// NOTE: obj.actions does not have to be an action so w cheat \ // NOTE: obj.actions does not have to be an action so w cheat \
// a bit here, then copy the mindings... // a bit here, then copy the mindings...
var tmp = Object.create(actions.MetaActions) var tmp = Object.create(actions.MetaActions)
obj.handlers obj.handlers
.forEach(function([a, h]){ .forEach(function([a, h]){
@ -319,7 +319,7 @@ object.Constructor('FeatureSet', {
__actions__: actions.Actions, __actions__: actions.Actions,
// NOTE: a feature is expected to write a reference to itself to the // NOTE: a feature is expected to write a reference to itself to the
// feature-set (context)... // feature-set (context)...
Feature: Feature, Feature: Feature,
@ -333,28 +333,28 @@ object.Constructor('FeatureSet', {
// build exclusive groups... // build exclusive groups...
// //
// Get all exclusive tags... // Get all exclusive tags...
// .getExclusive() // .getExclusive()
// .getExclusive('*') // .getExclusive('*')
// -> exclusive // -> exclusive
// //
// Get specific exclusive tags... // Get specific exclusive tags...
// .getExclusive(tag) // .getExclusive(tag)
// .getExclusive([tag, ..]) // .getExclusive([tag, ..])
// -> exclusive // -> exclusive
// //
// If features is given, only consider the features in list. // If features is given, only consider the features in list.
// If rev_exclusive is given, also build a reverse exclusive feature // If rev_exclusive is given, also build a reverse exclusive feature
// list. // list.
// //
// output format: // output format:
// { // {
// exclusive-tag: [ // exclusive-tag: [
// feature-tag, // feature-tag,
// ... // ...
// ], // ],
// ... // ...
// } // }
// //
getExclusive: function(tag, features, rev_exclusive, isDisabled){ getExclusive: function(tag, features, rev_exclusive, isDisabled){
tag = tag == null || tag == '*' ? '*' tag = tag == null || tag == '*' ? '*'
@ -376,26 +376,27 @@ object.Constructor('FeatureSet', {
.forEach(function(e){ .forEach(function(e){
// skip tags not explicitly requested... // skip tags not explicitly requested...
if(tag != '*' && tag.indexOf(e) < 0){ if(tag != '*' && tag.indexOf(e) < 0){
return return }
} exclusive[e] =
exclusive[e] = (exclusive[e] || []).concat([k]) (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...
// //
// .buildFeatureList() // .buildFeatureList()
// .buildFeatureList('*') // .buildFeatureList('*')
// -> data // -> data
// //
// .buildFeatureList(feature-tag) // .buildFeatureList(feature-tag)
// -> data // -> data
// //
// .buildFeatureList([feature-tag, .. ]) // .buildFeatureList([feature-tag, .. ])
// -> data // -> data
// //
// .buildFeatureList(.., filter) // .buildFeatureList(.., filter)
// -> data // -> data
// //
// //
// Requirements: // Requirements:
@ -419,24 +420,24 @@ object.Constructor('FeatureSet', {
// //
// NOTE: an exclusive group name can be used as an alias. // NOTE: an exclusive group name can be used as an alias.
// NOTE: if an alias is used and no feature from that exclusive group // NOTE: if an alias is used and no feature from that exclusive group
// is explicitly included then the actual loaded feature will // is explicitly included then the actual loaded feature will
// depend on the load order, which in an async world is not // depend on the load order, which in an async world is not
// deterministic... // deterministic...
// //
// //
// Algorithm: // Algorithm:
// - expand features: // - expand features:
// - handle dependencies (detect loops) // - handle dependencies (detect loops)
// - handle suggestions // - handle suggestions
// - handle explicitly disabled features (detect loops) // - handle explicitly disabled features (detect loops)
// - handle exclusive feature groups/aliases (handle conflicts) // - handle exclusive feature groups/aliases (handle conflicts)
// - sort list of features: // - sort list of features:
// - by priority // - by priority
// - by dependency (detect loops/errors) // - by dependency (detect loops/errors)
// //
// //
// Return format: // Return format:
// { // {
// // input feature feature tags... // // input feature feature tags...
// input: [ .. ], // input: [ .. ],
// //
@ -489,16 +490,16 @@ object.Constructor('FeatureSet', {
// feature-tag: [ feature-tag, .. ], // feature-tag: [ feature-tag, .. ],
// .. // ..
// }, // },
// } // }
// //
// XXX should exclusive conflicts resolve to first (current state) // XXX should exclusive conflicts resolve to first (current state)
// feature or last in an exclusive group??? // feature or last in an exclusive group???
// XXX PROBLEM: exclusive feature trees should be resolved accounting // XXX PROBLEM: exclusive feature trees should be resolved accounting
// feature applicablility... // feature applicablility...
// ...in this approach it is impossible... // ...in this approach it is impossible...
// ...one way to fix this is to make this interactively check // ...one way to fix this is to make this interactively check
// applicability, i.e. pass a context and check applicablility // applicability, i.e. pass a context and check applicablility
// when needed... // when needed...
buildFeatureList: function(lst, isDisabled){ buildFeatureList: function(lst, isDisabled){
var all = this.features var all = this.features
lst = (lst == null || lst == '*') ? lst = (lst == null || lst == '*') ?
@ -531,21 +532,21 @@ object.Constructor('FeatureSet', {
i = i < 0 ? Infinity : i i = i < 0 ? Infinity : i
j = j < 0 ? Infinity : j j = j < 0 ? Infinity : j
// 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)...
// //
// NOTE: closures are not used here as we need to pass different // NOTE: closures are not used here as we need to pass different
// stuff into data in different situations... // stuff into data in different situations...
var expand = function(target, lst, store, data, _seen){ var expand = function(target, lst, store, data, _seen){
data = data || {} data = data || {}
_seen = _seen || [] _seen = _seen || []
// clear disabled... // clear disabled...
// NOTE: we do as a separate stage to avoid loading a // NOTE: we do as a separate stage to avoid loading a
// feature before it is disabled in the same list... // feature before it is disabled in the same list...
lst = data.disabled ? lst = data.disabled ?
lst lst
.filter(function(n){ .filter(function(n){
@ -554,14 +555,16 @@ object.Constructor('FeatureSet', {
n = n.slice(1) n = n.slice(1)
if(_seen.indexOf(n) >= 0){ if(_seen.indexOf(n) >= 0){
// NOTE: a disable loop is when a feature tries to disable // NOTE: a disable loop is when a feature tries to disable
// a feature up in the same chain... // a feature up in the same chain...
// XXX should this break or accumulate??? // XXX should this break or accumulate???
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 =
data.disable_loops = (data.disable_loops || []).push(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 // 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)
@ -610,8 +613,7 @@ object.Constructor('FeatureSet', {
// merge lists... // merge lists...
;(target instanceof Array ? target : [target]) ;(target instanceof Array ? target : [target])
.forEach(function(t){ .forEach(function(t){
_lst = _lst.concat(feature[t] || []) _lst = _lst.concat(feature[t] || []) })
})
store[f] = _lst store[f] = _lst
// traverse down... // traverse down...
@ -622,24 +624,24 @@ object.Constructor('FeatureSet', {
// Expand feature dependencies and suggestions recursively... // Expand feature dependencies and suggestions recursively...
// //
// NOTE: this relies on the following values being in the closure: // NOTE: this relies on the following values being in the closure:
// loops - list of loop chains found // loops - list of loop chains found
// disable_loops - disable loops // disable_loops - disable loops
// when a feature containing a disable // when a feature containing a disable
// directive gets disabled as a result // directive gets disabled as a result
// disabled - list of disabled features // disabled - list of disabled features
// missing - list of missing features // missing - list of missing features
// missing_suggested // missing_suggested
// - list of missing suggested features and // - list of missing suggested features and
// suggested feature dependencies // suggested feature dependencies
// exclusive - exclusive feature index // exclusive - exclusive feature index
// suggests - index of feature suggestions (full) // suggests - index of feature suggestions (full)
// suggested - suggested feature dependency index // suggested - suggested feature dependency index
// NOTE: the above containers will get updated as a side-effect. // NOTE: the above containers will get updated as a side-effect.
// NOTE: all of the above values are defined near the location // NOTE: all of the above values are defined near the location
// they are first used/initiated... // they are first used/initiated...
// NOTE: closures are used here purely for simplicity and conciseness // NOTE: closures are used here purely for simplicity and conciseness
// as threading data would not add any flexibility but make // as threading data would not add any flexibility but make
// the code more complex... // the code more complex...
var expandFeatures = function(lst, features){ var expandFeatures = function(lst, features){
features = features || {} features = features || {}
@ -711,7 +713,9 @@ object.Constructor('FeatureSet', {
// user filter... // user filter...
// NOTE: we build this out of the full feature list... // NOTE: we build this out of the full feature list...
disabled = disabled disabled = disabled
.concat(isDisabled ? all.filter(isDisabled) : []) .concat(isDisabled ?
all.filter(isDisabled)
: [])
// build exclusive groups... // build exclusive groups...
// XXX need to sort the values to the same order as given features... // XXX need to sort the values to the same order as given features...
@ -733,7 +737,8 @@ object.Constructor('FeatureSet', {
// alias... // alias...
while(f in exclusive && done.indexOf(f) < 0){ while(f in exclusive && done.indexOf(f) < 0){
var candidates = (exclusive[f] || []) var candidates = (exclusive[f] || [])
.filter(function(c){ return c in features }) .filter(function(c){
return c in features })
// resolve alias to non-included feature... // resolve alias to non-included feature...
if(candidates.length == 0){ if(candidates.length == 0){
@ -748,18 +753,19 @@ object.Constructor('FeatureSet', {
// remove the alias... // remove the alias...
// NOTE: exclusive tag can match a feature tag, thus // NOTE: exclusive tag can match a feature tag, thus
// we do not want to delete such tags... // we do not want to delete such tags...
// NOTE: we are not removing to_remove here as they may // NOTE: we are not removing to_remove here as they may
// get added/expanded back in by other features... // get added/expanded back in by other features...
!(f in that) !(f in that)
&& to_remove.push(f) && to_remove.push(f)
// replace dependencies... // replace dependencies...
Object.keys(features) Object.keys(features)
.forEach(function(e){ .forEach(function(e){
var i = features[e] ? features[e].indexOf(f) : -1 var i = features[e] ?
features[e].indexOf(f)
: -1
i >= 0 i >= 0
&& features[e].splice(i, 1, target) && features[e].splice(i, 1, target) })
})
f = target f = target
done.push(f) } done.push(f) }
@ -787,8 +793,8 @@ object.Constructor('FeatureSet', {
// Handle disabled features and cleanup... // Handle disabled features and cleanup...
// reverse dependency index... // reverse dependency index...
// ...this is used to clear out orphaned features later and for // ...this is used to clear out orphaned features later and for
// introspection... // introspection...
var rev_features = {} var rev_features = {}
Object.keys(features) Object.keys(features)
.forEach(function(f){ .forEach(function(f){
@ -853,14 +859,14 @@ object.Constructor('FeatureSet', {
// //
// NOTE: this will expand lst in-place... // NOTE: this will expand lst in-place...
// NOTE: we are not checking for loops here -- mainly because // NOTE: we are not checking for loops here -- mainly because
// the input is expected to be loop-free... // the input is expected to be loop-free...
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...
for(var i=0; i < features[cur].length; i++){ for(var i=0; i < features[cur].length; i++){
var f = features[cur][i] var f = features[cur][i]
if(seen.indexOf(f) < 0){ if(seen.indexOf(f) < 0){
@ -878,15 +884,20 @@ object.Constructor('FeatureSet', {
// sort by priority... // sort by priority...
// //
// NOTE: this will attempt to only move features with explicitly // NOTE: this will attempt to only move features with explicitly
// defined priorities and keep the rest in the same order // defined priorities and keep the rest in the same order
// when possible... // when possible...
list = list list = list
// format: // format:
// [ <feature>, <index>, <priority> ] // [ <feature>, <index>, <priority> ]
.map(function(e, i){ .map(function(e, i){
return [e, i, (that[e] && that[e].getPriority) ? that[e].getPriority() : 0 ] }) return [e, i,
(that[e]
&& that[e].getPriority) ?
that[e].getPriority()
: 0 ] })
.sort(function(a, b){ .sort(function(a, b){
return a[2] - b[2] || a[1] - b[1] }) return a[2] - b[2]
|| a[1] - b[1] })
// cleanup... // cleanup...
.map(function(e){ return e[0] }) .map(function(e){ return e[0] })
// sort by the order features should be loaded... // sort by the order features should be loaded...
@ -895,7 +906,7 @@ object.Constructor('FeatureSet', {
// sort by dependency... // sort by dependency...
// //
// NOTE: this requires the list to be ordered from high to low // NOTE: this requires the list to be ordered from high to low
// priority, i.e. the same order they should be loaded in... // priority, i.e. the same order they should be loaded in...
// NOTE: dependency loops will throw this into and "infinite" loop... // NOTE: dependency loops will throw this into and "infinite" loop...
var loop_limit = list.length + 1 var loop_limit = list.length + 1
do { do {
@ -907,8 +918,7 @@ object.Constructor('FeatureSet', {
.forEach(function(e){ .forEach(function(e){
var deps = features[e] var deps = features[e]
if(!deps){ if(!deps){
return return }
}
var from = list.indexOf(e) var from = list.indexOf(e)
var to = list var to = list
.map(function(f, i){ return [f, i] }) .map(function(f, i){ return [f, i] })
@ -987,25 +997,25 @@ object.Constructor('FeatureSet', {
// This will set .features on the object. // This will set .features on the object.
// //
// .features format: // .features format:
// { // {
// // the current feature set object... // // the current feature set object...
// // XXX not sure about this -- revise... // // XXX not sure about this -- revise...
// FeatureSet: feature-set, // FeatureSet: feature-set,
// //
// // list of features not applicable in current context... // // list of features not applicable in current context...
// // // //
// // i.e. the features that defined .isApplicable(..) and it // // i.e. the features that defined .isApplicable(..) and it
// // returned false when called. // // returned false when called.
// unapplicable: [ feature-tag, .. ], // unapplicable: [ feature-tag, .. ],
// //
// // output of .buildFeatureList(..)... // // output of .buildFeatureList(..)...
// ... // ...
// } // }
// //
// NOTE: this will store the build result in .features of the output // NOTE: this will store the build result in .features of the output
// actions object. // actions object.
// NOTE: .features is reset even if a FeatureLinearizationError error // NOTE: .features is reset even if a FeatureLinearizationError error
// is thrown. // is thrown.
setup: function(obj, lst){ setup: function(obj, lst){
// no explicit object is given... // no explicit object is given...
if(lst == null){ if(lst == null){
@ -1029,7 +1039,7 @@ object.Constructor('FeatureSet', {
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
// later on... // later on...
features.disabled = features.disabled features.disabled = features.disabled
.filter(function(n){ .filter(function(n){
return unapplicable.indexOf(n) < 0 return unapplicable.indexOf(n) < 0
@ -1083,11 +1093,11 @@ object.Constructor('FeatureSet', {
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
// directly from actions while the feature .remove(..) method // directly from actions while the feature .remove(..) method
// is not... // is not...
// ...would be nice to expose the API to actions directly or // ...would be nice to expose the API to actions directly or
// keep it only in features... // keep it only in features...
remove: function(obj, lst){ remove: function(obj, lst){
lst = lst.constructor !== Array ? [lst] : lst lst = lst.constructor !== Array ? [lst] : lst
var that = this var that = this
@ -1115,8 +1125,7 @@ object.Constructor('FeatureSet', {
deps.length > 0 ? deps.length > 0 ?
deps.forEach(function(d){ deps.forEach(function(d){
graph += `\t"${f}" -> "${d}";\n` }) graph += `\t"${f}" -> "${d}";\n` })
: (graph += `\t"${f}";\n`) : (graph += `\t"${f}";\n`) })
})
graph += '}' graph += '}'
return graph }, return graph },
@ -1135,4 +1144,4 @@ module.Features = new FeatureSet()
/********************************************************************** /**********************************************************************
* vim:set ts=4 sw=4 : */ return module }) * vim:set ts=4 sw=4 : */ return module })