Skip to content

Commit 4dcd500

Browse files
committed
Cope with spaces in column header and body
1 parent a364e83 commit 4dcd500

File tree

2 files changed

+109
-6
lines changed

2 files changed

+109
-6
lines changed

ts/src/table.ts

+38-6
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,51 @@ export function asTableLines(output: KubectlOutput): Errorable<TableLines> {
5858
return { succeeded: false, reason: 'kubectl-error', error: output.stderr };
5959
}
6060

61+
interface TableColumn {
62+
readonly name: string;
63+
readonly startIndex: number;
64+
readonly endIndex?: number;
65+
}
66+
6167
function parseTableLines(table: TableLines, columnSeparator: RegExp): Dictionary<string>[] {
6268
if (table.header.length === 0 || table.body.length === 0) {
6369
return [];
6470
}
65-
const columnHeaders = table.header.toLowerCase().replace(columnSeparator, '|').split('|');
66-
return table.body.map((line) => parseLine(line, columnHeaders, columnSeparator));
71+
const columns = parseColumns(table.header, columnSeparator);
72+
return table.body.map((line) => parseLine(line, columns));
6773
}
6874

69-
function parseLine(line: string, columnHeaders: string[], columnSeparator: RegExp) {
75+
function parseLine(line: string, columns: TableColumn[]) {
7076
const lineInfoObject = Dictionary.of<string>();
71-
const bits = line.replace(columnSeparator, '|').split('|');
72-
bits.forEach((columnValue, index) => {
73-
lineInfoObject[columnHeaders[index].trim()] = columnValue.trim();
77+
columns.forEach((column) => {
78+
const text = line.substring(column.startIndex, column.endIndex).trim();
79+
lineInfoObject[column.name] = text;
7480
});
7581
return lineInfoObject;
7682
}
83+
84+
function parseColumns(columnHeaders: string, columnSeparator: RegExp): TableColumn[] {
85+
const columnStarts = parseColumnStarts(columnHeaders, columnSeparator);
86+
const columns = Array.of<TableColumn>();
87+
columnStarts.forEach((column, index) => {
88+
const endIndex = (index < columnStarts.length - 1) ?
89+
columnStarts[index + 1].startIndex - 1 :
90+
undefined;
91+
columns.push({ endIndex, ...column });
92+
});
93+
return columns;
94+
}
95+
96+
function parseColumnStarts(columnHeaders: string, columnSeparator: RegExp) {
97+
const columns = Array.of<TableColumn>();
98+
const columnNames = columnHeaders.replace(columnSeparator, '|').split('|');
99+
let takenTo = 0;
100+
for (const columnName of columnNames) {
101+
const startIndex = columnHeaders.indexOf(columnName, takenTo);
102+
if (startIndex >= 0) {
103+
takenTo = startIndex + columnName.length;
104+
columns.push({ name: columnName.toLowerCase(), startIndex });
105+
}
106+
}
107+
return columns;
108+
}

ts/test/table.ts

+71
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,27 @@ foo true false
1111
barbar false twice
1212
`.trim();
1313

14+
const KUBECTL_SAMPLE_GET_WIDE_RESULT =
15+
`
16+
NAMESPACE NAME FOO BAR RELEASE STATUS SPLINE LEVEL
17+
ns1 foo true false green reticulated
18+
ns2 barbar false twice dark orange none
19+
`.trim();
20+
21+
const KUBECTL_SAMPLE_MULTISPACE_RESULT =
22+
`
23+
NAMESPACE NAME FOO BAR RELEASE STATUS MOTTO
24+
ns1 foo true false green let the games begin
25+
ns2 barbar false twice dark orange none
26+
`.trim();
27+
28+
const KUBECTL_SAMPLE_SKIPPED_COLUMN_RESULT =
29+
`
30+
NAMESPACE NAME RELEASE STATUS MOTTO
31+
ns1 foo green hello
32+
ns2 barbar dark orange
33+
`.trim();
34+
1435
describe('asTableLines', () => {
1536
it('should report failure if kubectl failed to run', () => {
1637
const result = parser.asTableLines(undefined);
@@ -84,4 +105,54 @@ describe('parseTabular', () => {
84105
assert.equal(objects[1].foo, 'false');
85106
assert.equal(objects[1].bar, 'twice');
86107
});
108+
it('should parse headers with spaces correctly', () => {
109+
const result = parser.parseTabular({ code: 0, stdout: KUBECTL_SAMPLE_GET_WIDE_RESULT, stderr: '' });
110+
assert.equal(true, result.succeeded);
111+
const objects = (<Succeeded<Dictionary<string>[]>>result).result;
112+
assert.equal(objects.length, 2);
113+
assert.equal(objects[0].namespace, 'ns1');
114+
assert.equal(objects[0].name, 'foo');
115+
assert.equal(objects[0].foo, 'true');
116+
assert.equal(objects[0].bar, 'false');
117+
assert.equal(objects[0]['release status'], 'green');
118+
assert.equal(objects[0]['spline level'], 'reticulated');
119+
assert.equal(objects[1].namespace, 'ns2');
120+
assert.equal(objects[1].name, 'barbar');
121+
assert.equal(objects[1].foo, 'false');
122+
assert.equal(objects[1].bar, 'twice');
123+
assert.equal(objects[1]['release status'], 'dark orange');
124+
assert.equal(objects[1]['spline level'], 'none');
125+
});
126+
it('should parse lines with multiple spaces correctly', () => {
127+
const result = parser.parseTabular({ code: 0, stdout: KUBECTL_SAMPLE_MULTISPACE_RESULT, stderr: '' });
128+
assert.equal(true, result.succeeded);
129+
const objects = (<Succeeded<Dictionary<string>[]>>result).result;
130+
assert.equal(objects.length, 2);
131+
assert.equal(objects[0].namespace, 'ns1');
132+
assert.equal(objects[0].name, 'foo');
133+
assert.equal(objects[0].foo, 'true');
134+
assert.equal(objects[0].bar, 'false');
135+
assert.equal(objects[0]['release status'], 'green');
136+
assert.equal(objects[0]['motto'], 'let the games begin');
137+
assert.equal(objects[1].namespace, 'ns2');
138+
assert.equal(objects[1].name, 'barbar');
139+
assert.equal(objects[1].foo, 'false');
140+
assert.equal(objects[1].bar, 'twice');
141+
assert.equal(objects[1]['release status'], 'dark orange');
142+
assert.equal(objects[1]['motto'], 'none');
143+
});
144+
it('should parse tables with empty cells', () => {
145+
const result = parser.parseTabular({ code: 0, stdout: KUBECTL_SAMPLE_SKIPPED_COLUMN_RESULT, stderr: '' });
146+
assert.equal(true, result.succeeded);
147+
const objects = (<Succeeded<Dictionary<string>[]>>result).result;
148+
assert.equal(objects.length, 2);
149+
assert.equal(objects[0].namespace, 'ns1');
150+
assert.equal(objects[0].name, 'foo');
151+
assert.equal(objects[0]['release status'], 'green');
152+
assert.equal(objects[0].motto, 'hello');
153+
assert.equal(objects[1].namespace, 'ns2');
154+
assert.equal(objects[1].name, 'barbar');
155+
assert.equal(objects[1]['release status'], 'dark orange');
156+
assert.equal(objects[1].motto, '');
157+
});
87158
});

0 commit comments

Comments
 (0)