From 6a240ef261c6e3ac1b3a1dbc883e9ba6e58aea99 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Thu, 22 Jun 2017 19:14:22 +0300 Subject: [PATCH] more cleanup... Signed-off-by: Alex A. Naanou --- features.js | 484 +-------------------------------------------------- package.json | 2 +- 2 files changed, 4 insertions(+), 482 deletions(-) diff --git a/features.js b/features.js index a61e744..950597d 100755 --- a/features.js +++ b/features.js @@ -271,486 +271,6 @@ var FeatureSetProto = { && that[e] instanceof Feature }) }, - // Build list of features... - // - // Build list of all features for an empty object... - // .buildFeatureList() - // .buildFeatureList({}) - // .buildFeatureList({}, '*') - // -> data - // - // Build a list of features for a specific root feature and object... - // .buildFeatureList(object, feature) - // -> data - // - // Build a list of features for a specific set of root features and object... - // .buildFeatureList(object, [feature, ..]) - // -> data - // NOTE: to disable a feature and all of it's dependants prefix - // it's tag with '-' in the list. - // e.g. including 'some-feature' will include the feature - // and its dependants while '-some-feature' will remove - // it and it's dependants. - // - // - // This will build from user input a loadable list of features taking - // into account feature dependencies, priorities and suggestions. - // - // Roughly this is done in this order starting with the given features: - // - include all dependencies (recursively) - // - include all suggested features (recursively) - // - sort features by priority - // - sort features by dependency - // - check for feature applicability - // - remove non-applicable features and all dependants (recursively) - // - remove disabled features and all dependants (recursively) - // - check and resolve exclusivity conflicts (XXX needs revision) - // - check for missing features and dependencies - // - // - // Return format: - // { - // // list of input features... - // input: [ .. ], - // - // // features in correct load order... - // features: [ .. ], - // - // // features disabled explicitly and their dependants... - // disabled: [ .. ], - // // unapplicable features and their dependants... - // unapplicable: [ .. ], - // - // // features removed due to exclusivity conflict... - // excluded: [ .. ], - // - // missing: { - // // features explicitly given by user but missing... - // USER: [ .. ], - // // missing dependencies... - // : [ .. ], - // ... - // }, - // conflicts: { - // XXX - // }, - // } - // - // - // NOTE: obj (action set) here is used only for applicability testing... - // NOTE: some feature applicability checks (.isApplicable(..)) may - // require a real action set, thus for correct operation one - // should be provided. - // NOTE: all feature sorting is done maintaining relative feature order - // when possible... - // NOTE: meta-features are not included in the list as they do not - // need to be setup. - // ...this is because they are not Feature objects. - // - // XXX should meta-features be MetaFeature objects??? - // XXX not sure about handling excluded features (see inside)... - // XXX add dependency loops to .conflicts... - // XXX might be a good idea to check dependency loops on feature - // construction, too... (???) - /* - buildFeatureList: function(obj, lst){ - var that = this - obj = obj || {} - - lst = (lst == null || lst == '*') ? this.features : lst - lst = lst.constructor !== Array ? [lst] : lst - - var input = lst.slice() - var disabled = [] - var excluded = [] - var unapplicable = [] - var missing = {} - var conflicts = {} - - var exclusive = {} - - - // reverse dependency cache... - var dependants = {} - - // build dependency list... - var _buildDepList = function(n, seen){ - seen = seen || [] - return seen.indexOf(n) >= 0 ? [] - : seen.push(n) && dependants[n] ? [] - .concat.apply( - dependants[n], - dependants[n] - .map(function(n){ return _buildDepList(n, seen) })) - : [] - } - - - // missing stage 1: check if all user included features exist... - // NOTE: we'll ignore missing disabled features too... - lst.forEach(function(n){ - if(!that[n] && n[0] != '-'){ - var m = missing['USER'] = missing['USER'] || [] - m.push(n) - } - }) - - // include all dependencies... - // - // NOTE: this should never fall into an infinite loop as we do - // not include feature already seen... - // ...unless there is an infinite number of features, but - // I'll try to avoid that big a feature creep. - // XXX should we check for dependency loops here??? - // ...this would have been simple if this was a recursion - // (just check if cur is in path), but here it is not - // trivial... - for(var i=0; i < lst.length; i++){ - var k = lst[i] - - // skip disabled or missing features.... - if(k[0] == '-' || !that[k]){ - continue - } - - var deps = that[k].depends || [] - var refs = that[k].suggested || [] - var excl = that[k].exclusive || [] - - deps.forEach(function(n){ - // expand lst with dependencies.... - lst.indexOf(n) < 0 && lst.push(n) - - // build reverse dependency index... - var d = dependants[n] = dependants[n] || [] - d.indexOf(k) < 0 && d.push(k) - }) - - // expand lst with suggenstions.... - refs.forEach(function(n){ - lst.indexOf(n) < 0 && lst.push(n) - }) - - // build exclusive table... - excl.forEach(function(n){ - var l = exclusive[n] = exclusive[n] || [] - l.indexOf(k) < 0 && l.push(k) - }) - } - - // sort features by priority or position... - lst = lst - // remove undefined and non-features... - .filter(function(n){ - // feature disabled -> record and skip... - if(n[0] == '-'){ - disabled.push(n.slice(1)) - return false - } - var f = that[n] - // feature not defined or is not a feature... - if(f == null || !(f instanceof Feature)){ - return false - } - // check applicability... - if(f.isApplicable && !f.isApplicable.call(that, obj)){ - unapplicable.push(n) - return false - } - return true - }) - // remove disabled... - .filter(function(e){ return disabled.indexOf(e) < 0 }) - // build the sort table: [ , , ] - .map(function(e, i){ return [ that[e].getPriority(), i, e ] }) - // sort by priority then index... - // NOTE: JS compares lists as strings so we have to compare - // the list manually... - .sort(function(a, b){ return a[0] - b[0] || a[1] - b[1] }) - // cleanup -- drop the sort table... - .map(function(e){ return e[2] }) - - // remove dependants on not applicable and on disabled... - var _unapplicable = unapplicable.slice() - var _disabled = disabled.slice() - // build the full lists of features to remove... - _unapplicable - .forEach(function(n){ _unapplicable = _unapplicable.concat(_buildDepList(n)) }) - _disabled - .forEach(function(n){ _disabled = _disabled.concat(_buildDepList(n)) }) - // clear... - // NOTE: in case of intersection disabled has priority... - lst = lst - .filter(function(n){ - return _disabled.indexOf(n) >= 0 ? - disabled.push(n) && false - : _unapplicable.indexOf(n) >= 0 ? - unapplicable.push(n) && false - : true }) - - // missing stage 2: dependencies... - lst.forEach(function(k){ - (that[k].depends || []).forEach(function(d){ - // NOTE: we do not need to check disabled or unapplicable - // here as if the feature depended on dropped feature - // it would have been already dropped too... - // NOTE: we skip exclusive tags as they will get replaced - // with actual feature tags later... - // if a tag is exclusive then at least one feature - // with it is present... - if(!that[d] && !exclusive[d] && d[0] != '-'){ - var m = missing[k] = missing[k] || [] - m.push(d) - } - }) - }) - - // check exclusive -> excluded... - // - // NOTE: this is the right spot for this, just after priority - // sorting and clearing but before dependency sorting. - // - // XXX do we need to clear dependencies pulled by excluded features??? - // ways to go: - // - drop excluded and continue (current state) - // - disable excluded, add to original input and rebuild - // - err and let the user decide - var _exclusive = [] - lst = lst.filter(function(n){ - var e = that[n] - - // keep non-exclusive stuff... - if(!e || e.exclusive == null){ - return true - } - - // count the number of exclusive features already present... - var res = e.exclusive - .filter(function(n){ - if(_exclusive.indexOf(n) < 0){ - _exclusive.push(n) - return false - } - return true - }) - .length == 0 - - !res - && excluded.push(n) - // warn the user... - // XXX not sure if this is the right place for this... - && console.warn( - 'Excluding unaplicable:', n, '(reccomended to exclude manually)') - - return res - }) - - // sort by dependency... - var l = lst.length - // get maximum possible length... - // ...the worst case length appears to be (for full reversal): - // S(2*(n-1) + 1) - // S = n => n > 0 ? 2*(n-1)+1 + S(n-1) : 0 - // S = n => n > 0 ? 2*n-1 + S(n-1) : 0 - // - // 2 * S(n) - n - // S = n => n > 0 ? n + S(n-1) : 0 - // f = n => 2 * S(n) - n - // - // N^2 + C - // S = n => n * n - // - // NOTE: this is the brute force way to check if we have a - // dependency loop, need something faster... - // - // XXX is O(n^2) good enough worst case here? - // ...at this point I think it is acceptable as we'll not - // expect dependency graphs too saturated, and the average - // complexity is far better... - var max = l * l - - var moved = [] - var chain = [] - var chains = [] - - for(var i=0; i < lst.length; i++){ - var k = lst[i] - var depends = (that[k].depends || []).slice() - - // replace dependencies that are exclusive tags... - Object.keys(exclusive).forEach(function(e){ - var i = depends.indexOf(e) - i >= 0 - && exclusive[e].forEach(function(f){ - if(lst.indexOf(f) >= 0){ - //console.log('EXCL->DEP', e, f) - depends[i] = f - } - }) - }) - - // list of dependencies to move... - var move = [] - - lst - .slice(0, i) - .forEach(function(n, j){ - // if n is a dependency of k, prepare to move... - if(depends.indexOf(n) >= 0){ - delete lst[j] - move.push(n) - } - }) - - // move the dependencies after k... - // NOTE: this will keep the order within the dependencies... - if(move.length > 0){ - lst.splice.apply(lst, [i+1, 0].concat(move)) - - // unseen feature -> new chain... - if(moved.indexOf(k) < 0){ - chain = [] - } - - moved.push(k) - - // chain completed -> check and store... - if(chain.indexOf(k) >= 0){ - var c = JSON.stringify(chain) - // repeating chain -> loop or order conflict detected... - if(chains.indexOf(c) >= 0){ - // format the graph... - // XXX this catches strange things and not only loops... - // ...at this point I can't repeat this error, see - // ImageGrid.Viewer in nwjs load console output... - var graph = [] - chain.forEach(function(k){ - graph = graph - .concat((that[k].depends || []) - .filter(function(e){ - return chain.indexOf(e) >= 0 }) - .map(function(e){ - return `${k} \t-> ${e}` })) }) - console.error('Feature cyclic dependency or order conflict:\n\t' - + graph.join('\n\t')) - - // XXX should we give up completely (break) here - // or move on and find new loops??? - break - } - chains.push(c) - - // add item to chain... - } else { - chain.push(k) - } - } - - // check for cyclic dependencies... - // XXX loop signs: - // - the tail length stops changing -- we stop progressing to list end - // - the loop is packed - // - each element includes a set of dependencies - // - this set is of the same length when at a specific element - // - we only shift the same set of N elements over N iterations - // - ... - if(lst.length >= max){ - // XXX get the actual cycle... - console.error('Feature cyclic dependency:', chain) - break - } - } - - // cleanup after sort... - lst = lst - // remove undefined and non-features... - .filter(function(e){ - return that[e] != null && that[e] instanceof Feature }) - .reverse() - - - return { - input: input, - - features: lst, - - disabled: disabled, - unapplicable: unapplicable, - excluded: excluded, - - missing: missing, - conflicts: conflicts, - } - }, - setup: function(obj, lst){ - // if no explicit object is given, just the list... - if(lst == null){ - lst = obj - obj = null - } - - obj = obj || (this.__actions__ || actions.Actions)() - - lst = lst.constructor !== Array ? [lst] : lst - var features = this.buildFeatureList(obj, lst) - lst = features.features - - // check for conflicts... - if(Object.keys(features.conflicts).length != 0 - || Object.keys(features.missing).length != 0){ - var m = features.missing - var c = features.conflicts - - // build a report... - var report = [] - - // missing deps... - Object.keys(m).forEach(function(k){ - report.push(k + ': requires following missing features:\n' - +' ' + m[k].join(', ')) - }) - report.push('\n') - - // conflicts... - Object.keys(c).forEach(function(k){ - report.push(k + ': must setup after:\n ' + c[k].join(', ')) - }) - - // break... - throw 'Feature dependency error:\n ' + report.join('\n ') - } - - // report excluded features... - if(this.__verbose__ && features.excluded.length > 0){ - console.warn('Excluded features due to exclusivity conflict:', - features.excluded.join(', ')) - } - - // report unapplicable features... - if(this.__verbose__ && features.unapplicable.length > 0){ - console.log('Features not applicable in current context:', - features.unapplicable.join(', ')) - } - - // do the setup... - var that = this - var setup = FeatureProto.setup - lst.forEach(function(n){ - // setup... - if(that[n] != null){ - this.__verbose__ && console.log('Setting up feature:', n) - setup.call(that[n], obj) - } - }) - - // XXX should we extend this if it already was in the object??? - obj.features = features - - return obj - }, - //*/ - - // Build list of features in load order... // // .buildFeatureList() @@ -1111,7 +631,9 @@ var FeatureSetProto = { var excluded = [] Object.keys(conflicts) .forEach(function(group){ - // XXX is this how we decide which feature to keep??? + // XXX BUG: for some reason this does not behave + // deterministically and in some cases the order + // of the list is not stable... excluded = excluded.concat(conflicts[group].slice(1))}) disabled = disabled.concat(excluded) diff --git a/package.json b/package.json index 34e41a9..8c891e1 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ig-features", - "version": "3.1.2", + "version": "3.1.3", "description": "", "main": "features.js", "scripts": {