diff --git a/Array.js b/Array.js index 15c87d0..e923dac 100644 --- a/Array.js +++ b/Array.js @@ -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 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: -// { -// : , -// ... -// } -// -// 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([ -// [, ], -// ... -// ]) -// -// 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 [[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: + // { + // : , + // ... + // } + // + // 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([ + // [, ], + // ... + // ]) + // + // 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) diff --git a/Date.js b/Date.js index fc43a9c..5fae964 100644 --- a/Date.js +++ b/Date.js @@ -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() diff --git a/Map.js b/Map.js index 804494a..ff52e56 100644 --- a/Map.js +++ b/Map.js @@ -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) diff --git a/Object.js b/Object.js index d0bd7f9..7be22cd 100644 --- a/Object.js +++ b/Object.js @@ -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) diff --git a/Promise.js b/Promise.js index 5bca12e..3c44378 100644 --- a/Promise.js +++ b/Promise.js @@ -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 }) diff --git a/RegExp.js b/RegExp.js index 38ef065..6b8343d 100644 --- a/RegExp.js +++ b/RegExp.js @@ -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 diff --git a/Set.js b/Set.js index 6d62c25..845c738 100644 --- a/Set.js +++ b/Set.js @@ -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) diff --git a/String.js b/String.js index 77c6e33..9bfb46a 100644 --- a/String.js +++ b/String.js @@ -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) + diff --git a/generator.js b/generator.js index 26a61c2..964b631 100644 --- a/generator.js +++ b/generator.js @@ -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) diff --git a/package-lock.json b/package-lock.json index 24adee6..9571a84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index ee039a0..f604e74 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/test.js b/test.js index 930a5fd..93dd5a0 100755 --- a/test.js +++ b/test.js @@ -98,6 +98,9 @@ var cases = test.Cases({ // - sort Map: function(assert){ }, + + Generator: function(assert){ + }, String: function(assert){ assert(''.capitalize() == '')