Skip to content

Commit

Permalink
Cope with spaces in column header and body
Browse files Browse the repository at this point in the history
  • Loading branch information
itowlson committed Jan 29, 2021
1 parent a364e83 commit 4dcd500
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 6 deletions.
44 changes: 38 additions & 6 deletions ts/src/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,51 @@ export function asTableLines(output: KubectlOutput): Errorable<TableLines> {
return { succeeded: false, reason: 'kubectl-error', error: output.stderr };
}

interface TableColumn {
readonly name: string;
readonly startIndex: number;
readonly endIndex?: number;
}

function parseTableLines(table: TableLines, columnSeparator: RegExp): Dictionary<string>[] {
if (table.header.length === 0 || table.body.length === 0) {
return [];
}
const columnHeaders = table.header.toLowerCase().replace(columnSeparator, '|').split('|');
return table.body.map((line) => parseLine(line, columnHeaders, columnSeparator));
const columns = parseColumns(table.header, columnSeparator);
return table.body.map((line) => parseLine(line, columns));
}

function parseLine(line: string, columnHeaders: string[], columnSeparator: RegExp) {
function parseLine(line: string, columns: TableColumn[]) {
const lineInfoObject = Dictionary.of<string>();
const bits = line.replace(columnSeparator, '|').split('|');
bits.forEach((columnValue, index) => {
lineInfoObject[columnHeaders[index].trim()] = columnValue.trim();
columns.forEach((column) => {
const text = line.substring(column.startIndex, column.endIndex).trim();
lineInfoObject[column.name] = text;
});
return lineInfoObject;
}

function parseColumns(columnHeaders: string, columnSeparator: RegExp): TableColumn[] {
const columnStarts = parseColumnStarts(columnHeaders, columnSeparator);
const columns = Array.of<TableColumn>();
columnStarts.forEach((column, index) => {
const endIndex = (index < columnStarts.length - 1) ?
columnStarts[index + 1].startIndex - 1 :
undefined;
columns.push({ endIndex, ...column });
});
return columns;
}

function parseColumnStarts(columnHeaders: string, columnSeparator: RegExp) {
const columns = Array.of<TableColumn>();
const columnNames = columnHeaders.replace(columnSeparator, '|').split('|');
let takenTo = 0;
for (const columnName of columnNames) {
const startIndex = columnHeaders.indexOf(columnName, takenTo);
if (startIndex >= 0) {
takenTo = startIndex + columnName.length;
columns.push({ name: columnName.toLowerCase(), startIndex });
}
}
return columns;
}
71 changes: 71 additions & 0 deletions ts/test/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,27 @@ foo true false
barbar false twice
`.trim();

const KUBECTL_SAMPLE_GET_WIDE_RESULT =
`
NAMESPACE NAME FOO BAR RELEASE STATUS SPLINE LEVEL
ns1 foo true false green reticulated
ns2 barbar false twice dark orange none
`.trim();

const KUBECTL_SAMPLE_MULTISPACE_RESULT =
`
NAMESPACE NAME FOO BAR RELEASE STATUS MOTTO
ns1 foo true false green let the games begin
ns2 barbar false twice dark orange none
`.trim();

const KUBECTL_SAMPLE_SKIPPED_COLUMN_RESULT =
`
NAMESPACE NAME RELEASE STATUS MOTTO
ns1 foo green hello
ns2 barbar dark orange
`.trim();

describe('asTableLines', () => {
it('should report failure if kubectl failed to run', () => {
const result = parser.asTableLines(undefined);
Expand Down Expand Up @@ -84,4 +105,54 @@ describe('parseTabular', () => {
assert.equal(objects[1].foo, 'false');
assert.equal(objects[1].bar, 'twice');
});
it('should parse headers with spaces correctly', () => {
const result = parser.parseTabular({ code: 0, stdout: KUBECTL_SAMPLE_GET_WIDE_RESULT, stderr: '' });
assert.equal(true, result.succeeded);
const objects = (<Succeeded<Dictionary<string>[]>>result).result;
assert.equal(objects.length, 2);
assert.equal(objects[0].namespace, 'ns1');
assert.equal(objects[0].name, 'foo');
assert.equal(objects[0].foo, 'true');
assert.equal(objects[0].bar, 'false');
assert.equal(objects[0]['release status'], 'green');
assert.equal(objects[0]['spline level'], 'reticulated');
assert.equal(objects[1].namespace, 'ns2');
assert.equal(objects[1].name, 'barbar');
assert.equal(objects[1].foo, 'false');
assert.equal(objects[1].bar, 'twice');
assert.equal(objects[1]['release status'], 'dark orange');
assert.equal(objects[1]['spline level'], 'none');
});
it('should parse lines with multiple spaces correctly', () => {
const result = parser.parseTabular({ code: 0, stdout: KUBECTL_SAMPLE_MULTISPACE_RESULT, stderr: '' });
assert.equal(true, result.succeeded);
const objects = (<Succeeded<Dictionary<string>[]>>result).result;
assert.equal(objects.length, 2);
assert.equal(objects[0].namespace, 'ns1');
assert.equal(objects[0].name, 'foo');
assert.equal(objects[0].foo, 'true');
assert.equal(objects[0].bar, 'false');
assert.equal(objects[0]['release status'], 'green');
assert.equal(objects[0]['motto'], 'let the games begin');
assert.equal(objects[1].namespace, 'ns2');
assert.equal(objects[1].name, 'barbar');
assert.equal(objects[1].foo, 'false');
assert.equal(objects[1].bar, 'twice');
assert.equal(objects[1]['release status'], 'dark orange');
assert.equal(objects[1]['motto'], 'none');
});
it('should parse tables with empty cells', () => {
const result = parser.parseTabular({ code: 0, stdout: KUBECTL_SAMPLE_SKIPPED_COLUMN_RESULT, stderr: '' });
assert.equal(true, result.succeeded);
const objects = (<Succeeded<Dictionary<string>[]>>result).result;
assert.equal(objects.length, 2);
assert.equal(objects[0].namespace, 'ns1');
assert.equal(objects[0].name, 'foo');
assert.equal(objects[0]['release status'], 'green');
assert.equal(objects[0].motto, 'hello');
assert.equal(objects[1].namespace, 'ns2');
assert.equal(objects[1].name, 'barbar');
assert.equal(objects[1]['release status'], 'dark orange');
assert.equal(objects[1].motto, '');
});
});

0 comments on commit 4dcd500

Please sign in to comment.