-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathtable.ts
108 lines (96 loc) · 3.95 KB
/
table.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { Dictionary } from "./dictionary";
import { KubectlOutput } from ".";
import { Errorable, failed } from "./errorable";
const KUBECTL_OUTPUT_COLUMN_SEPARATOR = /\s\s+/g;
/**
* Provides a line-oriented view of tabular kubectl output.
*/
export interface TableLines {
readonly header: string;
readonly body: string[];
}
/**
* Parses tabular kubectl output into an array of objects. Each non-header row
* is mapped to an object in the output, and each object has a property per column,
* named as the lower-cased column header.
* @param output The result of invoking kubectl via the shell.
* @returns If kubectl ran successfully and produced tabular output, a success value
* containing an array of objects for the non-header rows. If kubectl did not run
* successfully, a failure value.
*/
export function parseTabular(output: KubectlOutput): Errorable<Dictionary<string>[]> {
const table = asTableLines(output);
if (failed(table)) {
return table;
}
const parsed = parseTableLines(table.result, KUBECTL_OUTPUT_COLUMN_SEPARATOR);
return { succeeded: true, result: parsed };
}
/**
* Parses tabular kubectl output into an array of lines. The first row is mapped
* as a header, and the remaining rows as a body array.
* @param output The result of invoking kubectl via the shell.
* @returns If kubectl ran successfully and produced tabular output, a success value
* containing an object with header (string) and body (array of string) properties.
* If kubectl ran successfully but produced no output, header is the empty string and
* body the empty array. If kubectl did not run successfully, a failure value.
*/
export function asTableLines(output: KubectlOutput): Errorable<TableLines> {
if (!output) {
return { succeeded: false, reason: 'failed-to-run', error: 'Unable to run kubectl' };
}
if (output.code === 0) {
const lines = output.stdout.split('\n');
const header = lines.shift();
if (!header) {
return { succeeded: true, result: { header: '', body: [] } };
}
const body = lines.filter((l) => l.length > 0);
return { succeeded: true, result: { header, body } };
}
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 columns = parseColumns(table.header, columnSeparator);
return table.body.map((line) => parseLine(line, columns));
}
function parseLine(line: string, columns: TableColumn[]) {
const lineInfoObject = Dictionary.of<string>();
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;
}