UNPKG

lamed_table

Version:
936 lines (860 loc) 34.1 kB
'use strict' // Comment out in test functions console.log(`Starting ${__filename}...`) // comment line to remove simple logging /* ------------------------------------------------------ * table.js * Purpose: The purpose of this module is to manage arrays as a table (cols, rows) * Date Created: 2019/12/12 * Created by : Perez Lamed van Niekerk --------------------------------------------------------- */ /* jshint esversion: 6 */ const _test = require('lamed_test') const { Ok, notOk, notOk_Then, con, unZip } = _test // eslint-disable-line con.useChalk(require('chalk')) // con.traceSet(0) con.trace('Starting table.js...') require('ltype_string') const _array = require('ltype_array') const _no = require('ltype_number') // const _convert = require('ltype_convert') const _name = require('lamed_name') const _table = require('./tableFunctions') const _cols = require('./tableCols') const _rows = require('./tableRows') const _key = require('lamed_keyvalue') /** * Generate the code require to populate a table with values * @param {object} table The table object * @param {bool} showResult - if true then show the result */ function GenerateRowSet (table, showResult = true) { const name = table.name let result = `// Table [${name}] -> ['${table.cols.join("',")}']\n` result += 'let row = []\n' for (let ii = 0; ii < table.cols.length; ii++) { const col = table.cols[ii] result += `${name}.Rows.Set(row, '${col}', '???')\n` } result += `${name}.Rows.Add(row)\n` if (showResult) { con.logLine(); con.logRed(result); con.logLine() } return result } /** * Show a generated template of code to loop through the table values * @param {object} table - The table object * @param {string} varName - The table variable name */ function GenerateTableLoop (table, showResult = true, varName = '') { let name = varName if (name === '') name = _name.nameSafe(table.name) let result = `// ${name} -> [${table.cols.join(', ')}]\n` result += ` for (let ii = 0; ii < ${name}.DATA.rows.length; ii++) {\n` result += ` let row = ${name}.DATA.rows[ii]\n` result += GenerateRow(table, false) result += '}' if (showResult) { con.logLine(); con.logRed(result); con.logLine() } return result } /** * For a given table, generate the code to get all the values of a row into variables * @param {object} table - The table object * @param {bool} showResult - If true then display the results */ function GenerateRow (table, showResult = true) { let result = ' let [ ' + table.cols.join(', ') + ' ] = row\n' result += ' con.log({ ' + table.cols.join(', ') + ' })\n' if (showResult) { con.logLine(); con.logRed(result); con.logLine() } return result } /** * Generate a sample row add code * @param {table} table - The table * @param {bool} showResult - If true, show the result * @returns {string} - The generated code */ function GenerateAdd (table, showResult = true) { const result = `${table.name}.Rows.Add([${table.cols.join(', ')}])` if (showResult) { con.logLine(); con.logRed(result); con.logLine() } return result } /** * Code generation functions */ function CodeGenerate (table) { this.DATA = table } CodeGenerate.prototype.RowGet = function (showResult = true) { return GenerateRow(this.DATA, showResult) } CodeGenerate.prototype.TableLoop = function (showResult = true, varName = '') { return GenerateTableLoop(this.DATA, showResult, varName) } CodeGenerate.prototype.RowSet = function (showResult = true) { return GenerateRowSet(this.DATA, showResult) } /* GenerateAdd */ CodeGenerate.prototype.RowAdd = function (showResult = true) { return GenerateAdd(this.DATA, showResult) } /** * Clone an existing table using the specified columns * @param {object} table - The table to clone from * @param {array} columns - The column names to clone * @param {*} tableName - The name of the new table */ function cloneTable (table, columns, tableName) { if (notOk(tableName)) { if (Ok(table.name)) tableName = table.name + 'Clone' // Table has no name else tableName = 'Clone' } else { if (tableName.includes('-->')) tableName = table.name + tableName // This shows history of clones } if (notOk(columns) || columns.length === 0) columns = table.cols const result = new TableDef(columns, tableName) const rows = table.rows for (let ii = 0; ii < rows.length; ii++) { // Rows const row = rows[ii] const row1 = [] let value = -1 for (let jj = 0; jj < columns.length; jj++) { // Columns const colName = columns[jj] const colNo = _cols.colsNumber(table.cols, colName, false) if (colNo === -1) value = '' // This is a new column else value = row[colNo] result.Rows.Set(row1, colName, value) } result.Rows.Add(row1) } // result.check(true) return result } /** * Clone table and Drop the specified columns * @param {table} table - Table object * @param {number / string / array} colNameOrNumber - The table number or name * @returns {table} - The cloned table */ function cloneTableDropColumn (table, colNameOrNumbers) { const colsNew = table.cols.slice(0) // clone cols array let items = [colNameOrNumbers] if (Array.isArray(colNameOrNumbers)) items = colNameOrNumbers for (let ii = 0; ii < items.length; ii++) { const item = items[ii] const index = _cols.colsNumber(colsNew, item) colsNew.splice(index, 1) } return cloneTable(table, colsNew, table.name) } /** * Compare two values * @param {any} item - Item to compare * @param {any} search - The search value * @param {string} operator - One of '='; '===' ; '>'; '>='; '<'; '<='; '!='; '!=='; 'like' */ function compareValue (item, search, operator = '=') { if (operator === '=' || operator === '===') return (item === search) else if (operator === '>') return (item > search) else if (operator === '>=') return (item >= search) else if (operator === '<') return (item < search) else if (operator === '<=') return (item <= search) else if (operator === '!=' || operator === '!==') return (item !== search) else if (operator === 'like') return item.includes(search) else throw new Error(`Operator '${operator}' not supported!\n`) } unZip(() => compareValue('val1', 'val2'), false) unZip(() => compareValue('val1', 'val1'), true) unZip(() => compareValue(10, 10), true) unZip(() => compareValue(10, 5, '>'), true) unZip(() => compareValue(10, 5, '>='), true) unZip(() => compareValue(10, 7, '<'), false) unZip(() => compareValue(10, 7, '<='), false) unZip(() => compareValue(7, 8, '!='), true) unZip(() => compareValue('this is the row', 'row'), false) unZip(() => compareValue('this is the row', 'row', 'like'), true) /** * Find a string in a specific column * @param {object} table - Table object * @param {string} colName - Column name * @param {string} value - value to search for * @param {bool} findFirst - if true, return the first positive */ function tableFind (table, colName, value, findFirst = false, operator = '=') { operator = operator.toLowerCase() // ensure 'like' const rows = [] const colNo = _cols.colsNumber(table.cols, colName) const isStr = (typeof value === 'string') if (isStr) value = value.toLowerCase() for (let ii = 0; ii < table.rows.length; ii++) { const row = table.rows[ii] let item = row[colNo] if (isStr) item = item.toLowerCase() if (compareValue(item, value, operator)) { if (findFirst) return row else rows.push(row) } } if (rows.length !== 0) { // Lets return a table const result = new TableDef(table.cols, 'rowFind') result.DATA.rows = rows return result } } /** * Return unique cloned table * @param {table} table - The table to work on * @returns {table} - Unique cloned table */ function tableUnique (table) { const cols = table.cols const result = new TableDef(cols, 'unique') const rows = table.rows const keys = [] // Let as loop the input and build the output for (let ii = 0; ii < rows.length; ii++) { // Read Rows const row = rows[ii] let key = '' for (let jj = 0; jj < cols.length; jj++) { // Read Columns const value = row[jj] // Create key based on all values in the row key += value + '_' // Add char to ensure key is unique } if (keys.indexOf(key) === -1) { // This is a new key --> add the data keys.push(key) result.Rows.Add(row) } } return result } /** * Merge table2 with first table. * @param {table} tableData - The table to merge to. * @param {table} table2 - If the value is not in the input table, then add it * @param {boolean} removeDuplicates - If true then duplicates are removed * @returns {table} - Returns cloned merged table */ function tableMerge (tableData, table2, removeDuplicates = false) { let result = cloneTable(tableData) const rows = table2.DATA.rows for (let ii = 0; ii < rows.length; ii++) { const row = rows[ii] // Read Rows result.Rows.Add(row) } if (removeDuplicates) result = tableUnique(result.DATA) return result } /** * Show statistics about a table * @param {table} table - The table object * @param {bool} show - If true then display the table statistics * @returns {object} */ function tableStats (table, show = true, details = false) { const rows = table.DATA.rows.length const cols = table.DATA.cols const fieldNames = [] const result = { name: table.DATA.name, columns: cols.length, rows, fields: [] } // field, unique, uniquePart, uniqueStr, multiple const dtField = new TableDef(['field', 'unique', 'uniquePart', 'uniqueStr', 'multiple', 'samples'], 'FieldStats') const dtStats = new TableDef(['column', 'value', 'count'], 'dtStats') dtStats.Rows.Add([result.name, 'columns', result.columns]) dtStats.Rows.Add([result.name, 'rows', result.rows]) if (show) { con.logLine() con.logGreen(`Table: '${result.name}'`) con.log(`Columns: ${result.columns}`) con.log(`Rows: ${result.rows}`) con.log('----[Column descriptions]----') } // let tables = [] for (let ii = 0; ii < cols.length; ii++) { const field = cols[ii] const group = table.groupBy(field) // group.show(true, 10) const unique = group.DATA.rows.length const uniquePart = unique * 100 / rows const uniqueStr = _no.toFixedStr(uniquePart, 2) + '%' // Doubles --------------------------------- const filter = group.filterNew() filter.add('count', 1, '>') const dblTable = group.filter(filter, [], false, false) // con.log({ dblTable }) let multiple = 0 if (Ok(dblTable)) { multiple = dblTable.totalRows() dblTable.sort('count', false) dblTable.DATA.name = field if (unique < 26) { // Copy results to dtStats // con.log({ field }) fieldNames.push(field) for (let ii = 0; ii < dblTable.DATA.rows.length; ii++) { const row = dblTable.DATA.rows[ii] const [fieldValue, countValue] = row // con.log({ field, fieldValue, countValue }) dtStats.Rows.Add([field, fieldValue, countValue]) } } } if (field === 'duration') { // Lets provide more info for durations const dtDuration = group.groupBy('', false, 'duration') dtStats.Rows.Add(['duration', 'avg', dtDuration.DATA.avg]) dtStats.Rows.Add(['duration', 'min', dtDuration.DATA.min]) dtStats.Rows.Add(['duration', 'max', dtDuration.DATA.max]) dtStats.Rows.Add(['duration', 'sum', dtDuration.DATA.sum]) } let samples = table.Cols.toArray(field).join(', ') samples = samples.substr(0, 45) if (show) { // Calculate space let len = 20 - field.length if (len < 0) len = 0 if (ii > 8) len-- const space = ' '.repeat(len) // Show fields const output = `${ii + 1}. ${field}${space} --> ${unique} unique and ${multiple} multiples; (${uniqueStr}); [${samples}...]` if (uniquePart > 70) con.logBold(output) else con.log(output) } dtField.Rows.Add([field, unique, uniquePart, uniqueStr, multiple, samples]) } if (details) { // Show the counts con.logLine() dtStats.show() con.logLine() } result.fields = dtField return { fieldNames, tableStats: result, dataStats: dtStats } } /** * Group a table by its columns. Also calculate totals * @param {array} groupCols - The columns array * @param {bool} show - If true, show the result * @param {string} sumColumn - The column to calculate total sum for. It shall be a column that contains numbers */ function tableGroupBy (table, groupCols, show = false, sumColumn = '') { // Check the input and try to correct wrong values if (Array.isArray(sumColumn)) sumColumn = sumColumn[0] // sumColumn can not be an array if (Array.isArray(groupCols) === false) { // groupCols must be array if (typeof groupCols === 'boolean') { show = groupCols // No groupCols & bool value --> use this as the show setting groupCols = [] } else if (notOk(groupCols)) groupCols = [] else groupCols = [groupCols] } // Check if groupCols is in table groupCols = _cols.colsNames(table.cols, groupCols) con.log({ groupCols }) // for (let ii = 0; ii < groupCols.length; ii++) { // const item = groupCols[ii] // _cols.colsNumber(table.cols, item, true) // Throws error if not found // } // ------------------------------------------------------------- // Get clone of the table of only the fields we are interested in groupCols.push('key') groupCols.push('count') if (sumColumn !== '') groupCols.push(`${sumColumn}_sum`) // Define output table let result = new TableDef(groupCols, table.name) // con.log({ groupCols }) if (sumColumn !== '' && groupCols.includes(sumColumn) === false) groupCols.push(sumColumn) // If sum- column is not there, add it // con.log({ groupCols }) const clone = cloneTable(table, groupCols) const rows = clone.DATA.rows // let rows = table.rows // this does not work const index = result.Cols.Number(sumColumn, false) // The group totals col const indexCount = result.Cols.Number('count') const indexSum = result.Cols.Number(`${sumColumn}_sum`, false) // con.log({ sumColumn, index, indexCount, indexSum }) const keys = []; const values = [] // Let as loop the input and build the output for (let ii = 0; ii < rows.length; ii++) { // Read Rows const row = rows[ii] let key = ''; let sum = 0 for (let jj = 0; jj < groupCols.length; jj++) { // Read Columns const value = row[jj] if (jj === index) { // This is the total column sum = value // sum has bigger scope than value values.push(value) // we will use this to calculate totals } if (index === -1 && jj === groupCols.length - 1) { // Special case sum = value // sum has bigger scope than value values.push(value) // we will use this to calculate totals break // Break the loop and do not update the key } // Create key based on values found key += value + '_' // Add char to ensure key is unique } // Calculate output if (keys.indexOf(key) === -1) { // This is a new key // con.log({ key }) keys.push(key) result.Rows.Set(row, 'key', key) result.Rows.Set(row, indexCount, 1) // count if (indexSum !== -1) result.Rows.Set(row, `${sumColumn}_sum`, sum) // set field_sum result.Rows.Add(row) } else { // The key exist, find it and add count, field_sum const row2 = result.find('key', key, true) row2[indexCount]++ // Count if (indexSum !== -1) { // calculate sum sum = sum + row2[indexSum] result.Rows.Set(row2, `${sumColumn}_sum`, sum) } } } result = result.cloneDropCol('key') // we no longer need the key // Calculate Totals ----------------------- result.DATA.count = result.DATA.rows.length if (sumColumn !== '') { const totals = _array.totals(values) result.DATA.totalCol = sumColumn result.DATA.min = totals.min result.DATA.max = totals.max result.DATA.sum = totals.sum result.DATA.avg = totals.avg } // Prepare results -------------------- if (result.DATA.cols.length === 1) { // Only count as result - let us ignore the table and return the count const count = result.DATA.rows[0][0] if (show) { con.logBold('GroupBy:'); con.log({ count }) } return count } // Show result and return it result.show(show) return result } /** * calculate statistics using two fields ito duration * @param {table} table - The Table to use as input * @param {table} twoFieldArray - The table with field1, field2 values. let twoFields = new _table.TableDef(['field1', 'field2'], 'twoFields') */ function durationStats_From2Fields (table, twoFieldArray, show = true) { // eslint-disable-line // Sample // table.statsDuration2Fields([ // 'Assignee-->month', // 'Assignee-->week', // 'Assignee-->summary' // ], show) const dtDuration2 = new TableDef(['Field1', 'value1', 'Field2', 'value2', 'count', 'duration'], 'dtDuration2') // twoFieldArray.CodeGenerate.TableLoop() // twoFieldArray -> ['key-->value', 'key-->value'] const keyValues = _key.keyValueFromTagArray(twoFieldArray, _key.enumError.Error) for (let ii = 0; ii < keyValues.length; ii++) { const keyValue = keyValues[ii] const { key, value } = keyValue durationStatsCalculate2(table, key, value, dtDuration2) } dtDuration2.show(show) return dtDuration2 } function durationStatsCalculate2 (result, field, field2, dtDuration) { const grpAssignee = tableGroupBy(result, [field, field2], false, 'duration') for (let ii = 0; ii < grpAssignee.DATA.rows.length; ii++) { const row = grpAssignee.DATA.rows[ii] const [fieldValue, fieldValue2, count, duration] = row // const avg = _convert.Convert.duration_MSec2Str((duration / count), false, false) dtDuration.Rows.Add([field, fieldValue, field2, fieldValue2, count, duration]) } } /** * Calculate duration statistics per field * @param {table} table - The input table * @param {array} fields - The fields */ function durationStats (table, fields = [], show = true) { if (Array.isArray(fields) === false) fields = [fields] const dtDuration = new TableDef(['Field', 'value', 'count', 'duration'], 'dtDuration') const durationGrp = tableGroupBy(table, '', false, 'duration') const total = durationGrp.DATA.sum const count = durationGrp.DATA.count dtDuration.Rows.Add(['Avg', 'duration', count, total]) for (let ii = 0; ii < fields.length; ii++) { const field = fields[ii] durationStatsCalculate(table, field, dtDuration) } dtDuration.show(show) return dtDuration } function durationStatsCalculate (result, field, dtDuration) { const grpAssignee = tableGroupBy(result, [field], false, 'duration') for (let ii = 0; ii < grpAssignee.DATA.rows.length; ii++) { const row = grpAssignee.DATA.rows[ii] const [fieldValue, count, duration] = row // const avg = _convert.Convert.duration_MSec2Str((duration / count), false, false) dtDuration.Rows.Add([field, fieldValue, count, duration]) } } /** * Calculate series statistics per month * @param {table} table - The input table * @param {array} fields - The fields */ function seriesMonth (table, fields = [], show = true) { if (Array.isArray(fields) === false) fields = [fields] if (fields.length === 0) fields = table.cols if (table.cols.includesAny('month') === false) throw new Error(`No month column in ${table.cols}\n`) const dtSeries = new TableDef(['field', 'fieldValue', 'month', 'total'], 'dtSeries') // const dtMonth = tableGroupBy(table, 'month', false) for (let ii = 0; ii < fields.length; ii++) { const field = fields[ii] if (field.includesAny('month', 'week', 'duration')) continue // skip these seriesMonthCalculate(table, field, dtSeries) } dtSeries.show(show) return dtSeries } function seriesMonthCalculate (result, field, dtSeries) { const grpBy = tableGroupBy(result, [field, 'month']) // grpBy.show() for (let ii = 0; ii < grpBy.DATA.rows.length; ii++) { const row = grpBy.DATA.rows[ii] const [fieldValue, month, total] = row dtSeries.Rows.Add([field, fieldValue, month, total]) } } /** * Calculate series statistics per week * @param {table} table - The input table * @param {array} fields - The fields */ function seriesWeek (table, fields = [], show = true) { if (Array.isArray(fields) === false) fields = [fields] if (fields.length === 0) fields = table.cols if (table.cols.includesAny('week') === false) throw new Error(`No week column in ${table.cols}\n`) const dtSeries = new TableDef(['field', 'fieldValue', 'week', 'total'], 'dtSeries') for (let ii = 0; ii < fields.length; ii++) { const field = fields[ii] if (field.includesAny('month', 'week', 'duration')) continue // skip these seriesWeekCalculate(table, field, dtSeries) } dtSeries.show(show) return dtSeries } function seriesWeekCalculate (result, field, dtSeries) { const grpBy = tableGroupBy(result, [field, 'week']) // grpBy.show() for (let ii = 0; ii < grpBy.DATA.rows.length; ii++) { const row = grpBy.DATA.rows[ii] const [fieldValue, week, total] = row dtSeries.Rows.Add([field, fieldValue, week, total]) } } /** * Joint child table with its parent table and return new table. * @param {table} tableMany - The many table * @param {string} idMany - The id of the many table * @param {table} tableParent - The parent table * @param {string} idParent - The id of the parent table * @param {bool} throwError - If true then throw error if no link was found else only show message * @returns - The joined table */ function TableJoin (tableMany, idMany, tableParent, idParent, columns = [], throwError = true) { // Id's idMany = idMany.toLowerCase() idParent = idParent.toLowerCase() const idHeadNo = _cols.colsNumber(tableMany.cols, idMany) // let idFilesNo = tableParent.Cols.Number(idParent) // Cols const colsHead = tableMany.cols const colsFiles = tableParent.DATA.cols let joinCols = colsHead.concat(colsFiles) joinCols = _array.uniqueArray(joinCols) // con.log({joinCols}) let dtJoin = cloneTable(tableMany, [], tableMany.name) // We will add the rest of the fields after join // dtJoin -> [id, heading, hash, files, levels, level1, level2, level3] for (let ii = 0; ii < dtJoin.DATA.rows.length; ii++) { // Do the join const row = dtJoin.DATA.rows[ii] const id = row[idHeadNo] const rowLink = tableParent.find(idParent, id, true) if (notOk(rowLink)) { const msg = `Join row ${ii + 1} not found ${idParent} = ${id}` if (throwError) throw new Error(msg + '\n') else con.logRed(msg) dtJoin.DATA.rows[ii] = row continue } // let idLink = rowLink[idFilesNo] // if (id !== idLink) throw new Error(`id(${id}) and idLink(${idLink}) does not match\n`) // Join the data // let rowData = row.slice(0,colsHead.length) // Remove the joined spaces const joinLink = ((idMany === idParent) ? rowLink.slice(1) : rowLink.slice(0)) // If link id is the same remove one const joinRow = row.concat(joinLink) // con.log({ row, rowLink, joinLink, joinRow }) // return dtJoin.DATA.rows[ii] = joinRow } // con.logTable(dtJoin.DATA) dtJoin.DATA.cols = joinCols // con.log(dtJoin.DATA.rows[0]) // con.log({joinCols, columns}) if (columns.length > 0) dtJoin = dtJoin.clone(columns) // Let us only return what was asked for // dtJoin.show() return dtJoin // Return new table } // ------------------------------------------------ function Filter (table) { this.DATA = table this.WHERE = [] } /* filterAdd */ Filter.prototype.add = function (column, value, operator = '=', andFilter = true) { filterAdd(this.DATA, this.WHERE, column, value, operator, andFilter) } Filter.prototype.reset = function () { this.WHERE = [] } Filter.prototype.show = function (show = true) { filterShow(this.WHERE, show) } /** * * @param {table} table - Table object * @param {object} where - The current WHERE condition * @param {string/number} column - The column name or number * @param {object} value - The value to test * @param {string} operator - One of '='; '===' ; '>'; '>='; '<'; '<='; '!='; '!=='; 'like' * @param {boolean} andFilter - If true then filter again on previous result else filter on origin and add results. * @returns {void} - Nothing */ function filterAdd (table, where, column, value, operator = '=', andFilter = true) { // con.log({ where, column, value, operator }) const index = _cols.colsNumber(table.cols, column, true) // Verify the column column = table.cols[index] operator = operator.toLowerCase() compareValue(0, 0, operator) // test operator where.push({ column, value, operator, andFilter }) } /** * Show the filter * @param {array} where - The WHERE array * @param {bool} show - Show indicator */ function filterShow (where, show = true) { if (show) { con.logBold('Table Filter:') con.log({ where }) if (where.length === 0) con.logRed('No filter defined!') else con.logTable(where) } } /** * * @param {*} table * @param {*} filter - See [>filterAdd()<] * @param {*} columns * @param {*} show * @param {*} show0Results * @param {*} removeDuplicates - Remove duplicates from the result */ function filterData (table, filter, columns = [], show = false, removeDuplicates = false) { const where = filter.WHERE if (columns.length === 0) columns = table.cols let resultTable = {} resultTable.DATA = table if (where.length === 0) resultTable = cloneTable(table) // let resultTable = cloneTable(table) const start = resultTable.DATA.rows.length for (let ii = 0; ii < where.length; ii++) { const item = where[ii] let { column, value, operator, andFilter } = item // eslint-disable-line // con.log({ ii }) if (andFilter || ii === 0) { // 'and' filter; First filter can only be 'and' filter if (Ok(resultTable)) resultTable = tableFind(resultTable.DATA, column, value, false, operator) } else { // 'or' filter; Do the filter on the original data let orTable = cloneTable(table) orTable = tableFind(orTable.DATA, column, value, false, operator) if (Ok(orTable)) { if (Ok(resultTable)) resultTable = tableMerge(resultTable.DATA, orTable, true) else resultTable = orTable // There is no results to merge with } } // con.log({ resultTable }) } // Show results if (notOk(resultTable)) { // con.logRed(`No results found for filter!`) return new TableDef(columns) // <------------------------------------ } const end = resultTable.DATA.rows.length const result = `-->Returned '${end}' of '${start}' records` filterShow(where, show) if (show) { if (start === end) con.logRed(result) else con.logGreen('\n' + result) } // resultTable.show(show) resultTable = cloneTable(resultTable.DATA, columns) // Remove the extra columns if (removeDuplicates) resultTable = tableUnique(resultTable.DATA) // Remove duplicates resultTable.show(show) return resultTable } /** * Return a value from a table * @param {table} table - The table to return value from * @param {string/number} columnNameOrNo - The column name or number * @param {number} rowNo - The row number to return value from. Default is -1 (and mean there should only be one row and data is returned from row 1) * @returns {any} - Return the value from the specified row */ function getValue (table, columnNameOrNo, rowNo = -1, throwError = true) { const rows = table.rows if (rowNo < 0) { if (rows.length !== 1) { if (throwError) throw new Error(`If no rowNo specified, then there must be only one row in the table. Table has ${rows.length} rows!\n`) else return undefined } rowNo = 0 } if (rowNo > rows.length - 1) throw new Error(`Row ${rowNo} does not exist.\n`) const colNo = _cols.colsNumber(table.cols, columnNameOrNo) const row = rows[rowNo] return row[colNo] } /** * Return the duplicate values for a column * @param {table} table - The table to return value from * @param {array} columns - The columns array of string / number * @param {bool} show - If true then show the result */ function qaDuplicates (table, columns, show = false) { const dubs = tableGroupBy(table, columns) const filter = dubs.filterNew() filter.add(1, 1, '>') const dubs2 = dubs.filter(filter) dubs2.show(show) return dubs2 } // ---------------------------------------------------------------------- /** * Create a new table def object * @param {*} columns * @param {*} tableName */ function TableDef (columns = [], tableName = 'tableName') { if (_test.isObject(columns)) { if (_table.TableCheck(columns, false, true)) { this.DATA = columns.DATA } else throw new Error('Cannot create table') } else this.DATA = _table.create(columns, tableName) this.Cols = new _cols.Cols(this.DATA) this.CodeGenerate = new CodeGenerate(this.DATA) this.Rows = new _rows.Rows(this.DATA) } // TableDef.prototype.CodeGenerate = new CodeGenerate(this.DATA) // function () { // return new CodeGenerate(this.DATA) // } TableDef.prototype.check = function (showTrace = false, showError = true) { _table.checkTableData(this.DATA, showTrace, showError) } TableDef.prototype.show = function (show = true, rowCount = -1, colCount = -1, maxColLength = -1) { _table.showTable(this.DATA, show, rowCount, colCount, maxColLength) } /* getValue */ TableDef.prototype.value = function (columnNameOrNo, rowNo = -1, throwError = true) { return getValue(this.DATA, columnNameOrNo, rowNo, throwError) } TableDef.prototype.sort = function (colNameOrNumber, ascending = true) { _table.sort(this.DATA, colNameOrNumber, ascending) } TableDef.prototype.sortColumns = function (colNamesOrNumbers, ascending = true) { _table.sortColumns(this.DATA, colNamesOrNumbers, ascending) } TableDef.prototype.toMD = function (boldColumn = -1, heading = '') { return _table.table2MD(this.DATA, boldColumn, heading) } TableDef.prototype.toCSV = function (addHeadings = true) { return _table.Table2CSV(this.DATA, addHeadings) } TableDef.prototype.toHTML = function () { return _table.table2HTML(this.DATA) } TableDef.prototype.arrayAppend = function (list, id = -1, noId = false) { _rows.TableArrayAppend(this.DATA, list, id, noId) } /* tableFunctions.TableArray2Col */ TableDef.prototype.array2Col = function (list, colName) { _cols.TableArray2Col(this.DATA, list, colName) } TableDef.prototype.array2Cols = function (list) { _table.TableArray2Columns(list, this) } TableDef.prototype.join = function (idMany, tableParent, idParent, columns = [], throwError = true) { return TableJoin(this.DATA, idMany, tableParent, idParent, columns, throwError) } TableDef.prototype.totalCols = function () { return _table.totalCols(this.DATA) } TableDef.prototype.totalRows = function () { return _table.totalRows(this.DATA) } TableDef.prototype.clone = function (columns, tableName = '') { return cloneTable(this.DATA, columns, tableName) } /* cloneTableDropColumn */ TableDef.prototype.cloneDropCol = function (colNameOrNumber) { return cloneTableDropColumn(this.DATA, colNameOrNumber) } /* {{tableFind}} */ TableDef.prototype.find = function (colName, value, findFirst = false) { return tableFind(this.DATA, colName, value, findFirst) } /* filterData */ TableDef.prototype.filter = function (filter, columns = [], show = false, show0Results = true, removeDuplicates = false) { return filterData(this.DATA, filter, columns, show, show0Results, removeDuplicates) } /* Filter */ TableDef.prototype.filterNew = function () { return new Filter(this.DATA) } /* tableMerge */ TableDef.prototype.merge = function (mergeTable, removeDuplicates = false) { return tableMerge(this.DATA, mergeTable, removeDuplicates) } /* tableUnique */ TableDef.prototype.unique = function () { return tableUnique(this.DATA) } /* tableGroupBy */ TableDef.prototype.groupBy = function (groupCols, show = false, sumColumn = '') { return tableGroupBy(this.DATA, groupCols, show, sumColumn) } /* tableStats */ TableDef.prototype.stats = function (show = true, details = false) { return tableStats(this, show, details) } TableDef.prototype.statsDuration = function (fields = [], show = true) { return durationStats(this.DATA, fields, show) } TableDef.prototype.statsDuration2Fields = function (twoFieldArray, show = true) { return durationStats_From2Fields(this.DATA, twoFieldArray, show) } TableDef.prototype.seriesMonth = function (fields = [], show = true) { return seriesMonth(this.DATA, fields, show) } TableDef.prototype.seriesWeek = function (fields = [], show = true) { return seriesWeek(this.DATA, fields, show) } /* {{qaDuplicates}} */ TableDef.prototype.qaDuplicates = function (columns, show = false) { return qaDuplicates(this.DATA, columns, show) } // Exports -------------------------- module.exports = { TableDef, TableCheck: _table.TableCheck }