UNPKG

@mi-sec/lightmap

Version:

Extension of Map - adds iterative extensions to Map like map, filter, reduce, sort, etc.

477 lines (429 loc) 12.2 kB
/** **************************************************************************************************** * File: index.js * Project: lightmap * @author Nick Soggin <iSkore@users.noreply.github.com> on 11-Jun-2018 *******************************************************************************************************/ 'use strict'; const { version } = require( './package.json' ); /** * LightMap * @augments Map */ class LightMap extends Map { /** * constructor * @param {array[]} [n] - constructed map * @param {object} [opts] - construction options * @param {boolean} [opts.deepTransformToMap=true] - deep transform map-looking arrays to a LightMap */ constructor( n = [], opts = { deepTransformToMap: true } ) { super( n ); if ( opts.deepTransformToMap ) { this.deepTransformToMap(); } } /** * @typedef {function(value, key, ref)} FilterCallback * @callback FilterCallback * @param {*} value - map value * @param {string | *} key - map key * @param {LightMap} refMap - original LightMap * @returns {boolean} - a comparison or boolean value to */ /** * filter * @description * Filter LightMap based on keys and values based in. * Comparisons can be made on the key and/or value. * @param {FilterCallback} fn - comparison method * @return {LightMap} - returns new LightMap with filtered results * @example * const _ = new LightMap(); * _.set( 'key', 'value' ); * _.set( 'key1', 'value1' ); * * const result = _.filter( * ( v, k ) => k === 'key' * ); * * // -> LightMap { 'key' => 'value' } */ filter( fn ) { const arr = new LightMap(); for ( const [ key, value ] of this ) { if ( fn( value, key, this ) ) { arr.set( key, value ); } } return arr; } /** * @typedef {function(value, key, ref)} MapCallback * @callback MapCallback * @param {*} value - map value * @param {string | *} key - map key * @param {LightMap} refMap - original LightMap * @returns {Array<string|*,*>} - the new key-value pair to be remapped */ /** * map * @description * Map LightMap with new key and/or value. * Return the new item in a "tuple" form matching the Map paradigm (`[ x, y ]`). * @param {MapCallback} fn - map method * @return {LightMap} - returns new LightMap with mapped results * @example * const _ = new LightMap(); * _.set( 'key', 'value' ); * * const result = _.map( * ( v, k ) => [ k + 1, v + 1 ] * ); * * // -> LightMap { 'key1' => 'value1' } */ map( fn ) { const arr = new LightMap(); for ( const [ key, value ] of this ) { const entry = fn( value, key, this ) || [ undefined, undefined ]; arr.set( entry[ 0 ] || key, entry[ 1 ] ); } return arr; } /** * @typedef {function(r, value, key, ref)} ReduceCallback * @callback ReduceCallback * @param {*} value - carriage value * @param {Array<string|*,*>} mapKeyPair - map key-value pair * @param {string | *} key - map key * @param {LightMap} refMap - original LightMap * @returns {*} - the carriage value */ /** * reduce * @description * Reduce LightMap with new value. * Must return the carriage value just like Array.reduce. * @param {ReduceCallback} fn - reducing method * @param {*} r - carriage value * @return {*} - returns reduced result * @example * const _ = new LightMap(); * _.set( 'key', 'value' ); * * const result = _.reduce( * ( r, [ k, v ] ) => { * r += `Key: ${ k }\n`; * r += `Value: ${ v }\n`; * return r; * }, '' * ); * * // -> 'Key: key\nValue: value\n' */ reduce( fn, r ) { for ( const [ key, value ] of this ) { r = fn( r, [ key, value ], key, this ); } return r; } /** * @typedef {function(value, key, ref)} FindCallback * @callback FindCallback * @param {*} value - map value * @param {string | *} key - map key * @param {LightMap} refMap - original LightMap * @returns {Array<string|*,*>} - the found value */ /** * find * @description * The `find()` method returns the tuple pair of the first element in the LightMap that satisfies the provided * testing function. * @param {FindCallback} fn - find method * @return {Array<*|*>|undefined} - returns tuple result or undefined if no matches are found * @example * const _ = new LightMap(); * _.set( 'key', 'value' ); * _.set( 'key1', 'value1' ); * * const result = _.find( * ( v, k, arr ) => { * return v === 'value'; * } * ); * * // -> [ 'key', 'value' ] */ find( fn ) { for ( const [ key, value ] of this ) { if ( fn( value, key, this ) ) { return [ key, value ]; } } return undefined; } /** * findAll * @description * Alias for `filter` * @param {FilterCallback} fn - same as filter method * @return {LightMap} - returns new LightMap with matching results * @example * const _ = new LightMap(); * _.set( 'key', 'value' ); * _.set( 'key1', 'value' ); * _.set( 'key2', 'value2' ); * * const result = _.findAll( * ( v, k, arr ) => { * return v === 'value'; * } * ); * * // -> LightMap { 'key' => 'value', 'key1' => 'value' } */ findAll( fn ) { return this.filter( fn ); } /** * sortKeys * @description * Map LightMap with sorted key-value pairs. * @param {Function} [fn] - sorting method * @return {LightMap} - returns new LightMap with sorted results * @example * const _ = new LightMap(); * _.set( 'key2', 'value2' ); * _.set( 'key1', 'value1' ); * _.set( 'key', 'value' ); * * const result = _.sortKeys(); * * // -> LightMap { 'key' => 'value', 'key1' => 'value1', 'key2' => 'value2' } */ sortKeys( fn = ( a, b ) => String( a ).localeCompare( String( b ) ) ) { const keys = [ ...this.keys() ].sort( fn ); let i = 0; return this.map( ( v, k, iterator ) => { const key = keys[ i++ ]; return [ key, iterator.get( key ) ]; } ); } /** * sortValues * @description * Map LightMap with sorted key-value pairs sorted by value. * @param {Function?} fn - sorting method * @return {LightMap} - returns new LightMap with sorted results * @example * const _ = new LightMap(); * _.set( 'key', 'value2' ); * _.set( 'key1', 'value1' ); * _.set( 'key2', 'value' ); * * const result = _.sortValues(); * * // -> LightMap { 'key2' => 'value', 'key1' => 'value1', 'key' => 'value2' } */ sortValues( fn = ( a, b ) => String( a ).localeCompare( String( b ) ) ) { const entries = [ ...this.entries() ].sort( ( a, b ) => fn( a[ 1 ], b[ 1 ] ) ); let i = 0; return this.map( () => { const [ key, value ] = entries[ i++ ]; return [ key, value ]; } ); } // TODO: improve this later, not sufficient yet equals( n ) { if ( n instanceof LightMap ) { return this.size === n.size; } return false; } /** * version * @description * return LightMap module version * @return {string} - returns LightMap module version * @example * this.version(); * * // -> v0.0.0 */ version() { return LightMap.version(); } /** * version * @description * return LightMap module version * @return {string} - returns LightMap module version * @example * LightMap.version(); * * // -> v0.0.0 */ static version() { return `v${ version }`; } /** * mapToArray * @description * maps a LightMap object to an array of arrays in the Map Pattern (re-constructable pattern) * @return {Array} - returns array of tuple key-value pairs * @example * * const _ = new LightMap(); * _.set( 'key', new LightMap( [ [ 'key1', 'value1' ] ] ) ); * * const result = _.mapToArray(); * * // -> [ [ 'key', [ [ 'key1', 'value1' ] ] ] ] */ mapToArray() { return this.reduce( ( r, [ k, v ] ) => { if ( v instanceof LightMap ) { v = v.toJSON(); } r.push( [ k, v ] ); return r; }, [] ); } /** * toObject * @description * maps a LightMap object's keys and values to an object * @return {Object} - returns Object of key-value pairs * @example * * const _ = new LightMap(); * _.set( 'key', new LightMap( [ [ 'key1', 'value1' ] ] ) ); * * const result = _.toObject(); * * // -> { key: { key1: 'value1' } } */ // TODO: check for circular references toObject() { const obj = Object.fromEntries( this ), keys = Object.keys( obj ); for ( let i = 0; i < keys.length; i++ ) { const key = keys[ i ]; if ( obj[ key ] instanceof Map ) { obj[ key ] = obj[ key ].toObject(); } } return obj; } toJSON() { return this.mapToArray(); } toString() { return JSON.stringify( this.mapToArray() ); } /** * indexOf * @description * returns the first index at which a given element can be found in the array, or -1 if it is not present * @param {string} n - key name to search for * @return {number} - index of the element */ indexOf( n ) { let i = -1; return this.reduce( ( r, [ k ] ) => { if ( r === -1 ) { i++; if ( k === n ) { r = i; } } return r; }, -1 ); } deepTransformToMap() { this.forEach( ( v, k ) => this.set( k, this[ Symbol.constructMapByPattern ]( v ) ) ); } [ Symbol.constructMapByPattern ]( n ) { return Array.isArray( n ) && n.every( v => Array.isArray( v ) && v.length === 2 ) ? new LightMap( n ) : n; } [ Symbol.search ]( n ) { return this.indexOf( n ); } /** * [ Symbol.replace ] * @description * symbol specifies the method that replaces matched substrings of a string * @param {LightMap} n - replacement mapping for string * @return {string} - string replaced with mapped values */ [ Symbol.replace ]( n ) { this.forEach( ( v, k ) => n = n.replace( k, v ) ); return n; } /** * [ Symbol.toPrimitive ] * @description * symbol that specifies a function valued property that is called to convert an object to a primitive value * @param {*} n - hint * @return {*} - hint handler */ [ Symbol.toPrimitive ]( n ) { if ( n === 'number' ) { return +this.size; } else { return this.toString(); } } get [ Symbol.toStringTag ]() { return this.constructor.name; } static get [ Symbol.species ]() { return Map; } static [ Symbol.hasInstance ]( instance ) { return !!instance && instance.constructor && instance.constructor.name === 'LightMap'; } } module.exports = LightMap;