mirror of
https://github.com/flynx/features.js.git
synced 2025-12-25 20:41:57 +00:00
cleanup + tweaking docs...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
a37e9f8cb0
commit
8a44d91c83
90
README.md
90
README.md
@ -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',
|
||||||
// ...
|
// ...
|
||||||
])
|
])
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
373
features.js
373
features.js
@ -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 })
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user