Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/web/ui/src/features/river-js/stringify.ts
5294 views
1
import { ObjectField, Value, ValueType } from './types';
2
3
/**
4
* Returns a native River config representation of the given Value.
5
*/
6
export function riverStringify(v: Value): string {
7
return riverStringifyImpl(v, 0);
8
}
9
10
function riverStringifyImpl(v: Value, indent: number): string {
11
switch (v.type) {
12
case ValueType.NULL: {
13
return 'null';
14
}
15
16
case ValueType.NUMBER: {
17
return v.value.toString();
18
}
19
20
case ValueType.STRING: {
21
return `"${escapeString(v.value)}"`;
22
}
23
24
case ValueType.BOOL: {
25
if (v.value) {
26
return 'true';
27
} else {
28
return 'false';
29
}
30
}
31
32
case ValueType.ARRAY: {
33
let result = '[';
34
v.value.forEach((element, idx) => {
35
result += riverStringifyImpl(element, indent);
36
if (idx + 1 < v.value.length) {
37
result += ', ';
38
}
39
});
40
result += ']';
41
return result;
42
}
43
44
case ValueType.OBJECT: {
45
if (v.value.length === 0) {
46
return '{}';
47
}
48
49
const partitions = partitionFields(v.value);
50
51
let result = '{\n';
52
53
partitions.forEach((partition) => {
54
// Find the maximum field length across all fields in this partition.
55
const keyLength = partitionKeyLength(partition);
56
57
return partition.forEach((element) => {
58
result += indentLine(indent + 1);
59
result += `${partitionKey(element, keyLength)} = ${riverStringifyImpl(element.value, indent + 1)}`;
60
result += ',\n';
61
});
62
});
63
64
result += indentLine(indent) + '}';
65
return result;
66
}
67
68
case ValueType.FUNCTION: {
69
return v.value;
70
}
71
72
case ValueType.CAPSULE: {
73
return v.value;
74
}
75
76
default: {
77
return 'null';
78
}
79
}
80
}
81
82
/**
83
* escapeString escapes special characters in a string so they can be printed
84
* inside a River string literal.
85
*/
86
function escapeString(input: string): string {
87
// TODO(rfratto): this should also escape Unicode characters into \u and \U
88
// forms.
89
return input.replace(/[\b\f\n\r\t\v\0'"\\]/g, (match) => {
90
switch (match) {
91
case '\b':
92
return '\\b';
93
case '\f':
94
return '\\f';
95
case '\n':
96
return '\\n';
97
case '\r':
98
return '\\r';
99
case '\t':
100
return '\\t';
101
case '\v':
102
return '\\v';
103
case "'":
104
return "\\'";
105
case '"':
106
return '\\"';
107
case '\\':
108
return '\\\\';
109
}
110
return '';
111
});
112
}
113
114
function indentLine(indentLevel: number): string {
115
if (indentLevel === 0) {
116
return '';
117
}
118
return '\t'.repeat(indentLevel);
119
}
120
121
/**
122
* partitionFields partitions fields in an object by fields which should have
123
* their equal signs aligned.
124
*
125
* A field which crosses multiple lines (i.e., recursively contains an object
126
* with more than one element) will cause a partition break, placing subsequent
127
* fields in another partition.
128
*/
129
function partitionFields(fields: ObjectField[]): ObjectField[][] {
130
const partitions = [];
131
132
let currentPartition: ObjectField[] = [];
133
fields.forEach((field) => {
134
currentPartition.push(field);
135
136
if (multilinedValue(field.value)) {
137
// Fields which cross multiple lines cause a partition break.
138
partitions.push(currentPartition);
139
currentPartition = [];
140
}
141
});
142
143
if (currentPartition.length !== 0) {
144
partitions.push(currentPartition);
145
}
146
147
return partitions;
148
}
149
150
/** multilinedValue returns true if value recrusively crosses multiple lines. */
151
function multilinedValue(value: Value): boolean {
152
switch (value.type) {
153
case ValueType.OBJECT:
154
// River objects cross more than one line whenever there is at least one
155
// element.
156
return value.value.length > 0;
157
158
case ValueType.ARRAY:
159
// River arrays cross more than one line if any of their elements cross
160
// more than one line.
161
return value.value.some((v) => multilinedValue(v));
162
}
163
164
// Other values never cross line barriers.
165
return false;
166
}
167
168
/**
169
* partitionKeyLength returns the length of keys within the partition. The
170
* length is determined by the longest field name in the partition.
171
*/
172
function partitionKeyLength(partition: ObjectField[]): number {
173
let keyLength = 0;
174
175
partition.forEach((f) => {
176
const fieldLength = partitionKey(f, 0).length;
177
if (fieldLength > keyLength) {
178
keyLength = fieldLength;
179
}
180
});
181
182
return keyLength;
183
}
184
185
/**
186
* partitionKey returns the text to use to display a key for a field within a
187
* partition.
188
*/
189
function partitionKey(field: ObjectField, keyLength: number): string {
190
let key = field.key;
191
if (!validIdentifier(key)) {
192
// Keys which aren't valid identifiers should be wrapped in quotes.
193
key = `"${key}"`;
194
}
195
196
if (key.length < keyLength) {
197
return key + ' '.repeat(keyLength - key.length);
198
}
199
return key;
200
}
201
202
/**
203
* validIdentifier reports whether the input is a valid River identifier.
204
*/
205
function validIdentifier(input: string): boolean {
206
return /^[_a-z][_a-z0-9]*$/i.test(input);
207
}
208
209