major refactoring, should not affect functionality but may have minor bugs...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-11-22 23:50:05 +03:00
parent 15e47ddfa7
commit 98e5346450
12 changed files with 703 additions and 666 deletions

594
Array.js
View File

@ -35,194 +35,7 @@ module.StopIteration =
//---------------------------------------------------------------------
// Array.prototype.flat polyfill...
//
// NOTE: .flat(..) is not yet supported in IE/Edge...
Array.prototype.flat
|| (Array.prototype.flat = function(depth){
depth = typeof(depth) == typeof(123) ? depth : 1
return this.reduce(function(res, e){
return res.concat(e instanceof Array && depth > 0 ?
e.flat(depth-1)
: [e]) }, []) })
// Array.prototype.includes polyfill...
//
Array.prototype.includes
|| (Array.prototype.includes = function(value){
return this.indexOf(value) >= 0 })
// 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(..)
Array.prototype.first
|| (Array.prototype.first = function(value){
return arguments.length > 0 ?
((this[0] = value), this)
: this[0]})
Array.prototype.last
|| (Array.prototype.last = function(value){
return arguments.length > 0 ?
((this[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...
Array.prototype.rol
|| (Array.prototype.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.
Array.prototype.compact = function(){
return this.filter(function(){ return true }) }
// like .length but for sparse arrays will return the element count...
//
'len' in Array.prototype
|| Object.defineProperty(Array.prototype, 'len', {
get : function () {
// NOTE: if we don't do .slice() here this can count array
// instance attributes...
return Object.keys(this.slice()).length },
set : function(val){},
})
// Return a new array with duplicate elements removed...
//
// NOTE: order is preserved...
Array.prototype.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())] }
Array.prototype.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...
Array.prototype.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...
Array.prototype.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(..)...
Array.prototype.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 }) }
// 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']
//
Array.prototype.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 }
// Mixins...
// Equivalent to .map(..) / .filter(..) / .reduce(..) / .. with support for
// StopIteration...
@ -241,10 +54,6 @@ var wrapIterFunc = function(iter){
} else if( err instanceof StopIteration){
return err.msg }
throw err } } }
Array.prototype.smap = wrapIterFunc('map')
Array.prototype.sfilter = wrapIterFunc('filter')
Array.prototype.sreduce = wrapIterFunc('reduce')
Array.prototype.sforEach = wrapIterFunc('forEach')
// Equivalent to .map(..) / .filter(..) / .reduce(..) that process the
@ -372,131 +181,304 @@ var makeChunkIter = function(iter, wrapper){
: c.push([i, e])
return res }, [[]])) }) } }
Array.prototype.CHUNK_SIZE = 50
Array.prototype.mapChunks = makeChunkIter('map')
Array.prototype.filterChunks = makeChunkIter('map',
function(res, func, array, e){
return !!func.call(this, e[1], e[0], array) ? [e[1]] : [] })
Array.prototype.reduceChunks = makeChunkIter('reduce',
function(total, func, array, res, e){
return func.call(this,
total.length > 0 ?
total.pop()
: res,
e[1], e[0], array) })
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var ArrayMixin =
module.ArrayMixin =
object.Mixin('ArrayMixin', 'soft', {
// 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...
: [] },
iter: function*(lst=[]){
yield* lst.iter() },
})
// 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...
Array.prototype.toKeys = function(normalize){
return normalize ?
this.reduce(function(r, e, i){
r[normalize(e)] = i
return r }, {})
: this.reduce(function(r, e, i){
r[e] = i
return r }, {}) }
var ArrayProtoMixin =
module.ArrayProtoMixin =
object.Mixin('ArrayProtoMixin', 'soft', {
// 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[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 }) },
// 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...
return Object.keys(this.slice()).length },
set len(val){},
// 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...
Array.prototype.toMap = function(normalize){
return normalize ?
this
.reduce(function(m, e, i){
m.set(normalize(e), i)
return m }, new Map())
: this
.reduce(function(m, e, i){
m.set(e, i)
return m }, new Map()) }
// 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 },
// zip(array, array, ...)
// -> [[item, item, ...], ...]
//
// zip(func, array, array, ...)
// -> [func(i, [item, item, ...]), ...]
//
Array.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 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...
Array.prototype.zip =
function(func, ...arrays){
return func instanceof Array ?
this.constructor.zip(this, func, ...arrays)
: this.constructor.zip(func, this, ...arrays) }
// 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 }) },
//
// Array.iter()
// Array.iter([ .. ])
// -> iterator
//
// array.iter()
// -> iterator
//
//
// XXX should this take an argument and be like map??
Array.prototype.iter =
function*(){
// 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
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)
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??
iter: function*(){
for(var e of this){
yield e } }
Array.iter =
function*(lst=[]){
yield* lst.iter() }
yield e } },
// Stoppable iteration...
//
smap: wrapIterFunc('map'),
sfilter: wrapIterFunc('filter'),
sreduce: wrapIterFunc('reduce'),
sforEach: wrapIterFunc('forEach'),
// 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)

121
Date.js
View File

@ -7,62 +7,20 @@
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var object = require('ig-object')
/*********************************************************************/
// 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
date.prototype.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))
: '') }
date.prototype.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)
: '') }
date.prototype.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 }
date.timeStamp = function(...args){
return (new this()).getTimeStamp(...args) }
date.fromTimeStamp = function(ts){
return (new this()).setTimeStamp(ts) }
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...
date.str2ms = function(str, dfl){
str2ms: function(str, dfl){
dfl = dfl || 'ms'
if(typeof(str) == typeof(123)){
@ -96,9 +54,68 @@ module.patchDate = function(date){
return c ?
val * c
: NaN }
: NaN },
})
// 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()

58
Map.js
View File

@ -7,35 +7,43 @@
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var object = require('ig-object')
/*********************************************************************/
// 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)
Map.prototype.sort = function(keys){
keys = (typeof(keys) == 'function'
|| keys === undefined) ?
[...this.keys()].sort(keys)
: keys
var del = Map.prototype.delete.bind(this)
var set = Map.prototype.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 }
var MapProtoMixin =
module.MapProtoMixin =
object.Mixin('MapProtoMixin', 'soft', {
// 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 },
})
MapProtoMixin(Map.prototype)

133
Object.js
View File

@ -16,97 +16,82 @@
/*********************************************************************/
require('object-run')
var object = require('ig-object')
/*********************************************************************/
// import stuff from object.js to Object...
var toObject = function(...keys){
keys.forEach(function(key){
Object[key]
|| (Object[key] = object[key]) }) }
var ObjectMixin =
module.ObjectMixin =
object.Mixin('ObjectMixin', 'soft', {
// stuff from object.js...
deepKeys: object.deepKeys,
toObject(
'deepKeys',
// XXX these should be called logically relative to Array.js and diff.js...
'match',
'matchPartial',
match: object.match,
matchPartial: object.matchPartial,
/* XXX not yet sure about these...
// XXX EXPERIMENTAL...
'parent',
'parentProperty',
'parentCall',
parent : object.parent,
parentProperty: object.parentProperty,
parentCall: object.parentCall,
'parentOf',
'childOf',
'related',
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...
Object.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...
Object.flatCopy = function(obj, constructor){
return Object.deepKeys(obj)
.reduce(
function(res, key){
res[key] = obj[key]
return res },
// 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 ?
{}
: constructor()) }
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 ?
{}
: 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 },
})
// 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...
Object.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 }
/* XXX this messes up things...
Object.prototype.sort
|| Object.defineProperty(Object.prototype, 'sort', {
writable: true,
configurable: true,
enumerable: false,
value: function(keys){
return Object.sort(this, keys) }, })
//*/
ObjectMixin(Object)

View File

@ -15,51 +15,9 @@ var object = require('ig-object')
/*********************************************************************/
// XXX does this need to be a distinct object/constructor???
Promise.cooperative = function(){
var handlers
return object.mixinFlat(
new Promise(function(resolve, reject){
handlers = { resolve, reject, } }),
{
get isSet(){
return handlers === false },
//
// Resolve promise with value...
// .set(value)
// -> this
//
// Reject promise with value...
// .set(value, false)
// -> this
//
set: function(value, resolve=true){
// can't set twice...
if(this.isSet){
throw new Error('Promise.cooperative().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 ?
handlers.resolve(value)
: handlers.reject(value) }
// cleanup and prevent setting twice...
handlers = false
return this },
}) }
//---------------------------------------------------------------------
// promise iterators...
// XXX should this be aborted on reject???
var IterablePromise =
module.IterablePromise =
Promise.iter =
object.Constructor('IterablePromise', Promise, {
//
// Format:
@ -246,6 +204,55 @@ object.Constructor('IterablePromise', Promise, {
//---------------------------------------------------------------------
var PromiseMixin =
module.PromiseMixin =
object.Mixin('PromiseMixin', 'soft', {
// XXX does this need to be a distinct object/constructor???
cooperative: function(){
var handlers
return object.mixinFlat(
new Promise(function(resolve, reject){
handlers = { resolve, reject, } }),
{
get isSet(){
return handlers === false },
//
// Resolve promise with value...
// .set(value)
// -> this
//
// Reject promise with value...
// .set(value, false)
// -> this
//
set: function(value, resolve=true){
// can't set twice...
if(this.isSet){
throw new Error('Promise.cooperative().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 ?
handlers.resolve(value)
: handlers.reject(value) }
// cleanup and prevent setting twice...
handlers = false
return this },
}) },
iter: IterablePromise,
})
PromiseMixin(Promise)
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })

View File

@ -7,17 +7,28 @@
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var object = require('ig-object')
/*********************************************************************/
// Quote a string and convert to RegExp to match self literally.
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') }
})
RegExpMixin(RegExp)
var quoteRegExp =
RegExp.quoteRegExp =
module.quoteRegExp =
function(str){
return str.replace(/([\.\\\/\(\)\[\]\$\*\+\-\{\}\@\^\&\?\<\>])/g, '\\$1') }
RegExp.quoteRegExp =
RegExp.quoteRegExp

63
Set.js
View File

@ -7,41 +7,48 @@
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var object = require('ig-object')
/*********************************************************************/
// Set set operation shorthands...
Set.prototype.unite = function(other=[]){
return new Set([...this, ...other]) }
Set.prototype.intersect = function(other){
var test = other.has ?
'has'
: 'includes'
return new Set([...this]
.filter(function(e){
return other[test](e) })) }
Set.prototype.subtract = function(other=[]){
other = new Set(other)
return new Set([...this]
.filter(function(e){
return !other.has(e) })) }
var SetProtoMixin =
module.SetProtoMixin =
object.Mixin('SetMixin', 'soft', {
// 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(keys=[]){
keys = (typeof(keys) == 'function'
|| keys === undefined) ?
[...this].sort(keys)
: keys
var del = this.delete.bind(this)
var add = this.add.bind(this)
new Set([...keys, ...this])
.forEach(function(e){
if(this.has(e)){
del(e)
add(e) } }.bind(this))
return this },
})
Map.prototype.sort = function(keys=[]){
keys = (typeof(keys) == 'function'
|| keys === undefined) ?
[...this].sort(keys)
: keys
var del = Set.prototype.delete.bind(this)
var add = Set.prototype.add.bind(this)
new Set([...keys, ...this])
.forEach(function(e){
if(this.has(e)){
del(e)
add(e) } }.bind(this))
return this }
SetProtoMixin(Set.prototype)

View File

@ -7,15 +7,23 @@
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var object = require('ig-object')
/*********************************************************************/
String.prototype.capitalize = function(){
return this == '' ?
this
: this[0].toUpperCase() + this.slice(1) }
var StringProtoMixin =
module.StringProtoMixin =
object.Mixin('StringProtoMixin', 'soft', {
capitalize: function(){
return this == '' ?
this
: this[0].toUpperCase() + this.slice(1) },
})
StringProtoMixin(String.prototype)

View File

@ -112,138 +112,147 @@ var makePromise = function(name){
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// XXX should this be a generator???
GeneratorPrototype.at = makeGenerator('at')
var GeneratorMixin =
module.GeneratorMixin =
object.Mixin('GeneratorMixin', 'soft', {
GeneratorPrototype.slice = makeGenerator('slice')
GeneratorPrototype.flat = makeGenerator('flat')
// XXX should this be a generator???
at: makeGenerator('at'),
GeneratorPrototype.map = makeGenerator('map')
GeneratorPrototype.filter = makeGenerator('filter')
GeneratorPrototype.reduce = makeGenerator('reduce')
slice: makeGenerator('slice'),
flat: makeGenerator('flat'),
// non-generators...
//
GeneratorPrototype.toArray = function(){
var that = this
return Object.assign(
function(){
return that(...arguments).toArray() },
{ toString: function(){
return that.toString()
+ '\n .toString()'}, }) }
GeneratorPrototype.pop = function(){
var that = this
return Object.assign(
function(){
return that(...arguments).toArray().pop() },
{ toString: function(){
return that.toString()
+ '\n .pop()'}, }) }
GeneratorPrototype.shift = function(){
var that = this
return Object.assign(
function(){
return that(...arguments).toArray().shift() },
{ toString: function(){
return that.toString()
+ '\n .shift()'}, }) }
map: makeGenerator('map'),
filter: makeGenerator('filter'),
reduce: makeGenerator('reduce'),
// promises...
//
GeneratorPrototype.then = makePromise('then')
GeneratorPrototype.catch = makePromise('catch')
GeneratorPrototype.finally = makePromise('finally')
// non-generators...
//
toArray: function(){
var that = this
return Object.assign(
function(){
return that(...arguments).toArray() },
{ toString: function(){
return that.toString()
+ '\n .toString()'}, }) },
pop: function(){
var that = this
return Object.assign(
function(){
return that(...arguments).toArray().pop() },
{ toString: function(){
return that.toString()
+ '\n .pop()'}, }) },
shift: function(){
var that = this
return Object.assign(
function(){
return that(...arguments).toArray().shift() },
{ toString: function(){
return that.toString()
+ '\n .shift()'}, }) },
// promises...
//
then: makePromise('then'),
catch: makePromise('catch'),
finally: makePromise('finally'),
})
var GeneratorProtoMixin =
module.GeneratorProtoMixin =
object.Mixin('GeneratorProtoMixin', 'soft', {
// XXX should this be a generator???
at: function*(i){
// sanity check...
if(i < 0){
throw new Error('.at(..): '
+'generator index can\'t be a negative value.')}
for(var e of this){
if(i-- == 0){
yield e
return } } },
//---------------------------------------------------------------------
// GeneratorPrototype instance methods...
// 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 untill it is fully done...
slice: function*(from=0, to=Infity){
// 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]
// XXX should this be a generator???
GeneratorPrototype.prototype.at = function*(i){
// sanity check...
if(i < 0){
throw new Error('.at(..): '
+'generator index can\'t be a negative value.')}
for(var e of this){
if(i-- == 0){
yield e
return } } },
} else {
yield* typeof(e[i].flat) == 'function' ?
e[i].flat(depth-1)
: e[i] } }
// item as-is...
} else {
yield e } } },
// 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 untill it is fully done...
GeneratorPrototype.prototype.slice = function*(from=0, to=Infity){
// 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???
GeneratorPrototype.prototype.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]
map: function*(func){
var i = 0
for(var e of this){
yield func(e, i++, this) } },
filter: function*(func){
var i = 0
for(var e of this){
if(func(e, i++, this)){
yield e } } },
reduce: function*(func, res){
var i = 0
for(var e of this){
res = func(res, e, i++, this) }
yield res },
} else {
yield* typeof(e[i].flat) == 'function' ?
e[i].flat(depth-1)
: e[i] } }
// item as-is...
} else {
yield e } } }
// non-generators...
//
toArray: function(){
return [...this] },
pop: function(){
return [...this].pop() },
shift: function(){
return [...this].shift() },
GeneratorPrototype.prototype.map = function*(func){
var i = 0
for(var e of this){
yield func(e, i++, this) } }
GeneratorPrototype.prototype.filter = function*(func){
var i = 0
for(var e of this){
if(func(e, i++, this)){
yield e } } }
GeneratorPrototype.prototype.reduce = function*(func, res){
var i = 0
for(var e of this){
res = func(res, e, i++, this) }
yield res }
// promises...
//
// XXX how do we handle reject(..) / .catch(..)???
promise: function(){
var that = this
return new Promise(function(resolve){
resolve([...that]) }) },
then: function(func){
return this.promise().then(func) },
catch: function(func){
return this.promise().catch(func) },
finally: function(func){
return this.promise().finally(func) },
})
// non-generators...
//
GeneratorPrototype.prototype.toArray = function(){
return [...this] }
GeneratorPrototype.prototype.pop = function(){
return [...this].pop() }
GeneratorPrototype.prototype.shift = function(){
return [...this].shift() }
// promises...
//
// XXX how do we handle reject(..) / .catch(..)???
GeneratorPrototype.prototype.promise = function(){
var that = this
return new Promise(function(resolve){
resolve([...that]) }) }
GeneratorPrototype.prototype.then = function(func){
return this.promise().then(func) }
GeneratorPrototype.prototype.catch = function(func){
return this.promise().catch(func) }
GeneratorPrototype.prototype.finally = function(func){
return this.promise().finally(func) }
GeneratorMixin(GeneratorPrototype)
GeneratorProtoMixin(GeneratorPrototype.prototype)

14
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "ig-types",
"version": "3.7.11",
"version": "3.7.14",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -265,14 +265,14 @@
}
},
"ig-object": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/ig-object/-/ig-object-5.4.2.tgz",
"integrity": "sha512-xRJRI7Y5Cw0u7FZL/Ln/noitjQ9HFVLwHbnVozCZ1/95p/F9h+kenOzagj/Cm0uBsEdTi3KJfGrdmsyH5AM7cg=="
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/ig-object/-/ig-object-5.4.11.tgz",
"integrity": "sha512-WPPQ5C41c6q3tPfa2fBbWE2xcLF7LoGRu2E6Wr/aoA5oxAyl8lAuE7Kqt4TyPwfW9jVI0+ifBztg9e1tR5mG1Q=="
},
"ig-test": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/ig-test/-/ig-test-1.4.6.tgz",
"integrity": "sha512-Pv0+Zj3VXWjGhC05ueEcyBs6F1piQoXWj1ZR8azpLMdkivgJ3aHJFmawXDGCrc2T/IY1wPNYH1EGUG0OzcRnhw==",
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/ig-test/-/ig-test-1.4.8.tgz",
"integrity": "sha512-TwSVA/874sHTc05RE+HtMW1BEbgws868CTQzpBwnVMYTKj6Th0mrNSls9SsTM/Ias2giPRZfusg+U/vc/JIcQQ==",
"dev": true,
"requires": {
"colors": "^1.4.0",

View File

@ -1,6 +1,6 @@
{
"name": "ig-types",
"version": "3.7.13",
"version": "3.7.15",
"description": "Generic JavaScript types and type extensions...",
"main": "main.js",
"scripts": {
@ -23,12 +23,12 @@
},
"homepage": "https://github.com/flynx/types.js#readme",
"dependencies": {
"ig-object": "^5.4.2",
"ig-object": "^5.4.11",
"object-run": "^1.0.1"
},
"devDependencies": {
"c8": "^7.3.5",
"color": "^3.1.3",
"ig-test": "^1.4.6"
"ig-test": "^1.4.8"
}
}

View File

@ -98,6 +98,9 @@ var cases = test.Cases({
// - sort
Map: function(assert){
},
Generator: function(assert){
},
String: function(assert){
assert(''.capitalize() == '')