UNPKG

lamed_table

Version:
386 lines (362 loc) 15.2 kB
'use strict' // Comment out in test functions console.log(`Starting ${__filename}...`) // comment line to remove simple logging /* ------------------------------------------------------ * tableCols.js * Purpose: The purpose of this module is to manage table columns * Date Created: 2019/12/12 * Created by : Perez Lamed van Niekerk --------------------------------------------------------- */ /* jshint esversion: 6 */ const _test = require('lamed_test') const { Ok, notOk, notOk_Then, Equal, notEqual, con, testAND, unZip } = _test // eslint-disable-line // con.setupChalk(require('chalk')) // con.traceSet(0 require('ltype_string') const _convert = require('ltype_convert') const _array = require('ltype_array') /** * Returns the col number from the name * @param {array} cols - The cols array of the table object * @param {string / number} colNameOrNumber - The column name * @param {bool} throwError - If true and colname is not found throw error else return -1 */ function colsNumber (cols, colNameOrNumber, throwError = true) { // Use cols as parameter to allow bigger scope // if (notOk(colNameOrNumber)) throw new Error(`colNameOrNumber not defined: '${colNameOrNumber}'`) // will return -1 if (typeof colNameOrNumber === 'number') { // Number col if (colNameOrNumber < -1 || colNameOrNumber >= cols.length) { if (throwError) throw new Error(`in colsNumber; Column number ${colNameOrNumber} does not exist.\n`) return -1 } return colNameOrNumber } const test = colNameOrNumber.toLowerCase() // con.log({ test }) // } const col = cols.indexOf(test) if (col === -1) { if (throwError) { throw new Error(`in colsNumber; Column '${test}' does not exist in ['${cols.join("','")}'].\n`) } return -1 } return col } /** * Return array of colNames * @param {array} cols - The cols array of the table object * @param {array} colNamesOrNumbers - The col names or numbers array. Numbers start at 1 * @returns {array} - Of column names */ function colsNames (cols, colNamesOrNumbers) { const result = [] for (let ii = 0; ii < colNamesOrNumbers.length; ii++) { let item = colNamesOrNumbers[ii] if (typeof item !== 'string') item = cols[item] result.push(item) } return result } unZip(() => colsNames(['col1', 'col2', 'col3'], [0, 1, 2]), ['col1', 'col2', 'col3']) unZip(() => colsNames(['col1', 'col2', 'col3'], [0, 'col3', 1]), ['col1', 'col3', 'col2']) unZip(() => colsNames(['col1', 'col2', 'col3'], []), []) unZip(() => colsNames(['col1', 'col2', 'col3'], [1]), ['col2']) /** * Set the columns to the table * @param {object} table - the table object * @param {array} colNames - string array of column names */ function colsCreate (table, colNames) { if (table.cols.length !== 0) throw new Error('in colSet(). Cannot set columns if there already exist columns') table.cols = colNames.toLowerCase() table.cols = table.cols.map(x => x.trim()) // Make sure the cols are trimed } /** * Add columns to a table object * @param {object} table - the table object * @param {array} colNames - string array of column names * @param {string} defaultValue - the default value for the new columns */ function colsAddColumns (table, colNames, defaultValue = '') { // eslint-disable-line const result = [] for (let ii = 0; ii < colNames.length; ii++) { const colName = colNames[ii].toLowerCase() // columns are in lower case const pos1 = table.cols.indexOf(colName) if (pos1 === -1) { const colNo = table.cols.push(colName) - 1 result.push(colNo) for (let jj = 0; jj < table.rows.length; jj++) { table.rows[jj][colNo] = defaultValue } } else result.push(pos1) // reuse existing column } if (result.length === 1) return result[0] else return result } /** * Add a column to a table object * @param {object} table - the table object * @param {array} colNames - string array of column names * @param {string} defaultValue - the defaultValue for the new column */ function colsAddColumn (table, colName, defaultValue = '') { return colsAddColumns(table, [colName], defaultValue) } /** * Drop a column from the table object * @param {object} table - the table object * @param {array of number / string} colNameOrNumbers - The Column names and/or numbers to drop */ function colsDropColumn (table, colNameOrNumbers) { // eslint-disable-line if (Array.isArray(colNameOrNumbers) === false) colNameOrNumbers = [colNameOrNumbers] for (let ii = 0; ii < colNameOrNumbers.length; ii++) { const colNameOrNumber = colNameOrNumbers[ii] const colNo = colsNumber(table.cols, colNameOrNumber) // con.log({ colNameOrNumber, colNo }) table.cols.splice(colNo, 1) for (let jj = 0; jj < table.rows.length; jj++) { const row = table.rows[jj] row.splice(colNo, 1) } } } /** * Change a column name from the table object * @param {object} table - the table object * @param {number / string} colNameOrNumber - The Column name of number * @param {string} newColName - The new name of the column */ function colsNameDef (table, colNameOrNumber, newColName) { if (notOk(newColName)) throw new Error(`newColName invalid value: ${newColName}`) if (colNameOrNumber.trim().toLowerCase() === newColName.trim().toLowerCase()) return const colNo = colsNumber(table.cols, colNameOrNumber.trim()) table.cols[colNo] = newColName.trim().toLowerCase() } /** * Change a column names from the table object * @param {object} table - the table object * @param {string} commands - The name change commands to be executed. oldname-->newName <-> etc. * @param {bool} debug - If true, show debug information */ function colsNameDefs (table, commands, debug = false) { const nameDefs = commands if (debug) con.log({ commands }) for (let ii = 0; ii < nameDefs.length; ii++) { const name = nameDefs[ii].trim().toLowerCase() if (name.indexOf('-->') === -1) throw new Error(`Setting '${name}' does not have "-->" definition.\n`) const oldName = name.split0('-->') const newName = name.split99('-->') if (notOk(newName)) throw new Error(`Setting '${name}' does not include valid value after "-->"\n`) if (debug) con.log({ name, oldName, newName }) colsNameDef(table, oldName, newName) } } /** * Return array of column values * @param {object} table - The table object * @param {string / number} colNameOrNumber - The column name or number * @param {int} rows - The number of rows to return * @returns {array} - Array of column values */ function col2Array (table, colNameOrNumber, rows = -1) { const colNo = colsNumber(table.cols, colNameOrNumber) let result = table.rows.map(function (value, index) { return value[colNo] }) if (rows !== -1) result = result.slice(0, rows) return result } /** * Return sample values from the column * @param {table} table - The table * @param {string / number} colNameOrNumber - The column name or number * @param {int} maxLength - The maximum length of the sample * @returns {array} - Array of string (Eg. [str...]) */ function colSamples (table, colNameOrNumber, maxLength = 45) { let samples = col2Array(table, colNameOrNumber).join(', ') if (maxLength !== -1 && samples.length > maxLength) samples = `[${samples.substr(0, maxLength)}...]` else samples = `[${samples.substr(0, maxLength)}]` return samples } /** * Add a column to a table and load the list items to the column * @param {table} table - The talbe to load the items from * @param {array} list - Array of items. Total must be equal to row count * @param {string} colName - The column name to add */ function TableArray2Col (table, list, colName) { // eslint-disable-line if (list.length !== table.rows.length) { if (Array.isArray(list) === false) throw new Error(`List '${list}' is not a list.\n`) throw new Error(`List (${list.length}) and table (${table.rows.length}) must have same row size.\n`) } const colNo = colsAddColumn(table, colName) // dtFiles -> [id, file, level] for (let ii = 0; ii < table.rows.length; ii++) { const row = table.rows[ii] row[colNo] = list[ii] } } /** * Merge two columns using the function provided * @param {string} colResult - The column where the result shall be stored * @param {table} table - The table * @param {string} col1 - The first column to use * @param {string} col2 - The second column to use * @param {function} func - The function to use */ function mergeColumns (colResult, table, col1, col2, func = (x, y) => { return x + y }) { const list1 = col2Array(table, col1) // Get list of col1 const list2 = col2Array(table, col2) // Get list of col2 const listMerge = _array.mergeArrays(list1, list2, func) // Merge the two lists TableArray2Col(table, listMerge, colResult) // Replace / add result column if (colResult === col1) colsDropColumn(table, col2) // If the result is part of the merge -> remove the other column else if (colResult === col2) colsDropColumn(table, col1) } /** * Execute a function on a column * @param {table} table - The table * @param {array} colNames - The columns to execute the function on * @param {function} func - The function to use * @param {string} - colNew - If one colName provided then copy results to colNew */ function calcColumn (table, colNames, func = (x, index) => `${index}. ${x}`, colNew = '') { if (Array.isArray(colNames) === false) colNames = [colNames] // Make it an array for (let ii = 0; ii < colNames.length; ii++) { let colName = colNames[ii] // con.log({ colName }) const list1 = col2Array(table, colName) // Get list of colName const result = list1.map(func) if (colNames.length === 1 && colNew !== '') colName = colNew // Only new column if one colName provided TableArray2Col(table, result, colName) // Update the column } } function calcDuration (table, fromColNameOrNumber, toColNameOrNumber, durationCol = 'duration') { const colNo1 = colsNumber(table.cols, fromColNameOrNumber) const colNo2 = colsNumber(table.cols, toColNameOrNumber) const result = [] for (let jj = 0; jj < table.rows.length; jj++) { const row = table.rows[jj] const from = row[colNo1] const to = row[colNo2] const duration = _convert.Convert.duration_AsHour(from, to) // duration in hours // con.log({ from, to, duration }) result.push(duration) } // con.log({ result }) TableArray2Col(table, result, durationCol) // Update the column } function compress (table, fromColNameOrNumber, compressItems = []) { if (Array.isArray(compressItems) === false) compressItems = [compressItems] // Ensure compressItems is array compressItems = compressItems.map(x => x.toLowerCase()) let list = col2Array(table, fromColNameOrNumber) // con.log({ compressItems, list }) for (let ii = 0; ii < compressItems.length; ii++) { let item = compressItems[ii] let itemSmall = item const pos = item.indexOf('-->') if (pos !== -1) { itemSmall = item.substr(pos + 3) item = item.substr(0, pos) // con.log({ item, itemSmall }) } list = list.map(x => (x.toLowerCase().includes(item)) ? itemSmall : x) } // con.log({ compressItems, list }) TableArray2Col(table, list, fromColNameOrNumber) // Update the column } // ------------------------------------------------------------------------------- /** * Manages the table columns * @param {object} table - The table object */ function Cols (data) { this.DATA = data this.Convert = new Convert(data) } /* mergeColumns */ Cols.prototype.Merge = function (colResult, col1, col2, func = (x, y) => { return x + y }) { mergeColumns(colResult, this.DATA, col1, col2, func) } /* calcColumn */ Cols.prototype.Calculate = function (colName, func = (x, index) => `${index}. ${x}`, colNew = '') { calcColumn(this.DATA, colName, func, colNew) } /* {{colsNumber}} */ Cols.prototype.Number = function (colNameOrNumber, throwError = true) { return colsNumber(this.DATA.cols, colNameOrNumber, throwError) } /* {{colsNames}} */ Cols.prototype.Names = function (colNamesOrNumbers) { return colsNames(this.DATA, colNamesOrNumbers) } /* {{colsCreate}} */ Cols.prototype.Create = function (colNames) { colsCreate(this.DATA, colNames) } /* {{colsAddColumn}} */ Cols.prototype.Add = function (colNames, defaultValue) { if (Array.isArray(colNames)) return colsAddColumns(this.DATA, colNames, defaultValue) else return colsAddColumn(this.DATA, colNames, defaultValue) } /* colsDropColumn */ Cols.prototype.Drop = function (colNameOrNumbers) { colsDropColumn(this.DATA, colNameOrNumbers) } /* colsNameDef */ Cols.prototype.NameDef = function (colNameOrNumber, newColName) { colsNameDef(this.DATA, colNameOrNumber, newColName) } /* colsNameDefs */ Cols.prototype.NameDefs = function (commands, debug = false) { colsNameDefs(this.DATA, commands, debug) } /* {{col2Array}} */ Cols.prototype.toArray = function (colNameOrNumber, rows = -1) { return col2Array(this.DATA, colNameOrNumber, rows) } Cols.prototype.samples = function (colNameOrNumber, maxLength = 45) { return colSamples(this.DATA, colNameOrNumber, maxLength) } /** * Convert columns to other types * @param {table} data - The table data */ function Convert (data) { this.DATA = data } /** * Convert columns to integer */ Convert.prototype.toInt = function (colNamesOrNumbers) { calcColumn(this.DATA, colNamesOrNumbers, (x) => parseInt(x)) } Convert.prototype.toFloat = function (colNamesOrNumbers) { calcColumn(this.DATA, colNamesOrNumbers, (x) => parseFloat(x)) } Convert.prototype.toDate = function (colNamesOrNumbers) { calcColumn(this.DATA, colNamesOrNumbers, (x) => _convert.Convert.dateFromStr(x)) } Convert.prototype.toBool = function (colNamesOrNumbers) { calcColumn(this.DATA, colNamesOrNumbers, (x) => _convert.Convert.boolFromStr(x)) } Convert.prototype.addDuration = function (fromColNameOrNumber, toColNameOrNumber, durationCol = 'duration') { calcDuration(this.DATA, fromColNameOrNumber, toColNameOrNumber, durationCol) } Convert.prototype.addMonth = function (colNamesOrNumbers) { calcColumn(this.DATA, colNamesOrNumbers, (x) => _convert.Convert.date2MonthStr(x), 'month') } Convert.prototype.addWeek = function (colNamesOrNumbers) { calcColumn(this.DATA, colNamesOrNumbers, (x) => _convert.Convert.date2Week(x), 'week') } Convert.prototype.compress = function (fromColNameOrNumber, compressItems = []) { compress(this.DATA, fromColNameOrNumber, compressItems) } /** * Expose private functions for testing & internal use */ function Private () { this.col2Array = col2Array } const _private = new Private() // Exports -------------------------- module.exports = { Cols, colsNumber, colsNames, colsCreate, colsAddColumn, TableArray2Col, _private }