| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							|  |  |  | ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)( | 
					
						
							|  |  |  | function(require){ var module={} // makes module AMD/node compatible...
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var object = require('ig-object') | 
					
						
							|  |  |  | var actions = module.actions = require('ig-actions') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | // XXX use object.Error as base when ready...
 | 
					
						
							|  |  |  | var FeatureLinearizationError | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | module.FeatureLinearizationError =  | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | object.Constructor('FeatureLinearizationError', Error, { | 
					
						
							|  |  |  | 	get name(){ | 
					
						
							|  |  |  | 		return this.constructor.name }, | 
					
						
							|  |  |  | 	toString: function(){ | 
					
						
							|  |  |  | 		return 'Failed to linearise' }, | 
					
						
							|  |  |  | 	__init__: function(data){ | 
					
						
							|  |  |  | 		this.data = data }, | 
					
						
							|  |  |  | }) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Base feature...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Feature(obj)
 | 
					
						
							|  |  |  | // 		-> feature
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Feature(feature-set, obj)
 | 
					
						
							|  |  |  | // 		-> feature
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Feature(tag, obj)
 | 
					
						
							|  |  |  | // 		-> feature
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Feature(tag, suggested)
 | 
					
						
							|  |  |  | // 		-> feature
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Feature(tag, actions)
 | 
					
						
							|  |  |  | // 		-> feature
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Feature(feature-set, tag, actions)
 | 
					
						
							|  |  |  | // 		-> feature
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // Feature attributes:
 | 
					
						
							|  |  |  | // 	.tag			- feature tag (string)
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | // 					  this is used to identify the feature, its event 
 | 
					
						
							|  |  |  | // 					  handlers and DOM elements.
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // 	.title			- feature name (string | null)
 | 
					
						
							|  |  |  | // 	.doc			- feature description (string | null)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	.priority		- feature priority
 | 
					
						
							|  |  |  | // 					  can be:
 | 
					
						
							|  |  |  | // 					  	- 'high' (99) | 'medium' (0) | 'low' (-99)
 | 
					
						
							|  |  |  | // 					  	- number
 | 
					
						
							|  |  |  | // 					  	- null (0, default)
 | 
					
						
							|  |  |  | // 					  features with higher priority will be setup first,
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | // 					  features with the same priority will be run in 
 | 
					
						
							|  |  |  | // 					  order of occurrence.
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | // 	.suggested		- list of optional suggested features, these are not 
 | 
					
						
							|  |  |  | // 					  required but setup if available.
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | // 					  This is useful for defining meta features but 
 | 
					
						
							|  |  |  | // 					  without making each sub-feature a strict dependency.
 | 
					
						
							|  |  |  | // 	.depends		- feature dependencies -- tags of features that must 
 | 
					
						
							|  |  |  | // 					  setup before the feature (list | null)
 | 
					
						
							|  |  |  | // 					  NOTE: a feature can depend on an exclusive tag, 
 | 
					
						
							|  |  |  | // 					  		this will remove the need to track which 
 | 
					
						
							|  |  |  | // 					  		specific exclusive tagged feature is loaded...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | // 	.exclusive		- feature exclusivity tags (list | null)
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | // 					  an exclusivity group enforces that only one feature
 | 
					
						
							|  |  |  | // 					  in it will be run, i.e. the first / highest priority.
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // 	.actions		- action object containing feature actions (ActionSet | null)
 | 
					
						
							|  |  |  | // 					  this will be mixed into the base object on .setup()
 | 
					
						
							|  |  |  | // 					  and mixed out on .remove()
 | 
					
						
							|  |  |  | // 	.config			- feature configuration, will be merged with base 
 | 
					
						
							|  |  |  | // 					  object's .config
 | 
					
						
							|  |  |  | // 					  NOTE: the final .config is an empty object with
 | 
					
						
							|  |  |  | // 					  		.__proto__ set to the merged configuration
 | 
					
						
							|  |  |  | // 					  		data...
 | 
					
						
							|  |  |  | // 	.handlers		- feature event handlers (list | null)
 | 
					
						
							|  |  |  | // 
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // .handlers format:
 | 
					
						
							|  |  |  | // 	[
 | 
					
						
							|  |  |  | // 		[ <event-spec>, <handler-function> ],
 | 
					
						
							|  |  |  | // 		...
 | 
					
						
							|  |  |  | // 	]
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: both <event-spec> and <handler-function> must be compatible with
 | 
					
						
							|  |  |  | // 		Action.on(..)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Feature applicability:
 | 
					
						
							|  |  |  | // 	If feature.isApplicable(..) returns false then the feature will not be
 | 
					
						
							|  |  |  | // 	considered on setup...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | var Feature = | 
					
						
							|  |  |  | module.Feature = | 
					
						
							|  |  |  | object.Constructor('Feature', { | 
					
						
							|  |  |  | 	//__featureset__: Features,
 | 
					
						
							|  |  |  | 	__featureset__: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 	__verbose: null, | 
					
						
							|  |  |  | 	get __verbose__(){ | 
					
						
							|  |  |  | 		return this.__verbose == null  | 
					
						
							|  |  |  | 			&& (this.__featureset__ || {}).__verbose__ }, | 
					
						
							|  |  |  | 	set __verbose__(value){ | 
					
						
							|  |  |  | 		this.__verbose = value }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// Attributes...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	tag: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	//title: null,
 | 
					
						
							|  |  |  | 	//doc: null,
 | 
					
						
							|  |  |  | 	//priority: null,
 | 
					
						
							|  |  |  | 	//exclusive: null,
 | 
					
						
							|  |  |  | 	//suggested: null,
 | 
					
						
							|  |  |  | 	//depends: null,
 | 
					
						
							|  |  |  | 	//actions: null,
 | 
					
						
							|  |  |  | 	//config: null,
 | 
					
						
							|  |  |  | 	//handlers: null,
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	isApplicable: function(actions){ return true }, | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// API...
 | 
					
						
							|  |  |  | 	getPriority: function(human_readable){ | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 		var res = this.priority || 0 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		res = res == 'high' ? 99 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 			: res == 'low' ? -99 | 
					
						
							|  |  |  | 			: res == 'medium' ? 0 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			: res == 'normal' ? 0 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 			: res | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		return human_readable ? | 
					
						
							|  |  |  | 				(res == 99 ? 'high' | 
					
						
							|  |  |  | 					: res == 0 ? 'normal' | 
					
						
							|  |  |  | 					: res == -99 ? 'low' | 
					
						
							|  |  |  | 					: res) | 
					
						
							|  |  |  | 			: res }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 	// XXX HANDLERS this could install the handlers in two locations:
 | 
					
						
							|  |  |  | 	// 		- the actions object...
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// 		- mixin if available...
 | 
					
						
							|  |  |  | 	// 		- base object (currently implemented)
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 	// 		...the handlers should theoreticly be stored neither in the 
 | 
					
						
							|  |  |  | 	// 		instance nor in the mixin but rather in the action-set itself 
 | 
					
						
							|  |  |  | 	// 		on feature creation... (???)
 | 
					
						
							|  |  |  | 	// 		...feels like user handlers and feature handlers should be 
 | 
					
						
							|  |  |  | 	// 		isolated...
 | 
					
						
							|  |  |  | 	// 		XXX setting handlers on the .__proto__ breaks...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	setup: function(actions){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// mixin actions...
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		// NOTE: this will only mixin functions and actions...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 		if(this.actions != null){ | 
					
						
							| 
									
										
										
										
											2016-12-11 03:34:30 +03:00
										 |  |  | 			this.tag ?  | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				// XXX HANDLERS
 | 
					
						
							|  |  |  | 				actions.mixin(this.actions, {source_tag: this.tag, action_handlers: true})  | 
					
						
							|  |  |  | 				: actions.mixin(this.actions, {action_handlers: true}) } | 
					
						
							|  |  |  | 				//actions.mixin(this.actions, {source_tag: this.tag}) 
 | 
					
						
							|  |  |  | 				//: actions.mixin(this.actions) }
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		/*/ XXX HANDLERS this is not needed if handlers are local to actions... | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 		// install handlers...
 | 
					
						
							|  |  |  | 		if(this.handlers != null){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 			this.handlers.forEach(function([a, h]){ | 
					
						
							|  |  |  | 				//actions.__proto__.on(a, that.tag, h) }) }
 | 
					
						
							|  |  |  | 				actions.on(a, that.tag, h) }) } | 
					
						
							|  |  |  | 		//*/
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// merge config...
 | 
					
						
							|  |  |  | 		// NOTE: this will merge the actual config in .config.__proto__
 | 
					
						
							|  |  |  | 		// 		keeping the .config clean for the user to lay with...
 | 
					
						
							|  |  |  | 		if(this.config != null  | 
					
						
							|  |  |  | 				|| (this.actions != null  | 
					
						
							|  |  |  | 					&& this.actions.config != null)){ | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			// sanity check -- warn of config shadowing...
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 			// XXX do we need this???
 | 
					
						
							|  |  |  | 			if(this.__verbose__  | 
					
						
							|  |  |  | 					&& this.config && (this.actions || {}).config){ | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 				console.warn('Feature config shadowed: ' | 
					
						
							|  |  |  | 					+'both .config (used) and .actions.config (ignored) are defined for:',  | 
					
						
							|  |  |  | 					this.tag,  | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 					this) } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 			var config = this.config = this.config || this.actions.config | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(actions.config == null){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				actions.config = Object.create({}) } | 
					
						
							|  |  |  | 			Object.keys(config) | 
					
						
							|  |  |  | 				.forEach(function(n){ | 
					
						
							|  |  |  | 					// NOTE: this will overwrite existing values...
 | 
					
						
							|  |  |  | 					actions.config.__proto__[n] = config[n] }) } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// custom setup...
 | 
					
						
							|  |  |  | 		// XXX is this the correct way???
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		this.hasOwnProperty('setup')  | 
					
						
							|  |  |  | 			&& this.setup !== Feature.prototype.setup | 
					
						
							|  |  |  | 			&& this.setup(actions) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// XXX need to revise this...
 | 
					
						
							|  |  |  | 	// 		- .mixout(..) is available directly from the object while 
 | 
					
						
							|  |  |  | 	// 			.remove(..) is not...
 | 
					
						
							|  |  |  | 	// 		- might be a good idea to add a specific lifecycle actions to
 | 
					
						
							|  |  |  | 	// 			enable feautures to handle their removal correctly... 
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	remove: function(actions){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		this.actions != null | 
					
						
							|  |  |  | 			&& actions.mixout(this.tag || this.actions) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		/*/ XXX HANDLERS do we need this if .handlers if local to action... | 
					
						
							|  |  |  | 		this.handlers != null | 
					
						
							|  |  |  | 			&& actions.off('*', this.tag) | 
					
						
							|  |  |  | 		//*/
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		// XXX revise naming...
 | 
					
						
							|  |  |  | 		this.hasOwnProperty('remove')  | 
					
						
							|  |  |  | 			&& this.setup !== Feature.prototype.remove | 
					
						
							|  |  |  | 			&& this.remove(actions) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// XXX EXPERIMENTAL: if called from a feature-set this will add self
 | 
					
						
							|  |  |  | 	// 		to that feature-set...
 | 
					
						
							|  |  |  | 	// XXX do we need this to be .__new__(..) and not .__init__(..)
 | 
					
						
							|  |  |  | 	__new__: function(context, feature_set, tag, obj){ | 
					
						
							|  |  |  | 		// NOTE: we need to account for context here -- inc length...
 | 
					
						
							|  |  |  | 		if(arguments.length == 3){ | 
					
						
							|  |  |  | 			// Feature(<tag>, <obj>)
 | 
					
						
							|  |  |  | 			if(typeof(feature_set) == typeof('str')){ | 
					
						
							|  |  |  | 				obj = tag | 
					
						
							|  |  |  | 				tag = feature_set | 
					
						
							|  |  |  | 				//feature_set = Features
 | 
					
						
							|  |  |  | 				// XXX EXPERIMENTAL...
 | 
					
						
							|  |  |  | 				feature_set = context instanceof FeatureSet ? | 
					
						
							|  |  |  | 					context | 
					
						
							|  |  |  | 					: (this.__featureset__ || Features) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Feature(<feature-set>, <obj>)
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				obj = tag | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				tag = null } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Feature(<obj>)
 | 
					
						
							|  |  |  | 		// NOTE: we need to account for context here -- inc length...
 | 
					
						
							|  |  |  | 		} else if(arguments.length == 2){ | 
					
						
							|  |  |  | 			obj = feature_set | 
					
						
							|  |  |  | 			//feature_set = Features
 | 
					
						
							|  |  |  | 			// XXX EXPERIMENTAL...
 | 
					
						
							|  |  |  | 			feature_set = context instanceof FeatureSet ? | 
					
						
							|  |  |  | 				context | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				: (this.__featureset__ || Features) } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		if(tag != null && obj.tag != null && obj.tag != tag){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 			throw new Error('tag and obj.tag mismatch, either use one or both must match.') } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		// actions...
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		if(obj instanceof actions.Action){ | 
					
						
							|  |  |  | 			if(tag == null){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				throw new Error('need a tag to make a feature out of an action') } | 
					
						
							|  |  |  | 			obj = { | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 				tag: tag, | 
					
						
							|  |  |  | 				actions: obj, | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// meta-feature...
 | 
					
						
							|  |  |  | 		} else if(obj.constructor === Array){ | 
					
						
							|  |  |  | 			if(tag == null){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				throw new Error('need a tag to make a meta-feature') } | 
					
						
							|  |  |  | 			obj = { | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 				tag: tag, | 
					
						
							|  |  |  | 				suggested: obj, | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 			} } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX HANDLERS setup .handlers...
 | 
					
						
							|  |  |  | 		if(obj.handlers){ | 
					
						
							|  |  |  | 			obj.actions = obj.actions || {} | 
					
						
							|  |  |  | 			// NOTE: obj.actions does not have to be an action so w cheat \
 | 
					
						
							|  |  |  | 			// 		a bit here, then copy the mindings...
 | 
					
						
							|  |  |  | 			var tmp = Object.create(actions.MetaActions) | 
					
						
							|  |  |  | 			obj.handlers | 
					
						
							|  |  |  | 				.forEach(function([a, h]){ | 
					
						
							|  |  |  | 					tmp.on(a, obj.tag, h) })  | 
					
						
							|  |  |  | 			Object.assign(obj.actions, tmp) } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// feature-set...
 | 
					
						
							|  |  |  | 		if(feature_set){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 			feature_set[obj.tag] = obj } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		return obj }, | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | }) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | var FeatureSet = | 
					
						
							|  |  |  | module.FeatureSet =  | 
					
						
							|  |  |  | object.Constructor('FeatureSet', { | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	// if true, .setup(..) will report things it's doing... 
 | 
					
						
							|  |  |  | 	__verbose__: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	__actions__: actions.Actions, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: a feature is expected to write a reference to itself to the 
 | 
					
						
							|  |  |  | 	// 		feature-set (context)...
 | 
					
						
							|  |  |  | 	Feature: Feature, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	// List of registered features...
 | 
					
						
							|  |  |  | 	get features(){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		return Object.keys(this) | 
					
						
							|  |  |  | 			.filter(function(e){  | 
					
						
							|  |  |  | 				return e != 'features'  | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 					&& that[e] instanceof Feature }) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// build exclusive groups...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Get all exclusive tags...
 | 
					
						
							|  |  |  | 	// 	.getExclusive()
 | 
					
						
							|  |  |  | 	// 	.getExclusive('*')
 | 
					
						
							|  |  |  | 	// 		-> exclusive
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Get specific exclusive tags...
 | 
					
						
							|  |  |  | 	// 	.getExclusive(tag)
 | 
					
						
							|  |  |  | 	// 	.getExclusive([tag, ..])
 | 
					
						
							|  |  |  | 	// 		-> exclusive
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// If features is given, only consider the features in list.
 | 
					
						
							|  |  |  | 	// If rev_exclusive is given, also build a reverse exclusive feature
 | 
					
						
							|  |  |  | 	// list.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// output format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		exclusive-tag: [
 | 
					
						
							|  |  |  | 	// 			feature-tag,
 | 
					
						
							|  |  |  | 	// 			...
 | 
					
						
							|  |  |  | 	// 		],
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	getExclusive: function(tag, features, rev_exclusive, isDisabled){ | 
					
						
							|  |  |  | 		tag = tag == null || tag == '*' ? '*' | 
					
						
							|  |  |  | 			: tag instanceof Array ? tag | 
					
						
							|  |  |  | 			: [tag] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		features = features || this.features | 
					
						
							|  |  |  | 		rev_exclusive = rev_exclusive || {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		var exclusive = {} | 
					
						
							|  |  |  | 		features | 
					
						
							|  |  |  | 			.filter(function(f){  | 
					
						
							|  |  |  | 				return !!that[f].exclusive  | 
					
						
							|  |  |  | 					&& (!isDisabled || !isDisabled(f)) }) | 
					
						
							|  |  |  | 			.forEach(function(k){ | 
					
						
							|  |  |  | 				var e = that[k].exclusive | 
					
						
							|  |  |  | 				;((e instanceof Array ? e : [e]) || []) | 
					
						
							|  |  |  | 					.forEach(function(e){ | 
					
						
							|  |  |  | 						// skip tags not explicitly requested...
 | 
					
						
							|  |  |  | 						if(tag != '*' && tag.indexOf(e) < 0){ | 
					
						
							|  |  |  | 							return | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						exclusive[e] = (exclusive[e] || []).concat([k])  | 
					
						
							|  |  |  | 						rev_exclusive[k] = (rev_exclusive[k] || []).concat([e]) }) }) | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		return exclusive }, | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// Build list of features in load order...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.buildFeatureList()
 | 
					
						
							|  |  |  | 	// 	.buildFeatureList('*')
 | 
					
						
							|  |  |  | 	// 		-> data
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.buildFeatureList(feature-tag)
 | 
					
						
							|  |  |  | 	// 		-> data
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.buildFeatureList([feature-tag, .. ])
 | 
					
						
							|  |  |  | 	// 		-> data
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// 	.buildFeatureList(.., filter)
 | 
					
						
							|  |  |  | 	// 		-> data
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// Requirements:
 | 
					
						
							|  |  |  | 	//	- features are pre-sorted by priority, original order is kept 
 | 
					
						
							|  |  |  | 	//		where possible
 | 
					
						
							|  |  |  | 	//	- a feature is loaded strictly after it's dependencies
 | 
					
						
							|  |  |  | 	//	- features depending on inapplicable feature(s) are also 
 | 
					
						
							|  |  |  | 	//		inapplicable (recursive up)
 | 
					
						
							|  |  |  | 	//	- inapplicable features are not loaded
 | 
					
						
							|  |  |  | 	//	- missing dependency -> missing dependency error
 | 
					
						
							|  |  |  | 	//	- suggested features (and their dependencies) do not produce 
 | 
					
						
							|  |  |  | 	//		dependency errors, unless explicitly included in dependency 
 | 
					
						
							|  |  |  | 	//		graph (i.e. explicitly depended on by some other feature)
 | 
					
						
							|  |  |  | 	//	- features with the same exclusive tag are grouped into an 
 | 
					
						
							|  |  |  | 	//		exclusive set
 | 
					
						
							|  |  |  | 	//	- only the first feature in an exclusive set is loaded, the rest
 | 
					
						
							|  |  |  | 	//		are *excluded*
 | 
					
						
							|  |  |  | 	//	- exclusive tag can be used to reference (alias) the loaded 
 | 
					
						
							|  |  |  | 	//		feature in exclusive set (i.e. exclusive tag can be used as 
 | 
					
						
							|  |  |  | 	//		a dependency)
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// NOTE: an exclusive group name can be used as an alias.
 | 
					
						
							|  |  |  | 	// NOTE: if an alias is used and no feature from that exclusive group
 | 
					
						
							|  |  |  | 	// 		is explicitly included then the actual loaded feature will 
 | 
					
						
							|  |  |  | 	// 		depend on the load order, which in an async world is not 
 | 
					
						
							|  |  |  | 	// 		deterministic...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// Algorithm:
 | 
					
						
							|  |  |  | 	// 	- expand features:
 | 
					
						
							|  |  |  | 	// 		- handle dependencies (detect loops)
 | 
					
						
							|  |  |  | 	// 		- handle suggestions
 | 
					
						
							|  |  |  | 	// 		- handle explicitly disabled features (detect loops)
 | 
					
						
							|  |  |  | 	// 		- handle exclusive feature groups/aliases (handle conflicts)
 | 
					
						
							|  |  |  | 	// 	- sort list of features:
 | 
					
						
							|  |  |  | 	// 		- by priority
 | 
					
						
							|  |  |  | 	// 		- by dependency (detect loops/errors)
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Return format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	//		// input feature feature tags...
 | 
					
						
							|  |  |  | 	//		input: [ .. ],
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	//		// output list of feature tags...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//		features: [ .. ],
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	//		// disabled features...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//		disabled: [ .. ],
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	//		// exclusive features that got excluded... 
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//		excluded: [ .. ],
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	//		// Errors...
 | 
					
						
							|  |  |  | 	//		error: null | {
 | 
					
						
							|  |  |  | 	//			// fatal/recoverable error indicator...
 | 
					
						
							|  |  |  | 	//			fatal: bool,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//			// missing dependencies...
 | 
					
						
							|  |  |  | 	//			// NOTE: this includes tags only included by .depends and 
 | 
					
						
							|  |  |  | 	//			//		ignores tags from .suggested...
 | 
					
						
							|  |  |  | 	//			missing: [ .. ],
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//			// exclusive feature conflicts...
 | 
					
						
							|  |  |  | 	//			// This will include the explicitly required conflicting
 | 
					
						
							|  |  |  | 	//			// exclusive features.
 | 
					
						
							|  |  |  | 	//			// NOTE: this is not an error, but indicates that the 
 | 
					
						
							|  |  |  | 	//			//		system tried to fix the state by disabling all
 | 
					
						
							|  |  |  | 	//			//		but the first feature.
 | 
					
						
							|  |  |  | 	//			conflicts: {
 | 
					
						
							|  |  |  | 	//				exclusive-tag: [ feature-tag, .. ],
 | 
					
						
							|  |  |  | 	//				..
 | 
					
						
							|  |  |  | 	//			},
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//			// detected dependency loops (if .length > 0 sets fatal)...
 | 
					
						
							|  |  |  | 	//			loops: [ .. ],
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//			// sorting loop overflow error (if true sets fatal)...
 | 
					
						
							|  |  |  | 	//			sort_loop_overflow: bool,
 | 
					
						
							|  |  |  | 	//		},
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//		// Introspection...
 | 
					
						
							|  |  |  | 	//		// index of features and their list of dependencies...
 | 
					
						
							|  |  |  | 	//		depends: {
 | 
					
						
							|  |  |  | 	//			feature-tag: [ feature-tag, .. ],
 | 
					
						
							|  |  |  | 	//			..
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//		},
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	//		// index of features and list of features depending on them...
 | 
					
						
							|  |  |  | 	//		// XXX should this include suggestions or should we do them 
 | 
					
						
							|  |  |  | 	//		//		in a separate list...
 | 
					
						
							|  |  |  | 	//		depended: { 
 | 
					
						
							|  |  |  | 	//			feature-tag: [ feature-tag, .. ],
 | 
					
						
							|  |  |  | 	//			..
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//		},
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// XXX should exclusive conflicts resolve to first (current state)
 | 
					
						
							|  |  |  | 	//		feature or last in an exclusive group???
 | 
					
						
							|  |  |  | 	// XXX PROBLEM: exclusive feature trees should be resolved accounting 
 | 
					
						
							|  |  |  | 	// 		feature applicablility...
 | 
					
						
							|  |  |  | 	// 		...in this approach it is impossible...
 | 
					
						
							|  |  |  | 	// 		...one way to fix this is to make this interactively check 
 | 
					
						
							|  |  |  | 	// 		applicability, i.e. pass a context and check applicablility 
 | 
					
						
							|  |  |  | 	// 		when needed...
 | 
					
						
							|  |  |  | 	buildFeatureList: function(lst, isDisabled){ | 
					
						
							|  |  |  | 		var all = this.features | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		lst = (lst == null || lst == '*') ?  | 
					
						
							|  |  |  | 			all  | 
					
						
							|  |  |  | 			: lst | 
					
						
							|  |  |  | 		lst = lst instanceof Array ?  | 
					
						
							|  |  |  | 			lst | 
					
						
							|  |  |  | 			: [lst]  | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		//isDisabled = isDisabled || function(){ return false }
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// Pre-sort exclusive feature by their occurrence in dependency
 | 
					
						
							|  |  |  | 		// tree...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: there can be a case when an exclusive alias is used but
 | 
					
						
							|  |  |  | 		//		no feature in a group is loaded, in this case which 
 | 
					
						
							|  |  |  | 		//		feature is actually loaded depends on the load order...
 | 
					
						
							|  |  |  | 		var sortExclusive = function(features){ | 
					
						
							|  |  |  | 			var loaded = Object.keys(features) | 
					
						
							|  |  |  | 			Object.keys(exclusive) | 
					
						
							|  |  |  | 				.forEach(function(k){ | 
					
						
							|  |  |  | 					exclusive[k] = exclusive[k] | 
					
						
							|  |  |  | 						.map(function(e, i){ return [e, i] }) | 
					
						
							|  |  |  | 						.sort(function(a, b){ | 
					
						
							|  |  |  | 							var i = loaded.indexOf(a[0]) | 
					
						
							|  |  |  | 							var j = loaded.indexOf(b[0])  | 
					
						
							|  |  |  | 							// keep the missing at the end...
 | 
					
						
							|  |  |  | 							i = i < 0 ? Infinity : i | 
					
						
							|  |  |  | 							j = j < 0 ? Infinity : j | 
					
						
							|  |  |  | 							// NOTE: Infinity - Infinity is NaN, so we need 
 | 
					
						
							|  |  |  | 							// 		to guard against it...
 | 
					
						
							|  |  |  | 							return i - j || 0 }) | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						.map(function(e){ return e[0] }) }) } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// Expand feature references (recursive)...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// NOTE: closures are not used here as we need to pass different
 | 
					
						
							|  |  |  | 		// 		stuff into data in different situations...
 | 
					
						
							|  |  |  | 		var expand = function(target, lst, store, data, _seen){ | 
					
						
							|  |  |  | 			data = data || {} | 
					
						
							|  |  |  | 			_seen = _seen || [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// clear disabled...
 | 
					
						
							|  |  |  | 			// NOTE: we do as a separate stage to avoid loading a 
 | 
					
						
							|  |  |  | 			// 		feature before it is disabled in the same list...
 | 
					
						
							|  |  |  | 			lst = data.disabled ? | 
					
						
							|  |  |  | 				lst | 
					
						
							|  |  |  | 					.filter(function(n){ | 
					
						
							|  |  |  | 						// feature disabled -> record and skip...
 | 
					
						
							|  |  |  | 						if(n[0] == '-'){ | 
					
						
							|  |  |  | 							n = n.slice(1) | 
					
						
							|  |  |  | 							if(_seen.indexOf(n) >= 0){ | 
					
						
							|  |  |  | 								// NOTE: a disable loop is when a feature tries to disable
 | 
					
						
							|  |  |  | 								// 		a feature up in the same chain...
 | 
					
						
							|  |  |  | 								// XXX should this break or accumulate???
 | 
					
						
							|  |  |  | 								console.warn(`Disable loop detected at "${n}" in chain: ${_seen}`) | 
					
						
							|  |  |  | 								var loop = _seen.slice(_seen.indexOf(n)).concat([n]) | 
					
						
							|  |  |  | 								data.disable_loops = (data.disable_loops || []).push(loop) | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 								return false } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 							// XXX STUB -- need to resolve actual loops and 
 | 
					
						
							|  |  |  | 							// 		make the disable global...
 | 
					
						
							|  |  |  | 							if(n in store){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 								console.warn('Disabling a feature after it is loaded:', n, _seen) } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 							data.disabled.push(n) | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 							return false } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 						// skip already disabled features...
 | 
					
						
							|  |  |  | 						if(data.disabled.indexOf(n) >= 0){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 							return false } | 
					
						
							|  |  |  | 						return true }) | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 				: lst | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// traverse the tree...
 | 
					
						
							|  |  |  | 			lst | 
					
						
							|  |  |  | 				// normalize the list -- remove non-features and resolve aliases...
 | 
					
						
							|  |  |  | 				.map(function(n){  | 
					
						
							|  |  |  | 					var f = that[n] | 
					
						
							|  |  |  | 					// exclusive tags...
 | 
					
						
							|  |  |  | 					if(f == null && data.exclusive && n in data.exclusive){ | 
					
						
							|  |  |  | 						store[n] = null | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						return false } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 					// feature not defined or is not a feature...
 | 
					
						
							|  |  |  | 					if(f == null){ | 
					
						
							|  |  |  | 						data.missing  | 
					
						
							|  |  |  | 							&& data.missing.indexOf(n) < 0 | 
					
						
							|  |  |  | 							&& data.missing.push(n) | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						return false } | 
					
						
							|  |  |  | 					return n }) | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 				.filter(function(e){ return e }) | 
					
						
							|  |  |  | 				// traverse down...
 | 
					
						
							|  |  |  | 				.forEach(function(f){ | 
					
						
							|  |  |  | 					// dependency loop detection...
 | 
					
						
							|  |  |  | 					if(_seen.indexOf(f) >= 0){ | 
					
						
							|  |  |  | 						var loop = _seen.slice(_seen.indexOf(f)).concat([f]) | 
					
						
							|  |  |  | 						data.loops  | 
					
						
							|  |  |  | 							&& data.loops.push(loop) | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						return } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 					// skip already done features...
 | 
					
						
							|  |  |  | 					if(f in store){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						return } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 					//var feature = store[f] = that[f]
 | 
					
						
							|  |  |  | 					var feature = that[f] | 
					
						
							|  |  |  | 					if(feature){ | 
					
						
							|  |  |  | 						var _lst = [] | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 						// merge lists...
 | 
					
						
							|  |  |  | 						;(target instanceof Array ? target : [target]) | 
					
						
							|  |  |  | 							.forEach(function(t){ | 
					
						
							|  |  |  | 								_lst = _lst.concat(feature[t] || []) | 
					
						
							|  |  |  | 							}) | 
					
						
							|  |  |  | 						store[f] = _lst  | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 						// traverse down...
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						expand(target, _lst, store, data, _seen.concat([f])) } }) | 
					
						
							| 
									
										
										
										
											2016-12-11 03:34:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 			return store } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// Expand feature dependencies and suggestions recursively...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// NOTE: this relies on the following values being in the closure:
 | 
					
						
							|  |  |  | 		// 		loops			- list of loop chains found
 | 
					
						
							|  |  |  | 		// 		disable_loops	- disable loops
 | 
					
						
							|  |  |  | 		// 							when a feature containing a disable 
 | 
					
						
							|  |  |  | 		// 							directive gets disabled as a result
 | 
					
						
							|  |  |  | 		// 		disabled		- list of disabled features
 | 
					
						
							|  |  |  | 		// 		missing			- list of missing features
 | 
					
						
							|  |  |  | 		// 		missing_suggested
 | 
					
						
							|  |  |  | 		// 						- list of missing suggested features and
 | 
					
						
							|  |  |  | 		// 							suggested feature dependencies
 | 
					
						
							|  |  |  | 		// 		exclusive		- exclusive feature index
 | 
					
						
							|  |  |  | 		// 		suggests		- index of feature suggestions (full)
 | 
					
						
							|  |  |  | 		// 		suggested		- suggested feature dependency index
 | 
					
						
							|  |  |  | 		// NOTE: the above containers will get updated as a side-effect.
 | 
					
						
							|  |  |  | 		// NOTE: all of the above values are defined near the location 
 | 
					
						
							|  |  |  | 		// 		they are first used/initiated...
 | 
					
						
							|  |  |  | 		// NOTE: closures are used here purely for simplicity and conciseness
 | 
					
						
							|  |  |  | 		// 		as threading data would not add any flexibility but make 
 | 
					
						
							|  |  |  | 		// 		the code more complex...
 | 
					
						
							|  |  |  | 		var expandFeatures = function(lst, features){ | 
					
						
							|  |  |  | 			features = features || {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// feature tree...
 | 
					
						
							|  |  |  | 			var expand_data = { | 
					
						
							|  |  |  | 				loops: loops,  | 
					
						
							|  |  |  | 				disabled: disabled,  | 
					
						
							|  |  |  | 				disable_loops: disable_loops,  | 
					
						
							|  |  |  | 				missing: missing, | 
					
						
							|  |  |  | 				exclusive: exclusive, | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			features = expand('depends', lst, features, expand_data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// suggestion list...
 | 
					
						
							|  |  |  | 			//	...this will be used to check if we need to break on missing 
 | 
					
						
							|  |  |  | 			//	features, e.g. if a feature is suggested we can silently skip 
 | 
					
						
							|  |  |  | 			//	it otherwise err...
 | 
					
						
							|  |  |  | 			//
 | 
					
						
							|  |  |  | 			// NOTE: this stage does not track suggested feature dependencies...
 | 
					
						
							|  |  |  | 			// NOTE: we do not need loop detection active here...
 | 
					
						
							|  |  |  | 			var s = expand('suggested', Object.keys(features), {},  | 
					
						
							|  |  |  | 				{  | 
					
						
							|  |  |  | 					disabled: disabled, | 
					
						
							|  |  |  | 					missing: missing_suggested, | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			s = Object.keys(s) | 
					
						
							|  |  |  | 				.filter(function(f){  | 
					
						
							|  |  |  | 					// populate the tree of feature suggestions...
 | 
					
						
							|  |  |  | 					suggests[f] = s[f] | 
					
						
							|  |  |  | 					// filter out what's in features already...
 | 
					
						
							|  |  |  | 					return !(f in features) }) | 
					
						
							|  |  |  | 			// get suggestion dependencies...
 | 
					
						
							|  |  |  | 			// NOTE: we do not care bout missing here...
 | 
					
						
							|  |  |  | 			s = expand('depends', s, {},  | 
					
						
							|  |  |  | 				{  | 
					
						
							|  |  |  | 					loops: loops,  | 
					
						
							|  |  |  | 					disabled: disabled,  | 
					
						
							|  |  |  | 					disable_loops: disable_loops,  | 
					
						
							|  |  |  | 					exclusive: exclusive, | 
					
						
							|  |  |  | 					missing: missing_suggested, | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			Object.keys(s) | 
					
						
							|  |  |  | 				.forEach(function(f){  | 
					
						
							|  |  |  | 					// keep only suggested features -- diff with features...
 | 
					
						
							|  |  |  | 					if(f in features){ | 
					
						
							|  |  |  | 						delete s[f] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// mix suggested into features...
 | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						features[f] = s[f] | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						suggested[f] = (s[f] || []).slice() } }) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			sortExclusive(features) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 			return features } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		//--------------------- Globals: filtering / exclusive tags ---
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		var loops = [] | 
					
						
							|  |  |  | 		var disable_loops = [] | 
					
						
							|  |  |  | 		var disabled = [] | 
					
						
							|  |  |  | 		var missing = [] | 
					
						
							|  |  |  | 		var missing_suggested = [] | 
					
						
							|  |  |  | 		var suggests = {} | 
					
						
							|  |  |  | 		var suggested = {} | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// user filter...
 | 
					
						
							|  |  |  | 		// NOTE: we build this out of the full feature list...
 | 
					
						
							|  |  |  | 		disabled = disabled | 
					
						
							|  |  |  | 			.concat(isDisabled ? all.filter(isDisabled) : []) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// build exclusive groups...
 | 
					
						
							|  |  |  | 		// XXX need to sort the values to the same order as given features...
 | 
					
						
							|  |  |  | 		var rev_exclusive = {} | 
					
						
							|  |  |  | 		var exclusive = this.getExclusive('*', all, rev_exclusive, isDisabled) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		//-------------------------------- Stage 1: expand features ---
 | 
					
						
							|  |  |  | 		var features = expandFeatures(lst) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		//-------------------------------- Exclusive groups/aliases ---
 | 
					
						
							|  |  |  | 		// Handle exclusive feature groups and aliases...
 | 
					
						
							|  |  |  | 		var conflicts = {} | 
					
						
							|  |  |  | 		var done = [] | 
					
						
							|  |  |  | 		var to_remove = [] | 
					
						
							|  |  |  | 		Object.keys(features) | 
					
						
							|  |  |  | 			.forEach(function(f){ | 
					
						
							|  |  |  | 				// alias...
 | 
					
						
							|  |  |  | 				while(f in exclusive && done.indexOf(f) < 0){ | 
					
						
							|  |  |  | 					var candidates = (exclusive[f] || []) | 
					
						
							|  |  |  | 						.filter(function(c){ return c in features }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// resolve alias to non-included feature...
 | 
					
						
							|  |  |  | 					if(candidates.length == 0){ | 
					
						
							|  |  |  | 						var target = exclusive[f][0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						// expand target to features...
 | 
					
						
							|  |  |  | 						expandFeatures([target], features) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// link alias to existing feature...
 | 
					
						
							|  |  |  | 					} else { | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						var target = candidates[0] } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					// remove the alias...
 | 
					
						
							|  |  |  | 					// NOTE: exclusive tag can match a feature tag, thus
 | 
					
						
							|  |  |  | 					// 		we do not want to delete such tags...
 | 
					
						
							|  |  |  | 					// NOTE: we are not removing to_remove here as they may
 | 
					
						
							|  |  |  | 					// 		get added/expanded back in by other features...
 | 
					
						
							|  |  |  | 					!(f in that) | 
					
						
							|  |  |  | 						&& to_remove.push(f) | 
					
						
							|  |  |  | 					// replace dependencies...
 | 
					
						
							|  |  |  | 					Object.keys(features) | 
					
						
							|  |  |  | 						.forEach(function(e){ | 
					
						
							|  |  |  | 							var i = features[e] ? features[e].indexOf(f) : -1 | 
					
						
							|  |  |  | 							i >= 0 | 
					
						
							|  |  |  | 								&& features[e].splice(i, 1, target) | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 					f = target | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 					done.push(f) } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 				 | 
					
						
							|  |  |  | 				// exclusive feature...
 | 
					
						
							|  |  |  | 				if(f in rev_exclusive){ | 
					
						
							|  |  |  | 					// XXX handle multiple groups... (???)
 | 
					
						
							|  |  |  | 					var group = rev_exclusive[f] | 
					
						
							|  |  |  | 					var candidates = (exclusive[group] || []) | 
					
						
							|  |  |  | 						.filter(function(c){ return c in features }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					if(!(group in conflicts) && candidates.length > 1){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						conflicts[group] = candidates } } }) | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// cleanup...
 | 
					
						
							|  |  |  | 		to_remove.forEach(function(f){ delete features[f] }) | 
					
						
							|  |  |  | 		// resolve any exclusivity conflicts found...
 | 
					
						
							|  |  |  | 		var excluded = [] | 
					
						
							|  |  |  | 		Object.keys(conflicts) | 
					
						
							|  |  |  | 			.forEach(function(group){ | 
					
						
							|  |  |  | 				// XXX should this resolve to the last of the first feature???
 | 
					
						
							|  |  |  | 				excluded = excluded.concat(conflicts[group].slice(1))}) | 
					
						
							|  |  |  | 		disabled = disabled.concat(excluded) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		//--------------------------------------- Disabled features ---
 | 
					
						
							|  |  |  | 		// Handle disabled features and cleanup...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// reverse dependency index...
 | 
					
						
							|  |  |  | 		// 	...this is used to clear out orphaned features later and for
 | 
					
						
							|  |  |  | 		// 	introspection...
 | 
					
						
							|  |  |  | 		var rev_features = {} | 
					
						
							|  |  |  | 		Object.keys(features) | 
					
						
							|  |  |  | 			.forEach(function(f){ | 
					
						
							|  |  |  | 				(features[f] || []) | 
					
						
							|  |  |  | 					.forEach(function(d){  | 
					
						
							|  |  |  | 						rev_features[d] = (rev_features[d] || []).concat([f]) }) }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// clear dependency trees containing disabled features...
 | 
					
						
							|  |  |  | 		var suggested_clear = [] | 
					
						
							|  |  |  | 		do { | 
					
						
							|  |  |  | 			var expanded_disabled = false | 
					
						
							|  |  |  | 			disabled | 
					
						
							|  |  |  | 				.forEach(function(d){  | 
					
						
							|  |  |  | 					// disable all features that depend on a disabled feature...
 | 
					
						
							|  |  |  | 					Object.keys(features) | 
					
						
							|  |  |  | 						.forEach(function(f){  | 
					
						
							|  |  |  | 							if(features[f] | 
					
						
							|  |  |  | 									&& features[f].indexOf(d) >= 0 | 
					
						
							|  |  |  | 									&& disabled.indexOf(f) < 0){ | 
					
						
							|  |  |  | 								expanded_disabled = true | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 								disabled.push(f) } }) | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					// delete the feature itself...
 | 
					
						
							|  |  |  | 					var s = suggests[d] || [] | 
					
						
							|  |  |  | 					delete suggests[d] | 
					
						
							|  |  |  | 					delete features[d]  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// clear suggested...
 | 
					
						
							|  |  |  | 					s | 
					
						
							|  |  |  | 						.forEach(function(f){ | 
					
						
							|  |  |  | 							if(disabled.indexOf(f) < 0  | 
					
						
							|  |  |  | 									// not depended/suggested by any of 
 | 
					
						
							|  |  |  | 									// the non-disabled features...
 | 
					
						
							|  |  |  | 									&& Object.values(features) | 
					
						
							|  |  |  | 										.concat(Object.values(suggests)) | 
					
						
							|  |  |  | 											.filter(n => n.indexOf(f) >= 0) | 
					
						
							|  |  |  | 											.length == 0){ | 
					
						
							|  |  |  | 								expanded_disabled = true | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 								disabled.push(f) } }) }) | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		} while(expanded_disabled) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// remove orphaned features...
 | 
					
						
							|  |  |  | 		// ...an orphan is a feature included by a disabled feature...
 | 
					
						
							|  |  |  | 		// NOTE: this should take care of missing features too...
 | 
					
						
							|  |  |  | 		Object.keys(rev_features) | 
					
						
							|  |  |  | 			.filter(function(f){ | 
					
						
							|  |  |  | 				return rev_features[f] | 
					
						
							|  |  |  | 					// keep non-disabled and existing sources only...
 | 
					
						
							|  |  |  | 					.filter(function(e){  | 
					
						
							|  |  |  | 						return !(e in features) || disabled.indexOf(e) < 0 }) | 
					
						
							|  |  |  | 					// keep features that have no sources left, i.e. orphans...
 | 
					
						
							|  |  |  | 					.length == 0 }) | 
					
						
							|  |  |  | 			.forEach(function(f){ | 
					
						
							|  |  |  | 				console.log('ORPHANED:', f) | 
					
						
							|  |  |  | 				disabled.push(f) | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				delete features[f] }) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		//---------------------------------- Stage 2: sort features ---
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Prepare for sort: expand dependency list in features... 
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: this will expand lst in-place...
 | 
					
						
							|  |  |  | 		// NOTE: we are not checking for loops here -- mainly because
 | 
					
						
							|  |  |  | 		// 		the input is expected to be loop-free...
 | 
					
						
							|  |  |  | 		var expanddeps = function(lst, cur, seen){ | 
					
						
							|  |  |  | 			seen = seen || [] | 
					
						
							|  |  |  | 			if(features[cur] == null){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				return } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			// expand the dep list recursively...
 | 
					
						
							|  |  |  | 			// NOTE: this will expand features[cur] in-place while 
 | 
					
						
							|  |  |  | 			// 		iterating over it...
 | 
					
						
							|  |  |  | 			for(var i=0; i < features[cur].length; i++){ | 
					
						
							|  |  |  | 				var f = features[cur][i] | 
					
						
							|  |  |  | 				if(seen.indexOf(f) < 0){ | 
					
						
							|  |  |  | 					seen.push(f) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					expanddeps(features[cur], f, seen) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					features[cur].forEach(function(e){ | 
					
						
							|  |  |  | 						lst.indexOf(e) < 0 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 							&& lst.push(e) }) } } } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// do the actual expansion...
 | 
					
						
							|  |  |  | 		var list = Object.keys(features) | 
					
						
							|  |  |  | 		list.forEach(function(f){ expanddeps(list, f) }) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// sort by priority...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: this will attempt to only move features with explicitly 
 | 
					
						
							|  |  |  | 		// 		defined priorities and keep the rest in the same order 
 | 
					
						
							|  |  |  | 		// 		when possible...
 | 
					
						
							|  |  |  | 		list = list | 
					
						
							|  |  |  | 			// format: 
 | 
					
						
							|  |  |  | 			// 	[ <feature>, <index>, <priority> ]
 | 
					
						
							|  |  |  | 			.map(function(e, i){  | 
					
						
							|  |  |  | 				return [e, i, (that[e] && that[e].getPriority) ? that[e].getPriority() : 0 ] }) | 
					
						
							|  |  |  | 			.sort(function(a, b){  | 
					
						
							|  |  |  | 				return a[2] - b[2] || a[1] - b[1] }) | 
					
						
							|  |  |  | 			// cleanup...
 | 
					
						
							|  |  |  | 			.map(function(e){ return e[0] }) | 
					
						
							|  |  |  | 			// sort by the order features should be loaded...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 			.reverse() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// sort by dependency...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: this requires the list to be ordered from high to low 
 | 
					
						
							|  |  |  | 		// 		priority, i.e. the same order they should be loaded in...
 | 
					
						
							|  |  |  | 		// NOTE: dependency loops will throw this into and "infinite" loop...
 | 
					
						
							|  |  |  | 		var loop_limit = list.length + 1 | 
					
						
							|  |  |  | 		do { | 
					
						
							|  |  |  | 			var moves = 0 | 
					
						
							|  |  |  | 			if(list.length == 0){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				break } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			list | 
					
						
							|  |  |  | 				.slice() | 
					
						
							|  |  |  | 				.forEach(function(e){ | 
					
						
							|  |  |  | 					var deps = features[e] | 
					
						
							|  |  |  | 					if(!deps){ | 
					
						
							|  |  |  | 						return | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					var from = list.indexOf(e) | 
					
						
							|  |  |  | 					var to = list | 
					
						
							|  |  |  | 						.map(function(f, i){ return [f, i] }) | 
					
						
							|  |  |  | 						.slice(from+1) | 
					
						
							|  |  |  | 						// keep only dependencies...
 | 
					
						
							|  |  |  | 						.filter(function(f){ return deps.indexOf(f[0]) >= 0 }) | 
					
						
							|  |  |  | 						.pop() | 
					
						
							|  |  |  | 					if(to){ | 
					
						
							|  |  |  | 						// place after last dependency...
 | 
					
						
							|  |  |  | 						list.splice(to[1]+1, 0, e) | 
					
						
							|  |  |  | 						list.splice(from, 1) | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 						moves++ } }) | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			loop_limit-- | 
					
						
							|  |  |  | 		} while(moves > 0 && loop_limit > 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		//-------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// remove exclusivity tags that were resolved...
 | 
					
						
							|  |  |  | 		var isMissing = function(f){ | 
					
						
							|  |  |  | 			return !( | 
					
						
							|  |  |  | 				// feature is a resolvable exclusive tag...
 | 
					
						
							|  |  |  | 				(exclusive[f] || []).length > 0  | 
					
						
							|  |  |  | 				// feature was resolved...
 | 
					
						
							|  |  |  | 				&& exclusive[f] | 
					
						
							|  |  |  | 					.filter(function(f){ return list.indexOf(f) >= 0 }) | 
					
						
							|  |  |  | 					.length > 0) } | 
					
						
							|  |  |  | 		 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 		return { | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			input: lst, | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			features: list, | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			disabled: disabled, | 
					
						
							|  |  |  | 			excluded: excluded, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 			// errors and conflicts...
 | 
					
						
							|  |  |  | 			error: (loops.length > 0  | 
					
						
							|  |  |  | 					|| Object.keys(conflicts).length > 0  | 
					
						
							|  |  |  | 					|| loop_limit <= 0  | 
					
						
							|  |  |  | 					|| missing.length > 0 | 
					
						
							|  |  |  | 					|| missing_suggested.length > 0) ? | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					missing: missing.filter(isMissing), | 
					
						
							|  |  |  | 					missing_suggested: missing_suggested.filter(isMissing), | 
					
						
							|  |  |  | 					conflicts: conflicts, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// fatal stuff...
 | 
					
						
							|  |  |  | 					fatal: loops.length > 0 || loop_limit <= 0, | 
					
						
							|  |  |  | 					loops: loops, | 
					
						
							|  |  |  | 					sort_loop_overflow: loop_limit <= 0, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// introspection...
 | 
					
						
							|  |  |  | 			depends: features, | 
					
						
							|  |  |  | 			depended: rev_features, | 
					
						
							|  |  |  | 			suggests: suggests, | 
					
						
							|  |  |  | 			suggested: suggested, | 
					
						
							|  |  |  | 			//exclusive: exclusive,
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// Setup features...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Setup features on existing actions object...
 | 
					
						
							|  |  |  | 	//	.setup(actions, [feature-tag, ...])
 | 
					
						
							|  |  |  | 	//		-> actions
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Setup features on a new actions object...
 | 
					
						
							|  |  |  | 	//	.setup(feature-tag)
 | 
					
						
							|  |  |  | 	//	.setup([feature-tag, ...])
 | 
					
						
							|  |  |  | 	//		-> actions
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This will set .features on the object.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// .features format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		// the current feature set object...
 | 
					
						
							|  |  |  | 	// 		// XXX not sure about this -- revise...
 | 
					
						
							|  |  |  | 	// 		FeatureSet: feature-set,
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// 		// list of features not applicable in current context...
 | 
					
						
							|  |  |  | 	// 		//
 | 
					
						
							|  |  |  | 	// 		// i.e. the features that defined .isApplicable(..) and it 
 | 
					
						
							|  |  |  | 	// 		// returned false when called.
 | 
					
						
							|  |  |  | 	// 		unapplicable: [ feature-tag, .. ],
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// 		// output of .buildFeatureList(..)...
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// NOTE: this will store the build result in .features of the output 
 | 
					
						
							|  |  |  | 	// 		actions object.
 | 
					
						
							|  |  |  | 	// NOTE: .features is reset even if a FeatureLinearizationError error
 | 
					
						
							|  |  |  | 	// 		is thrown.
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	setup: function(obj, lst){ | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		// no explicit object is given...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 		if(lst == null){ | 
					
						
							|  |  |  | 			lst = obj | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 			obj = null } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 		obj = obj || (this.__actions__ || actions.Actions)() | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		lst = lst instanceof Array ? lst : [lst] | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		var unapplicable = [] | 
					
						
							|  |  |  | 		var features = this.buildFeatureList(lst,  | 
					
						
							|  |  |  | 			(function(n){ | 
					
						
							|  |  |  | 				// if we already tested unapplicable, no need to test again...
 | 
					
						
							|  |  |  | 				if(unapplicable.indexOf(n) >= 0){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 					return true } | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 				var f = this[n] | 
					
						
							|  |  |  | 				// check applicability if possible...
 | 
					
						
							|  |  |  | 				if(f && f.isApplicable && !f.isApplicable.call(this, obj)){ | 
					
						
							|  |  |  | 					unapplicable.push(n) | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 					return true } | 
					
						
							|  |  |  | 				return false }).bind(this))  | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		features.unapplicable = unapplicable  | 
					
						
							|  |  |  | 		// cleanup disabled -- filter out unapplicable and excluded features...
 | 
					
						
							|  |  |  | 		// NOTE: this is done mainly for cleaner and simpler reporting 
 | 
					
						
							|  |  |  | 		// 		later on...
 | 
					
						
							|  |  |  | 		features.disabled = features.disabled | 
					
						
							|  |  |  | 			.filter(function(n){  | 
					
						
							|  |  |  | 				return unapplicable.indexOf(n) < 0  | 
					
						
							|  |  |  | 					&& features.excluded.indexOf(n) < 0 }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// if we have critical errors and set verbose...
 | 
					
						
							|  |  |  | 		var fatal = features.error  | 
					
						
							|  |  |  | 			&& (features.error.loops.length > 0 || features.error.sort_loop_overflow) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// report stuff...
 | 
					
						
							|  |  |  | 		if(this.__verbose__){ | 
					
						
							|  |  |  | 			var error = features.error | 
					
						
							|  |  |  | 			// report dependency loops...
 | 
					
						
							|  |  |  | 			error.loops.length > 0 | 
					
						
							|  |  |  | 				&& error.loops | 
					
						
							|  |  |  | 					.forEach(function(loop){ | 
					
						
							|  |  |  | 						console.warn('Feature dependency loops detected:\n\t'  | 
					
						
							|  |  |  | 							+ loop.join('\n\t\t-> ')) }) | 
					
						
							|  |  |  | 			// report conflicts...
 | 
					
						
							|  |  |  | 			Object.keys(error.conflicts) | 
					
						
							|  |  |  | 				.forEach(function(group){ | 
					
						
							|  |  |  | 					console.error('Exclusive "'+ group +'" conflict at:', error.conflicts[group]) }) | 
					
						
							|  |  |  | 			// report loop limit...
 | 
					
						
							|  |  |  | 			error.sort_loop_overflow | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				&& console.error('Hit loop limit while sorting dependencies!') } | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		features.FeatureSet = this | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		obj.features = features | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// fatal error -- can't load...
 | 
					
						
							|  |  |  | 		if(fatal){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 			throw new FeatureLinearizationError(features) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// mixout everything...
 | 
					
						
							|  |  |  | 		this.remove(obj,  | 
					
						
							|  |  |  | 			obj.mro('tag') | 
					
						
							|  |  |  | 				.filter(function(e){  | 
					
						
							|  |  |  | 					return !!e })) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// do the setup...
 | 
					
						
							|  |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 		var setup = Feature.prototype.setup | 
					
						
							|  |  |  | 		features.features.forEach(function(n){ | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 			// setup...
 | 
					
						
							|  |  |  | 			if(that[n] != null){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				this.__verbose__  | 
					
						
							|  |  |  | 					&& console.log('Setting up feature:', n) | 
					
						
							|  |  |  | 				setup.call(that[n], obj) } }) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		return obj }, | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// XXX revise...
 | 
					
						
							|  |  |  | 	// 		...the main problem here is that .mixout(..) is accesible 
 | 
					
						
							|  |  |  | 	// 		directly from actions while the feature .remove(..) method
 | 
					
						
							|  |  |  | 	// 		is not...
 | 
					
						
							|  |  |  | 	// 		...would be nice to expose the API to actions directly or 
 | 
					
						
							|  |  |  | 	// 		keep it only in features...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 	remove: function(obj, lst){ | 
					
						
							|  |  |  | 		lst = lst.constructor !== Array ? [lst] : lst | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		lst.forEach(function(n){ | 
					
						
							|  |  |  | 			if(that[n] != null){ | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 				this.__verbose__  | 
					
						
							|  |  |  | 					&& console.log('Removing feature:', n) | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 				that[n].remove(obj) } }) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | 	// Generate a Graphviz graph from features...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX experimental...
 | 
					
						
							|  |  |  | 	gvGraph: function(lst, dep){ | 
					
						
							|  |  |  | 		lst = lst || this.features | 
					
						
							|  |  |  | 		dep = dep || this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var graph = '' | 
					
						
							|  |  |  | 		graph += 'digraph ImageGrid {\n' | 
					
						
							|  |  |  | 		lst | 
					
						
							|  |  |  | 			.filter(function(f){ return f in dep }) | 
					
						
							|  |  |  | 			.forEach(function(f){ | 
					
						
							|  |  |  | 				var deps = dep[f].depends || [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				deps.length > 0 ? | 
					
						
							|  |  |  | 					deps.forEach(function(d){ | 
					
						
							|  |  |  | 						graph += `\t"${f}" -> "${d}";\n` }) | 
					
						
							|  |  |  | 					: (graph += `\t"${f}";\n`) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		graph += '}' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:23:36 +03:00
										 |  |  | 		return graph }, | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | }) | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-02 00:55:14 +03:00
										 |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | // default feature set...
 | 
					
						
							| 
									
										
										
										
											2016-10-25 18:18:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | var Features = | 
					
						
							|  |  |  | module.Features = new FeatureSet() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                               */ return module }) |