mirror of
				https://github.com/flynx/pWiki.git
				synced 2025-10-28 01:20:07 +00:00 
			
		
		
		
	lots of tweaks and fixes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									7ce3e6f8bc
								
							
						
					
					
						commit
						e7a9610d81
					
				
							
								
								
									
										15
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								Makefile
									
									
									
									
									
								
							| @ -13,19 +13,25 @@ LOCAL_MODULES := \ | ||||
| 	node_modules/ig-actions/actions.js \
 | ||||
| 	node_modules/ig-features/features.js | ||||
| 
 | ||||
| EXT_MODULES := \
 | ||||
| 	$(wildcard node_modules/pouchdb/dist/*) \
 | ||||
| 	$(wildcard node_modules/jszip/dist/*) \
 | ||||
| 	$(wildcard node_modules/showdown/dist/*) | ||||
| 
 | ||||
| POUCH_DB := \
 | ||||
| 	$(wildcard node_modules/pouchdb/dist/*) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ext-lib/pouchdb.js: node_modules $(POUCH_DB) | ||||
| 	cp $(POUCH_DB) ext-lib/ | ||||
| lib/types: node_modules | ||||
| 	mkdir -p $@ | ||||
| 	cp node_modules/ig-types/*js $@ | ||||
| 
 | ||||
| 
 | ||||
| bootstrap.js: scripts/bootstrap.js $(BOOTSTRAP_FILES) | ||||
| 	node $< | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| .PHONY: bootstrap | ||||
| bootstrap: bootstrap.js | ||||
| 
 | ||||
| @ -34,8 +40,9 @@ node_modules: | ||||
| 	npm install | ||||
| 
 | ||||
| 
 | ||||
| dev: node_modules ext-lib/pouchdb.js $(LOCAL_MODULES) bootstrap | ||||
| dev: node_modules lib/types $(EXT_MODULES) $(LOCAL_MODULES) bootstrap | ||||
| 	cp $(LOCAL_MODULES) lib/ | ||||
| 	cp $(EXT_MODULES) ext-lib/ | ||||
| 
 | ||||
| 
 | ||||
| clean: | ||||
|  | ||||
| @ -52,6 +52,10 @@ pwiki.store.update('@pouch', { | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| // XXX
 | ||||
| typeof(Bootstrap) != 'undefined' | ||||
| 	&& pwiki.store.load(Bootstrap) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
|  | ||||
							
								
								
									
										11577
									
								
								ext-lib/jszip.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11577
									
								
								ext-lib/jszip.js
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14
									
								
								ext-lib/jszip.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								ext-lib/jszip.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										5157
									
								
								ext-lib/showdown.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										5157
									
								
								ext-lib/showdown.js
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1
									
								
								ext-lib/showdown.js.map
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								ext-lib/showdown.js.map
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										5
									
								
								ext-lib/showdown.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								ext-lib/showdown.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								ext-lib/showdown.min.js.map
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								ext-lib/showdown.min.js.map
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -29,12 +29,49 @@ var WIKIWORD_PATTERN = | ||||
| 		'\\\\?\\[[^\\]]+\\]', | ||||
| 	].join('|') +')', 'g') | ||||
| 
 | ||||
| // XXX REVISE...
 | ||||
| var setWikiWords =  | ||||
| module.setWikiWords = | ||||
| function(text, show_brackets=true, skip){ | ||||
| 	skip = skip ?? [] | ||||
| 	skip = skip instanceof Array ?  | ||||
| 		skip  | ||||
| 		: [skip] | ||||
| 	return text  | ||||
| 		// set new...
 | ||||
| 		.replace( | ||||
| 			WIKIWORD_PATTERN, | ||||
| 			function(l){ | ||||
| 				// check if WikiWord is escaped...
 | ||||
| 				if(l[0] == '\\'){ | ||||
| 					return l.slice(1) } | ||||
| 
 | ||||
| 				var path = l[0] == '[' ?  | ||||
| 					l.slice(1, -1)  | ||||
| 					: l | ||||
| 				var i = [].slice.call(arguments).slice(-2)[0] | ||||
| 
 | ||||
| 				// XXX HACK check if we are inside a tag...
 | ||||
| 				var rest = text.slice(i+1) | ||||
| 				if(rest.indexOf('>') < rest.indexOf('<')){ | ||||
| 					return l } | ||||
| 
 | ||||
| 				return skip.indexOf(l) < 0 ?  | ||||
| 					('<a ' | ||||
| 						+'class="wikiword" ' | ||||
| 						+'href="#'+ path +'" ' | ||||
| 						+'bracketed="'+ (show_brackets && l[0] == '[' ? 'yes' : 'no') +'" ' | ||||
| 						//+'onclick="event.preventDefault(); go($(this).attr(\'href\').slice(1))" '
 | ||||
| 						+'>' | ||||
| 							+ (!!show_brackets ? path : l)  | ||||
| 						+'</a>') | ||||
| 					: l })} | ||||
| 
 | ||||
| module.wikiWord =  | ||||
| Filter( | ||||
| 	{quote: 'quote-wikiword'}, | ||||
| 	function(source){ | ||||
| 		// XXX
 | ||||
| 		return source }) | ||||
| 		return setWikiWords(source) }) | ||||
| module.quoteWikiWord =  | ||||
| function(source){ | ||||
| 	// XXX
 | ||||
|  | ||||
| @ -8,6 +8,7 @@ | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var base = require('./base') | ||||
| var showdown = require('showdown') | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| @ -16,8 +17,12 @@ module.markdown = | ||||
| base.Filter( | ||||
| 	{quote: 'quote-markdown'}, | ||||
| 	function(source){ | ||||
| 		// XXX
 | ||||
| 		return source }) | ||||
| 		var converter = new showdown.Converter({ | ||||
| 			strikethrough: true, | ||||
| 			tables: true, | ||||
| 			tasklists: true, | ||||
| 		}) | ||||
| 		return converter.makeHtml(source) }) | ||||
| 
 | ||||
| module.quoteMarkdown =  | ||||
| function(source){ | ||||
|  | ||||
| @ -217,7 +217,11 @@ function(obj){ | ||||
| 		// NOTE: we just created func(..) so no need to sanitize it, the 
 | ||||
| 		// 		only potential vector of atack (AFAIK) here is name and 
 | ||||
| 		// 		that is checked above...
 | ||||
| 		Object.defineProperty(func, 'name', {value: name}) | ||||
| 		func = eval('('+  | ||||
| 			func | ||||
| 				.toString() | ||||
| 				.replace(/function\(/, `function ${name}(`) +')') | ||||
| 		//Object.defineProperty(func, 'name', {value: name})
 | ||||
| 		/* XXX NAME... | ||||
| 		//func.name = name
 | ||||
| 		func.name != name | ||||
| @ -916,28 +920,15 @@ function Constructor(name, a, b, c){ | ||||
| 		obj.__init__ instanceof Function | ||||
| 			&& obj.__init__(...arguments) | ||||
| 		return obj } | ||||
| 
 | ||||
| 	// constructor naming...
 | ||||
| 	// rename the consructor...
 | ||||
| 	// NOTE: we are not using:
 | ||||
| 	//			Object.defineProperty(_constructor, 'name', { value: name })
 | ||||
| 	//		because this does not affect the name displayed by the Chrome
 | ||||
| 	//		DevTools. FF does not seem to care about either version of code...
 | ||||
| 	Object.defineProperty(_constructor, 'name', {value: name}) | ||||
| 	/* XXX NAME... | ||||
| 	//_constructor.name = name
 | ||||
| 	// just in case the browser/node refuses to change the name, we'll make
 | ||||
| 	// them a different offer ;)
 | ||||
| 	// NOTE: it is not possible to abstract this eval(..) into something 
 | ||||
| 	// 		like renameFunction(..) as reconstructing the function will
 | ||||
| 	// 		lose it's closure that we depend on here...
 | ||||
| 	// NOTE: this eval(..) should not be a risk as its inputs are
 | ||||
| 	// 		static and never infuenced by external inputs...
 | ||||
| 	_constructor.name != name | ||||
| 		&& (_constructor = eval('('+  | ||||
| 			_constructor | ||||
| 				.toString() | ||||
| 				.replace(/Constructor/g, name) +')')) | ||||
| 	//*/
 | ||||
| 	//		because this does not affect the name displayed by the DevTools.
 | ||||
| 	_constructor = eval('('+  | ||||
| 		_constructor | ||||
| 			.toString() | ||||
| 			.replace(/Constructor/g, name) +')') | ||||
| 
 | ||||
| 	// set .toString(..)...
 | ||||
| 	// NOTE: this test is here to enable mixinFlat(..) to overwrite 
 | ||||
| 	// 		.toString(..) below...
 | ||||
| @ -1309,6 +1300,8 @@ Constructor('Mixin', { | ||||
| 		// NOTE: we do not need to configure this any more, .defineProperty(..)
 | ||||
| 		// 		merges the descriptor into the original keeping any values not
 | ||||
| 		// 		explicitly overwritten...
 | ||||
| 		// XXX is this effective???
 | ||||
| 		// 		...will this show up in DevTools???
 | ||||
| 		Object.defineProperty(this, 'name', { value: name }) | ||||
| 		// create/merge .data...
 | ||||
| 		this.data = this.constructor.mixinFlat({},  | ||||
|  | ||||
							
								
								
									
										546
									
								
								lib/types/Array.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										546
									
								
								lib/types/Array.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,546 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| * XXX move .zip(..) here from diff.js | ||||
| * XXX do we need .at(..) / .to(..) methods here and in Map/Set/...??? | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| var stoppable = require('ig-stoppable') | ||||
| 
 | ||||
| var generator = require('./generator') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| // NOTE: this is used in a similar fashion to Python's StopIteration...
 | ||||
| var STOP = | ||||
| module.STOP = | ||||
| 	stoppable.STOP | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // Mixins...
 | ||||
| 
 | ||||
| // Wrap .map(..) / .filter(..) / .reduce(..) / .. to support STOP...
 | ||||
| // 
 | ||||
| // NOTE: internally these are implemented as for-of loops (./generator.js)
 | ||||
| var stoppableList = function(iter){ | ||||
| 	return function(func){ | ||||
| 		return [...this.iter()[iter](...arguments)] } } | ||||
| var stoppableValue = function(iter, no_return=false){ | ||||
| 	return function(func){ | ||||
| 		var res = this.iter()[iter](...arguments)  | ||||
| 		return no_return ? | ||||
| 			undefined | ||||
| 			: res } } | ||||
| 
 | ||||
| 
 | ||||
| // Equivalent to .map(..) / .filter(..) / .reduce(..) that process the 
 | ||||
| // contents in chunks asynchronously...
 | ||||
| //
 | ||||
| //	.mapChunks(func)
 | ||||
| //	.mapChunks(chunk_size, func)
 | ||||
| //	.mapChunks([item_handler, chunk_handler])
 | ||||
| //	.mapChunks(chunk_size, [item_handler, chunk_handler])
 | ||||
| //		-> promise(list)
 | ||||
| //	
 | ||||
| //	.filterChunks(func)
 | ||||
| //	.filterChunks(chunk_size, func)
 | ||||
| //	.filterChunks([item_handler, chunk_handler])
 | ||||
| //	.filterChunks(chunk_size, [item_handler, chunk_handler])
 | ||||
| //		-> promise(list)
 | ||||
| //	
 | ||||
| //	.reduceChunks(func, res)
 | ||||
| //	.reduceChunks(chunk_size, func, res)
 | ||||
| //	.reduceChunks([item_handler, chunk_handler], res)
 | ||||
| //	.reduceChunks(chunk_size, [item_handler, chunk_handler], res)
 | ||||
| //		-> promise(res)
 | ||||
| //
 | ||||
| //
 | ||||
| //	chunk_handler(chunk, result, offset)
 | ||||
| //
 | ||||
| //
 | ||||
| // chunk_size can be:
 | ||||
| // 	20			- chunk size
 | ||||
| // 	'20'		- chunk size
 | ||||
| // 	'20C'		- number of chunks
 | ||||
| //
 | ||||
| //
 | ||||
| // STOP can be thrown in func or chunk_handler at any time to 
 | ||||
| // abort iteration, this will resolve the promise.
 | ||||
| //	
 | ||||
| //
 | ||||
| // The main goal of this is to not block the runtime while processing a 
 | ||||
| // very long array by interrupting the processing with a timeout...
 | ||||
| //
 | ||||
| // XXX should these return a partial result on STOP?
 | ||||
| // XXX add generators:
 | ||||
| // 			.map(..) / .filter(..) / .reduce(..)
 | ||||
| // 		...the basis here should be the chunks, i.e. each cycle should
 | ||||
| // 		go through a chunk...
 | ||||
| //		...the mixin can be generic, i.e. applicable to Array, and 
 | ||||
| //		other stuff...
 | ||||
| // XXX add time-based chunk iteration...
 | ||||
| var makeChunkIter = function(iter, wrapper){ | ||||
| 	wrapper = wrapper | ||||
| 		|| function(res, func, array, e){ | ||||
| 			return func.call(this, e[1], e[0], array) } | ||||
| 	return function(size, func, ...rest){ | ||||
| 		var that = this | ||||
| 		var args = [...arguments] | ||||
| 		size = (args[0] instanceof Function  | ||||
| 				|| args[0] instanceof Array) ?  | ||||
| 			(this.CHUNK_SIZE || 50) | ||||
| 			: args.shift() | ||||
| 		size = typeof(size) == typeof('str') ? | ||||
| 				// number of chunks...
 | ||||
| 				(size.trim().endsWith('c') || size.trim().endsWith('C') ? | ||||
| 				 	Math.round(this.length / (parseInt(size) || 1)) || 1 | ||||
| 				: parseInt(size)) | ||||
| 			: size | ||||
| 		var postChunk | ||||
| 		func = args.shift() | ||||
| 		;[func, postChunk] = func instanceof Array ? func : [func] | ||||
| 		rest = args | ||||
| 
 | ||||
| 		// special case...
 | ||||
| 		// no need to setTimeout(..) if smaller than size...
 | ||||
| 		if(this.length <= size){ | ||||
| 			try { | ||||
| 				// handle iteration...
 | ||||
| 				var res = this[iter](func, ...rest) | ||||
| 				// handle chunk...
 | ||||
| 				postChunk | ||||
| 					&& postChunk.call(this, this, res, 0)  | ||||
| 				return Promise.all(res)  | ||||
| 			// handle STOP...
 | ||||
| 			} catch(err){ | ||||
| 				if(err === STOP){ | ||||
| 					return Promise.resolve()  | ||||
| 				} else if( err instanceof STOP){ | ||||
| 					return Promise.resolve(err.value) } | ||||
| 				throw err } } | ||||
| 
 | ||||
| 		var res = [] | ||||
| 		var _wrapper = wrapper.bind(this, res, func, this) | ||||
| 
 | ||||
| 		return new Promise(function(resolve, reject){ | ||||
| 				var next = function(chunks){ | ||||
| 					setTimeout(function(){ | ||||
| 						var chunk, val | ||||
| 						try { | ||||
| 							// handle iteration...
 | ||||
| 							res.push( | ||||
| 								val = (chunk = chunks.shift()) | ||||
| 									[iter](_wrapper, ...rest)) | ||||
| 							// handle chunk...
 | ||||
| 							postChunk | ||||
| 								&& postChunk.call(that,  | ||||
| 									chunk.map(function([i, v]){ return v }),  | ||||
| 									val, | ||||
| 									chunk[0][0]) | ||||
| 						// handle STOP...
 | ||||
| 						} catch(err){ | ||||
| 							if(err === STOP){ | ||||
| 								return resolve()  | ||||
| 							} else if( err instanceof STOP){ | ||||
| 								return resolve(err.value) } | ||||
| 							throw err } | ||||
| 
 | ||||
| 						// stop condition...
 | ||||
| 						chunks.length == 0 ? | ||||
| 							resolve(res.flat(2)) | ||||
| 							: next(chunks) }, 0) } | ||||
| 				next(that | ||||
| 					// split the array into chunks...
 | ||||
| 					.reduce(function(res, e, i){ | ||||
| 						var c = res.slice(-1)[0] | ||||
| 						c.length >= size ? | ||||
| 							// initial element in chunk...
 | ||||
| 							res.push([[i, e]]) | ||||
| 							// rest...
 | ||||
| 							: c.push([i, e]) | ||||
| 						return res }, [[]])) }) } } | ||||
| 
 | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | ||||
| 
 | ||||
| var ArrayMixin = | ||||
| module.ArrayMixin = | ||||
| object.Mixin('ArrayMixin', 'soft', { | ||||
| 	STOP: object.STOP, | ||||
| 
 | ||||
| 	// 	zip(array, array, ...)
 | ||||
| 	// 		-> [[item, item, ...], ...]
 | ||||
| 	//
 | ||||
| 	// 	zip(func, array, array, ...)
 | ||||
| 	// 		-> [func(i, [item, item, ...]), ...]
 | ||||
| 	//
 | ||||
| 	zip: function(func, ...arrays){ | ||||
| 		var i = arrays[0] instanceof Array ?  | ||||
| 			0  | ||||
| 			: arrays.shift() | ||||
| 		if(func instanceof Array){ | ||||
| 			arrays.splice(0, 0, func) | ||||
| 			func = null } | ||||
| 		// build the zip item...
 | ||||
| 		// NOTE: this is done this way to preserve array sparseness...
 | ||||
| 		var s = arrays | ||||
| 			.reduce( | ||||
| 				function(res, a, j){ | ||||
| 					//a.length > i
 | ||||
| 					i in a | ||||
| 						&& (res[j] = a[i]) | ||||
| 					return res },  | ||||
| 				new Array(arrays.length)) | ||||
| 		return arrays | ||||
| 				// check that at least one array is longer than i...
 | ||||
| 				.reduce(function(res, a){  | ||||
| 					return Math.max(res, i, a.length) }, 0) > i ? | ||||
| 			// collect zip item...
 | ||||
| 			[func ? func(i, s) : s] | ||||
| 				// get next...
 | ||||
| 				.concat(this.zip(func, i+1, ...arrays)) | ||||
| 			// done...
 | ||||
| 			: [] }, | ||||
| 
 | ||||
| 	// XXX not sure about the handler API here yet... 
 | ||||
| 	iter: function*(lst=[], handler=undefined){ | ||||
| 		yield* lst.iter( | ||||
| 			...(handler ?  | ||||
| 				[handler]  | ||||
| 				: [])) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| var ArrayProtoMixin = | ||||
| module.ArrayProtoMixin = | ||||
| object.Mixin('ArrayProtoMixin', 'soft', { | ||||
| 
 | ||||
| 	// A faster version of .indexOf(..)
 | ||||
| 	//
 | ||||
| 	// NOTE: this is not faster when looking for an item not in this,
 | ||||
| 	// 		for some reason the native .includes(..) and .indexOf(..)
 | ||||
| 	// 		search for non existant elements about an order of magnirude 
 | ||||
| 	// 		faster than if it existed...
 | ||||
| 	// 		...the funny thing is that at least on Crome .lastIndexOf(..)
 | ||||
| 	// 		is about as fast as this for an item in about the same relative 
 | ||||
| 	// 		location...
 | ||||
| 	// NOTE: this will get depricated as soon as JS redoes its .indexOf(..)
 | ||||
| 	index: function(value){ | ||||
| 		for(var i = 0; i < this.length && this[i] !== value; i++){} | ||||
| 		return i == this.length ? -1 : i }, | ||||
| 
 | ||||
| 
 | ||||
| 	// first/last element access short-hands...
 | ||||
| 	//
 | ||||
| 	//	.first()
 | ||||
| 	//	.last()
 | ||||
| 	//		-> elem
 | ||||
| 	//
 | ||||
| 	//	.first(value)
 | ||||
| 	//	.last(value)
 | ||||
| 	//		-> array
 | ||||
| 	//
 | ||||
| 	// NOTE: setting a value will overwrite an existing first/last value.
 | ||||
| 	// NOTE: for an empty array both .first(..)/.last(..) will return undefined 
 | ||||
| 	// 		when getting a value and set the 0'th value when setting...
 | ||||
| 	// NOTE: decided to keep these as methods and not props because methods
 | ||||
| 	// 		have one advantage: they can be chained
 | ||||
| 	// 		...while you can't chain assignment unless you wrap it in .run(..)
 | ||||
| 	first: function(value){ | ||||
| 		return arguments.length > 0 ? | ||||
| 			((this[0] = value), this) | ||||
| 			: this[0]}, | ||||
| 	last: function(value){ | ||||
| 		return arguments.length > 0 ? | ||||
| 			((this[Math.max(this.length - 1, 0)] = value), this) | ||||
| 			: this[this.length - 1]}, | ||||
| 
 | ||||
| 	// Roll left/right (in-place)...
 | ||||
| 	//
 | ||||
| 	// NOTE: to .rol(..) left just pass a negative n value...
 | ||||
| 	// NOTE: we can't use ...[..] for sparse arrays as the will expand undefined
 | ||||
| 	// 		inplace of empty positions, this is thereason the .splice(..) 
 | ||||
| 	// 		implementation was replaced by a less clear (but faster)
 | ||||
| 	// 		.copyWithin(..) version...
 | ||||
| 	rol: function(n=1){ | ||||
| 		var l = this.length | ||||
| 		n = (n >= 0 ?  | ||||
| 				n  | ||||
| 				: l - n) | ||||
| 			% l | ||||
| 		if(n != 0){ | ||||
| 			this.length += n | ||||
| 			this.copyWithin(l, 0, n) | ||||
| 			this.splice(0, n) } | ||||
| 		return this }, | ||||
| 
 | ||||
| 	// Compact a sparse array...
 | ||||
| 	//
 | ||||
| 	// NOTE: this will not compact in-place.
 | ||||
| 	compact: function(){ | ||||
| 		return this | ||||
| 			.filter(function(){ return true }) }, | ||||
| 
 | ||||
| 	// Remove sprse slots form start/end/both ends of array...
 | ||||
| 	//
 | ||||
| 	trim: function(){ | ||||
| 		var l = this.length | ||||
| 		var i = 0 | ||||
| 		while(!(i in this) && i < l){ i++ } | ||||
| 		var j = 0 | ||||
| 		while(!(l-j-1 in this) && j < l){ j++ } | ||||
| 		return this.slice(i, j == 0 ? l : -j) }, | ||||
| 	trimStart: function(){ | ||||
| 		var l = this.length | ||||
| 		var i = 0 | ||||
| 		while(!(i in this) && i < l){ i++ } | ||||
| 		return this.slice(i) }, | ||||
| 	trimEnd: function(){ | ||||
| 		var l = this.length | ||||
| 		var j = 0 | ||||
| 		while(!(l-j-1 in this) && j < l){ j++ } | ||||
| 		return this.slice(0, j == 0 ? l : -j) }, | ||||
| 
 | ||||
| 	// like .length but for sparse arrays will return the element count...
 | ||||
| 	get len(){ | ||||
| 		// NOTE: if we don't do .slice() here this can count array 
 | ||||
| 		// 		instance attributes...
 | ||||
| 		// NOTE: .slice() has an added menifit here of removing any 
 | ||||
| 		// 		attributes from the count...
 | ||||
| 		return Object.keys(this.slice()).length }, | ||||
| 
 | ||||
| 	// Return a new array with duplicate elements removed...
 | ||||
| 	//
 | ||||
| 	// NOTE: order is preserved... 
 | ||||
| 	unique: function(normalize){ | ||||
| 		return normalize ?  | ||||
| 			[...new Map(this | ||||
| 					.map(function(e){  | ||||
| 						return [normalize(e), e] })) | ||||
| 				.values()] | ||||
| 			// NOTE: we are calling .compact() here to avoid creating 
 | ||||
| 			// 		undefined items from empty slots in sparse arrays...
 | ||||
| 			: [...new Set(this.compact())] }, | ||||
| 	tailUnique: function(normalize){ | ||||
| 		return this | ||||
| 			.slice() | ||||
| 			.reverse() | ||||
| 			.unique(normalize) | ||||
| 			.reverse() }, | ||||
| 
 | ||||
| 	// Compare two arrays...
 | ||||
| 	//
 | ||||
| 	// NOTE: this is diffectent from Object.match(..) in that this compares 
 | ||||
| 	// 		self to other (internal) while match compares two entities 
 | ||||
| 	// 		externally.
 | ||||
| 	// 		XXX not sure if we need the destinction in name, will have to 
 | ||||
| 	// 			come back to this when refactoring diff.js -- all three 
 | ||||
| 	// 			have to be similar...
 | ||||
| 	cmp: function(other){ | ||||
| 		if(this === other){ | ||||
| 			return true } | ||||
| 		if(this.length != other.length){ | ||||
| 			return false } | ||||
| 		for(var i=0; i<this.length; i++){ | ||||
| 			if(this[i] != other[i]){ | ||||
| 				return false } } | ||||
| 		return true }, | ||||
| 
 | ||||
| 	// Compare two Arrays as sets...
 | ||||
| 	//
 | ||||
| 	// NOTE: this will ignore order and repeating elments...
 | ||||
| 	setCmp: function(other){ | ||||
| 		return this === other  | ||||
| 			|| (new Set([...this, ...other])).length  | ||||
| 				== (new Set(this)).length }, | ||||
| 
 | ||||
| 	// Sort as the other array...
 | ||||
| 	//
 | ||||
| 	// 	Sort as array placing the sorted items at head...
 | ||||
| 	// 	.sortAs(array)
 | ||||
| 	// 	.sortAs(array, 'head')
 | ||||
| 	// 		-> sorted
 | ||||
| 	//
 | ||||
| 	// 	Sort as array placing the sorted items at tail...
 | ||||
| 	// 	.sortAs(array, 'tail')
 | ||||
| 	// 		-> sorted
 | ||||
| 	//
 | ||||
| 	// This will sort the intersecting items in the head keeping the rest 
 | ||||
| 	// of the items in the same relative order...
 | ||||
| 	//
 | ||||
| 	// NOTE: if an item is in the array multiple times only the first 
 | ||||
| 	// 		index is used...
 | ||||
| 	//
 | ||||
| 	// XXX should this extend/patch .sort(..)???
 | ||||
| 	// 		...currently do not see a clean way to do this without 
 | ||||
| 	// 		extending and replacing Array or directly re-wrapping 
 | ||||
| 	// 		.sort(..)...
 | ||||
| 	sortAs: function(other, place='head'){ | ||||
| 		place = place == 'tail' ? -1 : 1  | ||||
| 		// NOTE: the memory overhead here is better than the time overhead 
 | ||||
| 		// 		when using .indexOf(..)...
 | ||||
| 		other = other.toMap() | ||||
| 		var orig = this.toMap() | ||||
| 		return this.sort(function(a, b){ | ||||
| 			var i = other.get(a) | ||||
| 			var j = other.get(b) | ||||
| 			return i == null && j == null ? | ||||
| 					orig.get(a) - orig.get(b) | ||||
| 				: i == null ?  | ||||
| 					place | ||||
| 				: j == null ?  | ||||
| 					-place | ||||
| 				: i - j }) }, | ||||
| 
 | ||||
| 	/*/ XXX EXPERIMENTAL... | ||||
| 	//		...if this is successful then merge this with .sortAs(..)
 | ||||
| 	sort: function(cmp){ | ||||
| 		return (arguments.length == 0 || typeof(cmp) == 'function') ? | ||||
| 			object.parentCall(ArrayProtoMixin.data.sort, this, ...arguments) | ||||
| 			: this.sortAs(...arguments) }, | ||||
| 	//*/
 | ||||
| 
 | ||||
| 	// Same as .sortAs(..) but will not change indexes of items not in other...
 | ||||
| 	//
 | ||||
| 	// Example:
 | ||||
| 	// 		['a', 3, 'b', 1, 2, 'c']
 | ||||
| 	// 			.inplaceSortAs([1, 2, 3, 3]) // -> ['a', 1, 'b', 2, 3, 'c']
 | ||||
| 	//
 | ||||
| 	inplaceSortAs: function(other){ | ||||
| 		// sort only the intersection...
 | ||||
| 		var sorted = this | ||||
| 			.filter(function(e){  | ||||
| 				return other.includes(e) }) | ||||
| 			.sortAs(other) | ||||
| 		// "zip" the sorted items back into this...
 | ||||
| 		this.forEach(function(e, i, l){ | ||||
| 			other.includes(e)  | ||||
| 				&& (l[i] = sorted.shift()) }) | ||||
| 		return this }, | ||||
| 
 | ||||
| 	// Convert an array to object...
 | ||||
| 	//
 | ||||
| 	// Format:
 | ||||
| 	// 	{
 | ||||
| 	// 		<item>: <index>,
 | ||||
| 	// 		...
 | ||||
| 	// 	}
 | ||||
| 	//
 | ||||
| 	// NOTE: items should be strings, other types will get converted to 
 | ||||
| 	// 		strings and thus may mess things up.
 | ||||
| 	// NOTE: this will forget repeating items...
 | ||||
| 	// NOTE: normalize will slow things down...
 | ||||
| 	toKeys: function(normalize){ | ||||
| 		return normalize ?  | ||||
| 			this.reduce(function(r, e, i){ | ||||
| 				r[normalize(e, i)] = i | ||||
| 				return r }, {}) | ||||
| 			: this.reduce(function(r, e, i){ | ||||
| 				r[e] = i | ||||
| 				return r }, {}) }, | ||||
| 
 | ||||
| 	// Convert an array to a map...
 | ||||
| 	//
 | ||||
| 	// This is similar to Array.prototype.toKeys(..) but does not 
 | ||||
| 	// restrict value type to string.
 | ||||
| 	//
 | ||||
| 	// Format:
 | ||||
| 	// 	Map([
 | ||||
| 	// 		[<item>, <index>],
 | ||||
| 	// 		...
 | ||||
| 	// 	])
 | ||||
| 	//
 | ||||
| 	// NOTE: this will forget repeating items...
 | ||||
| 	// NOTE: normalize will slow things down...
 | ||||
| 	toMap: function(normalize){ | ||||
| 		return normalize ?  | ||||
| 			this | ||||
| 				.reduce(function(m, e, i){ | ||||
| 					m.set(normalize(e, i), i) | ||||
| 					return m }, new Map()) | ||||
| 			: this | ||||
| 				.reduce(function(m, e, i){ | ||||
| 					m.set(e, i) | ||||
| 					return m }, new Map()) }, | ||||
| 
 | ||||
| 	// XXX would be nice for this to use the instance .zip(..) in recursion...
 | ||||
| 	// 		...this might be done by reversign the current implementation, i.e.
 | ||||
| 	// 		for instance .zip(..) to be the main implementation and for 
 | ||||
| 	// 		Array.zip(..) to be a proxy to that...
 | ||||
| 	zip: function(func, ...arrays){ | ||||
| 		return func instanceof Array ? | ||||
| 			this.constructor.zip(this, func, ...arrays) | ||||
| 			: this.constructor.zip(func, this, ...arrays) }, | ||||
| 
 | ||||
| 	// get iterator over array...
 | ||||
| 	//
 | ||||
| 	//	Array.iter()
 | ||||
| 	//	Array.iter([ .. ])
 | ||||
| 	//		-> iterator
 | ||||
| 	//
 | ||||
| 	//	array.iter()
 | ||||
| 	//		-> iterator
 | ||||
| 	//
 | ||||
| 	// XXX should this take an argument and be like map??
 | ||||
| 	// XXX this should handle throwing STOP!!!
 | ||||
| 	// 		...might also ne a good idea to isolate the STOP mechanics 
 | ||||
| 	// 		into a spearate module/package...
 | ||||
| 	iter: stoppable(function*(handler=undefined){ | ||||
| 		if(handler){ | ||||
| 			var i = 0 | ||||
| 			for(var e of this){ | ||||
| 				var res = handler.call(this, e, i++)  | ||||
| 				// treat non-iterables as single elements...
 | ||||
| 				if(typeof(res) == 'object'  | ||||
| 						&& Symbol.iterator in res){ | ||||
| 					yield* res | ||||
| 				} else { | ||||
| 					yield res } }  | ||||
| 		} else { | ||||
| 			yield* this }}), | ||||
| 
 | ||||
| 
 | ||||
| 	// Stoppable iteration...
 | ||||
| 	//
 | ||||
| 	// NOTE: internally these are generators...
 | ||||
| 	smap: stoppableList('map'), | ||||
| 	sfilter: stoppableList('filter'), | ||||
| 	sreduce: stoppableValue('reduce'), | ||||
| 	sreduceRight: stoppableValue('reduceRight'), | ||||
| 	sforEach: stoppableValue('map', true), | ||||
| 
 | ||||
| 	// Chunk iteration...
 | ||||
| 	//
 | ||||
| 	CHUNK_SIZE: 50, | ||||
| 	mapChunks: makeChunkIter('map'), | ||||
| 	filterChunks: makeChunkIter('map',  | ||||
| 		function(res, func, array, e){ | ||||
| 			return !!func.call(this, e[1], e[0], array) ? [e[1]] : [] }), | ||||
| 	reduceChunks: makeChunkIter('reduce', | ||||
| 		function(total, func, array, res, e){ | ||||
| 			return func.call(this,  | ||||
| 				total.length > 0 ?  | ||||
| 					total.pop()  | ||||
| 					: res,  | ||||
| 				e[1], e[0], array) }), | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| ArrayMixin(Array) | ||||
| ArrayProtoMixin(Array.prototype) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										147
									
								
								lib/types/Date.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										147
									
								
								lib/types/Date.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,147 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var DateMixin = | ||||
| module.DateMixin = | ||||
| object.Mixin('DateMixin', 'soft', { | ||||
| 	timeStamp: function(...args){ | ||||
| 		return (new this()).getTimeStamp(...args) }, | ||||
| 	fromTimeStamp: function(ts){ | ||||
| 		return (new this()).setTimeStamp(ts) }, | ||||
| 
 | ||||
| 	// convert string time period to milliseconds...
 | ||||
| 	str2ms: function(str, dfl){ | ||||
| 		dfl = dfl || 'ms' | ||||
| 
 | ||||
| 		// number -- dfl unit...
 | ||||
| 		if(typeof(str) == typeof(123)){ | ||||
| 			var val = str | ||||
| 			str = dfl | ||||
| 
 | ||||
| 		// 00:00:00:00:000 format...
 | ||||
| 		} else if(str.includes(':')){ | ||||
| 			var units = str.split(/\s*:\s*/g).reverse() | ||||
| 			// parse units...
 | ||||
| 			var ms = units[0].length == 3 ? | ||||
| 				parseFloat(units.shift() || 0) | ||||
| 				: 0 | ||||
| 			var [s=0, m=0, h=0, d=0] = units | ||||
| 			// merge...
 | ||||
| 			return ((((parseFloat(d || 0)*24  | ||||
| 				+ parseFloat(h || 0))*60  | ||||
| 					+ parseFloat(m || 0))*60  | ||||
| 						+ parseFloat(s || 0))*1000 + ms) | ||||
| 
 | ||||
| 		// 00sec format...
 | ||||
| 		} else { | ||||
| 			var val = parseFloat(str) | ||||
| 			str = str.trim() | ||||
| 			// check if a unit is given...
 | ||||
| 			str = str == val ?  | ||||
| 				dfl  | ||||
| 				: str } | ||||
| 
 | ||||
| 		// NOTE: this is a small hack to avoid overcomplicating the 
 | ||||
| 		// 		pattern to still match the passed dfl unit...
 | ||||
| 		str = ' '+str | ||||
| 		var c =  | ||||
| 			(/[^a-z](m(illi)?(-)?s(ec(ond(s)?)?)?)$/i.test(str) | ||||
| 		   			|| /^([0-9]*\.)?[0-9]+$/.test(str) ) ?  | ||||
| 				1 | ||||
| 			: /[^a-z]s(ec(ond(s)?)?)?$/i.test(str) ?  | ||||
| 				1000 | ||||
| 			: /[^a-z]m(in(ute(s)?)?)?$/i.test(str) ?  | ||||
| 				1000*60 | ||||
| 			: /[^a-z]h(our(s)?)?$/i.test(str) ?  | ||||
| 				1000*60*60 | ||||
| 			: /[^a-z]d(ay(s)?)?$/i.test(str) ?  | ||||
| 				1000*60*60*24 | ||||
| 			: null | ||||
| 
 | ||||
| 		return c ?  | ||||
| 			val * c  | ||||
| 			: NaN }, | ||||
| 
 | ||||
| 	isPeriod: function(str){ | ||||
| 		return !isNaN(this.str2ms(str)) }, | ||||
| 	isDateStr: function(str){ | ||||
| 		return !isNaN(new Date(str).valueOf()) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| // XXX should this be flat???
 | ||||
| var DateProtoMixin = | ||||
| module.DateProtoMixin = | ||||
| object.Mixin('DateProtoMixin', 'soft', { | ||||
| 	toShortDate: function(show_ms){ | ||||
| 		return ''  | ||||
| 			+ this.getFullYear() | ||||
| 			+'-'+ ('0'+(this.getMonth()+1)).slice(-2) | ||||
| 			+'-'+ ('0'+this.getDate()).slice(-2) | ||||
| 			+' '+ ('0'+this.getHours()).slice(-2) | ||||
| 			+':'+ ('0'+this.getMinutes()).slice(-2) | ||||
| 			+':'+ ('0'+this.getSeconds()).slice(-2) | ||||
| 			+ (show_ms ?  | ||||
| 				':'+(('000'+this.getMilliseconds()).slice(-3)) | ||||
| 				: '') }, | ||||
| 	getTimeStamp: function(show_ms){ | ||||
| 		return ''  | ||||
| 			+ this.getFullYear() | ||||
| 			+ ('0'+(this.getMonth()+1)).slice(-2) | ||||
| 			+ ('0'+this.getDate()).slice(-2) | ||||
| 			+ ('0'+this.getHours()).slice(-2) | ||||
| 			+ ('0'+this.getMinutes()).slice(-2) | ||||
| 			+ ('0'+this.getSeconds()).slice(-2) | ||||
| 			+ (show_ms ?  | ||||
| 				('000'+this.getMilliseconds()).slice(-3) | ||||
| 				: '') }, | ||||
| 	setTimeStamp: function(ts){ | ||||
| 		ts = ts.replace(/[^0-9]*/g, '') | ||||
| 		this.setFullYear(ts.slice(0, 4)) | ||||
| 		this.setMonth(ts.slice(4, 6)*1-1) | ||||
| 		this.setDate(ts.slice(6, 8)) | ||||
| 		this.setHours(ts.slice(8, 10)) | ||||
| 		this.setMinutes(ts.slice(10, 12)) | ||||
| 		this.setSeconds(ts.slice(12, 14)) | ||||
| 		this.setMilliseconds(ts.slice(14, 17) || 0) | ||||
| 		return this }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| // NOTE: repatching a date should not lead to any side effects as this
 | ||||
| // 		does not add any state...
 | ||||
| // NOTE: this is done differently as there are contexts where there may 
 | ||||
| // 		be multiple Date objects in different contexts (nw/electron/..)
 | ||||
| var patchDate = | ||||
| module.patchDate =  | ||||
| function(date){ | ||||
| 	date = date || Date | ||||
| 	DateMixin(date) | ||||
| 	DateProtoMixin(date.prototype) | ||||
| 	return date } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| // patch the root date...
 | ||||
| patchDate() | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										18
									
								
								lib/types/Function.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								lib/types/Function.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,18 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| var AsyncFunction = | ||||
| module.AsyncFunction = | ||||
| 	(async function(){}).constructor | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										70
									
								
								lib/types/Map.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										70
									
								
								lib/types/Map.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,70 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| var MapProtoMixin = | ||||
| module.MapProtoMixin = | ||||
| object.Mixin('MapProtoMixin', 'soft', { | ||||
| 	iter: function*(){ | ||||
| 		for(var e of this){ | ||||
| 			yield e } }, | ||||
| 
 | ||||
| 	// NOTE: we do not touch .__keys here as no renaming is ever done...
 | ||||
| 	//
 | ||||
| 	// XXX this essentially rewrites the whole map, is there a faster/better 
 | ||||
| 	// 		way to do this???
 | ||||
| 	// 		...one way would be to decouple order from the container, i.e.
 | ||||
| 	// 		store the order in a separate attr/prop but this would require
 | ||||
| 	// 		a whole new set of ordered "type" that would overload every single
 | ||||
| 	// 		iteration method, not sure if this is a good idea untill we 
 | ||||
| 	// 		reach a state whe JS "shuffles" (index-orders) its containers 
 | ||||
| 	// 		(a-la Python)
 | ||||
| 	sort: function(keys){ | ||||
| 		keys = (typeof(keys) == 'function'  | ||||
| 				|| keys === undefined) ? | ||||
| 			[...this.keys()].sort(keys) | ||||
| 			: keys | ||||
| 		var del = this.delete.bind(this) | ||||
| 		var set = this.set.bind(this) | ||||
| 		new Set([...keys, ...this.keys()]) | ||||
| 			.forEach(function(k){ | ||||
| 				if(this.has(k)){ | ||||
| 					var v = this.get(k) | ||||
| 					del(k) | ||||
| 					set(k, v) } }.bind(this)) | ||||
| 		return this }, | ||||
| 
 | ||||
| 	replaceKey: function(old, key, ordered=true){ | ||||
| 		if(!this.has(old)){ | ||||
| 			return this } | ||||
| 		if(ordered){ | ||||
| 			var order = [...this.keys()] | ||||
| 			order[order.lastIndexOf(old)] = key } | ||||
| 		// replace...
 | ||||
| 		var value = this.get(old) | ||||
| 		this.delete(old) | ||||
| 		this.set(key, value) | ||||
| 		ordered | ||||
| 			&& this.sort(order) | ||||
| 		return this }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| MapProtoMixin(Map.prototype) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										101
									
								
								lib/types/Object.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										101
									
								
								lib/types/Object.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,101 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| * XXX shoule we add these from object to Object? | ||||
| * 		- .parent(..) | ||||
| * 		- .parentProperty(..) | ||||
| * 		- .parentCall(..) | ||||
| * 		- .parentOf(..) | ||||
| * 		- .childOf(..) | ||||
| * 		- .related(..) | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| require('object-run') | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var ObjectMixin = | ||||
| module.ObjectMixin = | ||||
| object.Mixin('ObjectMixin', 'soft', { | ||||
| 	// stuff from object.js...
 | ||||
| 	deepKeys: object.deepKeys, | ||||
| 
 | ||||
| 	match: object.match, | ||||
| 	matchPartial: object.matchPartial, | ||||
| 
 | ||||
| 	/* XXX not yet sure about these... | ||||
| 	// XXX EXPERIMENTAL...
 | ||||
| 	parent : object.parent, | ||||
| 	parentProperty: object.parentProperty, | ||||
| 	parentCall: object.parentCall, | ||||
| 
 | ||||
| 	parentOf: object.parentOf, | ||||
| 	childOf: object.childOf, | ||||
| 	related: object.related, | ||||
| 	//*/
 | ||||
| 
 | ||||
| 
 | ||||
| 	// Make a copy of an object...
 | ||||
| 	//
 | ||||
| 	// This will:
 | ||||
| 	// 	- create a new object linked to the same prototype chain as obj
 | ||||
| 	// 	- copy obj own state
 | ||||
| 	//
 | ||||
| 	// NOTE: this will copy prop values and not props...
 | ||||
| 	copy: function(obj, constructor){ | ||||
| 		return Object.assign( | ||||
| 			constructor == null ? | ||||
| 				Object.create(obj.__proto__) | ||||
| 				: constructor(), | ||||
| 			obj) }, | ||||
| 
 | ||||
| 	// Make a full key set copy of an object...
 | ||||
| 	//
 | ||||
| 	// NOTE: this will copy prop values and not props...
 | ||||
| 	// NOTE: this will not deep-copy the values...
 | ||||
| 	flatCopy: function(obj, constructor){ | ||||
| 		return Object.deepKeys(obj) | ||||
| 			.reduce( | ||||
| 				function(res, key){ | ||||
| 					res[key] = obj[key]  | ||||
| 					return res }, | ||||
| 				constructor == null ? | ||||
| 					//Object.create(obj.__proto__)
 | ||||
| 					{} | ||||
| 					: constructor()) }, | ||||
| 
 | ||||
| 	// XXX for some reason neumric keys do not respect order...
 | ||||
| 	// 		to reproduce:
 | ||||
| 	// 			Object.keys({a:0, x:1, 10:2, 0:3, z:4, ' 1 ':5})
 | ||||
| 	// 			// -> ["0", "10", "a", "x", "z", " 1 "]
 | ||||
| 	// 		...this is the same across Chrome and Firefox...
 | ||||
| 	sort: function(obj, keys){ | ||||
| 		keys = (typeof(keys) == 'function' | ||||
| 				|| keys === undefined) ?  | ||||
| 			[...Object.keys(obj)].sort(keys) | ||||
| 			: keys | ||||
| 		new Set([...keys, ...Object.keys(obj)]) | ||||
| 			.forEach(function(k){ | ||||
| 				if(k in obj){ | ||||
| 					var v = Object.getOwnPropertyDescriptor(obj, k) | ||||
| 					delete obj[k] | ||||
| 					Object.defineProperty(obj, k, v) } }) | ||||
| 		return obj }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| ObjectMixin(Object) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										798
									
								
								lib/types/Promise.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										798
									
								
								lib/types/Promise.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,798 @@ | ||||
| /********************************************************************** | ||||
| * | ||||
| * This defines the following extensions to Promise: | ||||
| * | ||||
| * 	Promise.iter(seq) | ||||
| * 	<promise>.iter() | ||||
| * 		Iterable promise object. | ||||
| * 		Similar to Promise.all(..) but adds basic iterator API. | ||||
| * | ||||
| * 	Promise.interactive(handler) | ||||
| * 		Interactive promise object. | ||||
| * 		This adds a basic message passing API to the promise. | ||||
| * | ||||
| * 	Promise.cooperative() | ||||
| * 		Cooperative promise object. | ||||
| * 		Exposes the API to resolve/reject the promise object  | ||||
| * 		externally. | ||||
| * | ||||
| * 	<promise>.as(obj) | ||||
| * 		Promise proxy. | ||||
| * 		Proxies the methods available from obj to promise value. | ||||
| * | ||||
| * | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| //var generator = require('./generator')
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // Iterable promise...
 | ||||
| // 
 | ||||
| // Like Promise.all(..) but adds ability to iterate through results
 | ||||
| // via generators .map(..)/.reduce(..) and friends...
 | ||||
| // 
 | ||||
| // NOTE: the following can not be implemented here:
 | ||||
| // 			.splice(..)				- can't both modify and return
 | ||||
| // 									  a result...
 | ||||
| // 			.pop() / .shift()		- can't modify the promise, use 
 | ||||
| // 									  .first() / .last() instead.
 | ||||
| // 			[Symbol.iterator]()		- needs to be sync and we can't
 | ||||
| // 									  know the number of elements to
 | ||||
| // 									  return promises before the whole
 | ||||
| // 									  iterable promise is resolved.
 | ||||
| // NOTE: we are not using async/await here as we need to control the 
 | ||||
| // 		type of promise returned in cases where we know we are returning 
 | ||||
| // 		an array...
 | ||||
| // NOTE: there is no point in implementing a 1:1 version of this that 
 | ||||
| // 		would not support element expansion/contraction as it would only 
 | ||||
| // 		simplify a couple of methods that are 1:1 (like .map(..) and 
 | ||||
| // 		.some(..)) while methods like .filter(..) will throw everything
 | ||||
| // 		back to the complex IterablePromise...
 | ||||
| // 		
 | ||||
| // XXX how do we handle errors/rejections???
 | ||||
| // 		...mostly the current state is OK, but need more testing...
 | ||||
| // XXX add support for async generators...
 | ||||
| // 		
 | ||||
| 
 | ||||
| var iterPromiseProxy =  | ||||
| module.iterPromiseProxy =  | ||||
| function(name){ | ||||
| 	return function(...args){ | ||||
| 		return this.constructor( | ||||
| 			this.then(function(lst){ | ||||
| 				return lst[name](...args) })) } } | ||||
| var promiseProxy = | ||||
| module.promiseProxy = | ||||
| function(name){ | ||||
| 	return async function(...args){ | ||||
| 		return (await this)[name](...args) } } | ||||
| 
 | ||||
| var IterablePromise = | ||||
| module.IterablePromise = | ||||
| object.Constructor('IterablePromise', Promise, { | ||||
| 	get STOP(){ | ||||
| 		return Array.STOP }, | ||||
| 
 | ||||
| }, { | ||||
| 	// packed array...
 | ||||
| 	//
 | ||||
| 	// Holds promise state.
 | ||||
| 	//
 | ||||
| 	// Format:
 | ||||
| 	// 	[
 | ||||
| 	//		<non-array-value>,
 | ||||
| 	//		[ <value> ],
 | ||||
| 	//		<promise>,
 | ||||
| 	//		...
 | ||||
| 	// 	]
 | ||||
| 	//
 | ||||
| 	// This format has several useful features:
 | ||||
| 	// 	- concatenating packed lists results in a packed list
 | ||||
| 	// 	- adding an iterable promise (as-is) into a packed list results 
 | ||||
| 	// 		in a packed list
 | ||||
| 	//
 | ||||
| 	// NOTE: in general iterable promises are implicitly immutable, so
 | ||||
| 	// 		it is not recomended to ever edit this in-place...
 | ||||
| 	// NOTE: we are not isolating or "protecting" any internals to 
 | ||||
| 	// 		enable users to responsibly extend the code.
 | ||||
| 	__packed: null, | ||||
| 
 | ||||
| 	// low-level .__packed handlers/helpers...
 | ||||
| 	//
 | ||||
| 	// NOTE: these can be useful for debugging and extending...
 | ||||
| 	//
 | ||||
| 	// pack and oprionally transform/handle an array (sync)...
 | ||||
| 	//
 | ||||
| 	// NOTE: if 'types/Array' is imported this will support throwing STOP
 | ||||
| 	// 		from the handler.
 | ||||
| 	// 		Due to the async nature of promises though the way stops are 
 | ||||
| 	// 		handled may be unpredictable -- the handlers can be run out 
 | ||||
| 	// 		of order, as the nested promises resolve and thus throwing 
 | ||||
| 	// 		stop will stop the handlers not yet run and not the next 
 | ||||
| 	// 		handlers in sequence.
 | ||||
| 	// 		XXX EXPEREMENTAL: STOP...
 | ||||
| 	// XXX add support for async generators...
 | ||||
| 	// 		...an async generator is not "parallel", i.e. intil one 
 | ||||
| 	// 		returned promise is resolved the generator blocks (will not 
 | ||||
| 	// 		advance)...
 | ||||
| 	// 		...can we feed out a results one by one???
 | ||||
| 	__pack: function(list, handler=undefined){ | ||||
| 		var that = this | ||||
| 		// handle iterable promise list...
 | ||||
| 		if(list instanceof IterablePromise){ | ||||
| 			return this.__handle(list.__packed, handler) } | ||||
| 		// handle promise list...
 | ||||
| 		if(list instanceof Promise){ | ||||
| 			return list.then(function(list){ | ||||
| 				return that.__pack(list, handler) }) } | ||||
| 		// do the work...
 | ||||
| 		// NOTE: packing and handling are mixed here because it's faster
 | ||||
| 		// 		to do them both on a single list traverse...
 | ||||
| 		var handle = !!handler | ||||
| 		handler = handler  | ||||
| 			?? function(elem){  | ||||
| 				return [elem] } | ||||
| 
 | ||||
| 		//* XXX EXPEREMENTAL: STOP...
 | ||||
| 		var stoppable = false | ||||
| 		var stop = false | ||||
| 		var map = 'map' | ||||
| 		var pack = function(){ | ||||
| 			return [list].flat() | ||||
| 				[map](function(elem){ | ||||
| 					return elem && elem.then ? | ||||
| 							(stoppable ? | ||||
| 								// stoppable -- need to handle stop async...
 | ||||
| 								elem | ||||
| 									.then(function(res){ | ||||
| 										return !stop ? | ||||
| 											handler(res) | ||||
| 											: [] })  | ||||
| 									// NOTE: we are using .catch(..) here
 | ||||
| 									// 		instead of directly passing the
 | ||||
| 									// 		error handler to be able to catch
 | ||||
| 									// 		the STOP from the handler...
 | ||||
| 									.catch(handleSTOP) | ||||
| 								// non-stoppable...
 | ||||
| 								: elem.then(handler)) | ||||
| 						: elem instanceof Array ? | ||||
| 							handler(elem) | ||||
| 						// NOTE: we keep things that do not need protecting 
 | ||||
| 						// 		from .flat() as-is...
 | ||||
| 						: !handle ? | ||||
| 							elem | ||||
| 						: handler(elem) }) } | ||||
| 
 | ||||
| 		// pack (stoppable)...
 | ||||
| 		if(!!this.constructor.STOP){ | ||||
| 			stoppable = true | ||||
| 			map = 'smap' | ||||
| 			var handleSTOP = function(err){ | ||||
| 				stop = err | ||||
| 				if(err === that.constructor.STOP | ||||
| 						|| err instanceof that.constructor.STOP){ | ||||
| 					return 'value' in err ? | ||||
| 						err.value | ||||
| 						: [] } | ||||
| 				throw err } | ||||
| 			try{ | ||||
| 				return pack() | ||||
| 			}catch(err){ | ||||
| 				return handleSTOP(err) } } | ||||
| 
 | ||||
| 		// pack (non-stoppable)...
 | ||||
| 		return pack() }, | ||||
| 		/*/ | ||||
| 		return [list].flat() | ||||
| 			.map(function(elem){ | ||||
| 				return elem && elem.then ? | ||||
| 						//that.__pack(elem, handler)
 | ||||
| 						elem.then(handler) | ||||
| 					: elem instanceof Array ? | ||||
| 						handler(elem) | ||||
| 					// NOTE: we keep things that do not need protecting 
 | ||||
| 					// 		from .flat() as-is...
 | ||||
| 					: !handle ? | ||||
| 						elem | ||||
| 					: handler(elem) }) }, | ||||
| 		//*/
 | ||||
| 	// transform/handle packed array (sync)...
 | ||||
| 	__handle: function(list, handler=undefined){ | ||||
| 		var that = this | ||||
| 		if(typeof(list) == 'function'){ | ||||
| 			handler = list | ||||
| 			list = this.__packed } | ||||
| 		if(!handler){ | ||||
| 			return list } | ||||
| 		// handle promise list...
 | ||||
| 		if(list instanceof Promise){ | ||||
| 			return list.then(function(list){ | ||||
| 				return that.__handle(list, handler) }) } | ||||
| 		// do the work...
 | ||||
| 		// NOTE: since each section of the packed .__array is the same 
 | ||||
| 		// 		structure as the input we'll use .__pack(..) to handle 
 | ||||
| 		// 		them, this also keeps all the handling code in one place.
 | ||||
| 		//* XXX EXPEREMENTAL: STOP...
 | ||||
| 		var map = !!this.constructor.STOP ? | ||||
| 			'smap' | ||||
| 			: 'map' | ||||
| 		return list[map](function(elem){ | ||||
| 		/*/ | ||||
| 		return list.map(function(elem){ | ||||
| 		//*/
 | ||||
| 			return elem instanceof Array ? | ||||
| 					that.__pack(elem, handler) | ||||
| 				: elem instanceof Promise ? | ||||
| 					that.__pack(elem, handler) | ||||
| 						//.then(function(elem){
 | ||||
| 						.then(function([elem]){ | ||||
| 							return elem }) | ||||
| 				: [handler(elem)] }) | ||||
|    			.flat() }, | ||||
| 	// unpack array (async)...
 | ||||
| 	__unpack: async function(list){ | ||||
| 		list = list  | ||||
| 			?? this.__packed | ||||
| 		// handle promise list...
 | ||||
| 		return list instanceof Promise ? | ||||
| 			this.__unpack(await list) | ||||
| 			// do the work...
 | ||||
| 			: (await Promise.all(list)) | ||||
| 				.flat() }, | ||||
| 
 | ||||
| 
 | ||||
| 	// iterator methods...
 | ||||
| 	//
 | ||||
| 	// These will return a new IterablePromise instance...
 | ||||
| 	//
 | ||||
| 	// NOTE: these are different to Array's equivalents in that the handler
 | ||||
| 	// 		is called not in the order of the elements but rather in order 
 | ||||
| 	// 		of promise resolution...
 | ||||
| 	// NOTE: index of items is unknowable because items can expand and
 | ||||
| 	// 		contract depending on handlers (e.g. .filter(..) can remove 
 | ||||
| 	// 		items)...
 | ||||
| 	map: function(func){ | ||||
| 		return this.constructor(this,  | ||||
| 			function(e){ | ||||
| 				var res = func(e) | ||||
| 				return res instanceof Promise ? | ||||
| 		   			res.then(function(e){  | ||||
| 						return [e] }) | ||||
| 					: [res] }) }, | ||||
| 	filter: function(func){ | ||||
| 		return this.constructor(this,  | ||||
| 			function(e){ | ||||
| 				var res = func(e) | ||||
| 				var _filter = function(elem){ | ||||
| 					return res ? | ||||
| 						[elem] | ||||
| 						: [] } | ||||
| 				return res instanceof Promise ? | ||||
| 					res.then(_filter) | ||||
| 					: _filter(e) }) }, | ||||
| 	// NOTE: this does not return an iterable promise as we can't know 
 | ||||
| 	// 		what the user reduces to...
 | ||||
| 	// NOTE: the items can be handled out of order because the nested 
 | ||||
| 	// 		promises can resolve in any order...
 | ||||
| 	// NOTE: since order of execution can not be guaranteed there is no
 | ||||
| 	// 		point in implementing .reduceRight(..) in the same way 
 | ||||
| 	// 		(see below)...
 | ||||
| 	reduce: function(func, res){ | ||||
| 		return this.constructor(this,  | ||||
| 				function(e){ | ||||
| 					res = func(res, e) | ||||
| 					return [] }) | ||||
| 			.then(function(){  | ||||
| 				return res }) }, | ||||
| 
 | ||||
| 	// XXX .chain(..) -- see generator.chain(..)
 | ||||
| 
 | ||||
| 	flat: function(depth=1){ | ||||
| 		return this.constructor(this,  | ||||
| 			function(e){  | ||||
| 				return (depth > 1  | ||||
| 							&& e != null  | ||||
| 							&& e.flat) ?  | ||||
| 						e.flat(depth-1)  | ||||
| 					: depth != 0 ? | ||||
| 						e | ||||
| 					: [e] }) }, | ||||
| 	reverse: function(){ | ||||
| 		var lst = this.__packed | ||||
| 		return this.constructor( | ||||
| 			lst instanceof Promise ? | ||||
| 				lst.then(function(elems){ | ||||
| 					return elems instanceof Array ? | ||||
| 						elems.slice() | ||||
| 							.reverse() | ||||
| 						: elems }) | ||||
| 			: lst | ||||
| 				.map(function(elems){ | ||||
| 					return elems instanceof Array ? | ||||
| 							elems.slice() | ||||
| 								.reverse() | ||||
| 						: elems instanceof Promise ? | ||||
| 							elems.then(function(elems){ | ||||
| 								return elems.reverse() }) | ||||
| 						: elems }) | ||||
| 				.reverse(), | ||||
| 			'raw') }, | ||||
| 
 | ||||
| 	// NOTE: the following methods can create an unresolved promise from 
 | ||||
| 	// 		a resolved promise...
 | ||||
| 	concat: function(other){ | ||||
| 		var that = this | ||||
| 		var cur = this.__pack(this) | ||||
| 		var other = this.__pack(other) | ||||
| 		return this.constructor( | ||||
| 			// NOTE: we need to keep things as exposed as possible, this 
 | ||||
| 			// 		is why we're not blanketing all the cases with 
 | ||||
| 			// 		Promise.all(..)...
 | ||||
| 			(cur instanceof Promise  | ||||
| 					&& other instanceof Promise) ? | ||||
| 				[cur, other] | ||||
| 			: cur instanceof Promise ? | ||||
| 				[cur, ...other] | ||||
| 			: other instanceof Promise ? | ||||
| 				[...cur, other] | ||||
| 			: [...cur, ...other], | ||||
| 			'raw') }, | ||||
| 	push: function(elem){ | ||||
| 		return this.concat([elem]) }, | ||||
| 	unshift: function(elem){ | ||||
| 		return this.constructor([elem]) | ||||
| 			.concat(this) }, | ||||
| 
 | ||||
| 	// proxy methods...
 | ||||
| 	//
 | ||||
| 	// These require the whole promise to resolve to trigger.
 | ||||
| 	//
 | ||||
| 	// An exception to this would be .at(0)/.first() and .at(-1)/.last()
 | ||||
| 	// that can get the target element if it's accessible.
 | ||||
| 	//
 | ||||
| 	// NOTE: methods that are guaranteed to return an array will return
 | ||||
| 	// 		an iterable promise (created with iterPromiseProxy(..))...
 | ||||
| 	//
 | ||||
| 	at: async function(i){ | ||||
| 		var list = this.__packed | ||||
| 		return ((i != 0 && i != -1) | ||||
| 					|| list instanceof Promise | ||||
| 					// XXX not sure if this is correct...
 | ||||
| 					|| list.at(i) instanceof Promise) ? | ||||
| 				(await this).at(i) | ||||
| 			// NOTE: we can only reason about first/last explicit elements, 
 | ||||
| 			// 		anything else is non-deterministic...
 | ||||
| 			: list.at(i) instanceof Promise ? | ||||
| 				[await list.at(i)].flat().at(i) | ||||
| 			: list.at(i) instanceof Array ? | ||||
| 				list.at(i).at(i) | ||||
| 			: list.at(i) }, | ||||
| 	first: function(){ | ||||
| 		return this.at(0) }, | ||||
| 	last: function(){ | ||||
| 		return this.at(-1) }, | ||||
| 	 | ||||
| 	// NOTE: unlike .reduce(..) this needs the parent fully resolved 
 | ||||
| 	// 		to be able to iterate from the end.
 | ||||
| 	// XXX is it faster to do .reverse().reduce(..) ???
 | ||||
| 	reduceRight: promiseProxy('reduceRight'), | ||||
| 
 | ||||
| 	// NOTE: there is no way we can do a sync generator returning 
 | ||||
| 	// 		promises for values because any promise in .__packed makes 
 | ||||
| 	// 		the value count/index non-deterministic...
 | ||||
| 	sort: iterPromiseProxy('sort'), | ||||
| 	slice: iterPromiseProxy('slice'), | ||||
| 
 | ||||
| 	entries: iterPromiseProxy('entries'), | ||||
| 	keys: iterPromiseProxy('keys'), | ||||
| 	values: iterPromiseProxy('values'), | ||||
| 
 | ||||
| 	indexOf: promiseProxy('indexOf'), | ||||
| 	lastIndexOf: promiseProxy('lastIndexOf'), | ||||
| 	includes: promiseProxy('includes'), | ||||
| 
 | ||||
| 	//
 | ||||
| 	// 	.find(<func>)
 | ||||
| 	// 	.find(<func>, 'value')
 | ||||
| 	// 		-> <promise>(<value>)
 | ||||
| 	//
 | ||||
| 	// 	.find(<func>, 'result')
 | ||||
| 	// 		-> <promise>(<result>)
 | ||||
| 	//
 | ||||
| 	// 	.find(<func>, 'bool')
 | ||||
| 	// 		-> <promise>(<bool>)
 | ||||
| 	//
 | ||||
| 	// NOTE: this is slightly different to Array's .find(..) in that it 
 | ||||
| 	// 		accepts the result value enabling returning both the value 
 | ||||
| 	// 		itself ('value', default), the test function's result 
 | ||||
| 	// 		('result') or true/false ('bool') -- this is added to be 
 | ||||
| 	// 		able to distinguish between the undefined as a stored value 
 | ||||
| 	// 		and undefined as a "nothing found" result.
 | ||||
| 	// NOTE: I do not get how essentially identical methods .some(..) 
 | ||||
| 	// 		and .find(..) got added to JS's Array...
 | ||||
| 	// 		the only benefit is that .some(..) handles undefined values 
 | ||||
| 	// 		stored in the array better...
 | ||||
| 	// NOTE: this will return the result as soon as it's available but 
 | ||||
| 	// 		it will not stop the created but unresolved at the time 
 | ||||
| 	// 		promises from executing, this is both good and bad:
 | ||||
| 	// 		+ it will not break other clients waiting for promises 
 | ||||
| 	// 			to resolve...
 | ||||
| 	// 		- if no clients are available this can lead to wasted 
 | ||||
| 	// 			CPU time...
 | ||||
| 	find: async function(func, result='value'){ | ||||
| 		var that = this | ||||
| 		// NOTE: not using pure await here as this is simpler to actually 
 | ||||
| 		// 		control the moment the resulting promise resolves without 
 | ||||
| 		// 		the need for juggling state...
 | ||||
| 		return new Promise(function(resolve, reject){ | ||||
| 			var resolved = false | ||||
| 			that.map(function(elem){ | ||||
| 					var res = func(elem) | ||||
| 					if(res){ | ||||
| 						resolved = true | ||||
| 						resolve( | ||||
| 							result == 'bool' ? | ||||
| 								true | ||||
| 							: result == 'result' ? | ||||
| 								res | ||||
| 							: elem) | ||||
| 						// XXX EXPEREMENTAL: STOP...
 | ||||
| 						// NOTE: we do not need to throw STOP here 
 | ||||
| 						// 		but it can prevent some overhead...
 | ||||
| 						if(that.constructor.STOP){ | ||||
| 							throw that.constructor.STOP } } }) | ||||
| 				.then(function(){ | ||||
| 					resolved | ||||
| 						|| resolve( | ||||
| 							result == 'bool' ? | ||||
| 								false | ||||
| 								: undefined) }) }) }, | ||||
| 	findIndex: promiseProxy('findIndex'), | ||||
| 
 | ||||
| 	// NOTE: this is just a special-case of .find(..)
 | ||||
| 	some: async function(func){ | ||||
| 		return this.find(func, 'bool') }, | ||||
| 	every: promiseProxy('every'), | ||||
| 
 | ||||
| 
 | ||||
| 	join: async function(){ | ||||
| 		return [...(await this)] | ||||
| 			.join(...arguments) }, | ||||
| 
 | ||||
| 
 | ||||
| 	// this is defined globally as Promise.prototype.iter(..)
 | ||||
| 	//
 | ||||
| 	// for details see: PromiseMixin(..) below...
 | ||||
| 	//iter: function(handler=undefined){ ... },
 | ||||
| 
 | ||||
| 
 | ||||
| 	// promise api...
 | ||||
| 	//
 | ||||
| 	// Overload .then(..), .catch(..) and .finally(..) to return a plain 
 | ||||
| 	// Promise instnace...
 | ||||
| 	//
 | ||||
| 	// NOTE: .catch(..) and .finally(..) are implemented through .then(..)
 | ||||
| 	// 		so we do not need to overload those...
 | ||||
| 	// NOTE: this is slightly different from .then(..) in that it can be 
 | ||||
| 	// 		called without arguments and return a promise wrapper. This can
 | ||||
| 	// 		be useful to hide special promise functionality...
 | ||||
| 	then: function (onfulfilled, onrejected){ | ||||
| 		var p = new Promise( | ||||
| 			function(resolve, reject){ | ||||
| 				Promise.prototype.then.call(this, | ||||
| 					// NOTE: resolve(..) / reject(..) return undefined so
 | ||||
| 					// 		we can't pass them directly here...
 | ||||
| 					function(res){  | ||||
| 						resolve(res) | ||||
| 						return res }, | ||||
| 					function(res){ | ||||
| 						reject(res) | ||||
| 						return res }) }.bind(this)) | ||||
| 		return arguments.length > 0 ? | ||||
| 			p.then(...arguments)  | ||||
| 			: p }, | ||||
| 
 | ||||
| 
 | ||||
| 	// constructor...
 | ||||
| 	//
 | ||||
| 	//	Promise.iter([ .. ])
 | ||||
| 	//		-> iterable-promise
 | ||||
| 	//
 | ||||
| 	//	Promise.iter([ .. ], handler)
 | ||||
| 	//		-> iterable-promise
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	// 	handler(e)
 | ||||
| 	// 		-> [value, ..]
 | ||||
| 	// 		-> []
 | ||||
| 	// 		-> <promise>
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	// NOTE: element index is unknowable until the full list is expanded
 | ||||
| 	// 		as handler(..)'s return value can expand to any number of 
 | ||||
| 	// 		items...
 | ||||
| 	// 		XXX we can make the index a promise, then if the client needs
 | ||||
| 	// 			the value they can wait for it...
 | ||||
| 	// 			...this may be quite an overhead...
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	// Special cases useful for extending this constructor...
 | ||||
| 	//
 | ||||
| 	//	Set raw .__packed without any pre-processing...
 | ||||
| 	//	Promise.iter([ .. ], 'raw')
 | ||||
| 	//		-> iterable-promise
 | ||||
| 	//
 | ||||
| 	//	Create a rejected iterator...
 | ||||
| 	//	Promise.iter(false)
 | ||||
| 	//		-> iterable-promise
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	// NOTE: if 'types/Array' is imported this will support throwing STOP,
 | ||||
| 	// 		for more info see notes for .__pack(..)
 | ||||
| 	// 		XXX EXPEREMENTAL: STOP...
 | ||||
| 	__new__: function(_, list, handler){ | ||||
| 		// instance...
 | ||||
| 		var promise | ||||
| 		var obj = Reflect.construct( | ||||
| 			IterablePromise.__proto__,  | ||||
| 			[function(resolve, reject){ | ||||
| 				// NOTE: this is here for Promise compatibility...
 | ||||
| 				if(typeof(list) == 'function'){ | ||||
| 					return list.call(this, ...arguments) }  | ||||
| 				// initial reject... 
 | ||||
| 				if(list === false){ | ||||
| 					return reject() } | ||||
| 				promise = {resolve, reject} }],  | ||||
| 			IterablePromise) | ||||
| 
 | ||||
| 		// populate new instance...
 | ||||
| 		if(promise){ | ||||
| 			// handle/pack input data...
 | ||||
| 			if(handler != 'raw'){ | ||||
| 				list = list instanceof IterablePromise ? | ||||
| 					this.__handle(list.__packed, handler) | ||||
| 					: this.__pack(list, handler) } | ||||
| 			Object.defineProperty(obj, '__packed', { | ||||
| 				value: list, | ||||
| 				enumerable: false, | ||||
| 			}) | ||||
| 			// handle promise state...
 | ||||
| 			this.__unpack(list) | ||||
| 				.then(function(list){ | ||||
| 					promise.resolve(list) }) | ||||
| 				.catch(promise.reject) } | ||||
| 
 | ||||
| 		return obj }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // Interactive promise...
 | ||||
| // 
 | ||||
| // Adds ability to send messages to the running promise.
 | ||||
| // 
 | ||||
| 
 | ||||
| var InteractivePromise = | ||||
| module.InteractivePromise = | ||||
| object.Constructor('InteractivePromise', Promise, { | ||||
| 	// XXX do we need a way to remove handlers???
 | ||||
| 	__message_handlers: null, | ||||
| 
 | ||||
| 	send: function(...args){ | ||||
| 		var that = this | ||||
| 		;(this.__message_handlers || []) | ||||
| 			.forEach(function(h){ h.call(that, ...args) }) | ||||
| 		return this }, | ||||
| 
 | ||||
| 	then: IterablePromise.prototype.then, | ||||
| 
 | ||||
| 	//
 | ||||
| 	//	Promise.interactive(handler)
 | ||||
| 	//		-> interacive-promise
 | ||||
| 	//
 | ||||
| 	//	handler(resolve, reject, onmessage)
 | ||||
| 	//
 | ||||
| 	//	onmessage(func)
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	__new__: function(_, handler){ | ||||
| 		var handlers = [] | ||||
| 
 | ||||
| 		var onmessage = function(func){ | ||||
| 			// remove all handlers...
 | ||||
| 			if(func === false){ | ||||
| 				var h = (obj == null ? | ||||
| 					handlers | ||||
| 					: (obj.__message_handlers || [])) | ||||
| 				h.splice(0, handlers.length) | ||||
| 			// remove a specific handler...
 | ||||
| 			} else if(arguments[1] === false){ | ||||
| 				var h = (obj == null ? | ||||
| 					handlers | ||||
| 					: (obj.__message_handlers || [])) | ||||
| 				h.splice(h.indexOf(func), 1) | ||||
| 			// register a handler...
 | ||||
| 			} else { | ||||
| 				var h = obj == null ? | ||||
| 					// NOTE: we need to get the handlers from 
 | ||||
| 					// 		.__message_handlers unless we are not 
 | ||||
| 					// 		fully defined yet, then use the bootstrap 
 | ||||
| 					// 		container (handlers)...
 | ||||
| 					// 		...since we can call onmessage(..) while 
 | ||||
| 					// 		the promise is still defined there is no 
 | ||||
| 					// 		way to .send(..) until it returns a promise 
 | ||||
| 					// 		object, this races here are highly unlikely...
 | ||||
| 					handlers | ||||
| 					: (obj.__message_handlers =  | ||||
| 						obj.__message_handlers ?? []) | ||||
| 				handlers.push(func) } } | ||||
| 
 | ||||
| 		var obj = Reflect.construct( | ||||
| 			InteractivePromise.__proto__,  | ||||
| 			!handler ? | ||||
| 				[] | ||||
| 				: [function(resolve, reject){ | ||||
| 					return handler(resolve, reject, onmessage) }],  | ||||
| 			InteractivePromise) | ||||
| 		Object.defineProperty(obj, '__message_handlers', { | ||||
| 			value: handlers, | ||||
| 			enumerable: false, | ||||
| 			// XXX should this be .configurable???
 | ||||
| 			configurable: true, | ||||
| 		}) | ||||
|    		return obj }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // Cooperative promise...
 | ||||
| // 
 | ||||
| // A promise that can be resolved/rejected externally.
 | ||||
| //
 | ||||
| // NOTE: normally this has no internal resolver logic...
 | ||||
| // 
 | ||||
| 
 | ||||
| var CooperativePromise = | ||||
| module.CooperativePromise = | ||||
| object.Constructor('CooperativePromise', Promise, { | ||||
| 	__handlers: null, | ||||
| 
 | ||||
| 	get isSet(){ | ||||
| 		return this.__handlers === false }, | ||||
| 
 | ||||
| 	set: function(value, resolve=true){ | ||||
| 		// can't set twice...
 | ||||
| 		if(this.isSet){ | ||||
| 			throw new Error('.set(..): can not set twice') } | ||||
| 		// bind to promise...
 | ||||
| 		if(value && value.then && value.catch){ | ||||
| 			value.then(handlers.resolve) | ||||
| 			value.catch(handlers.reject) | ||||
| 		// resolve with value...
 | ||||
| 		} else { | ||||
| 			resolve ? | ||||
| 				this.__handlers.resolve(value)  | ||||
| 				: this.__handlers.reject(value) } | ||||
| 		// cleanup and prevent setting twice...
 | ||||
| 		this.__handlers = false | ||||
| 		return this }, | ||||
| 
 | ||||
| 	then: IterablePromise.prototype.then, | ||||
| 
 | ||||
| 	__new__: function(){ | ||||
| 		var handlers | ||||
| 		var resolver = arguments[1] | ||||
| 
 | ||||
| 		var obj = Reflect.construct( | ||||
| 			CooperativePromise.__proto__,  | ||||
| 			[function(resolve, reject){ | ||||
| 				handlers = {resolve, reject}  | ||||
| 				// NOTE: this is here to support builtin .then(..)
 | ||||
| 				resolver | ||||
| 					&& resolver(resolve, reject) }],  | ||||
| 			CooperativePromise)  | ||||
| 
 | ||||
| 		Object.defineProperty(obj, '__handlers', { | ||||
| 			value: handlers, | ||||
| 			enumerable: false, | ||||
| 			writable: true, | ||||
| 		}) | ||||
| 		return obj }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| // XXX EXPEREMENTAL...
 | ||||
| var ProxyPromise = | ||||
| module.ProxyPromise = | ||||
| object.Constructor('ProxyPromise', Promise, { | ||||
| 
 | ||||
| 	then: IterablePromise.prototype.then, | ||||
| 
 | ||||
| 	__new__: function(context, other, nooverride=false){ | ||||
| 		var proto = 'prototype' in other ? | ||||
| 			other.prototype | ||||
| 			: other | ||||
| 		var obj = Reflect.construct( | ||||
| 			ProxyPromise.__proto__,  | ||||
| 			[function(resolve, reject){ | ||||
| 				context.then(resolve) | ||||
| 				context.catch(reject) }],  | ||||
| 			ProxyPromise)  | ||||
| 		// populate...
 | ||||
| 		// NOTE: we are not using object.deepKeys(..) here as we need 
 | ||||
| 		// 		the key origin not to trigger property getters...
 | ||||
| 		var seen = new Set() | ||||
| 		nooverride = nooverride instanceof Array ? | ||||
| 			new Set(nooverride) | ||||
| 			: nooverride | ||||
| 		while(proto != null){ | ||||
| 			Object.entries(Object.getOwnPropertyDescriptors(proto)) | ||||
| 				.forEach(function([key, value]){ | ||||
| 					// skip overloaded keys...
 | ||||
| 					if(seen.has(key)){ | ||||
| 						return } | ||||
| 					// skip non-functions...
 | ||||
| 					if(typeof(value.value) != 'function'){ | ||||
| 						return } | ||||
| 					// skip non-enumerable except for Object.prototype.run(..)...
 | ||||
| 					if(!(key == 'run'  | ||||
| 								&& Object.prototype.run === value.value)  | ||||
| 							&& !value.enumerable){ | ||||
| 						return } | ||||
| 					// do not override existing methods...
 | ||||
| 					if(nooverride === true ?  | ||||
| 								key in obj | ||||
| 							: nooverride instanceof Set ? | ||||
| 								nooverride.has(key) | ||||
| 							: nooverride){ | ||||
| 						return } | ||||
| 					// proxy...
 | ||||
| 					obj[key] = promiseProxy(key) }) | ||||
| 			proto = proto.__proto__ }  | ||||
| 		return obj }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| var PromiseMixin = | ||||
| module.PromiseMixin = | ||||
| object.Mixin('PromiseMixin', 'soft', { | ||||
| 	iter: IterablePromise, | ||||
| 	interactive: InteractivePromise, | ||||
| 	cooperative: CooperativePromise, | ||||
| }) | ||||
| 
 | ||||
| PromiseMixin(Promise) | ||||
| 
 | ||||
| 
 | ||||
| var PromiseProtoMixin = | ||||
| module.PromiseProtoMixin = | ||||
| object.Mixin('PromiseProtoMixin', 'soft', { | ||||
| 	as: ProxyPromise, | ||||
| 	iter: function(handler=undefined){ | ||||
| 		return IterablePromise(this, handler) }, | ||||
| }) | ||||
| 
 | ||||
| PromiseProtoMixin(Promise.prototype) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 nowrap :                        */ return module }) | ||||
							
								
								
									
										78
									
								
								lib/types/RegExp.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										78
									
								
								lib/types/RegExp.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,78 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var RegExpMixin = | ||||
| module.RegExpMixin = | ||||
| object.Mixin('RegExpMixin', 'soft', { | ||||
| 	// Quote a string and convert to RegExp to match self literally.
 | ||||
| 	quoteRegExp: function(str){ | ||||
| 		return str | ||||
| 			.replace(/([\.\\\/\(\)\[\]\$\*\+\-\{\}\@\^\&\?\<\>])/g, '\\$1') } | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| var GROUP_PATERN = | ||||
| //module.GROUP_PATERN = /(^\(|[^\\]\()/g
 | ||||
| module.GROUP_PATERN = new RegExp([ | ||||
| 	'^\\(', | ||||
| 	// non-escaped braces...
 | ||||
| 	'[^\\\\]\\(', | ||||
| 	// XXX ignore braces in ranges...
 | ||||
| 	// XXX '\\[.*(.*\\]',
 | ||||
| ].join('|')) | ||||
| 
 | ||||
| // Pattern group introspection...
 | ||||
| var RegExpProtoMixin = | ||||
| module.RegExpProtoMixin = | ||||
| object.Mixin('RegExpProtoMixin', 'soft', { | ||||
| 	// Format:
 | ||||
| 	// 	[
 | ||||
| 	// 		{
 | ||||
| 	//			index: <index>,
 | ||||
| 	//			name: <name>,
 | ||||
| 	//			pattern: <string>,
 | ||||
| 	//			offset: <offset>,
 | ||||
| 	// 		},
 | ||||
| 	// 		...
 | ||||
| 	// 	]
 | ||||
| 	// XXX cache this...
 | ||||
| 	get groups(){ | ||||
| 		this.toString() | ||||
| 			.matchAll(GROUP_PATERN) | ||||
| 	}, | ||||
| 	get namedGroups(){ | ||||
| 		return this.groups | ||||
| 			.reduce(function(res, e){ | ||||
| 				e.name | ||||
| 					&& (res[name] = e) | ||||
| 				return res }, {}) }, | ||||
| 	get groupCount(){ | ||||
| 		return this.groups.length }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| RegExpMixin(RegExp) | ||||
| // XXX EXPEREMENTAL...
 | ||||
| //RegExpProtoMixin(RegExp.prototype)
 | ||||
| 
 | ||||
| 
 | ||||
| var quoteRegExp = | ||||
| RegExp.quoteRegExp =  | ||||
| 	RegExp.quoteRegExp | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										122
									
								
								lib/types/Set.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										122
									
								
								lib/types/Set.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,122 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var SetProtoMixin = | ||||
| module.SetProtoMixin = | ||||
| object.Mixin('SetMixin', 'soft', { | ||||
| 	iter: function*(){ | ||||
| 		for(var e of this){ | ||||
| 			yield e } }, | ||||
| 
 | ||||
| 	// Set set operation shorthands...
 | ||||
| 	unite: function(other=[]){  | ||||
| 		return new Set([...this, ...other]) }, | ||||
| 	intersect: function(other){ | ||||
| 		var test = other.has ?   | ||||
| 			'has'  | ||||
| 			: 'includes' | ||||
| 		return new Set([...this] | ||||
| 			.filter(function(e){  | ||||
| 				return other[test](e) })) }, | ||||
| 	subtract: function(other=[]){ | ||||
| 		other = new Set(other) | ||||
| 		return new Set([...this] | ||||
| 			.filter(function(e){  | ||||
| 				return !other.has(e) })) }, | ||||
| 
 | ||||
| 	sort: function(values=[]){ | ||||
| 		values = (typeof(values) == 'function'  | ||||
| 				|| values === undefined) ? | ||||
| 			[...this].sort(values) | ||||
| 			: values | ||||
| 		var del = this.delete.bind(this) | ||||
| 		var add = this.add.bind(this) | ||||
| 		new Set([...values, ...this]) | ||||
| 			.forEach(function(e){ | ||||
| 				if(this.has(e)){ | ||||
| 					del(e) | ||||
| 					add(e) } }.bind(this)) | ||||
| 		return this }, | ||||
| 
 | ||||
| 	replace: function(old, value, ordered=true){ | ||||
| 		// nothing to do...
 | ||||
| 		if(!this.has(old) || old === value){ | ||||
| 			return this } | ||||
| 		if(ordered){ | ||||
| 			var order = [...this] | ||||
| 			// XXX is this fast enough???
 | ||||
| 			order[order.lastIndexOf(old)] = value } | ||||
| 		// replace...
 | ||||
| 		this.delete(old) | ||||
| 		this.add(value) | ||||
| 		ordered | ||||
| 			&& this.sort(order) | ||||
| 		return this }, | ||||
| 	// NOTE: if index is <0 then the value is prepended to the set, if 
 | ||||
| 	// 		it's >=this.size then the value will be appended.
 | ||||
| 	// 		if ordered is set to false in both cases the value is appended.
 | ||||
| 	// XXX should this be implemented via .splice(..) ???
 | ||||
| 	replaceAt: function(index, value, ordered=true){ | ||||
| 		// append...
 | ||||
| 		if(index >= this.size){ | ||||
| 			this.add(value) | ||||
| 			return this } | ||||
| 		// prepend...
 | ||||
| 		if(index < 0){ | ||||
| 			index = 0 | ||||
| 			var order = [, ...this] | ||||
| 		// replace...
 | ||||
| 		} else { | ||||
| 			var order = [...this] | ||||
| 			var old = order[index]  | ||||
| 			// nothing to do...
 | ||||
| 			if(old === value){ | ||||
| 				return this } } | ||||
| 		ordered | ||||
| 			&& (order[index] = value) | ||||
| 
 | ||||
| 		// replace...
 | ||||
| 		this.has(old) | ||||
| 			&& this.delete(old) | ||||
| 		this.add(value) | ||||
| 
 | ||||
| 		ordered | ||||
| 			&& this.sort(order) | ||||
| 		return this }, | ||||
| 
 | ||||
| 	// XXX do we need this???
 | ||||
| 	// 		...should this be enough???
 | ||||
| 	// 			new Set([...set].splice(..))
 | ||||
| 	splice: function(from, count, ...items){ | ||||
| 		var that = this | ||||
| 		var order = [...this] | ||||
| 		var removed = order.splice(...arguments) | ||||
| 
 | ||||
| 		// update the set...
 | ||||
| 		removed.forEach(this.delete.bind(this)) | ||||
| 		items.forEach(this.add.bind(this)) | ||||
| 		this.sort(order)  | ||||
| 
 | ||||
| 		return removed }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| SetProtoMixin(Set.prototype) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										43
									
								
								lib/types/String.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										43
									
								
								lib/types/String.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,43 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var StringProtoMixin = | ||||
| module.StringProtoMixin = | ||||
| object.Mixin('StringProtoMixin', 'soft', { | ||||
| 	capitalize: function(){ | ||||
| 		return this == '' ?  | ||||
| 			this  | ||||
| 			: this[0].toUpperCase() + this.slice(1) }, | ||||
| 
 | ||||
| 	// Indent a block of text...
 | ||||
| 	//
 | ||||
| 	// 	.indent(<width>)
 | ||||
| 	// 	.indent(<str>)
 | ||||
| 	// 		-> <str>
 | ||||
| 	//
 | ||||
| 	indent: function(indent){ | ||||
| 		indent = typeof(indent) == typeof('str') ? | ||||
| 			indent | ||||
| 			: ' '.repeat(indent) | ||||
| 		return indent + this.split(/\n/).join('\n'+ indent) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| StringProtoMixin(String.prototype) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										19
									
								
								lib/types/_module.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								lib/types/_module.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,19 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										211
									
								
								lib/types/containers.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										211
									
								
								lib/types/containers.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,211 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| ***********************************************/ /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| require('./Map') | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var UniqueKeyMap =  | ||||
| module.UniqueKeyMap =  | ||||
| object.Constructor('UniqueKeyMap', Map, { | ||||
| 
 | ||||
| 	// Format:
 | ||||
| 	// 	Map([
 | ||||
| 	// 		[ <elem>, Set([
 | ||||
| 	// 				<original-name>, 
 | ||||
| 	// 				...
 | ||||
| 	// 			]) ],
 | ||||
| 	// 		...
 | ||||
| 	// 	])
 | ||||
| 	//
 | ||||
| 	__keys_index: null, | ||||
| 	get __keys(){ | ||||
| 		return (this.__keys_index =  | ||||
| 			this.__keys_index || new Map()) }, | ||||
| 
 | ||||
| 	// Format:
 | ||||
| 	// 	Map([
 | ||||
| 	// 		[<unique-key>, <orig-key>],
 | ||||
| 	// 		...
 | ||||
| 	// 	])
 | ||||
| 	//
 | ||||
| 	__reverse_index: null, | ||||
| 	get __reverse(){ | ||||
| 		return (this.__reverse_index =  | ||||
| 			this.__reverse_index || new Map()) }, | ||||
| 
 | ||||
| 
 | ||||
| 	// Pattern to be used to generate unique key...
 | ||||
| 	//
 | ||||
| 	__key_pattern__: '$KEY ($COUNT)', | ||||
| 
 | ||||
| 	// If true then a value can not be stored under the same key more 
 | ||||
| 	// than once...
 | ||||
| 	//
 | ||||
| 	// Example:
 | ||||
| 	// 	var u = UniqueKeyMap()
 | ||||
| 	// 	u.set('x', 123)
 | ||||
| 	// 	// if .__unique_key_value__ is true this will have no effect, 
 | ||||
| 	// 	// otherwise 123 will be stored under 'x (1)'
 | ||||
| 	// 	u.set('x', 123)
 | ||||
| 	//
 | ||||
| 	__unique_key_value__: false, | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 	// helpers...
 | ||||
| 	//
 | ||||
| 	originalKey: function(key){ | ||||
| 		return this.__reverse.get(key) }, | ||||
| 	uniqieKey: function(key){ | ||||
| 		var n = key | ||||
| 		var i = 0 | ||||
| 		while(this.has(n)){ | ||||
| 			i++ | ||||
| 			n = this.__key_pattern__ | ||||
| 				.replace(/\$KEY/, key) | ||||
| 				.replace(/\$COUNT/, i) } | ||||
| 		return n }, | ||||
| 	keysOf: function(elem, mode='original'){ | ||||
| 		// get unique keys...
 | ||||
| 		if(mode == 'unique'){ | ||||
| 			return this | ||||
| 				.entries() | ||||
| 				.reduce(function(res, [k, e]){ | ||||
| 					e === elem | ||||
| 						&& res.push(k) | ||||
| 					return res }, []) } | ||||
| 		// get keys used to set the values...
 | ||||
| 		return [...(this.__keys.get(elem) || [])] }, | ||||
| 
 | ||||
| 	// NOTE: this will never overwrite a key's value, to overwrite use .reset(..)
 | ||||
| 	set: function(key, elem, return_key=false){ | ||||
| 		// index...
 | ||||
| 		var names | ||||
| 		this.__keys.set(elem,  | ||||
| 			names = this.__keys.get(elem) || new Set()) | ||||
| 		// key/elem already exists...
 | ||||
| 		if(this.__unique_key_value__  | ||||
| 				&& names.has(key)){ | ||||
| 			return return_key ? | ||||
| 				key | ||||
| 				: this } | ||||
| 		names.add(key) | ||||
| 		// add the elem with the unique name...
 | ||||
| 		var n | ||||
| 		var res = object.parentCall( | ||||
| 			UniqueKeyMap.prototype,  | ||||
| 			'set',  | ||||
| 			this,  | ||||
| 			n = this.uniqieKey(key),  | ||||
| 			elem)  | ||||
| 		// reverse index...
 | ||||
| 		this.__reverse.set(n, key) | ||||
| 		return return_key ? | ||||
| 			n | ||||
| 			: res }, | ||||
| 	// NOTE: this will never generate a new name...
 | ||||
| 	reset: function(key, elem){ | ||||
| 		// rewrite...
 | ||||
| 		if(this.has(key)){ | ||||
| 			// remove old elem/key from .__keys...
 | ||||
| 			var o = this.originalKey(key) | ||||
| 			var s = this.__keys.get(this.get(key)) | ||||
| 			s.delete(o) | ||||
| 			s.size == 0 | ||||
| 				&& this.__keys.delete(this.get(key)) | ||||
| 			// add new elem/key to .__keys...
 | ||||
| 			var n | ||||
| 			this.__keys.set(elem, (n = this.__keys.get(elem) || new Set())) | ||||
| 			n.add(o) | ||||
| 			 | ||||
| 			return object.parentCall(UniqueKeyMap.prototype, 'set', this, key, elem)  | ||||
| 		// add...
 | ||||
| 		} else { | ||||
| 			return this.set(key, elem) } }, | ||||
| 	delete: function(key){ | ||||
| 		var s = this.__keys.get(this.get(key)) | ||||
| 		if(s){ | ||||
| 			// XXX will this delete if key is with an index???
 | ||||
| 			//s.delete(key)
 | ||||
| 			s.delete(this.originalKey(key)) | ||||
| 			this.__reverse.delete(key) | ||||
| 			s.size == 0 | ||||
| 				&& this.__keys.delete(this.get(key)) } | ||||
| 		return object.parentCall(UniqueKeyMap.prototype, 'delete', this, key) }, | ||||
| 
 | ||||
| 	// NOTE: this maintains the item order. This is done by rewriting 
 | ||||
| 	// 		items in sequence, this may be slow and trigger lots of write 
 | ||||
| 	// 		observer callbacks. to avoid this use .unOrderedRename(..)
 | ||||
| 	// XXX do not see how can we avoid rewriting the map if we want to 
 | ||||
| 	// 		keep the order...
 | ||||
| 	orderedRename: function(from, to, return_key=false){ | ||||
| 		var keys = [...this.keys()] | ||||
| 		// rename the element...
 | ||||
| 		var e = this.get(from) | ||||
| 		this.delete(from) | ||||
| 		var n = this.set(to, e, true)  | ||||
| 		// keep order...
 | ||||
| 		keys.splice(keys.indexOf(from), 1, n) | ||||
| 		this.sort(keys) | ||||
| 		return return_key ? | ||||
|    			n | ||||
| 			: this }, | ||||
| 	// NOTE: the renamed item is appended to the map...
 | ||||
| 	unorderedRename: function(from, to, return_key=false){ | ||||
| 		var e = this.get(from) | ||||
| 		this.delete(from) | ||||
| 		return this.set(to, e, return_key) }, | ||||
| 
 | ||||
| 	__unorderd_rename__: false, | ||||
| 	rename: function(from, to, return_key=false){ | ||||
| 		return this.__unorderd_writes__ ? | ||||
| 			this.unorderedRename(...arguments) | ||||
| 			: this.orderedRename(...arguments) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| // XXX should this be a map???
 | ||||
| // XXX make two variants: ordered and unordered...
 | ||||
| // 		...the ordered variant should have the same API as an array...
 | ||||
| var ObjectWithAutoKeys =  | ||||
| module.ObjectWithAutoKeys =  | ||||
| object.Constructor('ObjectWithAutoKeys', { | ||||
| 
 | ||||
| 	__last_key: null, | ||||
| 	__new_key__: function(value){ | ||||
| 		return (this.__last_key = (this.__last_key || -1) + 1) }, | ||||
| 
 | ||||
| 	// 
 | ||||
| 	// 	.push(value)
 | ||||
| 	// 		-> key
 | ||||
| 	//
 | ||||
| 	// 	.push(value, ..)
 | ||||
| 	// 		-> [key, ..]
 | ||||
| 	//
 | ||||
| 	push: function(...values){ | ||||
| 		var that = this | ||||
| 		var res = values | ||||
| 			.map(function(value){ | ||||
| 				that[that.__new_key__(value)] = value }) | ||||
| 		return values.length == 1 ? | ||||
|    			res.pop() | ||||
| 			: res }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										382
									
								
								lib/types/event.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										382
									
								
								lib/types/event.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,382 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| * XXX need ability to extend event to implement proxy events... | ||||
| * 		...i.e. .on(..) / ... get called on one object but the handler | ||||
| * 		bound on a different object via a proxy event method... | ||||
| * XXX is types/events the right place for this??? | ||||
| * XXX should we have .pre/.post events??? | ||||
| * XXX should we propogate event handling to parent/overloaded events??? | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| // Event method wrappers...
 | ||||
| 
 | ||||
| var EventCommand =  | ||||
| module.EventCommand =  | ||||
| object.Constructor('EventCommand', { | ||||
| 	name: null, | ||||
| 	__init__: function(name, data={}){ | ||||
| 		Object.assign(this, data, {name}) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| module.TRIGGER = module.EventCommand('TRIGGER') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| // Create an "eventful" method...
 | ||||
| //
 | ||||
| // The resulting method can be either called directly or via .trigger(..).
 | ||||
| // Handlrs can be bound to it via .on(..) and unbound via .off(..) and 
 | ||||
| // calling it will trigger the handlers either after the user func(..)
 | ||||
| // return or when the user calles the passed handler(..) function.
 | ||||
| //
 | ||||
| // 	Eventful(name[, options])
 | ||||
| // 		-> method
 | ||||
| //
 | ||||
| // 	Eventful(name, func[, options])
 | ||||
| // 		-> method
 | ||||
| //
 | ||||
| //
 | ||||
| //	Trigger the event...
 | ||||
| //	method(...args)
 | ||||
| //		-> ..
 | ||||
| //
 | ||||
| //
 | ||||
| // 	func(handle, ...args)
 | ||||
| // 		-> ..
 | ||||
| //
 | ||||
| //
 | ||||
| //	trigger event handlers...
 | ||||
| //	handle()
 | ||||
| //	handle(true)
 | ||||
| //		-> true
 | ||||
| //		-> false
 | ||||
| //
 | ||||
| //	trigger event handlers and overload handler arguments...
 | ||||
| //	handle(true, ...)
 | ||||
| //		-> true
 | ||||
| //		-> false
 | ||||
| //
 | ||||
| //	prevent event handlers from triggering...
 | ||||
| //	handle(false)
 | ||||
| //		-> undefined
 | ||||
| //
 | ||||
| //
 | ||||
| //
 | ||||
| // Special case: EventCommand...
 | ||||
| //
 | ||||
| //	EventCommand instance can be passed as the first argument of method, 
 | ||||
| //	in this case the event function will get it but the event handlers 
 | ||||
| //	will not...
 | ||||
| //	This is done to be able to externally pass commands to event methods
 | ||||
| //	that get handled in a special way by the function but not passed to 
 | ||||
| //	the event handlers...
 | ||||
| //
 | ||||
| // 		method(<event-command>, ...args)
 | ||||
| // 			-> ..
 | ||||
| //
 | ||||
| // 		func(handle, <event-command>, ...args)
 | ||||
| // 			-> ..
 | ||||
| //
 | ||||
| //
 | ||||
| //
 | ||||
| // NOTE: calling handle(false) will exiplicitly disable calling the 
 | ||||
| // 		handlers for that call...
 | ||||
| var Eventful = | ||||
| module.Eventful = | ||||
| object.Constructor('Eventful', { | ||||
| 
 | ||||
| 	handlerLocation: 'context', | ||||
| 
 | ||||
| 	name: null, | ||||
| 	func: null, | ||||
| 
 | ||||
| 	toString: function(){ | ||||
| 		return this.func ? | ||||
| 			`${this.constructor.name} ` | ||||
| 				+(this.func.toString() | ||||
| 					.replace(/^(function[^(]*\()[^,)]*, ?/, '$1'))  | ||||
| 			: `${this.constructor.name} function ${this.name}(){}` }, | ||||
| 
 | ||||
| 	__event_handlers__: null, | ||||
| 	bind: function(context, handler){ | ||||
| 		var handlers =  | ||||
| 			// local...
 | ||||
| 			this.handlerLocation == 'method' ? | ||||
| 				(this.__event_handlers__ = this.__event_handlers__ || []) | ||||
| 			// context (default)...
 | ||||
| 			//: (context.__event_handlers__ == null ?
 | ||||
| 			: !context.hasOwnProperty('__event_handlers__') ? | ||||
| 				Object.defineProperty(context, '__event_handlers__', { | ||||
| 						value: {[this.name]: (handlers = [])}, | ||||
| 						enumerable: false, | ||||
| 						configurable: true, | ||||
| 						writable: true, | ||||
| 					})  | ||||
| 					&& handlers | ||||
| 			: (context.__event_handlers__[this.name] =  | ||||
| 				context.__event_handlers__[this.name] || []) | ||||
| 		// add handler...
 | ||||
| 		handlers.push(handler) | ||||
| 		return this }, | ||||
| 	unbind: function(context, handler){ | ||||
| 		var handlers =  | ||||
| 			this.handlerLocation == 'method' ? | ||||
| 				method.__event_handlers__ | ||||
| 			//: (context.__event_handlers__ || {})[this.name]) || []
 | ||||
| 			: context.hasOwnProperty('__event_handlers__') ? | ||||
| 				(context.__event_handlers__ || {})[this.name] || [] | ||||
| 			: [] | ||||
| 		handlers.splice(0, handlers.length, | ||||
| 			...handlers.filter(function(h){ | ||||
| 				return h !== handler | ||||
| 					&& h.__event_original_handler !== handler })) | ||||
| 		return this }, | ||||
| 
 | ||||
| 	__call__: function(context, ...args){ | ||||
| 		var that = this | ||||
| 		var handlers =  | ||||
| 			this.handlerLocation == 'method' ? | ||||
| 				(this.__event_handlers__ || []) | ||||
| 			: [] | ||||
| 		// context (default)...
 | ||||
| 		// NOTE: these are allways called...
 | ||||
| 		handlers = handlers | ||||
| 			//.concat((context.__event_handlers__ || {})[this.name] || [])
 | ||||
| 			.concat(context.hasOwnProperty('__event_handlers__') ? | ||||
| 				(context.__event_handlers__ || {})[this.name] || [] | ||||
| 				: []) | ||||
| 
 | ||||
| 		// NOTE: this will stop event handling if one of the handlers 
 | ||||
| 		// 		explicitly returns false...
 | ||||
| 		// NOTE: if the user does not call handle() it will be called 
 | ||||
| 		// 		after the event action is done but before it returns...
 | ||||
| 		// NOTE: to explicitly disable calling the handlers func must 
 | ||||
| 		// 		call handle(false)
 | ||||
| 		var did_handle = false | ||||
| 		var handle = function(run=true, ...alt_args){ | ||||
| 			did_handle = true | ||||
| 			var a = (run === true  | ||||
| 					&& arguments.length > 1) ? | ||||
| 				alt_args | ||||
| 				: args | ||||
| 			a = a[0] instanceof EventCommand ? | ||||
| 				a.slice(1) | ||||
| 				: a | ||||
| 			return run ? | ||||
| 				handlers | ||||
| 					.reduce(function(res, handler){  | ||||
| 						return res === true  | ||||
| 							&& handler.call(context, that.name, ...a) !== false }, true)  | ||||
| 				: undefined }  | ||||
| 
 | ||||
| 		// call...
 | ||||
| 		var res = this.func ? | ||||
| 			this.func.call(context, handle, ...args) | ||||
| 			: undefined | ||||
| 
 | ||||
| 		// call the handlers if the user either didn't call handle()
 | ||||
| 		// or explicitly called handle(false)...
 | ||||
| 		!did_handle | ||||
| 			&& handle() | ||||
| 		return res }, | ||||
| 
 | ||||
| 	__init__: function(name, func, options={}){ | ||||
| 		options = func && typeof(func) != 'function' ? | ||||
| 			func | ||||
| 			: options | ||||
| 		Object.assign(this, options) | ||||
| 		Object.defineProperty(this, 'name', { value: name }) | ||||
| 		func  | ||||
| 			&& typeof(func) == 'function' | ||||
| 			&& Object.defineProperty(this, 'func', {  | ||||
| 				value: func,  | ||||
| 				enumerable: false, | ||||
| 			}) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| // Extends Eventful(..) adding ability to bind events via the 
 | ||||
| // resulting method directly by passing it a function...
 | ||||
| //
 | ||||
| //	Event(name[, options])
 | ||||
| //		-> method
 | ||||
| //
 | ||||
| //	Event(name, func[, options])
 | ||||
| //		-> method
 | ||||
| //
 | ||||
| //
 | ||||
| //	Bind handler...
 | ||||
| //	method(handler)
 | ||||
| //		-> this
 | ||||
| //
 | ||||
| //	Unbind handler...
 | ||||
| //	method(handler, false)
 | ||||
| //		-> this
 | ||||
| //
 | ||||
| //	Trigger handlers...
 | ||||
| //	method(...args)
 | ||||
| //		-> this
 | ||||
| //
 | ||||
| //
 | ||||
| //	func(handle, ...args)
 | ||||
| //
 | ||||
| //
 | ||||
| // Special case:
 | ||||
| //
 | ||||
| //	Force trigger event...
 | ||||
| //	method(TRIGGER, ...args)
 | ||||
| //		-> this
 | ||||
| //
 | ||||
| // This will pass args to the event action regardless whether the first 
 | ||||
| // arg is a function or not...
 | ||||
| var Event = | ||||
| module.Event = | ||||
| object.Constructor('Event', Eventful, { | ||||
| 	toString: function(){ | ||||
| 		return this.orig_func ? | ||||
| 			'Event ' | ||||
| 				+this.orig_func.toString() | ||||
| 					.replace(/^(function[^(]*\()[^,)]*, ?/, '$1') | ||||
| 			: `Event function ${this.name}(){}`}, | ||||
| 	__call__: function(context, ...args){ | ||||
| 		// NOTE: when the first arg is an event command this will
 | ||||
| 		// 		fall through to calling the action...
 | ||||
| 		typeof(args[0]) == 'function' ? | ||||
| 			// add handler...
 | ||||
| 			this.bind(context, args[0]) | ||||
| 			// call the action...
 | ||||
| 			: object.parentCall(Event.prototype.__call__, this, context, ...args) | ||||
| 			// XXX workaround for above line -- remove when fully tested...
 | ||||
| 			//: Eventful.prototype.__call__.call(this, context, ...args)
 | ||||
| 		return context },  | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| // Like Event(..) but produces an event method that can only be triggered 
 | ||||
| // via .trigger(name, ...), calling this is a no-op...
 | ||||
| var PureEvent = | ||||
| module.PureEvent = | ||||
| object.Constructor('PureEvent', Event, { | ||||
| 	toString: function(){ | ||||
| 		return `PureEvent ${this.name}(){}`}, | ||||
| 	__init__: function(name, options={}){ | ||||
| 		object.parentCall(PureEvent.prototype.__init__, this, | ||||
| 			name,  | ||||
| 			function(handle, trigger){  | ||||
| 				trigger === module.TRIGGER  | ||||
| 					|| handle(false) }, options) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // Mixins...
 | ||||
| 
 | ||||
| // XXX might be nice to add support to pre/post handlers...
 | ||||
| // XXX still not sure about the builtin-local event control flow...
 | ||||
| // XXX do we need to be able to force global handler???
 | ||||
| var EventHandlerMixin =  | ||||
| module.EventHandlerMixin = object.Mixin('EventHandlerMixin', { | ||||
| 	//__event_handlers__: null,
 | ||||
| 
 | ||||
| 	on: function(evt, func){ | ||||
| 		// event...
 | ||||
| 		if(evt in this  | ||||
| 				&& this[evt].bind){ | ||||
| 			this[evt].bind(this, func) | ||||
| 		// non-event...
 | ||||
| 		} else { | ||||
| 			//this.__event_handlers__ == null
 | ||||
| 			!this.hasOwnProperty('__event_handlers__') | ||||
| 				&& Object.defineProperty(this, '__event_handlers__', { | ||||
| 					value: {}, | ||||
| 					enumerable: false, | ||||
| 					configurable: true, | ||||
| 					writable: true, | ||||
| 				}) | ||||
| 			;(this.__event_handlers__[evt] =  | ||||
| 					this.__event_handlers__[evt] || []) | ||||
| 				.push(func) } | ||||
| 		return this }, | ||||
| 	one: function(evt, func){ | ||||
| 		var handler | ||||
| 		this.on(evt,  | ||||
| 			handler = Object.assign( | ||||
| 				function(handle, ...args){ | ||||
| 					this.off(evt, handler) | ||||
| 					return func.call(this, handle, ...args) }.bind(this), | ||||
| 				{__event_original_handler: func})) | ||||
| 		return this }, | ||||
| 	// XXX do we need .off(evt, 'all')
 | ||||
| 	off: function(evt, func){ | ||||
| 		// event...
 | ||||
| 		if(evt in this  | ||||
| 				&& this[evt].unbind){ | ||||
| 			this[evt].unbind(this, func) | ||||
| 		// non-event...
 | ||||
| 		} else { | ||||
| 			var handlers = this.__event_handlers__ | ||||
| 				&& (this.__event_handlers__[evt] || []) | ||||
| 			handlers | ||||
| 				&& handlers.splice(0, handlers.length, | ||||
| 					...handlers.filter(function(h){ | ||||
| 						return h !== func  | ||||
| 							&& h.__event_original_handler !== func })) } | ||||
| 		return this }, | ||||
| 	// XXX revise...
 | ||||
| 	trigger: function(evt, ...args){ | ||||
| 		evt in this ? | ||||
| 			// XXX add a better check...
 | ||||
| 			this[evt](module.TRIGGER, ...args) | ||||
| 			//: this.__event_handlers__
 | ||||
| 			: this.hasOwnProperty('__event_handlers__') | ||||
| 				&& (this.__event_handlers__[evt] || []) | ||||
| 					.forEach(function(h){ h(evt, ...args) })  | ||||
| 		return this }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| // NOTE: this can't be added via Object.assign(..), use object.mixinFlat(..) 
 | ||||
| // 		instead...
 | ||||
| var EventDocMixin =  | ||||
| module.EventDocMixin = object.Mixin('EventDocMixin', { | ||||
| 	get eventful(){ | ||||
| 		return object.deepKeys(this) | ||||
| 			.filter(function(n){  | ||||
| 				// avoid triggering props...
 | ||||
| 				return !object.values(this, n, true).next().value.get | ||||
| 					// XXX this is too strict...
 | ||||
| 					&& (this[n] || {}).constructor === Eventful}.bind(this)) }, | ||||
| 	get events(){ | ||||
| 		return object.deepKeys(this) | ||||
| 			.filter(function(n){  | ||||
| 				// avoid triggering props...
 | ||||
| 				return !object.values(this, n, true).next().value.get | ||||
| 					// XXX this is too strict...
 | ||||
| 					&& (this[n] || {}).constructor === Event }.bind(this)) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| var EventMixin =  | ||||
| module.EventMixin =  | ||||
| object.Mixin('EventMixin',  | ||||
| 	EventHandlerMixin, | ||||
| 	EventDocMixin) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										622
									
								
								lib/types/generator.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										622
									
								
								lib/types/generator.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,622 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| var stoppable = require('ig-stoppable') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| // NOTE: this is used in a similar fashion to Python's StopIteration...
 | ||||
| var STOP = | ||||
| module.STOP = | ||||
| 	stoppable.STOP | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // The generator hierarchy in JS is a bit complicated.
 | ||||
| //
 | ||||
| // Consider the following:
 | ||||
| //
 | ||||
| // 		// this is the generator function (i.e. the constructor)
 | ||||
| // 		var Iter = function*(lst){
 | ||||
| // 			for(var e of lst){
 | ||||
| // 				yield e }}
 | ||||
| //
 | ||||
| // 		// this is the generator instance (constructed instance)...
 | ||||
| // 		var iter = Iter([1,2,3])
 | ||||
| //
 | ||||
| //
 | ||||
| // In this module we need to add methods to be visible from either Iter
 | ||||
| // or iter from the above example, so we need to access the prototypes 
 | ||||
| // of each of them.
 | ||||
| // So, below we will define:
 | ||||
| //
 | ||||
| // 	Generator.prototype 
 | ||||
| // 		prototype of the generator constructors (i.e. Iter(..) from the 
 | ||||
| // 		above example)
 | ||||
| //
 | ||||
| // 	Generator.prototype.prototype
 | ||||
| // 		generator instance prototype (i.e. iter for the above code)
 | ||||
| //
 | ||||
| //
 | ||||
| // Also the following applies:
 | ||||
| //
 | ||||
| //		iter instanceof Iter		// -> true
 | ||||
| //
 | ||||
| // 		Iter instanceof Generator
 | ||||
| //
 | ||||
| //
 | ||||
| // NOTE: there appears to be no way to test if iter is instance of some 
 | ||||
| // 		generic Generator...
 | ||||
| //
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| var Generator =  | ||||
| module.Generator = | ||||
| 	(function*(){}).constructor | ||||
| 
 | ||||
| var AsyncGenerator = | ||||
| module.AsyncGenerator = | ||||
| 	(async function*(){}).constructor | ||||
| 
 | ||||
| 
 | ||||
| // base iterator prototypes...
 | ||||
| var ITERATOR_PROTOTYPES = [ | ||||
| 	Array, | ||||
| 	Set, | ||||
| 	Map, | ||||
| ].map(function(e){  | ||||
| 	return (new e()).values().__proto__ }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // generic generator wrapper...
 | ||||
| 
 | ||||
| // helper...
 | ||||
| var __iter =  | ||||
| module.__iter = | ||||
| function*(lst=[]){ | ||||
| 	if(typeof(lst) == 'object'  | ||||
| 			&& Symbol.iterator in lst){ | ||||
| 		yield* lst  | ||||
| 	} else { | ||||
| 		yield lst } } | ||||
| 
 | ||||
| // XXX updatae Array.js' version for compatibility...
 | ||||
| // XXX DOCS!!!
 | ||||
| var iter =  | ||||
| module.iter =  | ||||
| Generator.iter = | ||||
| 	stoppable(function(lst=[]){ | ||||
| 		// handler -> generator-constructor...
 | ||||
| 		if(typeof(lst) == 'function'){ | ||||
| 			// we need to be callable...
 | ||||
| 			var that = this instanceof Function ? | ||||
| 				this | ||||
| 				// generic root generator...
 | ||||
| 				: module.__iter | ||||
| 			return function*(){ | ||||
| 				yield* that(...arguments).iter(lst) } } | ||||
| 		// no handler -> generator instance...
 | ||||
| 		return module.__iter(lst) }) | ||||
| 
 | ||||
| // NOTE: we need .iter(..) to both return generators if passed an iterable
 | ||||
| // 		and genereator constructos if passed a function...
 | ||||
| iter.__proto__ = Generator.prototype | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // Generator.prototype "class" methods...
 | ||||
| //
 | ||||
| // the following are effectively the same:
 | ||||
| // 	1) Wrapper
 | ||||
| // 		var combined = function(...args){
 | ||||
| // 			return someGenerator(...args)
 | ||||
| // 				.filter(function(e){ ... })
 | ||||
| // 				.map(function(e){ ... }) }
 | ||||
| //
 | ||||
| // 		combined( .. )
 | ||||
| //
 | ||||
| // 	2) Static generator methods...
 | ||||
| // 		var combined = someGenerator
 | ||||
| // 			.filter(function(e){ ... })
 | ||||
| // 			.map(function(e){ ... })
 | ||||
| //
 | ||||
| // 		combined( .. )
 | ||||
| //
 | ||||
| //
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | ||||
| // Helpers...
 | ||||
| 
 | ||||
| //
 | ||||
| // 	makeGenerator(<name>)
 | ||||
| // 	makeGenerator(<name>, <handler>)
 | ||||
| // 		-> <func>
 | ||||
| //
 | ||||
| // 	makeGenerator('async', <name>)
 | ||||
| // 	makeGenerator('async', <name>, <handler>)
 | ||||
| // 		-> <func>
 | ||||
| //
 | ||||
| //
 | ||||
| // 	<func>(...args)
 | ||||
| // 		-> <Generator>
 | ||||
| //
 | ||||
| // 	<Generator>(...inputs)
 | ||||
| // 		-> <generator>
 | ||||
| //
 | ||||
| // 	<handler>(args, ...inputs)
 | ||||
| // 		-> args
 | ||||
| //
 | ||||
| //
 | ||||
| // XXX this needs to be of the correct type... (???)
 | ||||
| // XXX need to accept generators as handlers...
 | ||||
| var makeGenerator = function(name, pre){ | ||||
| 	var sync = true | ||||
| 	if(name == 'async'){ | ||||
| 		sync = false | ||||
| 		var [name, pre] = [...arguments].slice(1) } | ||||
| 	return function(...args){ | ||||
| 		var that = this | ||||
| 		return Object.assign( | ||||
| 			// NOTE: the two branches here are identical, the only 
 | ||||
| 			// 		difference is the async keyword...
 | ||||
| 			sync ? | ||||
| 				function*(){ | ||||
| 					var a = pre ?  | ||||
| 						pre.call(this, args, ...arguments) | ||||
| 						: args | ||||
| 					yield* that(...arguments)[name](...a) } | ||||
| 				: async function*(){ | ||||
| 					var a = pre ?  | ||||
| 						pre.call(this, args, ...arguments) | ||||
| 						: args | ||||
| 					yield* that(...arguments)[name](...a) },  | ||||
| 			{ toString: function(){ | ||||
| 				return [ | ||||
| 					that.toString(),  | ||||
| 					// XXX need to normalize args better...
 | ||||
| 					`.${ name }(${ args.join(', ') })`, | ||||
| 				].join('\n    ') }, }) } } | ||||
| 
 | ||||
| // XXX do a better doc...
 | ||||
| var makePromise = function(name){ | ||||
| 	return function(...args){ | ||||
| 		var that = this | ||||
| 		return function(){ | ||||
| 			return that(...arguments)[name](func) } } } | ||||
| 
 | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | ||||
| 
 | ||||
| var GeneratorMixin = | ||||
| module.GeneratorMixin = | ||||
| object.Mixin('GeneratorMixin', 'soft', { | ||||
| 	STOP: object.STOP, | ||||
| 
 | ||||
| 	iter: module.iter, | ||||
| 
 | ||||
| 	gat: makeGenerator('gat'), | ||||
| 	at: function(i){ | ||||
| 		var that = this | ||||
| 		return Object.assign( | ||||
| 			function(){ | ||||
| 				return that(...arguments).at(i) }, | ||||
| 			{ toString: function(){ | ||||
| 				return that.toString()  | ||||
| 					+ '\n    .at('+ i +')'}, }) }, | ||||
| 
 | ||||
| 	slice: makeGenerator('slice'), | ||||
| 	flat: makeGenerator('flat'), | ||||
| 
 | ||||
| 	map: makeGenerator('map'), | ||||
| 	filter: makeGenerator('filter'), | ||||
| 	reduce: makeGenerator('reduce'), | ||||
| 	reduceRight: makeGenerator('reduceRight'), | ||||
| 
 | ||||
| 	// non-generators...
 | ||||
| 	//
 | ||||
| 	toArray: function(){ | ||||
| 		var that = this | ||||
| 		return Object.assign( | ||||
| 			function(){ | ||||
| 				return that(...arguments).toArray() }, | ||||
| 			{ toString: function(){ | ||||
| 				return that.toString()  | ||||
| 					+ '\n    .toArray()'}, }) }, | ||||
| 	gpop: makeGenerator('gpop'), | ||||
| 	pop: function(){ | ||||
| 		var that = this | ||||
| 		return Object.assign( | ||||
| 			function(){ | ||||
| 				//return that(...arguments).toArray().pop() },
 | ||||
| 				return that(...arguments).pop() }, | ||||
| 			{ toString: function(){ | ||||
| 				return that.toString()  | ||||
| 					+ '\n    .pop()'}, }) }, | ||||
| 	push: makeGenerator('push'), | ||||
| 	gshift: makeGenerator('gshift'), | ||||
| 	shift: function(){ | ||||
| 		var that = this | ||||
| 		return Object.assign( | ||||
| 			function(){ | ||||
| 				//return that(...arguments).toArray().shift() }, 
 | ||||
| 				return that(...arguments).shift() },  | ||||
| 			{ toString: function(){ | ||||
| 				return that.toString()  | ||||
| 					+ '\n    .shift()'}, }) }, | ||||
| 	unshift: makeGenerator('unshift'), | ||||
| 
 | ||||
| 	// promises...
 | ||||
| 	//
 | ||||
| 	then: makePromise('then'), | ||||
| 	catch: makePromise('catch'), | ||||
| 	finally: makePromise('finally'), | ||||
| 
 | ||||
| 	// combinators...
 | ||||
| 	//
 | ||||
| 	chain: makeGenerator('chain'), | ||||
| 	concat: makeGenerator('concat',  | ||||
| 		// initialize arguments...
 | ||||
| 		function(next, ...args){ | ||||
| 			return next | ||||
| 				.map(function(e){ | ||||
| 					return (e instanceof Generator | ||||
| 							|| typeof(e) == 'function') ? | ||||
| 						e(...args) | ||||
| 						: e }) }), | ||||
| 	//zip: makeGenerator('zip'),
 | ||||
| 	 | ||||
| 	enumerate: makeGenerator('enumerate'), | ||||
| 
 | ||||
| 	// XXX should this have a .gjoin(..) companion...
 | ||||
| 	join: function(){ | ||||
| 		var args = [...arguments] | ||||
| 		var that = this | ||||
| 		return Object.assign( | ||||
| 			function(){ | ||||
| 				//return that(...arguments).toArray().shift() }, 
 | ||||
| 				return that(...arguments).join(...args) },  | ||||
| 			{ toString: function(){ | ||||
| 				return that.toString()  | ||||
| 					+ '\n    .join()'}, }) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| var GeneratorProtoMixin = | ||||
| module.GeneratorProtoMixin = | ||||
| object.Mixin('GeneratorProtoMixin', 'soft', { | ||||
| 	// XXX use module.iter(..) ???
 | ||||
| 	iter: stoppable(function*(handler){  | ||||
| 		if(handler){ | ||||
| 			var i = 0 | ||||
| 			for(var elem of this){ | ||||
| 				var res = handler.call(this, elem, i)  | ||||
| 				// expand iterables...
 | ||||
| 				if(typeof(res) == 'object'  | ||||
| 						&& Symbol.iterator in res){ | ||||
| 					yield* res | ||||
| 				// as-is...
 | ||||
| 				} else { | ||||
| 					yield res }} | ||||
| 		// no handler...
 | ||||
| 		} else { | ||||
| 			yield* this } }), | ||||
| 	//*/
 | ||||
| 
 | ||||
| 	at: function(i){ | ||||
| 		return this.gat(i).next().value }, | ||||
| 	// XXX this needs the value to be iterable... why???
 | ||||
| 	gat: function*(i){ | ||||
| 		// sanity check...
 | ||||
| 		if(i < 0){ | ||||
| 			throw new Error('.gat(..): ' | ||||
| 				+'generator index can\'t be a negative value.')} | ||||
| 		for(var e of this){ | ||||
| 			if(i-- == 0){ | ||||
| 				yield e  | ||||
| 				return } } }, | ||||
| 
 | ||||
| 	// NOTE: this is different from Array's .slice(..) in that it does not 
 | ||||
| 	// 		support negative indexes -- this is done because there is no way 
 | ||||
| 	// 		to judge the length of a generator until it is fully done...
 | ||||
| 	slice: function*(from=0, to=Infinity){ | ||||
| 		// sanity check...
 | ||||
| 		if(from < 0 || to < 0){ | ||||
| 			throw new Error('.slice(..): ' | ||||
| 				+'generator form/to indexes can\'t be negative values.')} | ||||
| 		var i = 0 | ||||
| 		for(var e of this){ | ||||
| 			// stop at end of seq...
 | ||||
| 			if(i >= to){ | ||||
| 				return } | ||||
| 			// only yield from from...
 | ||||
| 			if(i >= from){ | ||||
| 				yield e } | ||||
| 			i++ } }, | ||||
| 	// XXX do we need a version that'll expand generators???
 | ||||
| 	flat: function*(depth=1){ | ||||
| 		if(depth == 0){ | ||||
| 			return this } | ||||
| 		for(var e of this){ | ||||
| 			// expand array...
 | ||||
| 			if(e instanceof Array){ | ||||
| 				for(var i=0; i < e.length; i++){ | ||||
| 					if(depth <= 1){ | ||||
| 						yield e[i] | ||||
| 
 | ||||
| 					} else { | ||||
| 						yield* typeof(e[i].flat) == 'function' ? | ||||
| 							e[i].flat(depth-1) | ||||
| 							: e[i] } } | ||||
| 			// item as-is...
 | ||||
| 			} else { | ||||
| 				yield e } } }, | ||||
| 
 | ||||
| 	// NOTE: if func is instanceof Generator then it's result (iterator) 
 | ||||
| 	// 		will be expanded...
 | ||||
| 	// NOTE: there is no point to add generator-handler support to either 
 | ||||
| 	// 		.filter(..)  or .reduce(..)
 | ||||
| 	map: stoppable( | ||||
| 		function*(func){ | ||||
| 			var i = 0 | ||||
| 			if(func instanceof Generator){ | ||||
| 				for(var e of this){ | ||||
| 					yield* func(e, i++, this) }  | ||||
| 			} else { | ||||
| 				for(var e of this){ | ||||
| 					yield func(e, i++, this) } } }), | ||||
| 	filter: stoppable(function*(func){ | ||||
| 			var i = 0 | ||||
| 			try{ | ||||
| 				for(var e of this){ | ||||
| 					if(func(e, i++, this)){ | ||||
| 						yield e } }  | ||||
| 			// normalize the stop value...
 | ||||
| 			} catch(err){ | ||||
| 				if(err instanceof STOP){ | ||||
| 					if(!err.value){ | ||||
| 						throw STOP } | ||||
| 					err.value = e } | ||||
| 				throw err } }), | ||||
| 
 | ||||
| 	reduce: stoppable(function(func, res){ | ||||
| 		var i = 0 | ||||
| 		for(var e of this){ | ||||
| 			res = func(res, e, i++, this) } | ||||
| 		return res }), | ||||
| 	greduce: function*(func, res){ | ||||
| 		yield this.reduce(...arguments) }, | ||||
| 
 | ||||
| 	pop: function(){ | ||||
| 		return [...this].pop() }, | ||||
| 	// XXX this needs the value to be iterable...
 | ||||
| 	gpop: function*(){ | ||||
| 		yield [...this].pop() }, | ||||
| 	push: function*(value){ | ||||
| 		yield* this | ||||
| 		yield value }, | ||||
| 	shift: function(){ | ||||
| 		return this.next().value }, | ||||
| 	// XXX this needs the value to be iterable...
 | ||||
| 	gshift: function*(){ | ||||
| 		yield this.next().value }, | ||||
| 	unshift: function*(value){ | ||||
| 		yield value | ||||
| 		yield* this }, | ||||
| 
 | ||||
| 	// non-generators...
 | ||||
| 	//
 | ||||
| 	toArray: function(){ | ||||
| 		return [...this] }, | ||||
| 
 | ||||
| 	// promises...
 | ||||
| 	//
 | ||||
| 	then: function(onresolve, onreject){ | ||||
| 		var that = this | ||||
| 		var p = new Promise( | ||||
| 			function(resolve){ | ||||
| 				resolve([...that]) })  | ||||
| 		p = (onresolve || onreject) ? | ||||
| 			p.then(...arguments) | ||||
| 			: p | ||||
| 		return p }, | ||||
| 	catch: function(func){ | ||||
| 		return this.then().catch(func) }, | ||||
| 	finally: function(func){ | ||||
| 		return this.then().finally(func) }, | ||||
| 
 | ||||
| 	// combinators...
 | ||||
| 	//
 | ||||
| 	chain: function*(...next){ | ||||
| 		yield* next | ||||
| 			.reduce(function(cur, next){ | ||||
| 				return next(cur) }, this) }, | ||||
| 	concat: function*(...next){ | ||||
| 		yield* this | ||||
| 		for(var e of next){ | ||||
| 			yield* e } }, | ||||
| 
 | ||||
| 	// XXX EXPERIMENTAL...
 | ||||
| 	/* XXX not sure how to do this yet... | ||||
| 	tee: function*(...next){ | ||||
| 		// XXX take the output of the current generator and feed it into 
 | ||||
| 		// 		each of the next generators... (???)
 | ||||
| 	}, | ||||
| 	zip: function*(...items){ | ||||
| 		// XXX
 | ||||
| 	}, | ||||
| 	//*/
 | ||||
| 
 | ||||
| 	enumerate: function*(){ | ||||
| 		var i = 0 | ||||
| 		for(var e of this){ | ||||
| 			yield [i++, e] } }, | ||||
| 
 | ||||
| 	join: function(){ | ||||
| 		return [...this] | ||||
| 			.join(...arguments) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| GeneratorMixin(Generator.prototype) | ||||
| GeneratorProtoMixin(Generator.prototype.prototype) | ||||
| 
 | ||||
| 
 | ||||
| // Extend base iterators...
 | ||||
| ITERATOR_PROTOTYPES | ||||
| 	.forEach(function(proto){ | ||||
| 		GeneratorProtoMixin(proto) }) | ||||
| 
 | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | ||||
| // XXX EXPERIMENTAL...
 | ||||
| 
 | ||||
| var AsyncGeneratorMixin = | ||||
| module.AsyncGeneratorMixin = | ||||
| object.Mixin('AsyncGeneratorMixin', 'soft', { | ||||
| 	// XXX TEST...
 | ||||
| 	iter: makeGenerator('async', 'iter'), | ||||
| 
 | ||||
| 	map: makeGenerator('async', 'map'), | ||||
| 	filter: makeGenerator('async', 'filter'), | ||||
| 	reduce: makeGenerator('async', 'reduce'), | ||||
| }) | ||||
| 
 | ||||
| var AsyncGeneratorProtoMixin = | ||||
| module.AsyncGeneratorProtoMixin = | ||||
| object.Mixin('AsyncGeneratorProtoMixin', 'soft', { | ||||
| 	// promise...
 | ||||
| 	//
 | ||||
| 	// NOTE: this will unwind the generator...
 | ||||
| 	// XXX create an iterator promise???
 | ||||
| 	// XXX should we unwind???
 | ||||
| 	then: function(resolve, reject){ | ||||
| 		var that = this | ||||
| 		var p = new Promise(async function(_resolve, _reject){ | ||||
| 			var res = [] | ||||
| 			for await(var elem of that){ | ||||
| 				res.push(elem) } | ||||
| 			_resolve(res) })  | ||||
| 		p = (resolve || reject) ? | ||||
| 			p.then(...arguments) | ||||
| 			: p | ||||
| 		return p }, | ||||
| 	catch: function(func){ | ||||
| 		return this.then().catch(func) }, | ||||
| 	finally: function(){ | ||||
| 		return this.then().finally(func) }, | ||||
| 
 | ||||
| 	// XXX might be a good idea to use this approach above...
 | ||||
| 	iter: stoppable(async function*(handler=undefined){ | ||||
| 		var i = 0 | ||||
| 		if(handler){ | ||||
| 			for await(var e of this){ | ||||
| 				var res = handler.call(this, e, i++) | ||||
| 				if(typeof(res) == 'object'  | ||||
| 						&& Symbol.iterator in res){ | ||||
| 					yield* res | ||||
| 				} else { | ||||
| 					yield res } } | ||||
| 		} else { | ||||
| 			yield* this } }), | ||||
| 
 | ||||
| 	map: async function*(func){ | ||||
| 		yield* this.iter(function(elem, i){ | ||||
| 			return [func.call(this, elem, i)] }) }, | ||||
| 	filter: async function*(func){ | ||||
| 		yield* this.iter(function(elem, i){ | ||||
| 			return func.call(this, elem, i) ? | ||||
| 	   			[elem] | ||||
| 				: [] }) }, | ||||
| 	// NOTE: there is not much point in .reduceRight(..) in an async 
 | ||||
| 	// 		generator as we'll need to fully unwind it then go from the 
 | ||||
| 	// 		end...
 | ||||
| 	reduce: async function(func, state){ | ||||
| 		this.iter(function(elem, i){ | ||||
| 			state = func.call(this, state, elem, i)  | ||||
| 			return [] }) | ||||
| 		return state }, | ||||
| 
 | ||||
| 	// XXX TEST...
 | ||||
| 	chain: async function*(...next){ | ||||
| 		yield* next | ||||
| 			.reduce(function(cur, next){ | ||||
| 				return next(cur) }, this) }, | ||||
| 
 | ||||
| 	flat: async function*(){ | ||||
| 		for await(var e of this){ | ||||
| 			if(e instanceof Array){ | ||||
| 				yield* e | ||||
| 			} else { | ||||
| 				yield e }}}, | ||||
| 
 | ||||
| 	concat: async function*(other){ | ||||
| 		yield* this | ||||
| 		yield* other }, | ||||
| 	push: async function*(elem){ | ||||
| 		yield* this | ||||
| 		yield elem }, | ||||
| 	unsift: async function*(elem){ | ||||
| 		yield elem  | ||||
| 		yield* this }, | ||||
| 
 | ||||
| 	join: async function(){ | ||||
| 		return [...(await this)] | ||||
| 			.join(...arguments) }, | ||||
| 
 | ||||
| 	// XXX
 | ||||
| 	// 	slice -- not sure if we need this...
 | ||||
| 	// 	...
 | ||||
| }) | ||||
| 
 | ||||
| AsyncGeneratorMixin(AsyncGenerator.prototype) | ||||
| AsyncGeneratorProtoMixin(AsyncGenerator.prototype.prototype) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // Generators...
 | ||||
| 
 | ||||
| // NOTE: step can be 0, this will repeat the first element infinitely...
 | ||||
| var range = | ||||
| module.range = | ||||
| function*(from, to, step){ | ||||
| 	if(to == null){ | ||||
| 		to = from | ||||
| 		from = 0 } | ||||
| 	step = step ?? (from > to ? -1 : 1) | ||||
| 	while(step > 0 ?  | ||||
| 			from < to  | ||||
| 			: from > to){ | ||||
| 		yield from  | ||||
| 		from += step } } | ||||
| 
 | ||||
| 
 | ||||
| var repeat = | ||||
| module.repeat = | ||||
| function*(value=true, stop){ | ||||
| 	while( typeof(stop) == 'function' && stop(value) ){ | ||||
| 		yield value } } | ||||
| 
 | ||||
| 
 | ||||
| var produce = | ||||
| module.produce = | ||||
| stoppable(function*(func){ | ||||
| 	while(true){ | ||||
| 		yield func() } }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										45
									
								
								lib/types/main.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										45
									
								
								lib/types/main.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,45 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| // Extend built-in types...
 | ||||
| require('./Object') | ||||
| require('./Array') | ||||
| require('./Set') | ||||
| require('./Map') | ||||
| require('./String') | ||||
| require('./RegExp') | ||||
| require('./Promise') | ||||
| module.patchDate = require('./Date').patchDate | ||||
| 
 | ||||
| 
 | ||||
| // Additional types...
 | ||||
| module.containers = require('./containers') | ||||
| module.func = require('./Function') | ||||
| module.generator = require('./generator') | ||||
| module.event = require('./event') | ||||
| module.runner = require('./runner') | ||||
| 
 | ||||
| 
 | ||||
| // Shorthands...
 | ||||
| module.STOP = object.STOP | ||||
| 
 | ||||
| // frequently used stuff...
 | ||||
| module.AsyncFunction = module.func.AsyncFunction | ||||
| module.Generator = module.generator.Generator | ||||
| module.AsyncGenerator = module.generator.AsyncGenerator | ||||
| // XXX doc...
 | ||||
| module.iter = module.generator.iter | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										975
									
								
								lib/types/runner.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										975
									
								
								lib/types/runner.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,975 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| * | ||||
| * XXX would be helpful to define a task api... | ||||
| * 		task('abort') vs. task.abort(), task state,  ...etc. | ||||
| * 	then define Task and TaskQueue(Queue) and extended api to: | ||||
| * 		- task state introspection | ||||
| * 		- stop/resume tasks (or task queue?) | ||||
| * 		- serialize tasks | ||||
| * 		- ... | ||||
| * 	would be nice to make the task just a slightly extended or better | ||||
| * 	defined function/generator, ideally to make them interchangable... | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| require('./Array') | ||||
| require('./Promise') | ||||
| 
 | ||||
| var events = require('./event') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| module.STOP = object.STOP | ||||
| 
 | ||||
| module.SKIP = {doc: 'skip queue item',} | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // Queue...
 | ||||
| //
 | ||||
| // A means to manage execution of large-ish number of small tasks...
 | ||||
| //
 | ||||
| // A queue is a list of async functions that get executed in order and 
 | ||||
| // not more than .pool_size can run at a time, i.e. new tasks get 
 | ||||
| // started only only when tasks in the running pool either finish or 
 | ||||
| // release their spot in the pool.
 | ||||
| //
 | ||||
| // XXX would be nice to:
 | ||||
| // 		- nest queues -- DONE
 | ||||
| // 		- chain queues
 | ||||
| // 		- weave queues -- a-la generator's .map(..) etc.
 | ||||
| // XXX need to configure to run a specific amount of jobs on each start...
 | ||||
| // XXX document the Queue({handler: e => e*e}, 1,2,3,4) use-case...
 | ||||
| // 		...this is essentially a .map(..) variant...
 | ||||
| var Queue = | ||||
| module.Queue =  | ||||
| object.Constructor('Queue', Array, { | ||||
| 	// create a running queue...
 | ||||
| 	//
 | ||||
| 	runTasks: function(...tasks){ | ||||
| 		if(typeof(tasks[0]) != 'function' | ||||
| 				&& !(tasks[0] instanceof Queue) | ||||
| 				&& typeof(tasks[0].finally) != 'function'){ | ||||
| 			var [options, ...tasks] = arguments } | ||||
| 		return this( | ||||
| 			Object.assign({}, | ||||
| 				options || {}, | ||||
| 				{ state: 'running' }),  | ||||
| 			...tasks) }, | ||||
| 
 | ||||
| 	// Create a handler queue...
 | ||||
| 	//
 | ||||
| 	// 	Queue.handle(func, ...data)
 | ||||
| 	// 	Queue.handle(options, func, ...data)
 | ||||
| 	// 		-> queue
 | ||||
| 	//
 | ||||
| 	// NOTE: func(..) should be compatible with .handler(..) instance method...
 | ||||
| 	// NOTE: this is a shorthand for:
 | ||||
| 	// 			Queue({handler: func, ...}, ...data)
 | ||||
| 	handle: function(handler, ...data){ | ||||
| 		// NOTE: this is a simpler test than in .runTasks(..) above because
 | ||||
| 		// 		here we are expecting a function as the first arg in the 
 | ||||
| 		// 		general case while above a non-task is the exception..
 | ||||
| 		if(typeof(handler) != 'function'){ | ||||
| 			var [options, handler, ...data] = arguments } | ||||
| 		return this( | ||||
| 			Object.assign({}, | ||||
| 				options || {}, | ||||
| 				{handler}),  | ||||
| 			...data) }, | ||||
| 
 | ||||
| }, events.EventMixin('flat', { | ||||
| 	// Config...
 | ||||
| 	//
 | ||||
| 	// Number of tasks to be running at the same time...
 | ||||
| 	pool_size: 8, | ||||
| 
 | ||||
| 	// Number of tasks to run before letting go of the exec frame...
 | ||||
| 	pause_after_sync: 4, | ||||
| 
 | ||||
| 	// XXX
 | ||||
| 	auto_start: false, | ||||
| 
 | ||||
| 	// Start synchronously...
 | ||||
| 	//
 | ||||
| 	// NOTE: this affects the start only, all other timeouts apply as-is... 
 | ||||
| 	sync_start: false, | ||||
| 
 | ||||
| 	catch_errors: true, | ||||
| 
 | ||||
| 	// If true, stop after queue is depleted...
 | ||||
| 	auto_stop: false, | ||||
| 
 | ||||
| 	// NOTE: for long running queues this may get quite big...
 | ||||
| 	collect_results: true, | ||||
| 
 | ||||
| 	// Sub-queue handling mode...
 | ||||
| 	//
 | ||||
| 	// This can be:
 | ||||
| 	// 	'wait'		- wait fot the sun-queue to stop
 | ||||
| 	// 	'unwind'	- run sub-task and requeue parent
 | ||||
| 	//
 | ||||
| 	// XXX do we need this???
 | ||||
| 	// XXX should the nested queue decide??? ...how???
 | ||||
| 	sub_queue: 'unwind', | ||||
| 
 | ||||
| 	// If true only add unique items to queue...
 | ||||
| 	// XXX not implemented yet... 
 | ||||
| 	// 		...how should this be done?
 | ||||
| 	// 			- keep a set of seen elements and check against it?
 | ||||
| 	// 			- check against the queue contents?
 | ||||
| 	unique_items: false, | ||||
| 
 | ||||
| 	// Timeouts...
 | ||||
| 	//
 | ||||
| 	// Time to wait when pool is full...
 | ||||
| 	// if 'auto', wait the average task time * .busy_timeout_scale.
 | ||||
| 	// XXX revise defaults...
 | ||||
| 	busy_timeout: 50, | ||||
| 
 | ||||
| 	//busy_timeout: 'auto',
 | ||||
| 	busy_timeout_scale: 5, | ||||
| 
 | ||||
| 	// Time to wait between checks for new tasks in an empty queue...
 | ||||
| 	poling_timeout: 200, | ||||
| 
 | ||||
| 	// Time to pause after a set of .pause_after_sync sync tasks...
 | ||||
| 	pause_timeout: 0, | ||||
| 
 | ||||
| 	// Runtime statistics...
 | ||||
| 	//
 | ||||
| 	// To disable set to false
 | ||||
| 	//
 | ||||
| 	// NOTE: this, if true, will get replaced with the stats...
 | ||||
| 	runtime_stats: true, | ||||
| 
 | ||||
| 
 | ||||
| 	//
 | ||||
| 	// This can be:
 | ||||
| 	// 		'running'
 | ||||
| 	// 		'stopped'
 | ||||
| 	//
 | ||||
| 	__state: null, | ||||
| 	get state(){ | ||||
| 		return this.__state  | ||||
| 			|| 'stopped' }, | ||||
| 	set state(value){ | ||||
| 		if(value == 'running'){ | ||||
| 			this.start() | ||||
| 		} else if(value == 'stopped'){ | ||||
| 			this.stop()  | ||||
| 		} else if(value == 'aborted'){ | ||||
| 			this.abort() } }, | ||||
| 
 | ||||
| 	// events/actions - state transitions...
 | ||||
| 	//
 | ||||
| 	// NOTE: to start synchronously call .start(true), this will not 
 | ||||
| 	// 		affect further operation...
 | ||||
| 	//
 | ||||
| 	// XXX would be nice to run a specific number of tasks and stop...
 | ||||
| 	// XXX might be a good idea to let the user set .__wait_for_items...
 | ||||
| 	// XXX should we wait for items on empty?
 | ||||
| 	__wait_for_items: null, | ||||
| 	start: events.Event('start', function(handle, sync){ | ||||
| 		// first start -- wait for items...
 | ||||
| 		if('__state' in this){ | ||||
| 			this.__wait_for_items = true } | ||||
| 		// can't start while running...
 | ||||
| 		if(this.__state == 'running' || this.__state == 'aborted'){ | ||||
| 			return handle(false) } | ||||
| 		this.__state = 'running' | ||||
| 		// XXX if empty start polling...
 | ||||
| 		this.__run_tasks__(sync) }), | ||||
| 	stop: events.Event('stop', function(handle){ | ||||
| 		// can't stop while not running...
 | ||||
| 		if(this.state == 'stopped' || this.state == 'aborted'){ | ||||
| 			return handle(false) } | ||||
| 		this.__state = 'stopped' }), | ||||
| 
 | ||||
| 	// events...
 | ||||
| 	//
 | ||||
| 	// 	.tasksAdded(func(evt, [task, ..]))
 | ||||
| 	// 	.taskStarting(func(evt, task))
 | ||||
| 	// 	.taskCompleted(func(evt, task, res))
 | ||||
| 	// 	.queueEmpty(func(evt))
 | ||||
| 	//
 | ||||
| 	tasksAdded: events.PureEvent('tasksAdded'), | ||||
| 	taskStarting: events.PureEvent('taskStarting'), | ||||
| 	taskCompleted: events.PureEvent('taskCompleted'), | ||||
| 	taskFailed: events.PureEvent('taskFailed'), | ||||
| 	queueEmpty: events.PureEvent('queueEmpty'), | ||||
| 
 | ||||
| 
 | ||||
| 	// Runner API...
 | ||||
| 	//
 | ||||
| 	//	Run the given task type...
 | ||||
| 	//	.handler(task[, next])
 | ||||
| 	//		-> STOP
 | ||||
| 	//		-> STOP(value)
 | ||||
| 	//		-> SKIP
 | ||||
| 	//		-> queue
 | ||||
| 	//		-> promise
 | ||||
| 	//		-> func
 | ||||
| 	//		-> ...
 | ||||
| 	//
 | ||||
| 	// NOTE: this intentionally does not handle results as that whould 
 | ||||
| 	// 		require this to also handle events, and other runtime stuff...
 | ||||
| 	// 		...to add a new task/result type either handle the non-standard
 | ||||
| 	// 		result here or wrap it into a standard return value like a 
 | ||||
| 	// 		promise...
 | ||||
| 	handler: function(task, next){ | ||||
| 		return typeof(task) == 'function' ? | ||||
| 				task() | ||||
| 			: (task instanceof Queue  | ||||
| 					&& this.sub_queue == 'unwind') ? | ||||
| 				(task.runTask(next), task) | ||||
| 			: (task instanceof Queue  | ||||
| 					&& this.sub_queue == 'wait') ? | ||||
| 				task.start() | ||||
| 			: task }, | ||||
| 	//
 | ||||
| 	// 	Hanlde 'running' state (async)...
 | ||||
| 	// 	.__run_tasks__()
 | ||||
| 	// 		-> this
 | ||||
| 	//
 | ||||
| 	// NOTE: .sync_start affects only the first run...
 | ||||
| 	// NOTE: we do not store the exec results...
 | ||||
| 	// NOTE: not intended for direct use and will likely have no effect
 | ||||
| 	// 		if called directly... 
 | ||||
| 	//
 | ||||
| 	// XXX will this be collected by the GC if it is polling???
 | ||||
| 	__running: null, | ||||
| 	__run_tasks__: function(sync){ | ||||
| 		var that = this | ||||
| 		sync = sync == null ? | ||||
| 				this.sync_start | ||||
| 			: sync == 'async' ? | ||||
| 				false | ||||
| 			: !!sync | ||||
| 
 | ||||
| 		var run = function(){ | ||||
| 			var c = 0 | ||||
| 			var pause = this.pause_after_sync | ||||
| 			var running = this.__running || [] | ||||
| 
 | ||||
| 			// run queue...
 | ||||
| 			while(this.length > 0  | ||||
| 					&& this.state == 'running' | ||||
| 					// do not exceed pool size...
 | ||||
| 					&& running.length < this.pool_size | ||||
| 					// do not run too many sync tasks without a break...
 | ||||
| 					&& (pause == null | ||||
| 						|| c < pause)){ | ||||
| 				var p = running.length | ||||
| 				delete this.__wait_for_items | ||||
| 
 | ||||
| 				this.runTask(this.__run_tasks__.bind(this))  | ||||
| 
 | ||||
| 				// NOTE: only count sync stuff that does not get added 
 | ||||
| 				// 		to the pool...
 | ||||
| 				p == running.length | ||||
| 					&& c++ } | ||||
| 
 | ||||
| 			// empty queue -> pole or stop...
 | ||||
| 			//
 | ||||
| 			// NOTE: we endup here in two cases:
 | ||||
| 			// 		- the pool is full
 | ||||
| 			// 		- the queue is empty
 | ||||
| 			// NOTE: we do not care about stopping the timer when changing 
 | ||||
| 			// 		state as .__run_tasks__() will stop itself...
 | ||||
| 			if(this.state == 'running'){ | ||||
| 				var timeout =  | ||||
| 					// idle -- empty queue...
 | ||||
| 					this.length == 0 ? | ||||
| 						this.poling_timeout | ||||
| 					// busy poling -- pool full...
 | ||||
| 					: c < pause ? | ||||
| 						//this.busy_timeout
 | ||||
| 						(this.runtime_stats && this.busy_timeout == 'auto' ? | ||||
| 							(this.runtime_stats.avg_t || 50) * (this.busy_timeout_scale || 2) | ||||
| 						: this.busy_timeout == 'auto' ? | ||||
| 							50 * (this.busy_timeout_scale || 2) | ||||
| 						: this.busy_timeout) | ||||
| 					// pause -- let other stuff run...
 | ||||
| 					: (this.pause_timeout || 0) | ||||
| 
 | ||||
| 				;(this.length == 0  | ||||
| 						&& this.auto_stop  | ||||
| 						&& !this.__wait_for_items) ? | ||||
| 					// auto-stop...
 | ||||
| 					this.__onempty__() | ||||
| 					// pole / pause...
 | ||||
| 					: timeout != null | ||||
| 						&& setTimeout( | ||||
| 							this.__run_tasks__.bind(this), timeout) } }.bind(this) | ||||
| 
 | ||||
| 		this.state == 'running' | ||||
| 			&& (sync ? | ||||
| 				run() | ||||
| 				: setTimeout(run, 0)) | ||||
| 		return this }, | ||||
| 	__onempty__: function(){ | ||||
| 		var that = this | ||||
| 		this.poling_timeout != null ? | ||||
| 			// wait a bit then stop if still empty...
 | ||||
| 			setTimeout(function(){ | ||||
| 				that.length > 0 ? | ||||
| 					that.__run_tasks__() | ||||
| 					: that.stop() | ||||
| 				}, this.poling_timeout) | ||||
| 			// stop now...
 | ||||
| 			: this.stop() | ||||
| 		return this }, | ||||
| 	// run one task from queue...
 | ||||
| 	// NOTE: this does not care about .state...
 | ||||
| 	// XXX revise error handling...
 | ||||
| 	// XXX ABORT: added nested abort support...
 | ||||
| 	__results: null, | ||||
| 	runTask: function(next){ | ||||
| 		var that = this | ||||
| 		var running = this.__running = this.__running || [] | ||||
| 
 | ||||
| 		// can't run... 
 | ||||
| 		if(this.length == 0 | ||||
| 				|| running.length >= this.pool_size ){ | ||||
| 			return this } | ||||
| 
 | ||||
| 		// closure: running, task, res, stop, next...
 | ||||
| 		var taskCompleted = function(){ | ||||
| 			// calculate runtime statistics...
 | ||||
| 			if(that.runtime_stats){ | ||||
| 				var x = Date.now() - t0 | ||||
| 				var s = that.runtime_stats =  | ||||
| 					that.runtime_stats  | ||||
| 						|| {max_t: x, min_t: x, avg_t: x, count: 0} | ||||
| 				s.max_t = Math.max(s.max_t, x) | ||||
| 				s.min_t = Math.min(s.min_t, x) | ||||
| 				var i = ++s.count | ||||
| 				var a = s.avg_t  | ||||
| 				s.avg_t = a + (x - a)/i } | ||||
| 			// report...
 | ||||
| 			that.trigger('taskCompleted', task, res) } | ||||
| 		var fail = {doc: 'fail runningDone(..)'} | ||||
| 		var runningDone = function(mode){ | ||||
| 			running.splice(0, running.length,  | ||||
| 				// NOTE: there can be multiple occurrences of res...
 | ||||
| 				...running | ||||
| 					.filter(function(e){ return e !== res }))  | ||||
| 			mode === fail | ||||
| 				|| taskCompleted() | ||||
| 			!stop && next | ||||
| 				&& next() } | ||||
| 
 | ||||
| 		var task = this.shift() | ||||
| 
 | ||||
| 		this.trigger('taskStarting', task) | ||||
| 		var t0 = this.runtime_stats && Date.now() | ||||
| 
 | ||||
| 		// run...
 | ||||
| 		// catch and pass errors to .taskFailed(...)
 | ||||
| 		if(this.catch_errors){ | ||||
| 			var err | ||||
| 			try { | ||||
| 				var res = this.handler(task, next) | ||||
| 
 | ||||
| 			} catch(err){ | ||||
| 				this.trigger('taskFailed', task, err) } | ||||
| 
 | ||||
| 			// promise result...
 | ||||
| 			// XXX is the err test here needed???
 | ||||
| 			res  | ||||
| 				&& err === undefined  | ||||
| 				&& res.catch | ||||
| 				&& res.catch(function(err){ | ||||
| 					that.trigger('taskFailed', task, err) }) | ||||
| 
 | ||||
| 		// let errors rain...
 | ||||
| 		} else { | ||||
| 			var res = this.handler(task, next) } | ||||
| 
 | ||||
| 		// handle stop...
 | ||||
| 		var stop = res === module.STOP  | ||||
| 			|| res instanceof module.STOP | ||||
| 		res = res instanceof module.STOP ? | ||||
| 				res.value | ||||
| 			: res === module.STOP ? | ||||
| 				undefined | ||||
| 			: res | ||||
| 
 | ||||
| 		// collect results...
 | ||||
| 		this.collect_results | ||||
| 			&& res !== module.SKIP | ||||
| 			&& (this.__results = this.__results || []).push(res) | ||||
| 
 | ||||
| 		// handle task results...
 | ||||
| 		//
 | ||||
| 		// queue -- as a set of tasks...
 | ||||
| 		if(res instanceof Queue | ||||
| 				&& this.sub_queue == 'unwind'){ | ||||
| 			if(res.length > 0){ | ||||
| 				this.push(res) } | ||||
| 			taskCompleted() | ||||
| 
 | ||||
| 		// queue -- as a task...
 | ||||
| 		} else if(res instanceof Queue | ||||
| 				&& this.sub_queue == 'wait'){ | ||||
| 			if(res.state == 'stopped'){ | ||||
| 				taskCompleted() | ||||
| 
 | ||||
| 			} else { | ||||
| 				running.push(res) | ||||
| 				res.stop(function(){ | ||||
| 					// not fully done yet -- re-queue... 
 | ||||
| 					res.length > 0 | ||||
| 						&& that.push(res)  | ||||
| 					runningDone() }) } | ||||
| 
 | ||||
| 		// pool async (promise) task...
 | ||||
| 		// XXX REVISE...
 | ||||
| 		// XXX do we need to report errors???
 | ||||
| 		} else if(typeof((res || {}).then) == 'function' | ||||
| 				// one post handler is enough... 
 | ||||
| 				// XXX will this prevent some tasks from reporting???
 | ||||
| 				&& !running.includes(res)){ | ||||
| 			running.push(res)  | ||||
| 			//res.finally(runningDone)
 | ||||
| 			res.then( | ||||
| 				runningDone, | ||||
| 				...(this.catch_errors ? | ||||
| 					[function(err){ | ||||
| 						runningDone(fail) | ||||
| 						that.trigger('taskFailed', task, err) }] | ||||
| 					// let errors propagate...
 | ||||
| 					: [])) | ||||
| 
 | ||||
| 		// func -> re-queue tasks...
 | ||||
| 		} else if(typeof(res) == 'function'){ | ||||
| 			taskCompleted() | ||||
| 			this.push(res) | ||||
| 
 | ||||
| 		// completed sync task...
 | ||||
| 		} else { | ||||
| 			taskCompleted() } | ||||
| 
 | ||||
| 		this.length == 0 | ||||
| 			&& this.trigger('queueEmpty') | ||||
| 
 | ||||
| 		stop | ||||
| 			&& this.stop() | ||||
| 
 | ||||
| 		return res }, | ||||
| 
 | ||||
| 
 | ||||
| 	// helpers...
 | ||||
| 	//
 | ||||
| 	// move tasks to head/tail of queue resp.
 | ||||
| 	prioritize: function(...tasks){ | ||||
| 		return this.sortAs(tasks) }, | ||||
| 	delay: function(...tasks){ | ||||
| 		return this.sortAs(tasks, true) }, | ||||
| 
 | ||||
| 
 | ||||
| 	// edit/add API...
 | ||||
| 	//
 | ||||
| 	// trigger .tasksAdded(..) on relevant methods...
 | ||||
| 	//
 | ||||
| 	// NOTE: adding tasks via the [..] notation will not trigger the 
 | ||||
| 	// 		event...
 | ||||
| 	// NOTE: the events will not be triggered on no-op calls...
 | ||||
| 	//
 | ||||
| 	// XXX add methods that can shorten the queue (like .pop()/.shift()/..)
 | ||||
| 	// 		to test and trigger .queueEmpty()
 | ||||
| 	// 		...this is not and will not be done on polling as that would 
 | ||||
| 	// 		introduce issues -- queue can change between task runs... (revise!)
 | ||||
| 	push: function(...tasks){ | ||||
| 		var res = object.parentCall(Queue.prototype.push, this, ...tasks) | ||||
| 		tasks.length > 0 | ||||
| 			&& this.trigger('tasksAdded', tasks) | ||||
| 		return res }, | ||||
| 	unsift: function(...tasks){ | ||||
| 		var res = object.parentCall(Queue.prototype.unshift, this, ...tasks) | ||||
| 		tasks.length > 0 | ||||
| 			&& this.trigger('tasksAdded', tasks) | ||||
| 		return res }, | ||||
| 	splice: function(...args){ | ||||
| 		var l = this.length | ||||
| 		var res = object.parentCall(Queue.prototype.splice, this, ...args) | ||||
| 		var tasks = args.slice(2) | ||||
| 		tasks.length > 0 | ||||
| 			&& this.trigger('tasksAdded', tasks) | ||||
| 		// length changed...
 | ||||
| 		l != 0 && this.length == 0 | ||||
| 			&& this.trigger('queueEmpty') | ||||
| 		return res }, | ||||
| 
 | ||||
| 	// shorthands...
 | ||||
| 	//
 | ||||
| 	// NOTE: this helps get around the argument number limitation in JS...
 | ||||
| 	add: function(tasks){ | ||||
| 		// handle too large a number of args...
 | ||||
| 		var MAX_ARGS = 10000 | ||||
| 		if(tasks.length > MAX_ARGS){ | ||||
| 			while(tasks.length > 0){ | ||||
| 				this.push(...tasks.splice(0, MAX_ARGS)) } | ||||
| 			return this } | ||||
| 		this.push(...tasks) | ||||
| 		return this }, | ||||
| 	// NOTE: this will also clear the results cache...
 | ||||
| 	clear: function(full=false){ | ||||
| 		full | ||||
| 			&& (delete this.__results) | ||||
| 		this.splice(0, this.length) }, | ||||
| 
 | ||||
| 
 | ||||
| 	// constructor argument handling...
 | ||||
| 	//
 | ||||
| 	// 	Queue()
 | ||||
| 	// 		-> queue
 | ||||
| 	//
 | ||||
| 	// 	Queue(..,tasks)
 | ||||
| 	// 		-> queue
 | ||||
| 	//
 | ||||
| 	// 	Queue(options)
 | ||||
| 	// 	Queue(options, ..,tasks)
 | ||||
| 	// 		-> queue
 | ||||
| 	//
 | ||||
| 	__init__: function(options){  | ||||
| 		// options...
 | ||||
| 		if(!(this[0] instanceof Queue) | ||||
| 				&& this[0] instanceof Object  | ||||
| 				&& typeof(this[0]) != 'function' | ||||
| 				// XXX do we need this test???
 | ||||
| 				&& typeof(this[0].finally) != 'function'){ | ||||
| 			Object.assign(this, this.shift()) } | ||||
| 		this.length > 0 | ||||
| 			&& this.trigger('tasksAdded', [...this]) | ||||
| 		// see if we need to start...
 | ||||
| 		this.__run_tasks__() }, | ||||
| })) | ||||
| 
 | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | ||||
| // Like Queue(..) but adds terminal states and conversion to promises...
 | ||||
| //
 | ||||
| // XXX should this .freeze()??/
 | ||||
| // XXX find a better name...
 | ||||
| var FinalizableQueue = | ||||
| module.FinalizableQueue = | ||||
| object.Constructor('FinalizableQueue', Queue, { | ||||
| 	auto_stop: true, | ||||
| 
 | ||||
| 	__onempty__: function(){ | ||||
| 		return this.trigger('done') }, | ||||
| 
 | ||||
| 	done: events.Event('done', function(handle){ | ||||
| 		// abort only once...
 | ||||
| 		if(this.state == 'aborted' || this.state == 'done'){ | ||||
| 			return handle(false) } | ||||
| 		this.__state = 'done'  | ||||
| 		Object.freeze(this) }), | ||||
| 	abort: events.Event('abort', function(handle){ | ||||
| 		// abort only once...
 | ||||
| 		if(this.state == 'aborted' || this.state == 'done'){ | ||||
| 			return handle(false) } | ||||
| 		this.__state = 'aborted'  | ||||
| 		Object.freeze(this) }), | ||||
| 
 | ||||
| 	// NOTE: each handler will get called once when the next time the 
 | ||||
| 	// 		queue is emptied...
 | ||||
| 	promise: function(){ | ||||
| 		var that = this | ||||
| 		return new Promise(function(resolve, reject){ | ||||
| 			that | ||||
| 				.one('done', function(){ | ||||
| 					resolve(...(that.collect_results ?  | ||||
| 						[that.__results || []] | ||||
| 						: [])) }) | ||||
| 				.one('abort', reject) }) }, | ||||
| 	then: function(onresolve, onreject){ | ||||
| 		return this.promise().then(...arguments) }, | ||||
| 	catch: function(onreject){ | ||||
| 		return this.promise().catch(...arguments) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // Task manager...
 | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | ||||
| // Helpres...
 | ||||
| 
 | ||||
| // Task ticket...
 | ||||
| //
 | ||||
| // This lets the client control the task object and receive messages 
 | ||||
| // from it.
 | ||||
| //
 | ||||
| // NOTE: this is not intended for direct use...
 | ||||
| var TaskTicket = | ||||
| // XXX do we let the user see this???
 | ||||
| module.TaskTicket = | ||||
| object.Constructor('TaskTicket', Promise, { | ||||
| 	__data: null, | ||||
| 
 | ||||
| 	title: null, | ||||
| 	task: null, | ||||
| 
 | ||||
| 	get state(){ | ||||
| 		return this.__data.state }, | ||||
| 
 | ||||
| 	resolve: function(...args){ | ||||
| 		if(this.__data.state == 'pending'){ | ||||
| 			this.__data.state = 'resolved' | ||||
| 			this.__data.resolve(...args) } | ||||
| 		return this }, | ||||
| 	reject: function(...args){ | ||||
| 		if(this.__data.state == 'pending'){ | ||||
| 			this.__data.state = 'rejected' | ||||
| 			this.__data.reject(...args) } | ||||
| 		return this }, | ||||
| 	onmessage: function(msg, func){ | ||||
| 		this.__data.onmessage( | ||||
| 			typeof(msg) == 'function' ? | ||||
| 				msg | ||||
| 				: function(m, ...args){ | ||||
| 					m == msg | ||||
| 						&& func(...args) }) | ||||
| 		return this }, | ||||
| 
 | ||||
| 	then: Promise.iter.prototype.then, | ||||
| 
 | ||||
| 	__new__: function(_, title, resolve, reject, onmessage, task){ | ||||
| 		var handlers | ||||
| 		var resolver = arguments[1] | ||||
| 
 | ||||
| 		var obj = Reflect.construct( | ||||
| 			TaskTicket.__proto__,  | ||||
| 			[function(resolve, reject){ | ||||
| 				handlers = {resolve, reject}  | ||||
| 				// NOTE: this is here to support builtin .then(..)
 | ||||
| 				typeof(resolver) == 'function' | ||||
| 					&& resolver(resolve, reject) }],  | ||||
| 			TaskTicket)  | ||||
| 		// if we got a resolver then it's an internal constructor we are
 | ||||
| 		// not using (likely in base .then(..)) so there is no point in 
 | ||||
| 		// moving on...
 | ||||
| 		// NOTE: this may be a potential source of bugs so we need to 
 | ||||
| 		// 		keep tracking this (XXX)
 | ||||
| 		if(typeof(resolver) == 'function'){ | ||||
| 			return obj } | ||||
| 
 | ||||
| 		// bind this to external resolve/reject...
 | ||||
| 		obj.then( | ||||
| 			function(){ | ||||
| 				resolve(...arguments) },  | ||||
| 			function(){ | ||||
| 				reject(...arguments) }) | ||||
| 		// setup state...
 | ||||
| 		obj.title = title | ||||
| 		obj.task = task | ||||
| 		Object.defineProperty(obj, '__data', { | ||||
| 			value: { | ||||
| 				resolve: handlers.resolve,  | ||||
| 				reject: handlers.reject,  | ||||
| 				onmessage, | ||||
| 				state: 'pending', | ||||
| 			}, | ||||
| 			enumerable: false, | ||||
| 		})  | ||||
| 		return obj }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| // NOTE: this is not intended for direct use...
 | ||||
| var TaskMixin =  | ||||
| module.TaskMixin = | ||||
| object.Mixin('TaskMixin', 'soft', { | ||||
| 	// standard messages...
 | ||||
| 	stop: function(){ | ||||
| 		this.send('stop', ...arguments) }, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | ||||
| // Task manager...
 | ||||
| //
 | ||||
| // Externally manage/influence long running tasks...
 | ||||
| //
 | ||||
| // A task can be:
 | ||||
| // 	- Promise.interactive(..)
 | ||||
| // 	- Queue(..)
 | ||||
| // 	- function(ticket, ..)
 | ||||
| // 	- object supporting task protocol
 | ||||
| //
 | ||||
| //
 | ||||
| // The task is controlled by passing messages, default messages include:
 | ||||
| // 	- .stop(..)
 | ||||
| //
 | ||||
| //
 | ||||
| // Task protocol:
 | ||||
| // 	.then(..)		- registers a completion handler (a-la Promise)
 | ||||
| // 	.stop(..)		- triggers a task to stop
 | ||||
| //
 | ||||
| //
 | ||||
| // NOTE: we should keep the API here similar to Queue...
 | ||||
| // 		...but this is not a queue in principle (internal vs. external 
 | ||||
| // 		management) so we'll also need to keep them different enough to 
 | ||||
| // 		avoid confusion...
 | ||||
| //
 | ||||
| // XXX should a task manager have a pool size???
 | ||||
| // 		...if yes it would be fun to use the queue to manage the pool...
 | ||||
| // XXX revise .abort(..)
 | ||||
| var TaskManager = | ||||
| module.TaskManager = | ||||
| object.Constructor('TaskManager', Array, events.EventMixin('flat', { | ||||
| 	__task_ticket__: TaskTicket, | ||||
| 	__task_mixin__: TaskMixin, | ||||
| 
 | ||||
| 	// settings...
 | ||||
| 	//
 | ||||
| 	// if true start/end times will be set on the task:
 | ||||
| 	// 	.time_started
 | ||||
| 	// 	.time_ended
 | ||||
| 	record_times: true, | ||||
| 
 | ||||
| 	// if true the task will be started sync before .Task(..) is returns..
 | ||||
| 	//
 | ||||
| 	// NOTE: this is not recommended as the default as this can block the
 | ||||
| 	// 		manager...
 | ||||
| 	sync_start: false, | ||||
| 
 | ||||
| 
 | ||||
| 	//
 | ||||
| 	//	.titled(title)
 | ||||
| 	//	.titled(title, ..)
 | ||||
| 	//		-> manager
 | ||||
| 	//
 | ||||
| 	titled: function(title){ | ||||
| 		if(title == 'all' || title == '*'){ | ||||
| 			return this } | ||||
| 		var titles = new Set([...arguments]) | ||||
| 		return this | ||||
| 			.filter(function(task){  | ||||
| 				return titles.has(task.title)  | ||||
| 					|| titles.has(task.name) }) }, | ||||
| 
 | ||||
| 	// actions...
 | ||||
| 	//
 | ||||
| 	// commands to test as methods...
 | ||||
| 	// 	i.e. task.send(cmd, ...args) -> task[cmd](...args)
 | ||||
| 	__send_commands__: [ 'stop', 'abort'], | ||||
| 	send: function(title, ...args){ | ||||
| 		var that = this | ||||
| 		if(title == 'all' || title == '*'){ | ||||
| 			;[...this].forEach(function(task){ | ||||
| 				'send' in task ? | ||||
| 					task.send(...args)  | ||||
| 				: that.__send_commands__.includes(args[0]) ? | ||||
| 					task[args[0]](...args.slice(1)) | ||||
| 				// XXX
 | ||||
| 				: console.warn('.send(..): can\'t .send(..) to:', task)  | ||||
| 			}) | ||||
| 			return this } | ||||
| 		return this.titled( | ||||
| 				...(title instanceof Array) ? | ||||
| 					title | ||||
| 					: [title]) | ||||
| 			.send('all', ...args) }, | ||||
| 	// XXX should this be an event???
 | ||||
| 	//		the best way to go would be to proxy this to task-specific
 | ||||
| 	//		variants and register handlers on the tasks...
 | ||||
| 	//		...should work with .on(..) / ... and other event methods...
 | ||||
| 	stop: function(title='all'){ | ||||
| 		this.send(title, 'stop')  | ||||
| 		return this }, | ||||
| 	// XXX
 | ||||
| 	abort: function(title='all'){ | ||||
| 		this.send(title, 'abort')  | ||||
| 		return this },   | ||||
| 
 | ||||
| 	// events...
 | ||||
| 	//
 | ||||
| 	// XXX need to be able to bind to proxy events...
 | ||||
| 	done: events.PureEvent('done'), | ||||
| 	error: events.PureEvent('error'), | ||||
| 	tasksDone: events.PureEvent('tasksDone'), | ||||
| 
 | ||||
| 
 | ||||
| 	// Create/start a task...
 | ||||
| 	//
 | ||||
| 	//	Create a task...
 | ||||
| 	//	.Task(task)
 | ||||
| 	//	.Task(title, task)
 | ||||
| 	//		-> task-handler
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	//	Create a function task...
 | ||||
| 	//	.Task(func, ..)
 | ||||
| 	//	.Task(title, func, ..)
 | ||||
| 	//		-> task-handler
 | ||||
| 	//
 | ||||
| 	//	func(ticket, ..)
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	// A task can be:
 | ||||
| 	// 	- Promise.cooperative instance
 | ||||
| 	// 	- Queue instance
 | ||||
| 	// 	- function
 | ||||
| 	// 	- Promise instance
 | ||||
| 	//
 | ||||
| 	// The task-manager is a Promise.interactive(..) instance with 
 | ||||
| 	// TaskMixin added.
 | ||||
| 	//
 | ||||
| 	// The ticket is a TaskTicket instance, see it for reference...
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	// We can also force a specific task to start sync/async regardless 
 | ||||
| 	// of the .sync_start setting:
 | ||||
| 	//
 | ||||
| 	//	.Task('sync', task)
 | ||||
| 	//	.Task('sync', title, task)
 | ||||
| 	//	.Task(title, 'sync', task)
 | ||||
| 	//		-> task-handler
 | ||||
| 	//
 | ||||
| 	//	.Task('async', task)
 | ||||
| 	//	.Task('async', title, task)
 | ||||
| 	//	.Task(title, 'async', task)
 | ||||
| 	//		-> task-handler
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	// sync/async start mode apply only to function tasks and tasks that
 | ||||
| 	// have a .start() method like Queue's...
 | ||||
| 	//
 | ||||
| 	//
 | ||||
| 	// NOTE: 'sync' more for a blocking task will block the task manager.
 | ||||
| 	// NOTE: only function tasks accept args.
 | ||||
| 	// NOTE: the task is started as soon as it is accepted.
 | ||||
| 	// NOTE: tasks trigger events only on the task-manager instance that
 | ||||
| 	// 		they were created in...
 | ||||
| 	// 		XXX try and make all event handlers to be registered in the 
 | ||||
| 	// 			task itself and all the manager events just be proxies 
 | ||||
| 	// 			to tasks...
 | ||||
| 	// 			...this needs to work with all the event methods...
 | ||||
| 	Task: function(title, task, ...args){ | ||||
| 		var that = this | ||||
| 		var _args = [...arguments] | ||||
| 
 | ||||
| 		// parse args...
 | ||||
| 		var sync_start = this.sync_start | ||||
| 		if(title == 'sync' || title == 'async'){ | ||||
| 			;[sync_start, title, task, ...args] = _args | ||||
| 			sync_start = sync_start == 'sync' | ||||
| 
 | ||||
| 		} else if(task == 'sync' || task == 'async'){ | ||||
| 			;[title, sync_start, task, ...args] = _args | ||||
| 			sync_start = sync_start == 'sync' } | ||||
| 
 | ||||
| 		// anonymous task...
 | ||||
| 		if(typeof(title) != typeof('str')){ | ||||
| 			;[task, ...args] = _args | ||||
| 			title = null } | ||||
| 
 | ||||
| 		// normalize handler...
 | ||||
| 		var run | ||||
| 		var handler =  | ||||
| 			// queue...
 | ||||
| 			// NOTE: queue is task-compatible...
 | ||||
| 			task instanceof FinalizableQueue ? | ||||
| 				task | ||||
| 			// task protocol...
 | ||||
| 			: task && task.then  | ||||
| 					&& (task.stop || task.send) ? | ||||
| 				task | ||||
| 			: this.__task_mixin__( | ||||
| 				// interactive promise...
 | ||||
| 				task instanceof Promise.interactive ? | ||||
| 					task | ||||
| 				// dumb promise -- will ignore all the messages...
 | ||||
| 				// XXX should we complain about this???
 | ||||
| 				: task instanceof Promise ? | ||||
| 					Promise.interactive( | ||||
| 						function(resolve, reject, onmsg){ | ||||
| 							// NOTE: since this is a promise, we can't
 | ||||
| 							// 		stop it externally...
 | ||||
| 							onmsg(function(msg){ | ||||
| 								msg == 'stop' | ||||
| 									&& reject('stop') }) | ||||
| 							task.then(resolve, reject) }) | ||||
| 				// function...
 | ||||
| 				: Promise.interactive( | ||||
| 					function(resolve, reject, onmessage){ | ||||
| 						// NOTE: we need to start this a bit later hence 
 | ||||
| 						// 		we wrap this into run(..) and call it when
 | ||||
| 						// 		the context is ready...
 | ||||
| 						run = function(){ | ||||
| 							var res =  | ||||
| 								task( | ||||
| 									that.__task_ticket__(title, resolve, reject, onmessage, handler),  | ||||
| 									...args)  | ||||
| 							// NOTE: if the client calls resolve(..) this 
 | ||||
| 							// 		second resolve(..) call has no effect,
 | ||||
| 							// 		and the same is true with reject...
 | ||||
| 							// XXX is double binding like this (i.e. 
 | ||||
| 							// 		ticket + .then()) a good idea???
 | ||||
| 							res instanceof Promise | ||||
| 								&& res.then(resolve, reject) } })) | ||||
| 		// set handler title...
 | ||||
| 		// NOTE: this will override the title of the handler if it was 
 | ||||
| 		// 		set before...
 | ||||
| 		if(title){ | ||||
| 			handler.title | ||||
| 				&& console.warn( | ||||
| 					'TaskManager.Task(..): task title already defined:', handler.title, | ||||
| 					'overwriting with:', title) | ||||
| 			Object.assign(handler, {title}) } | ||||
| 
 | ||||
| 		this.push(handler) | ||||
| 
 | ||||
| 		this.record_times | ||||
| 			&& (handler.time_started = Date.now()) | ||||
| 
 | ||||
| 		// handle task manager state...
 | ||||
| 		var cleanup = function(evt){ | ||||
| 			return function(res){ | ||||
| 				that.record_times | ||||
| 					&& (handler.time_ended = Date.now()) | ||||
| 				that.splice(that.indexOf(handler), 1) | ||||
| 				that.trigger(evt, task, res)  | ||||
| 				that.length == 0 | ||||
| 					&& that.trigger('tasksDone') } } | ||||
| 		handler | ||||
| 			.then(cleanup('done'), cleanup('error')) | ||||
| 
 | ||||
| 		// start...
 | ||||
| 		var start = function(){ | ||||
| 			run ? | ||||
| 				run() | ||||
| 			: task.start ? | ||||
| 				task.start() | ||||
| 	   		: null } | ||||
| 		// trigger task start...
 | ||||
| 		sync_start ? | ||||
| 			start() | ||||
| 			: setTimeout(start, 0) | ||||
| 
 | ||||
| 		return handler }, | ||||
| })) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
							
								
								
									
										680
									
								
								lib/types/test.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										680
									
								
								lib/types/test.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,680 @@ | ||||
| #!/usr/bin/env node
 | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************/  /* c8 ignore next 2 */ | ||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||
| (function(require){ var module={} // make module AMD/node compatible...
 | ||||
| /*********************************************************************/ | ||||
| 
 | ||||
| var colors = require('colors') | ||||
| var test = require('ig-test') | ||||
| var object = require('ig-object') | ||||
| 
 | ||||
| var types = require('./main') | ||||
| var promise = require('./Promise') | ||||
| 
 | ||||
| var containers = require('./containers') | ||||
| 	var generator = require('./generator') | ||||
| var events = require('./event') | ||||
| var runner = require('./runner') | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| /* | ||||
| var setups = test.Setups({ | ||||
| }) | ||||
| 
 | ||||
| var modifiers = test.Modifiers({ | ||||
| }) | ||||
| 
 | ||||
| var tests = test.Tests({ | ||||
| }) | ||||
| //*/
 | ||||
| 
 | ||||
| 
 | ||||
| var cases = test.Cases({ | ||||
| 	// Object.js
 | ||||
| 	//
 | ||||
| 	Object: function(assert){ | ||||
| 		var o = Object.assign( | ||||
| 			Object.create({ | ||||
| 				x: 111, | ||||
| 				y: 222, | ||||
| 			}), { | ||||
| 				y: 333, | ||||
| 				z: 444, | ||||
| 			}) | ||||
| 		var oo = assert(Object.flatCopy(o), 'Object.flatCopy(..)') | ||||
| 
 | ||||
| 		assert(Object.match(oo, {x: 111, y: 333, z: 444}), 'Object.match(..)') | ||||
| 
 | ||||
| 		var k = ['z', 'x', 'y'] | ||||
| 		assert(Object.keys(Object.sort(oo, k)).cmp(k), 'Object.sort(,,)') | ||||
| 
 | ||||
| 		assert(Object.keys(Object.sort(oo)).cmp(k.slice().sort()), 'Object.sort(,,)') | ||||
| 
 | ||||
| 		var cmp = function(a, b){ | ||||
| 			return a == 'y' ? | ||||
| 					1 | ||||
| 				: a == 'z' ? | ||||
| 					-1 | ||||
| 				: 0 } | ||||
| 		assert(Object.keys(Object.sort(oo, cmp)).cmp(k.slice().sort(cmp)), 'Object.sort(,,)') | ||||
| 	}, | ||||
| 
 | ||||
| 	// Array.js
 | ||||
| 	// 	- flat (???)
 | ||||
| 	// 	- includes (???)
 | ||||
| 	// 	- first
 | ||||
| 	// 	- last
 | ||||
| 	// 	- compact
 | ||||
| 	// 	- len
 | ||||
| 	// 	- unique
 | ||||
| 	// 	- tailUnique
 | ||||
| 	// 	- cmp
 | ||||
| 	// 	- setCmp
 | ||||
| 	// 	- sortAs
 | ||||
| 	// 	- mapChunks
 | ||||
| 	// 	- filterChunks
 | ||||
| 	// 	- reduceChunks
 | ||||
| 	// 	- toKeys
 | ||||
| 	// 	- toMap
 | ||||
| 	Array: function(assert){ | ||||
| 	}, | ||||
| 
 | ||||
| 	// Set.js
 | ||||
| 	// 	- unite
 | ||||
| 	// 	- intersect
 | ||||
| 	// 	- subtract
 | ||||
| 	// 	- sort
 | ||||
| 	Set: function(assert){ | ||||
| 	}, | ||||
| 	 | ||||
| 	// Map.js
 | ||||
| 	// 	- sort
 | ||||
| 	Map: function(assert){ | ||||
| 	}, | ||||
| 
 | ||||
| 	Generator: function(assert){ | ||||
| 	}, | ||||
| 	 | ||||
| 	String: function(assert){ | ||||
| 		assert(''.capitalize() == '') | ||||
| 		assert('a'.capitalize() == 'A') | ||||
| 		assert('abc'.capitalize() == 'Abc') | ||||
| 	}, | ||||
| 
 | ||||
| 	RegExp: function(assert){ | ||||
| 	}, | ||||
| 
 | ||||
| 	IterablePromise: test.TestSet(function(){ | ||||
| 		var create = function(assert, value){ | ||||
| 			return { | ||||
| 				input: value,  | ||||
| 				output: assert(Promise.iter(value), 'Promise.iter(', value, ')'), | ||||
| 			} } | ||||
| 
 | ||||
| 		this.Setup({ | ||||
| 			empty: function(assert){ | ||||
| 				return create(assert, []) }, | ||||
| 			value: function(assert){ | ||||
| 				return create(assert, 123) }, | ||||
| 			array: function(assert){ | ||||
| 				return create(assert, [1, 2, 3]) }, | ||||
| 			nested_array: function(assert){ | ||||
| 				return create(assert, [1, 2, [3]]) }, | ||||
| 			promise_value: function(assert){ | ||||
| 				return create(assert, Promise.resolve(123)) }, | ||||
| 			promise_array: function(assert){ | ||||
| 				return create(assert, Promise.resolve([1, 2, 3])) }, | ||||
| 			promise_nested_array: function(assert){ | ||||
| 				return create(assert, Promise.resolve([1, 2, [3]])) }, | ||||
| 			array_mixed: function(assert){ | ||||
| 				return create(assert, [1, Promise.resolve(2), 3]) }, | ||||
| 			nested_array_mixed: function(assert){ | ||||
| 				return create(assert, [ | ||||
| 					1,  | ||||
| 					Promise.resolve(2),  | ||||
| 					[3],  | ||||
| 					Promise.resolve([4]), | ||||
| 				]) }, | ||||
| 			promise_array_mixed: function(assert){ | ||||
| 				return create(assert, Promise.resolve([1, Promise.resolve(2), 3])) }, | ||||
| 			promise_nested_array_mixed: function(assert){ | ||||
| 				return create(assert, Promise.resolve([ | ||||
| 					1,  | ||||
| 					Promise.resolve(2),  | ||||
| 					[3],  | ||||
| 					Promise.resolve([4]), | ||||
| 				])) }, | ||||
| 		}) | ||||
| 		this.Modifier({ | ||||
| 			nest: function(assert, setup){ | ||||
| 				setup.output = Promise.iter(setup.output) | ||||
| 				return setup }, | ||||
| 			iter: function(assert, setup){ | ||||
| 				setup.output = setup.output.iter() | ||||
| 				return setup }, | ||||
| 
 | ||||
| 			map_asis: function(assert, setup){ | ||||
| 				setup.output = setup.output | ||||
| 					.map(function(e){  | ||||
| 						return e })  | ||||
| 				return setup }, | ||||
| 			map_promise: function(assert, setup){ | ||||
| 				setup.output = setup.output | ||||
| 					.map(function(e){  | ||||
| 						return Promise.resolve(e) })  | ||||
| 				return setup }, | ||||
| 
 | ||||
| 			filter_all: function(assert, setup){ | ||||
| 				setup.output = setup.output | ||||
| 					.filter(function(e){ return true })  | ||||
| 				return setup }, | ||||
| 			// XXX either the test is worng or something is broken...
 | ||||
| 			filter_none: function(assert, setup){ | ||||
| 				return { | ||||
| 					input: [], | ||||
| 					output: setup.output | ||||
| 						.filter(function(e){ return false }), | ||||
| 				} }, | ||||
| 
 | ||||
| 			/* XXX need tuning... | ||||
| 			concat_basic: function(assert, {input, output}){ | ||||
| 				return { | ||||
| 					input: [input].flat() | ||||
| 						.concat(['a', 'b', 'c']), | ||||
| 					output: output | ||||
| 						.concat(['a', 'b', 'c']), | ||||
| 				} }, | ||||
| 			concat_nested_array: function(assert, {input, output}){ | ||||
| 				return { | ||||
| 					input: [input].flat() | ||||
| 						.concat(['a', ['b'], 'c']), | ||||
| 					output: output | ||||
| 						.concat(['a', ['b'], 'c']), | ||||
| 				} }, | ||||
| 			//*/
 | ||||
| 
 | ||||
| 		}) | ||||
| 		this.Test({ | ||||
| 			value: async function(assert, {input, output}){ | ||||
| 
 | ||||
| 				var res = await output | ||||
| 
 | ||||
| 				assert(res instanceof Array, 'result is array') | ||||
| 
 | ||||
| 				input instanceof Array ? | ||||
| 					// XXX this does not catch some errors -- map_promise specifically...
 | ||||
| 					assert.array(res,  | ||||
| 						await Promise.all(input),  | ||||
| 							'array -> array') | ||||
| 				: (input instanceof Promise && await input instanceof Array) ? | ||||
| 					assert.array(res,  | ||||
| 						await input,  | ||||
| 							'promise array -> array') | ||||
| 				: input instanceof Promise ? | ||||
| 					assert.array(res,  | ||||
| 						[await input],  | ||||
| 							'promise value -> array') | ||||
| 				: assert.array(res,  | ||||
| 					[input],  | ||||
| 					'value -> array') }, | ||||
| 		}) | ||||
| 	}), | ||||
| 	Promise: async function(assert){ | ||||
| 		var p = assert(Promise.cooperative(), '.cooperative()') | ||||
| 		 | ||||
| 		assert(!p.isSet, 'promise unset') | ||||
| 		 | ||||
| 		var RESOLVE = 123 | ||||
| 		var then = false | ||||
| 		var done = false | ||||
| 
 | ||||
| 		p.then(function(v){ | ||||
| 			then = assert(v == RESOLVE, '.then(..) handled') }) | ||||
| 
 | ||||
| 		p.finally(function(){ | ||||
| 			done = assert(true, '.finally(..) handled') }) | ||||
| 
 | ||||
| 		assert(!p.isSet, 'promise unset') | ||||
| 
 | ||||
| 		p.set(RESOLVE) | ||||
| 
 | ||||
| 		assert(p.isSet, 'promise set') | ||||
| 
 | ||||
| 		// allow the promise to finalize...
 | ||||
| 		await p | ||||
| 
 | ||||
| 		assert(then, '.then(..): confirmed') | ||||
| 		assert(done, '.done(..): confirmed') | ||||
| 
 | ||||
| 		// XXX
 | ||||
| 
 | ||||
| 		assert(await Promise.iter([1, Promise.resolve(2), [3]]), '.iter(..)') | ||||
| 
 | ||||
| 		assert.array( | ||||
| 			await Promise.iter([1, 2, 3]),  | ||||
| 			[1, 2, 3],  | ||||
| 				'basic array') | ||||
| 		assert.array( | ||||
| 			await Promise.iter([1, Promise.resolve(2), 3]),  | ||||
| 			[1, 2, 3],  | ||||
| 				'promises as elements') | ||||
| 	}, | ||||
| 
 | ||||
| 	// Date.js
 | ||||
| 	Date: function(assert){ | ||||
| 		var d = new Date() | ||||
| 
 | ||||
| 		var ts = assert(d.getTimeStamp(), '.getTimeStamp()') | ||||
| 		var tsm = assert(d.getTimeStamp(true), '.getTimeStamp(true)') | ||||
| 
 | ||||
| 		var dd = assert(Date.fromTimeStamp(tsm), '.fromTimeStamp(..)') | ||||
| 		var dds = assert(Date.fromTimeStamp(ts), '.fromTimeStamp(..)') | ||||
| 
 | ||||
| 		assert(d.toString() == dd.toString(), 'timestamp reversable') | ||||
| 
 | ||||
| 		assert(d.toShortDate() == dd.toShortDate(), '.toShortDate()') | ||||
| 		assert(d.toShortDate() == dds.toShortDate(), '.toShortDate()') | ||||
| 		assert(d.toShortDate(true) == dd.toShortDate(true), '.toShortDate(true)') | ||||
| 
 | ||||
| 		var a = Date.timeStamp() | ||||
| 		var b = Date.timeStamp(true) | ||||
| 
 | ||||
| 		assert(a == Date.fromTimeStamp(a).getTimeStamp()) | ||||
| 		assert(a + '000' == Date.fromTimeStamp(a).getTimeStamp(true)) | ||||
| 		assert(b == Date.fromTimeStamp(b).getTimeStamp(true)) | ||||
| 
 | ||||
| 		assert(Date.str2ms('1') == 1, 'Date.str2ms("1")') | ||||
| 		assert(Date.str2ms(1) == 1, 'Date.str2ms(1)') | ||||
| 		assert(Date.str2ms('1ms') == 1, 'Date.str2ms("1ms")') | ||||
| 		assert(Date.str2ms('1s') == 1000, 'Date.str2ms("1s")') | ||||
| 		assert(Date.str2ms('1m') == 60*1000, 'Date.str2ms("1m")') | ||||
| 		assert(Date.str2ms('1h') == 60*60*1000, 'Date.str2ms("1h")') | ||||
| 		assert(Date.str2ms('1d') == 24*60*60*1000, 'Date.str2ms("1d")') | ||||
| 
 | ||||
| 		assert(Date.str2ms('5 sec') == 5000, 'Date.str2ms("1 sec")') | ||||
| 		assert(Date.str2ms('5 second') == 5000, 'Date.str2ms("1 second")') | ||||
| 		assert(Date.str2ms('5 seconds') == 5000, 'Date.str2ms("1 seconds")') | ||||
| 
 | ||||
| 		assert(Date.str2ms('2hour') == 2*60*60*1000, 'Date.str2ms("1hour")') | ||||
| 		assert(Date.str2ms('2 Hour') == 2*60*60*1000, 'Date.str2ms("1 Hour")') | ||||
| 		assert(Date.str2ms('2 hours') == 2*60*60*1000, 'Date.str2ms("1 hours")') | ||||
| 
 | ||||
| 		assert(Number.isNaN(Date.str2ms('moo')), 'Date.str2ms("moo")') | ||||
| 		assert(Number.isNaN(Date.str2ms('123 moo')), 'Date.str2ms("moo")') | ||||
| 	}, | ||||
| 
 | ||||
| 	// containers.js
 | ||||
| 	// XXX move this out to a separate test set...
 | ||||
| 	UniqueKeyMap: function(assert){ | ||||
| 		var a = assert(containers.UniqueKeyMap(), '') | ||||
| 		var b = assert(containers.UniqueKeyMap([]), '') | ||||
| 		var c = assert(containers.UniqueKeyMap([ | ||||
| 			['a', 111], | ||||
| 			['b', 222], | ||||
| 			['a', 333], | ||||
| 		]), '') | ||||
| 		 | ||||
| 		assert(a.size == 0) | ||||
| 		assert(b.size == 0) | ||||
| 		assert(c.size == 3) | ||||
| 
 | ||||
| 		assert(c.get('a') == 111) | ||||
| 
 | ||||
| 		assert(c.has('a (1)')) | ||||
| 		assert(c.get('a (1)') == 333) | ||||
| 
 | ||||
| 
 | ||||
| 		var n | ||||
| 
 | ||||
| 		assert((n = c.set('a', 444, true)) == 'a (2)') | ||||
| 		assert(c.get(n) == 444) | ||||
| 
 | ||||
| 		assert(c.reset(n, 555)) | ||||
| 		assert(c.get(n) == 555) | ||||
| 
 | ||||
| 		assert(c.delete('a (1)')) | ||||
| 		assert(!c.has('a (1)')) | ||||
| 
 | ||||
| 		assert((n = c.set('a', 222, true)) == 'a (1)') | ||||
| 
 | ||||
| 		assert(c.keysOf(222).sort().cmp(['b', 'a'].sort())) | ||||
| 
 | ||||
| 		var k = [...c.keys()] | ||||
| 		k[k.indexOf('a')] = 'b (1)' | ||||
| 
 | ||||
| 		assert((n = c.rename('a', 'b', true)) == 'b (1)') | ||||
| 
 | ||||
| 		// check order...
 | ||||
| 		// XXX since we are using Array's .cmp(..) would be nice to be 
 | ||||
| 		// 		able to fail this test if that fails to test correctly...
 | ||||
| 		// 		...not sure how to do this though...
 | ||||
| 		assert(k.cmp([...c.keys()]), '.rename(..) order') | ||||
| 	}, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| var PromiseTests = test.TestSet() | ||||
| test.Case('PromiseTests', PromiseTests) | ||||
| 
 | ||||
| PromiseTests.setups({ | ||||
| 	cooperative: function(assert){ | ||||
| 		return { | ||||
| 			a: assert(Promise.cooperative(), '.cooperative()') | ||||
| 		} }, | ||||
| }) | ||||
| 
 | ||||
| PromiseTests.modifiers({ | ||||
| }) | ||||
| 
 | ||||
| PromiseTests.tests({ | ||||
| 
 | ||||
| }) | ||||
| 	 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // UniqueKeyMap testing...
 | ||||
| 
 | ||||
| var UniqueKeyMap = test.TestSet() | ||||
| test.Case('UniqueKeyMap-new', UniqueKeyMap) | ||||
| 	 | ||||
| // XXX
 | ||||
| UniqueKeyMap.setups({ | ||||
| 	empty: function(assert){ | ||||
| 		return { | ||||
| 			value: assert(containers.UniqueKeyMap()), | ||||
| 			// metadata...
 | ||||
| 			size: 0, | ||||
| 		}}, | ||||
| 	'empty-input': function(assert){ | ||||
| 		return { | ||||
| 			value: assert(containers.UniqueKeyMap([])),  | ||||
| 			// metadata...
 | ||||
| 			size: 0, | ||||
| 		}}, | ||||
| 	// XXX non-empty input...
 | ||||
| 	// XXX intersecting unput...
 | ||||
| }) | ||||
| 
 | ||||
| // XXX
 | ||||
| UniqueKeyMap.modifiers({ | ||||
| 	set: function(assert, e, k='a', o){ | ||||
| 		o = o || {} | ||||
| 		var n | ||||
| 
 | ||||
| 		var exists = e.value.has(k) | ||||
| 		var expected = e.value.uniqieKey(k) | ||||
| 
 | ||||
| 		assert(n = e.value.set(k, o, true), '.set(..)') | ||||
| 
 | ||||
| 		// key update...
 | ||||
| 		assert(n.startsWith(k), 'key prefix') | ||||
| 		assert(n == expected, 'unexpected key') | ||||
| 		exists | ||||
| 			|| assert(n == k, 'key unexpectedly changed') | ||||
| 
 | ||||
| 		// size...
 | ||||
| 		e.size += 1 | ||||
| 		assert(e.value.size == e.size, 'inc size') | ||||
| 
 | ||||
| 		return e }, | ||||
| 	reset: function(assert, e, k='a', o){ | ||||
| 		o = o || {} | ||||
| 
 | ||||
| 		var exists = e.value.has(k) | ||||
| 
 | ||||
| 		assert(e.value.reset(k, o)) | ||||
| 		assert(e.value.get(k) === o) | ||||
| 
 | ||||
| 		// size...
 | ||||
| 		exists | ||||
| 			|| (e.size += 1) | ||||
| 		assert(e.value.size == e.size) | ||||
| 
 | ||||
| 		return e }, | ||||
| 	delete: function(assert, e, k='a'){ | ||||
| 
 | ||||
| 		var exists = e.value.has(k) | ||||
| 
 | ||||
| 		assert(e.value.delete(k) == exists, '.delete(..)') | ||||
| 		assert(!e.value.has(k), 'delete successful') | ||||
| 
 | ||||
| 		// size...
 | ||||
| 		exists | ||||
| 			&& (e.size -= 1) | ||||
| 		assert(e.value.size == e.size) | ||||
| 
 | ||||
| 		return e }, | ||||
| 
 | ||||
| 	'set-set': function(assert, e){ | ||||
| 		this.set(assert, e, 'x') | ||||
| 		this.set(assert, e, 'y') | ||||
| 		this.set(assert, e, 'x') | ||||
| 		return e }, | ||||
| }) | ||||
| 
 | ||||
| // XXX
 | ||||
| UniqueKeyMap.tests({ | ||||
| 	consistent: function(assert, e){ | ||||
| 
 | ||||
| 		assert(e.value.size == e.size, '.size') | ||||
| 		assert([...e.value.keys()].length == e.value.size, '.keys() same size as .size') | ||||
| 
 | ||||
| 		return e } | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // events...
 | ||||
| 
 | ||||
| var Events = test.TestSet() | ||||
| test.Case('Events', Events) | ||||
| 
 | ||||
| // XXX test aborting handlers...
 | ||||
| Events.cases({ | ||||
| 	base: function(assert){ | ||||
| 		var called = {} | ||||
| 
 | ||||
| 		// object with events...
 | ||||
| 		var ObjWithEvents =  | ||||
| 			assert( | ||||
| 				events.EventMixin('flat', { | ||||
| 					// NOTE: we will also use virtual events later -- 'moo' 
 | ||||
| 					// 		and 'foo', these do not have to be defined to 
 | ||||
| 					// 		be usable...
 | ||||
| 
 | ||||
| 					// blank events...
 | ||||
| 					bareEventBlank: assert( | ||||
| 						events.Eventful('bareEventBlank'),  | ||||
| 						'.Eventful(..): blank'), | ||||
| 					eventBlank: assert( | ||||
| 						events.Event('eventBlank'),  | ||||
| 						'.Event(..): blank'), | ||||
| 
 | ||||
| 					// normal events...
 | ||||
| 					bareEvent: assert(events.Eventful('bareEvent',  | ||||
| 						function(handle, ...args){ | ||||
| 							called['bareEvent-call'] = true | ||||
| 							assert(handle(), '.Eventful(..) -> handle(..)') | ||||
| 							return 'bareEvent' | ||||
| 						}), '.Eventful(..)'), | ||||
| 					event: assert(events.Event('event',  | ||||
| 						function(handle, ...args){ | ||||
| 							called['event-call'] = true | ||||
| 							assert(handle(), '.Event(..) -> handle(..)') | ||||
| 						}), '.Event(..)'), | ||||
| 				}),  | ||||
| 				'object with event mixin created.') | ||||
| 
 | ||||
| 		// create an "instance"...
 | ||||
| 		var obj = Object.create(ObjWithEvents) | ||||
| 
 | ||||
| 
 | ||||
| 		// test event list...
 | ||||
| 		assert.array(obj.events, ['event', 'eventBlank'], '.events') | ||||
| 		assert.array(obj.eventful, ['bareEvent', 'bareEventBlank'], '.eventful') | ||||
| 
 | ||||
| 		// bind...
 | ||||
| 		var bind = function(evt){ | ||||
| 			assert(obj.on(evt, function(evt, ...args){ | ||||
| 				called[evt +'-handle'] = true | ||||
| 			}) === obj, 'bind: <obj-w-events>.on("'+ evt +'", ..)') } | ||||
| 
 | ||||
| 		;['moo',  | ||||
| 				...obj.events, | ||||
| 				...obj.eventful] | ||||
| 			.forEach(bind) | ||||
| 
 | ||||
| 		assert(obj.event(function(evt, ...args){ | ||||
| 			called['event-handle-2'] = true | ||||
| 		}) === obj, 'bind: <obj-w-events>.event(<func>)') | ||||
| 
 | ||||
| 
 | ||||
| 		// trigger 
 | ||||
| 		var trigger = function(evt, triggerd=true, handled=true){ | ||||
| 			var res = assert(obj.trigger(evt), 'trigger: <obj-w-events>.trigger("'+ evt +'")') | ||||
| 			triggerd | ||||
| 				&& !evt.endsWith('Blank') | ||||
| 				&& assert(called[evt +'-call'], 'trigger: "'+ evt +'" event triggered') | ||||
| 			handled | ||||
| 				&& assert(called[evt +'-handle'], 'trigger: "'+ evt +'" event handled')  | ||||
| 			delete called[evt +'-call'] | ||||
| 			delete called[evt +'-handle']  | ||||
| 			return res } | ||||
| 		var call = function(evt, triggered=true, handled=true){ | ||||
| 			var res = assert(obj[evt](), 'trigger: <obj-w-events>.'+ evt +'(..)') | ||||
| 			triggered | ||||
| 				&& !evt.endsWith('Blank') | ||||
| 				&& assert(called[evt +'-call'], 'trigger: "'+ evt +'" event triggered') | ||||
| 			handled | ||||
| 				&& assert(called[evt +'-handle'], 'trigger: "'+ evt +'" event handled') | ||||
| 			delete called[evt +'-call'] | ||||
| 			delete called[evt +'-handle']  | ||||
| 			return res } | ||||
| 
 | ||||
| 		trigger('foo', false, false) | ||||
| 		trigger('moo', false) | ||||
| 
 | ||||
| 		obj.events | ||||
| 			.forEach(function(e){  | ||||
| 				trigger(e)  | ||||
| 				call(e) }) | ||||
| 
 | ||||
| 		assert(called['event-handle-2'], 'trigger: "event" event handled') | ||||
| 		delete called['event-handle-2'] | ||||
| 
 | ||||
| 		assert(call('event') === obj, '<obj-w-events>.event(..) return value.') | ||||
| 		assert(call('bareEvent') == 'bareEvent', '<obj-w-events>.bareEvent(..) return value.') | ||||
| 
 | ||||
| 
 | ||||
| 		// unbind: .one(..) / .off(..) 
 | ||||
| 		// XXX this is triggered twice for some reason...
 | ||||
| 		obj.one('event', function(){  | ||||
| 			called['event-one-time-handler'] =  | ||||
| 				(called['event-one-time-handler'] || 0) + 1 }) | ||||
| 		obj | ||||
| 			.event() | ||||
| 			//.event()
 | ||||
| 			//.event()
 | ||||
| 		assert(called['event-one-time-handler'] == 1, '.one("event", ..) handler cleared.') | ||||
| 		delete called['event-one-time-handler'] | ||||
| 
 | ||||
| 		// special case...
 | ||||
| 		obj.trigger('event', function(){ called['trigger-function-called'] = true }) | ||||
| 		assert(called['trigger-function-called'] === undefined, '.trigger(..) should not call it\'s args') | ||||
| 		 | ||||
| 		// XXX test passing args...
 | ||||
| 
 | ||||
| 		// XXX test different mode events...
 | ||||
| 
 | ||||
| 		// re-bind...
 | ||||
| 
 | ||||
| 	}, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| // events...
 | ||||
| 
 | ||||
| var Runner = test.TestSet() | ||||
| test.Case('Runner', Runner) | ||||
| 
 | ||||
| // XXX test aborting handlers...
 | ||||
| Runner.cases({ | ||||
| 	base: function(assert){ | ||||
| 		//var q = assert(runner.Queue({auto_stop: true}), 'Queue()')
 | ||||
| 		var q = assert(runner.Queue(), 'Queue()') | ||||
| 
 | ||||
| 		// empty states...
 | ||||
| 		assert(q.state == 'stopped', '.state: stopped') | ||||
| 
 | ||||
| 		q.start() | ||||
| 
 | ||||
| 		assert(q.state == 'running', '.state: running') | ||||
| 
 | ||||
| 		q.start() | ||||
| 
 | ||||
| 		assert(q.state == 'running', '.state: running') | ||||
| 
 | ||||
| 		q.stop() | ||||
| 
 | ||||
| 		assert(q.state == 'stopped', '.state: stopped') | ||||
| 
 | ||||
| 		var tasks_run = [] | ||||
| 		var a = function(){ tasks_run.push('a') } | ||||
| 		var b = function(){ tasks_run.push('b') } | ||||
| 		var c = function(){ tasks_run.push('c') } | ||||
| 
 | ||||
| 		q.push(a) | ||||
| 		q.push(b) | ||||
| 		q.push(c) | ||||
| 		 | ||||
| 		assert(q.length == 3, '.length is 3') | ||||
| 
 | ||||
| 		q.runTask() | ||||
| 		q.runTask() | ||||
| 		q.runTask() | ||||
| 
 | ||||
| 		assert.array(tasks_run, ['a', 'b', 'c'], 'all tasks run') | ||||
| 
 | ||||
| 		// XXX need to figure out how to test async...
 | ||||
| 
 | ||||
| 		tasks_run = [] | ||||
| 
 | ||||
| 		var q = assert(runner.Queue({sync_start: true}), 'Queue({sync_start: true})') | ||||
| 
 | ||||
| 		q.push(a) | ||||
| 		q.push(b) | ||||
| 		q.push(c) | ||||
| 
 | ||||
| 		q.start() | ||||
| 
 | ||||
| 		assert.array(tasks_run, ['a', 'b', 'c'], 'all tasks run') | ||||
| 
 | ||||
| 
 | ||||
| 		//console.log('\n>>>', q.state)
 | ||||
| 	}, | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| typeof(__filename) != 'undefined' | ||||
| 	&& __filename == (require.main || {}).filename | ||||
| 	&& test.run() | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                               */ return module }) | ||||
| @ -12,7 +12,8 @@ | ||||
|     "jszip": "*", | ||||
|     "pouchdb": "^7.3.0", | ||||
|     "pouchdb-browser": "^7.3.0", | ||||
|     "requirejs": "*" | ||||
|     "requirejs": "*", | ||||
|     "showdown": "^2.1.0" | ||||
|   }, | ||||
|   "disabled-dependencies": { | ||||
|     "peer": "*", | ||||
|  | ||||
							
								
								
									
										8
									
								
								page.js
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								page.js
									
									
									
									
									
								
							| @ -482,6 +482,7 @@ module.PAGE_NOT_FOUND = '404: PAGE NOT FOUND: $PATH' | ||||
| // XXX PATH_VARS need to handle path variables...
 | ||||
| // XXX filters (and macros?) should be features for simpler plugin handlng (???)
 | ||||
| // XXX STUB filters...
 | ||||
| // XXX rename to pWikiPage???
 | ||||
| var Page = | ||||
| module.Page =  | ||||
| object.Constructor('Page', BasePage, { | ||||
| @ -526,6 +527,9 @@ object.Constructor('Page', BasePage, { | ||||
| 
 | ||||
| 		markdown: markdown.markdown, | ||||
| 		'quote-markdown': markdown.quoteMarkdown, | ||||
| 
 | ||||
| 		text: function(source){ | ||||
| 			return `<pre>${source}</pre>` }, | ||||
| 	}, | ||||
| 
 | ||||
| 	//
 | ||||
| @ -1054,7 +1058,9 @@ var System = | ||||
| module.System = { | ||||
| 	// base templates...
 | ||||
| 	//
 | ||||
| 	_text: {  | ||||
| 	// XXX revise this...
 | ||||
| 	// 		...need to be able to do: /some/path/_text
 | ||||
| 	_text: { | ||||
| 		text: '<macro src="." join="\n">@source(.)</macro>' }, | ||||
| 	NotFound: {  | ||||
| 		text: module.PAGE_NOT_FOUND | ||||
|  | ||||
							
								
								
									
										89
									
								
								pwiki2.html
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								pwiki2.html
									
									
									
									
									
								
							| @ -27,38 +27,95 @@ | ||||
| <style> | ||||
| </style> | ||||
| 
 | ||||
| <script src="ext-lib/pouchdb.js"></script> | ||||
| <!-- XXX do we need this??? --> | ||||
| <script src="bootstrap.js"></script> | ||||
| 
 | ||||
| <!--script data-main="pwiki2" src="ext-lib/require.js"></script--> | ||||
| <script src="ext-lib/require.js"></script> | ||||
| <script> | ||||
| 
 | ||||
| 
 | ||||
| var makeFallbacks =  | ||||
| function(paths, search=['lib']){ | ||||
| 	return Object.entries(paths) | ||||
| 		.map(function([key, path]){ | ||||
| 			// package... | ||||
| 			if(path.endsWith('/')){ | ||||
| 				return [key, path] } | ||||
| 			return [ | ||||
| 				key, | ||||
| 				[ | ||||
| 					path, | ||||
| 					...search | ||||
| 						.map(function(base){ | ||||
| 							return base +'/'+ path.split(/[\\\/]+/g).pop() }), | ||||
| 				] | ||||
| 			] }) | ||||
| 		.reduce(function(res, [key, value]){ | ||||
| 			res[key] = value | ||||
| 			return res }, {}) } | ||||
| 
 | ||||
| 
 | ||||
| require.config({ | ||||
| 	paths: { | ||||
| 		'ig-doc': 'node_modules/ig-doc/doc', | ||||
| 		'ig-stoppable': 'node_modules/ig-stoppable/stoppable', | ||||
| 		'ig-object': 'node_modules/ig-object/object', | ||||
| 		'ig-types': 'node_modules/ig-types/', | ||||
| 		'object-run': 'node_modules/object-run/run', | ||||
| 		...makeFallbacks({ | ||||
| 			'ig-doc': 'node_modules/ig-doc/doc', | ||||
| 			'ig-stoppable': 'node_modules/ig-stoppable/stoppable', | ||||
| 			'object-run': 'node_modules/object-run/run', | ||||
| 			'ig-object': 'node_modules/ig-object/object', | ||||
| 		}), | ||||
| 
 | ||||
| 		'pouchdb': 'node_modules/pouchdb-browser/lib/index', | ||||
| 		// packages... | ||||
| 		'ig-types': [ | ||||
| 			'node_modules/ig-types', | ||||
| 			'lib/types', | ||||
| 		], | ||||
| 
 | ||||
| 		// external stuff... | ||||
| 		...makeFallbacks({ | ||||
| 			'jszip': 'node_modules/jszip/dist/jszip', | ||||
| 			'pouchdb': 'node_modules/pouchdb/dist/pouchdb', | ||||
| 			'showdown': 'node_modules/showdown/dist/showdown', | ||||
| 		}, ['ext-lib']), | ||||
| 	}, | ||||
| 	packages: [ | ||||
| 		'ig-types', | ||||
| 	] | ||||
| }) | ||||
| 
 | ||||
| var setup = function(){ | ||||
| 	require(['./browser'], function(m){  | ||||
| 		window.pwiki = m.pwiki | ||||
| 	}) | ||||
| } | ||||
| // start loading pWiki... | ||||
| require(['./browser'], function(m){  | ||||
| 	window.pwiki = m.pwiki  | ||||
| 
 | ||||
| 	// handle location.hash (both directions) | ||||
| 	var _debounceHashChange = false | ||||
| 	pwiki.onNavigate(async function(){ | ||||
| 		// update location.hash without retriggering... | ||||
| 		_debounceHashChange = true | ||||
| 		location.hash = this.path | ||||
| 		setTimeout(function(){ | ||||
| 			_debounceHashChange = false }, 0)  | ||||
| 		// render... | ||||
| 		document.querySelector('#pWiki').innerHTML = await this.text }) | ||||
| 	window.addEventListener('hashchange', function(evt){ | ||||
| 		evt.preventDefault() | ||||
| 		if(_debounceHashChange){ | ||||
| 			return } | ||||
| 		var [path, hash] = location.hash.slice(1).split('#') | ||||
| 		path = path.trim() == '' ?  | ||||
| 			'/'  | ||||
| 			: path | ||||
| 		pwiki.path = path }) | ||||
| 
 | ||||
| 	// show current page... | ||||
| 	pwiki.path = location.hash.slice(1) | ||||
| })  | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <body onload="setup()"> | ||||
| <body> | ||||
| 
 | ||||
| pWiki2 | ||||
| 
 | ||||
| <div class="wiki" /> | ||||
| <div id="pWiki" /> | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
|  | ||||
							
								
								
									
										18
									
								
								pwiki2.js
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								pwiki2.js
									
									
									
									
									
								
							| @ -1,17 +1,28 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * XXX wikiword filter seems to hang on / | ||||
| * XXX do filters affect the whole page or only what comes after??? | ||||
| * XXX BUG: need to be able to affect the default render wrpaper...  | ||||
| * 		i.e.: /some/path vs. /some/path/_text vs. /some/path/_raw | ||||
| * | ||||
| * | ||||
| * XXX ROADMAP: | ||||
| * 	- run in browser | ||||
| * 		- basics, loading 							-- DONE | ||||
| * 		- test localStorage / sessionStorage 		-- DONE | ||||
| * 		- test pouch								-- DONE | ||||
| * 		- render page | ||||
| * 		- render page								-- DONE | ||||
| * 		- navigation | ||||
| * 			- hash/anchor | ||||
| * 			- hash/anchor							-- DONE | ||||
| * 			- service worker | ||||
| * 				...handle relative urls (???) | ||||
| * 		- migrate bootstrap | ||||
| * 	- test pwa | ||||
| * 		- store topology | ||||
| * 	- WikiWord | ||||
| * 	- markdown										-- DONE | ||||
| * 	- service worker | ||||
| * 	- pwa | ||||
| * 	- archive old code | ||||
| * 	- update docs | ||||
| * 	- refactor and cleanup | ||||
| @ -59,6 +70,7 @@ | ||||
| * TODO: | ||||
| * 	- <page>.then() -- resolve when all pending write operations done ??? | ||||
| * 	- an async REPL??? | ||||
| * 	- custom element??? | ||||
| * | ||||
| * | ||||
| * | ||||
|  | ||||
| @ -13,12 +13,7 @@ var types = require('ig-types') | ||||
| var pwpath = require('../lib/path') | ||||
| var base = require('../store/base') | ||||
| 
 | ||||
| // XXX HACK: trick requirejs to delay module loading...
 | ||||
| var req = require | ||||
| module.PouchDB =  | ||||
| 	typeof(PouchDB) != 'undefined' ? | ||||
| 		PouchDB | ||||
| 		: req('pouchdb') | ||||
| var pouchdb = require('pouchdb') | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| @ -35,7 +30,7 @@ module.PouchDBStore = { | ||||
| 	__data: undefined, | ||||
| 	get data(){ | ||||
| 		return this.__data  | ||||
| 			?? (this.__data = new module.PouchDB(this.__path__)) }, | ||||
| 			?? (this.__data = new pouchdb.PouchDB(this.__path__)) }, | ||||
| 	set data(value){ | ||||
| 		this.__data = value }, | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user