diff --git a/DOC.md b/DOC.md index d3c8f9d9..7738615f 100644 --- a/DOC.md +++ b/DOC.md @@ -13210,6 +13210,16 @@ function table( |options|Table options| |return |Table string | +### parse + +Parse table string back to object. + +|Name |Type | +|-------|-------------| +|table |Table string | +|options|Table options| +|return |Table data | + ```javascript table([ ['', 'firstName', 'lastName'], diff --git a/DOC_CN.md b/DOC_CN.md index f8dcefc1..c3bc59d3 100644 --- a/DOC_CN.md +++ b/DOC_CN.md @@ -13198,9 +13198,17 @@ function table( |参数名|说明| |-----|---| |rows|表格数据| -|options|选项| +|options|表格选项| |返回值|表格字符串| +### parse + +|参数名|说明| +|-----|---| +|table|表格字符串| +|options|表格选项| +|返回值|表格数据| + ```javascript table([ ['', 'firstName', 'lastName'], diff --git a/i18n/table.md b/i18n/table.md index 36da6cd5..657591e1 100644 --- a/i18n/table.md +++ b/i18n/table.md @@ -5,5 +5,13 @@ |参数名|说明| |-----|---| |rows|表格数据| -|options|选项| +|options|表格选项| |返回值|表格字符串| + +### parse + +|参数名|说明| +|-----|---| +|table|表格字符串| +|options|表格选项| +|返回值|表格数据| diff --git a/index.json b/index.json index 50c44850..30c41dd8 100644 --- a/index.json +++ b/index.json @@ -6413,7 +6413,11 @@ "map", "repeat", "cloneDeep", - "defaults" + "defaults", + "trim", + "rtrim", + "filter", + "last" ], "description": "Output table string.", "env": [ diff --git a/src/table.js b/src/table.js index 9331c004..64d39262 100644 --- a/src/table.js +++ b/src/table.js @@ -5,6 +5,16 @@ * |rows |Table data | * |options|Table options| * |return |Table string | + * + * ### parse + * + * Parse table string back to object. + * + * |Name |Type | + * |-------|-------------| + * |table |Table string | + * |options|Table options| + * |return |Table data | */ /* example @@ -50,7 +60,7 @@ * ): string; */ -_('each strWidth map repeat cloneDeep defaults'); +_('each strWidth map repeat cloneDeep defaults trim rtrim filter last'); exports = function(rows, options = {}) { rows = cloneDeep(rows); @@ -158,6 +168,114 @@ function renderBorder(type, options) { return ret; } +exports.parse = function(table, options = {}) { + options.border = options.border || {}; + defaults(options.border, defBorder); + const lines = splitLines(table, options.border); + + return parseLines(lines, options); +}; + +function splitLines(table, border) { + const lines = table.split(/\n/); + const trimLines = []; + let chars = ' '; + each(border, val => (chars += val)); + each(lines, (line, idx) => { + line = trim(line); + line = trim(line, chars); + trimLines[idx] = line; + }); + + return filter(lines, (line, idx) => trimLines[idx] !== ''); +} + +function parseLines(lines, options) { + const { border } = options; + + let maxLen = 0; + each(lines, line => { + const len = strWidth(line); + if (len > maxLen) { + maxLen = len; + } + }); + lines = map(lines, line => line + repeat(' ', maxLen - strWidth(line))); + + let start = -1; + let end = -1; + const firstLine = lines[0]; + if (border.bodyLeft) { + start = firstLine.indexOf(border.bodyLeft); + } + if (border.bodyRight) { + end = firstLine.lastIndexOf(border.bodyRight); + } + lines = map(lines, line => { + if (start > -1) { + if (end > -1) { + line = line.slice(start + 1, end); + } else { + line = line.slice(start + 1); + } + } + return line; + }); + maxLen = lines[0].length; + + const rows = []; + const rowCount = lines.length; + let column = []; + for (let i = 0; i < maxLen; i++) { + let isSeparator = true; + let isFakeColumn = false; + + for (let r = 0; r < rowCount; r++) { + column[r] = column[r] || ''; + const c = lines[r][i] || ''; + if (c !== border.bodyJoin) { + isSeparator = false; + } + column[r] += lines[r][i]; + } + + if (isSeparator || i === maxLen - 1) { + let emptyLineCount = 0; + each(column, data => { + data = rtrim(data, ' ' + border.bodyJoin); + if (data === '') { + emptyLineCount++; + } + }); + if (emptyLineCount >= rowCount - 1) { + isFakeColumn = true; + } + if (isSeparator) { + column = map(column, data => data.slice(0, data.length - 1)); + } + + column = map(column, data => trim(data)); + for (let r = 0; r < rowCount; r++) { + const row = rows[r] || []; + const data = column[r]; + + if (isFakeColumn) { + if (row.length !== 0 && data) { + row[row.length - 1] += border.bodyJoin + data; + } + } else { + row.push(data); + } + + rows[r] = row; + } + column = []; + } + } + + return rows; +} + const defBorder = { topBody: '─', topJoin: '┬', diff --git a/test/table.js b/test/table.js index 37254e28..3a810237 100644 --- a/test/table.js +++ b/test/table.js @@ -19,8 +19,7 @@ it('stringify', () => { │ father │ John │ Smith │ ├──────────┼───────────┼──────────┤ │ mother │ Jane │ Smith │ - └──────────┴───────────┴──────────┘ - ` + └──────────┴───────────┴──────────┘` ]); }); @@ -44,7 +43,123 @@ it('custom table borders', () => { │ father John Smith │ ├─────────────────────────────────┤ │ mother Jane Smith │ - └─────────────────────────────────┘ - ` + └─────────────────────────────────┘` + ]); +}); + +it('parse', () => { + expect( + table.parse(` + ┌──────────┬───────────┬──────────┐ + │ │ firstName │ lastName │ + ├──────────┼───────────┼──────────┤ + │ daughter │ Emily │ Smith │ + ├──────────┼───────────┼──────────┤ + │ father │ John │ Smith │ + ├──────────┼───────────┼──────────┤ + │ mother │ Jane │ Smith │ + └──────────┴───────────┴──────────┘`) + ).to.eql(data); + expect( + table.parse( + ` + firstName lastName + daughter Emily Smith + father John Smith + mother Jane Smith`, + { + border: { + bodyLeft: '', + bodyJoin: ' ', + bodyRight: '' + } + } + ) + ).to.eql(data); + expect( + table.parse(` + ┌─────┬───────┬───────┬─────────────────┐ + │ PID│TTY │TIME │CMD │ + ├─────┼───────┼───────┼─────────────────┤ + │68667│ttys000│0:00.01│login -fp runzisu│ + └─────┴───────┴───────┴─────────────────┘`) + ).to.eql([ + ['PID', 'TTY', 'TIME', 'CMD'], + ['68667', 'ttys000', '0:00.01', 'login -fp runzisu'] + ]); + expect( + table.parse( + ` + PID TTY TIME CMD + 68667 ttys000 0:00.01 login -fp runzisu`, + { + border: { + bodyLeft: '', + bodyJoin: ' ', + bodyRight: '' + } + } + ) + ).to.eql([ + ['PID', 'TTY', 'TIME', 'CMD'], + ['68667', 'ttys000', '0:00.01', 'login -fp runzisu'] + ]); + expect( + table.parse( + ` + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ ARGS + 19132 shell 20 0 2.0G 4.0M 3.0M R 27.7 0.1 0:00.13 top -n 1 + 11635 shell 20 0 2.0G 1.9M 1.8M S 5.5 0.0 5:15.83 process-tracker --interval 1000`, + { + border: { + bodyLeft: '', + bodyJoin: ' ', + bodyRight: '' + } + } + ) + ).to.eql([ + [ + 'PID', + 'USER', + 'PR', + 'NI', + 'VIRT', + 'RES', + 'SHR', + 'S', + '%CPU', + '%MEM', + 'TIME+', + 'ARGS' + ], + [ + '19132', + 'shell', + '20', + '0', + '2.0G', + '4.0M', + '3.0M', + 'R', + '27.7', + '0.1', + '0:00.13', + 'top -n 1' + ], + [ + '11635', + 'shell', + '20', + '0', + '2.0G', + '1.9M', + '1.8M', + 'S', + '5.5', + '0.0', + '5:15.83', + 'process-tracker --interval 1000' + ] ]); });