@google-cloud/kcmd 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +203 -0
- package/README.md +264 -2
- package/build/ts/kcmd/cli.js +20252 -0
- package/build/ts/kcmd/gcp/alloydb.d.ts +12 -0
- package/build/ts/kcmd/gcp/alloydb.js +70 -0
- package/build/ts/kcmd/gcp/api.d.ts +29 -0
- package/build/ts/kcmd/gcp/api.js +162 -0
- package/build/ts/kcmd/gcp/biglake.d.ts +18 -0
- package/build/ts/kcmd/gcp/biglake.js +59 -0
- package/build/ts/kcmd/gcp/bigquery.d.ts +57 -0
- package/build/ts/kcmd/gcp/bigquery.js +134 -0
- package/build/ts/kcmd/gcp/context.d.ts +10 -0
- package/build/ts/kcmd/gcp/context.js +74 -0
- package/build/ts/kcmd/gcp/crm.d.ts +13 -0
- package/build/ts/kcmd/gcp/crm.js +91 -0
- package/build/ts/kcmd/gcp/dataplex.d.ts +147 -0
- package/build/ts/kcmd/gcp/dataplex.js +406 -0
- package/build/ts/kcmd/gcp/index.d.ts +3 -0
- package/build/ts/kcmd/gcp/index.js +21 -0
- package/build/ts/kcmd/gcp/spanner.d.ts +73 -0
- package/build/ts/kcmd/gcp/spanner.js +126 -0
- package/build/ts/kcmd/index.d.ts +7 -0
- package/build/ts/kcmd/index.js +46 -0
- package/build/ts/kcmd/layout.d.ts +16 -0
- package/build/ts/kcmd/layout.js +31 -0
- package/build/ts/kcmd/layouts/documents.d.ts +24 -0
- package/build/ts/kcmd/layouts/documents.js +264 -0
- package/build/ts/kcmd/layouts/okf.d.ts +31 -0
- package/build/ts/kcmd/layouts/okf.js +386 -0
- package/build/ts/kcmd/layouts/semantic-model.d.ts +74 -0
- package/build/ts/kcmd/layouts/semantic-model.js +621 -0
- package/build/ts/kcmd/layouts/standard.d.ts +13 -0
- package/build/ts/kcmd/layouts/standard.js +108 -0
- package/build/ts/kcmd/manifest.d.ts +34 -0
- package/build/ts/kcmd/manifest.js +262 -0
- package/build/ts/kcmd/metadata.d.ts +25 -0
- package/build/ts/kcmd/metadata.js +8 -0
- package/build/ts/kcmd/okf/catalog_residue.d.ts +14 -0
- package/build/ts/kcmd/okf/catalog_residue.js +124 -0
- package/build/ts/kcmd/okf/concept.d.ts +14 -0
- package/build/ts/kcmd/okf/concept.js +110 -0
- package/build/ts/kcmd/okf/concept_to_entry.d.ts +10 -0
- package/build/ts/kcmd/okf/concept_to_entry.js +281 -0
- package/build/ts/kcmd/okf/init_okf_workspace.d.ts +15 -0
- package/build/ts/kcmd/okf/init_okf_workspace.js +113 -0
- package/build/ts/kcmd/okf/links.d.ts +23 -0
- package/build/ts/kcmd/okf/links.js +468 -0
- package/build/ts/kcmd/okf/okf_metadata.d.ts +12 -0
- package/build/ts/kcmd/okf/okf_metadata.js +99 -0
- package/build/ts/kcmd/semantic/bigquery.d.ts +22 -0
- package/build/ts/kcmd/semantic/bigquery.js +469 -0
- package/build/ts/kcmd/semantic/binding.d.ts +3 -0
- package/build/ts/kcmd/semantic/binding.js +92 -0
- package/build/ts/kcmd/semantic/catalog_entry.d.ts +24 -0
- package/build/ts/kcmd/semantic/catalog_entry.js +35 -0
- package/build/ts/kcmd/semantic/converters/owl/convert.d.ts +26 -0
- package/build/ts/kcmd/semantic/converters/owl/convert.js +41 -0
- package/build/ts/kcmd/semantic/converters/owl/model.d.ts +98 -0
- package/build/ts/kcmd/semantic/converters/owl/model.js +30 -0
- package/build/ts/kcmd/semantic/converters/owl/parse.d.ts +11 -0
- package/build/ts/kcmd/semantic/converters/owl/parse.js +634 -0
- package/build/ts/kcmd/semantic/converters/owl/to_ir.d.ts +19 -0
- package/build/ts/kcmd/semantic/converters/owl/to_ir.js +390 -0
- package/build/ts/kcmd/semantic/deploy_bigquery.d.ts +20 -0
- package/build/ts/kcmd/semantic/deploy_bigquery.js +226 -0
- package/build/ts/kcmd/semantic/deploy_knowledge_catalog.d.ts +104 -0
- package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +1367 -0
- package/build/ts/kcmd/semantic/deploy_spanner.d.ts +15 -0
- package/build/ts/kcmd/semantic/deploy_spanner.js +150 -0
- package/build/ts/kcmd/semantic/deployment_target.d.ts +93 -0
- package/build/ts/kcmd/semantic/deployment_target.js +379 -0
- package/build/ts/kcmd/semantic/graph_ddl.d.ts +44 -0
- package/build/ts/kcmd/semantic/graph_ddl.js +656 -0
- package/build/ts/kcmd/semantic/import_model.d.ts +72 -0
- package/build/ts/kcmd/semantic/import_model.js +1698 -0
- package/build/ts/kcmd/semantic/ir.d.ts +589 -0
- package/build/ts/kcmd/semantic/ir.js +247 -0
- package/build/ts/kcmd/semantic/kc_actions.d.ts +45 -0
- package/build/ts/kcmd/semantic/kc_actions.js +531 -0
- package/build/ts/kcmd/semantic/kc_constraints.d.ts +44 -0
- package/build/ts/kcmd/semantic/kc_constraints.js +279 -0
- package/build/ts/kcmd/semantic/kc_converter.d.ts +37 -0
- package/build/ts/kcmd/semantic/kc_converter.js +839 -0
- package/build/ts/kcmd/semantic/kc_custom_types.d.ts +63 -0
- package/build/ts/kcmd/semantic/kc_custom_types.js +751 -0
- package/build/ts/kcmd/semantic/kc_entries.d.ts +155 -0
- package/build/ts/kcmd/semantic/kc_entries.js +284 -0
- package/build/ts/kcmd/semantic/kc_ids.d.ts +102 -0
- package/build/ts/kcmd/semantic/kc_ids.js +188 -0
- package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +29 -0
- package/build/ts/kcmd/semantic/knowledge_catalog.js +960 -0
- package/build/ts/kcmd/semantic/loader.d.ts +258 -0
- package/build/ts/kcmd/semantic/loader.js +3576 -0
- package/build/ts/kcmd/semantic/names.d.ts +10 -0
- package/build/ts/kcmd/semantic/names.js +85 -0
- package/build/ts/kcmd/semantic/osi_converter.d.ts +60 -0
- package/build/ts/kcmd/semantic/osi_converter.js +910 -0
- package/build/ts/kcmd/semantic/parameters.d.ts +19 -0
- package/build/ts/kcmd/semantic/parameters.js +171 -0
- package/build/ts/kcmd/semantic/pull_kc.d.ts +10 -0
- package/build/ts/kcmd/semantic/pull_kc.js +272 -0
- package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +59 -0
- package/build/ts/kcmd/semantic/resolve_inheritance.js +346 -0
- package/build/ts/kcmd/semantic/resolve_profiles.d.ts +291 -0
- package/build/ts/kcmd/semantic/resolve_profiles.js +2003 -0
- package/build/ts/kcmd/semantic/runtime/agent_tools.d.ts +93 -0
- package/build/ts/kcmd/semantic/runtime/agent_tools.js +243 -0
- package/build/ts/kcmd/semantic/runtime/dialect.d.ts +13 -0
- package/build/ts/kcmd/semantic/runtime/dialect.js +39 -0
- package/build/ts/kcmd/semantic/runtime/runtime.d.ts +88 -0
- package/build/ts/kcmd/semantic/runtime/runtime.js +301 -0
- package/build/ts/kcmd/semantic/runtime/store.d.ts +112 -0
- package/build/ts/kcmd/semantic/runtime/store.js +344 -0
- package/build/ts/kcmd/semantic/skills.d.ts +110 -0
- package/build/ts/kcmd/semantic/skills.js +697 -0
- package/build/ts/kcmd/semantic/source_dialect.d.ts +17 -0
- package/build/ts/kcmd/semantic/source_dialect.js +311 -0
- package/build/ts/kcmd/semantic/source_entries.d.ts +12 -0
- package/build/ts/kcmd/semantic/source_entries.js +341 -0
- package/build/ts/kcmd/semantic/spanner.d.ts +18 -0
- package/build/ts/kcmd/semantic/spanner.js +60 -0
- package/build/ts/kcmd/semantic/sql_expr_utils.d.ts +23 -0
- package/build/ts/kcmd/semantic/sql_expr_utils.js +394 -0
- package/build/ts/kcmd/semantic/sql_identifiers.d.ts +6 -0
- package/build/ts/kcmd/semantic/sql_identifiers.js +267 -0
- package/build/ts/kcmd/semantic/sql_parser.d.ts +47 -0
- package/build/ts/kcmd/semantic/sql_parser.js +400 -0
- package/build/ts/kcmd/semantic/transpile.d.ts +76 -0
- package/build/ts/kcmd/semantic/transpile.js +581 -0
- package/build/ts/kcmd/semantic/validate.d.ts +70 -0
- package/build/ts/kcmd/semantic/validate.js +2266 -0
- package/build/ts/kcmd/semantic/yaml_options.d.ts +6 -0
- package/build/ts/kcmd/semantic/yaml_options.js +58 -0
- package/build/ts/kcmd/semantic/yaml_update.d.ts +26 -0
- package/build/ts/kcmd/semantic/yaml_update.js +782 -0
- package/build/ts/kcmd/snapshot.d.ts +37 -0
- package/build/ts/kcmd/snapshot.js +386 -0
- package/build/ts/kcmd/source.d.ts +31 -0
- package/build/ts/kcmd/source.js +119 -0
- package/build/ts/kcmd/sources/bq-dataset.d.ts +17 -0
- package/build/ts/kcmd/sources/bq-dataset.js +131 -0
- package/build/ts/kcmd/sources/entrygroup.d.ts +21 -0
- package/build/ts/kcmd/sources/entrygroup.js +83 -0
- package/build/ts/kcmd/sources/kb.d.ts +21 -0
- package/build/ts/kcmd/sources/kb.js +82 -0
- package/build/ts/kcmd/sources/semantic-model.d.ts +18 -0
- package/build/ts/kcmd/sources/semantic-model.js +48 -0
- package/build/ts/kcmd/sync.d.ts +35 -0
- package/build/ts/kcmd/sync.js +734 -0
- package/package.json +62 -4
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Changes the text of a YAML document to hold a new value. Each part whose
|
|
3
|
+
// value stays comes back byte for byte, and a changed part is written in the
|
|
4
|
+
// file's indentation and line endings.
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.parseForUpdate = parseForUpdate;
|
|
40
|
+
exports.linkNodes = linkNodes;
|
|
41
|
+
exports.updateText = updateText;
|
|
42
|
+
const yaml = __importStar(require("yaml"));
|
|
43
|
+
const loader_1 = require("./loader");
|
|
44
|
+
const yaml_options_1 = require("./yaml_options");
|
|
45
|
+
/**
|
|
46
|
+
* Parses `text` for `updateText`. A JSON file, which is one flow mapping, is
|
|
47
|
+
* first rewritten as block YAML, which the returned `text` holds.
|
|
48
|
+
*/
|
|
49
|
+
function parseForUpdate(text) {
|
|
50
|
+
const parse = (t) => yaml.parseDocument(t, { ...yaml_options_1.YAML_OPTIONS, keepSourceTokens: true });
|
|
51
|
+
const document = parse(text);
|
|
52
|
+
const root = document.contents;
|
|
53
|
+
if (document.errors.length > 0 || !yaml.isMap(root) || !root.flow) {
|
|
54
|
+
return { document, text };
|
|
55
|
+
}
|
|
56
|
+
// An empty list or mapping stays `[]` or `{}`. A blank line before a
|
|
57
|
+
// collection's first item is dropped, since it would print as a line of
|
|
58
|
+
// spaces or a `- ` alone on its line. A string a YAML 1.1 reader takes for
|
|
59
|
+
// another type, such as `yes`, is quoted, as in every changed part.
|
|
60
|
+
yaml.visit(document, {
|
|
61
|
+
Collection: (_, n) => {
|
|
62
|
+
n.flow = n.items.length === 0;
|
|
63
|
+
const first = n.items[0];
|
|
64
|
+
const owner = yaml.isPair(first) ? first.key : first;
|
|
65
|
+
if (yaml.isNode(owner))
|
|
66
|
+
owner.spaceBefore = false;
|
|
67
|
+
},
|
|
68
|
+
Scalar: (_, n) => void delete n.type,
|
|
69
|
+
});
|
|
70
|
+
document.setSchema('1.2', { ...yaml_options_1.YAML_OPTIONS, compat: 'yaml-1.1' });
|
|
71
|
+
const yamlText = document.toString({ lineWidth: 0 });
|
|
72
|
+
return { document: parse(yamlText), text: yamlText };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Maps each object and array in `value` to the node it was copied from.
|
|
76
|
+
* `value` must be a plain copy of `node`.
|
|
77
|
+
*/
|
|
78
|
+
function linkNodes(value, node, links = new Map()) {
|
|
79
|
+
if (Array.isArray(value) && yaml.isSeq(node)) {
|
|
80
|
+
links.set(value, node);
|
|
81
|
+
value.forEach((v, i) => linkNodes(v, node.items[i], links));
|
|
82
|
+
}
|
|
83
|
+
else if ((0, loader_1.isRecord)(value) && yaml.isMap(node)) {
|
|
84
|
+
links.set(value, node);
|
|
85
|
+
for (const pair of node.items) {
|
|
86
|
+
if (yaml.isScalar(pair.key)) {
|
|
87
|
+
linkNodes(value[keyOf(pair)], pair.value, links);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return links;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Returns `text`, which `doc` parses, changed to hold `value`. An object or
|
|
95
|
+
* array keeps the node `links` maps it to, and any other value keeps the node
|
|
96
|
+
* under the same key, or the list item with the same `name` or value. A kept
|
|
97
|
+
* node whose value stays keeps its text and the comments and blank lines
|
|
98
|
+
* around it. A changed scalar keeps its quoting, and a removed item takes its
|
|
99
|
+
* comments and the blank lines above it. A flow collection over several lines
|
|
100
|
+
* that changes is written again in block style. A value that would be written
|
|
101
|
+
* as a block scalar that keeps its final line breaks, as `|+` does, is
|
|
102
|
+
* double-quoted instead.
|
|
103
|
+
*/
|
|
104
|
+
function updateText(text, doc, value, links) {
|
|
105
|
+
const pairs = [];
|
|
106
|
+
yaml.visit(doc, { Pair: (_, p, path) => void pairs.push([p, path.at(-1)]) });
|
|
107
|
+
const u = {
|
|
108
|
+
text,
|
|
109
|
+
doc,
|
|
110
|
+
links,
|
|
111
|
+
pairs,
|
|
112
|
+
held: new Map(),
|
|
113
|
+
used: new Set(),
|
|
114
|
+
copies: new Map([...links].map(([v, n]) => [n, v])),
|
|
115
|
+
edits: [],
|
|
116
|
+
dirty: new Set(),
|
|
117
|
+
aliases: [],
|
|
118
|
+
nl: /\r?\n/.exec(text)?.[0] ?? '\n',
|
|
119
|
+
...indents(doc, text),
|
|
120
|
+
};
|
|
121
|
+
hold(u, value, doc.contents);
|
|
122
|
+
patch(u, value, doc.contents, { col: 0, prefix: '', flow: false });
|
|
123
|
+
// An alias whose anchor changed or went is written out.
|
|
124
|
+
for (const [alias, v, slot] of u.aliases) {
|
|
125
|
+
const anchor = alias.resolve(doc);
|
|
126
|
+
const [a, b] = anchor.range;
|
|
127
|
+
if (u.dirty.has(anchor) || u.edits.some(([p, q]) => p < b && q > a)) {
|
|
128
|
+
replace(u, v, alias, slot);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// The empty lines after a block scalar that keeps its final line breaks, as
|
|
132
|
+
// `|+` does, are its text. An edit that removes the text right after one
|
|
133
|
+
// whose text stays also removes the blank lines it would leave there.
|
|
134
|
+
const ends = new Set();
|
|
135
|
+
yaml.visit(doc, {
|
|
136
|
+
Scalar: (_, n) => {
|
|
137
|
+
const [a, b] = n.range;
|
|
138
|
+
if (keepsBreaks(text, n) && !u.edits.some(([p, q]) => p < b && q > a)) {
|
|
139
|
+
ends.add(b);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const edits = u.edits.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
144
|
+
edits.forEach(([from, to, s], i) => {
|
|
145
|
+
const [p, q, inserted] = edits[i - 1] ?? [];
|
|
146
|
+
if (s || !ends.has(from) || p === from && q === from && inserted)
|
|
147
|
+
return;
|
|
148
|
+
edits[i][1] = skipBlank(text, to, edits[i + 1]?.[0] ?? text.length);
|
|
149
|
+
ends.add(edits[i][1]);
|
|
150
|
+
});
|
|
151
|
+
let out = '';
|
|
152
|
+
let at = 0;
|
|
153
|
+
for (const [from, to, s] of edits) {
|
|
154
|
+
// An edit inside another would write part of the text twice.
|
|
155
|
+
if (from < at)
|
|
156
|
+
throw new Error(`Overlapping edits at offset ${from}.`);
|
|
157
|
+
out += text.slice(at, from) + s;
|
|
158
|
+
at = to;
|
|
159
|
+
}
|
|
160
|
+
out += text.slice(at);
|
|
161
|
+
// A text with no final newline keeps none, unless the text of a block scalar
|
|
162
|
+
// that keeps its final line breaks reaches its end.
|
|
163
|
+
return text.endsWith('\n') || ends.has(text.length) ?
|
|
164
|
+
out :
|
|
165
|
+
out.replace(/\r?\n$/, '');
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Fills `held` for `value` in place of `node`: the node `links` maps each
|
|
169
|
+
* object to, and each mapping or list whose items `patch` keeps. The value
|
|
170
|
+
* that keeps a node's items in place holds it.
|
|
171
|
+
*/
|
|
172
|
+
function hold(u, value, node) {
|
|
173
|
+
if (!isObject(value))
|
|
174
|
+
return;
|
|
175
|
+
const linked = u.links.get(value);
|
|
176
|
+
if (linked && !u.held.has(linked))
|
|
177
|
+
u.held.set(linked, value);
|
|
178
|
+
const kept = isFull(value) && yaml.isCollection(node) &&
|
|
179
|
+
Array.isArray(value) === yaml.isSeq(node) ?
|
|
180
|
+
matchItems(u, entriesOf(value), node) :
|
|
181
|
+
[];
|
|
182
|
+
if (kept.some(i => i >= 0))
|
|
183
|
+
u.held.set(node, value);
|
|
184
|
+
entriesOf(value).forEach(([, v], i) => {
|
|
185
|
+
const item = yaml.isCollection(node) ? node.items[kept[i] ?? -1] : undefined;
|
|
186
|
+
hold(u, v, yaml.isPair(item) ? item.value : item);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function patch(u, value, node, slot) {
|
|
190
|
+
const edits = u.edits.length;
|
|
191
|
+
// A flow collection over several lines that changes is written again in
|
|
192
|
+
// block style.
|
|
193
|
+
if (yaml.isCollection(node) && node.flow && spansLines(u, node) &&
|
|
194
|
+
canonical(node.toJS(u.doc)) !== canonical(value)) {
|
|
195
|
+
replace(u, value, node, slot);
|
|
196
|
+
}
|
|
197
|
+
else if (isFull(value) && yaml.isCollection(node) &&
|
|
198
|
+
Array.isArray(value) === yaml.isSeq(node)) {
|
|
199
|
+
patchItems(u, value, node, slot);
|
|
200
|
+
}
|
|
201
|
+
else if (yaml.isScalar(node) && !isObject(value)) {
|
|
202
|
+
if (node.value !== value)
|
|
203
|
+
setScalar(u, node, value, slot);
|
|
204
|
+
}
|
|
205
|
+
else if (isObject(value) && !isFull(value) && yaml.isCollection(node) &&
|
|
206
|
+
node.items.length === 0 && Array.isArray(value) === yaml.isSeq(node)) {
|
|
207
|
+
// An empty list or mapping that stays empty keeps its text.
|
|
208
|
+
}
|
|
209
|
+
else if (yaml.isAlias(node) && canonical(node.toJS(u.doc)) === canonical(value)) {
|
|
210
|
+
u.aliases.push([node, value, slot]);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
replace(u, value, node, slot);
|
|
214
|
+
}
|
|
215
|
+
if (u.edits.length > edits)
|
|
216
|
+
u.dirty.add(node);
|
|
217
|
+
}
|
|
218
|
+
function patchItems(u, value, node, slot) {
|
|
219
|
+
const t = u.text;
|
|
220
|
+
const flow = slot.flow || !!node.flow;
|
|
221
|
+
const items = node.items;
|
|
222
|
+
const entries = entriesOf(value);
|
|
223
|
+
const at = matchItems(u, entries, node);
|
|
224
|
+
if (at.every(i => i < 0))
|
|
225
|
+
return replace(u, value, node, slot);
|
|
226
|
+
// The kept item each new entry goes after, or -1.
|
|
227
|
+
const follows = at.map((j, i) => j < 0 ? Math.max(-1, ...at.slice(0, i)) : NaN);
|
|
228
|
+
const g = geometry(u, node, flow);
|
|
229
|
+
const col = g[0].start - lineStart(t, g[0].start);
|
|
230
|
+
// The text between two items.
|
|
231
|
+
const sep = g.length > 1 ? t.slice(g[0].end, g[1].start) : ', ';
|
|
232
|
+
const slotOf = (i) => {
|
|
233
|
+
const item = items[i];
|
|
234
|
+
const sep = yaml.isPair(item) ?
|
|
235
|
+
item.srcToken?.sep?.find(s => s.type === 'map-value-ind')?.offset :
|
|
236
|
+
g[i].start;
|
|
237
|
+
const prefix = yaml.isPair(item) ? ':' : '-';
|
|
238
|
+
return { col, prefix, after: sep === undefined ? sep : sep + 1, flow };
|
|
239
|
+
};
|
|
240
|
+
// Removes each run of items no entry keeps.
|
|
241
|
+
for (let a = 0; a < items.length; a++) {
|
|
242
|
+
if (at.includes(a))
|
|
243
|
+
continue;
|
|
244
|
+
let b = a;
|
|
245
|
+
while (b + 1 < items.length && !at.includes(b + 1))
|
|
246
|
+
b++;
|
|
247
|
+
const next = g[b + 1];
|
|
248
|
+
if (flow && next) {
|
|
249
|
+
u.edits.push([g[a].start, next.start, '']);
|
|
250
|
+
}
|
|
251
|
+
else if (flow) {
|
|
252
|
+
// The comma after the item that becomes last goes, unless a new entry
|
|
253
|
+
// comes after it.
|
|
254
|
+
u.edits.push([follows.includes(a - 1) ? g[a].start : g[a - 1].end, g[b].end, '']);
|
|
255
|
+
}
|
|
256
|
+
else if (g[a].inline && next) {
|
|
257
|
+
// The next item moves up onto the `- ` line, and its comments go above
|
|
258
|
+
// that line. A comment indented deeper than that line takes its
|
|
259
|
+
// indentation.
|
|
260
|
+
const above = lineStart(t, g[a].start);
|
|
261
|
+
const own = lineStart(t, next.start);
|
|
262
|
+
const indent = /^[ \t]*/.exec(t.slice(above))[0];
|
|
263
|
+
const comments = t.slice(skipBlank(t, next.head, own), own)
|
|
264
|
+
.replace(/^[ \t]*(?=#)/gm, w => w.length > indent.length ? indent : w);
|
|
265
|
+
u.edits.push([above, above, comments], [g[a].start, next.start, '']);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
// The blank lines above an old first item stay, and those above the
|
|
269
|
+
// item that becomes first go.
|
|
270
|
+
const first = a === 0 && at[0] >= 0;
|
|
271
|
+
const from = first ? skipBlank(t, g[a].head, g[a].start) : g[a].head;
|
|
272
|
+
const to = first && next ? skipBlank(t, g[b].end, next.start) : g[b].end;
|
|
273
|
+
u.edits.push([from, to, '']);
|
|
274
|
+
}
|
|
275
|
+
a = b;
|
|
276
|
+
}
|
|
277
|
+
// Updates each kept item, and adds each new one after the item before it.
|
|
278
|
+
const added = new Map();
|
|
279
|
+
entries.forEach(([k, v], i) => {
|
|
280
|
+
const item = items[at[i]];
|
|
281
|
+
if (yaml.isPair(item)) {
|
|
282
|
+
const key = item.key;
|
|
283
|
+
// A key renamed in the document, such as `datasets` to `entities`.
|
|
284
|
+
if (yaml.isScalar(key) && typeof key.value === 'string' &&
|
|
285
|
+
key.source !== key.value) {
|
|
286
|
+
setScalar(u, key, key.value, slotOf(at[i]));
|
|
287
|
+
}
|
|
288
|
+
patch(u, v, item.value, slotOf(at[i]));
|
|
289
|
+
}
|
|
290
|
+
else if (at[i] >= 0) {
|
|
291
|
+
patch(u, v, item, slotOf(at[i]));
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const before = follows[i];
|
|
295
|
+
const key = yaml.isMap(node) ? `${scalarText(u, k, 0, flow)}:` : '-';
|
|
296
|
+
const text = !flow ? block(u, key, v, col, String(k)) :
|
|
297
|
+
yaml.isMap(node) ? [`${key} ${flowValue(u, String(k), v)}`] :
|
|
298
|
+
[scalarText(u, v, 0, true)];
|
|
299
|
+
added.set(before, [...(added.get(before) ?? []), ...text]);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
for (const [j, parts] of added) {
|
|
303
|
+
const near = g[Math.max(j, 0)];
|
|
304
|
+
if (flow) {
|
|
305
|
+
// New items go before the item after the one they follow.
|
|
306
|
+
const text = parts.join(sep);
|
|
307
|
+
const next = g[j + 1];
|
|
308
|
+
const last = !at.some(i => i > j);
|
|
309
|
+
u.edits.push(next ? [next.start, next.start, last ? text : text + sep] :
|
|
310
|
+
[near.end, near.end, sep + text]);
|
|
311
|
+
}
|
|
312
|
+
else if (j < 0 && near.inline) {
|
|
313
|
+
const text = parts.join(u.nl).slice(col) + u.nl + ' '.repeat(col);
|
|
314
|
+
u.edits.push([near.start, near.start, text]);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
const pos = j < 0 ? near.head : near.end;
|
|
318
|
+
const text = parts.join(u.nl);
|
|
319
|
+
u.edits.push(pos === t.length && !t.endsWith('\n') ? [pos, pos, u.nl + text] :
|
|
320
|
+
[pos, pos, text + u.nl]);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* For each of `entries`, the index of the item of `node` it keeps, or -1. A
|
|
326
|
+
* mapping's entry keeps the pair with its key. A list's entry keeps the item
|
|
327
|
+
* `links` maps it to, or else a free item with the same `name` or value, and
|
|
328
|
+
* the kept items stay in their order.
|
|
329
|
+
*/
|
|
330
|
+
function matchItems(u, entries, node) {
|
|
331
|
+
const items = node.items;
|
|
332
|
+
if (yaml.isMap(node)) {
|
|
333
|
+
return entries.map(([k]) => node.items.findIndex(p => keyOf(p) === k));
|
|
334
|
+
}
|
|
335
|
+
const at = entries.map(([, v]) => {
|
|
336
|
+
const linked = isObject(v) ? u.links.get(v) : undefined;
|
|
337
|
+
return linked ? items.indexOf(linked) : -2;
|
|
338
|
+
});
|
|
339
|
+
const free = items.flatMap((n, j) => at.includes(j) ?
|
|
340
|
+
[] :
|
|
341
|
+
[{ j, key: itemKey(u.copies.get(n) ?? n.toJS(u.doc)) }]);
|
|
342
|
+
let last = -1;
|
|
343
|
+
return at.map((j, i) => {
|
|
344
|
+
if (j === -2) {
|
|
345
|
+
const f = free.findIndex(f => f.key === itemKey(entries[i][1]));
|
|
346
|
+
j = f < 0 ? -1 : free.splice(f, 1)[0].j;
|
|
347
|
+
}
|
|
348
|
+
if (j >= 0 && j < last)
|
|
349
|
+
return -1;
|
|
350
|
+
last = Math.max(last, j);
|
|
351
|
+
return j;
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
function geometry(u, node, flow) {
|
|
355
|
+
const t = u.text;
|
|
356
|
+
const dash = flow ? [] : dashes(node);
|
|
357
|
+
let prevEnd = 0;
|
|
358
|
+
return node.items.map((item, i) => {
|
|
359
|
+
const first = (yaml.isPair(item) ? item.key : item);
|
|
360
|
+
const last = (yaml.isPair(item) ? item.value ?? item.key : item);
|
|
361
|
+
const start = yaml.isPair(item) || flow ? first.range[0] : dash[i];
|
|
362
|
+
const ls = lineStart(t, start);
|
|
363
|
+
const inline = !flow && t.slice(ls, start).trim() !== '';
|
|
364
|
+
// An empty value, as in `a:`, ends on its key's line, and so does the item.
|
|
365
|
+
const end = flow ? last.range[1] :
|
|
366
|
+
blockEnd(t, Math.max(last.range[2], nextLine(t, start)), start - ls, last);
|
|
367
|
+
// The root's first item owns no comments: those above it are the file's.
|
|
368
|
+
const floor = i > 0 ? prevEnd : node === u.doc.contents ? ls : 0;
|
|
369
|
+
let head = inline || flow ? start : ls;
|
|
370
|
+
while (head > floor &&
|
|
371
|
+
/^[ \t]*(#.*)?\r?\n$/.test(t.slice(lineStart(t, head - 1), head))) {
|
|
372
|
+
head = lineStart(t, head - 1);
|
|
373
|
+
}
|
|
374
|
+
prevEnd = end;
|
|
375
|
+
return { start, end, head, inline };
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Where a block item at column `col` ends, given where its `node` ends: at a
|
|
380
|
+
* line's start, before the blank lines and the comments at its column or less
|
|
381
|
+
* under it, since those are above the next item. When `node` ends in a block
|
|
382
|
+
* scalar, the lines of that scalar's text stay in the item, such as the empty
|
|
383
|
+
* lines that end a `|+` block scalar.
|
|
384
|
+
*/
|
|
385
|
+
function blockEnd(t, end, col, node) {
|
|
386
|
+
if (t.slice(lineStart(t, end), end).trim() === '')
|
|
387
|
+
end = lineStart(t, end);
|
|
388
|
+
let last = node;
|
|
389
|
+
while (yaml.isCollection(last) && last.items.length > 0) {
|
|
390
|
+
const item = last.items.at(-1);
|
|
391
|
+
last = yaml.isPair(item) ? item.value : item;
|
|
392
|
+
}
|
|
393
|
+
const floor = yaml.isScalar(last) && /^BLOCK/.test(last.type ?? '') ?
|
|
394
|
+
lineStart(t, last.range[1]) :
|
|
395
|
+
0;
|
|
396
|
+
let p = end;
|
|
397
|
+
for (let q = lineStart(t, p - 1); p > floor; p = q, q = lineStart(t, p - 1)) {
|
|
398
|
+
const line = t.slice(q, lineEnd(t, q));
|
|
399
|
+
if (line.trim() && (!/^\s*#/.test(line) || line.search(/\S/) > col))
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
return p;
|
|
403
|
+
}
|
|
404
|
+
function setScalar(u, node, value, slot) {
|
|
405
|
+
const [from, to, lead] = rangeOf(u, node);
|
|
406
|
+
const kept = typeof node.value === 'string' && typeof value === 'string';
|
|
407
|
+
const type = kept ? node.type : undefined;
|
|
408
|
+
let s = scalarText(u, value, slot.col, slot.flow, type);
|
|
409
|
+
// A value of several lines in place of a plain or double-quoted scalar is
|
|
410
|
+
// double-quoted. As a block scalar it would take the comment after it into
|
|
411
|
+
// its text. A single-quoted one stays single-quoted.
|
|
412
|
+
if (!/^(BLOCK|QUOTE_SINGLE)/.test(node.type ?? '') && s.includes('\n')) {
|
|
413
|
+
s = scalarText(u, value, slot.col, slot.flow, 'QUOTE_DOUBLE');
|
|
414
|
+
}
|
|
415
|
+
// The text after a block scalar's header, such as a comment, stays after
|
|
416
|
+
// the new header, or after the new value when it is not a block scalar.
|
|
417
|
+
const tail = headerTail(u.text, from) ?? '';
|
|
418
|
+
s = /^[|>]/.test(s) ? s.replace(/^[|>][-+0-9]*/, h => h + tail) : s + tail;
|
|
419
|
+
s = lead + s;
|
|
420
|
+
u.edits.push([from, to, u.text[to - 1] === '\n' ? s + u.nl : s]);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Writes `value` in place of `node`. The comment on the line where `node`
|
|
424
|
+
* starts stays on that line.
|
|
425
|
+
*/
|
|
426
|
+
function replace(u, value, node, slot) {
|
|
427
|
+
const t = u.text;
|
|
428
|
+
const [from, to, lead] = yaml.isNode(node) && node.range ?
|
|
429
|
+
rangeOf(u, node) :
|
|
430
|
+
[slot.after ?? 0, slot.after ?? 0, ''];
|
|
431
|
+
// A flow collection on one line stays flow.
|
|
432
|
+
if (slot.flow || slot.after === undefined ||
|
|
433
|
+
yaml.isCollection(node) && node.flow && isObject(value) &&
|
|
434
|
+
!spansLines(u, node)) {
|
|
435
|
+
u.edits.push([from, to, lead + scalarText(u, value, slot.col, true)]);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const keyLine = lineEnd(t, slot.after);
|
|
439
|
+
// A block collection's range can run over the comments after it and into
|
|
440
|
+
// the next line's indentation. The comments at its key's column or less
|
|
441
|
+
// stay.
|
|
442
|
+
const stop = blockEnd(t, to, slot.col, node);
|
|
443
|
+
const end = lineEnd(t, Math.max(from, stop - 1));
|
|
444
|
+
// A scalar, alias or flow collection on the line under its key is replaced
|
|
445
|
+
// there, so the comments above it and after it stay.
|
|
446
|
+
const under = from > keyLine && (!yaml.isCollection(node) || node.flow);
|
|
447
|
+
const col = under && isFull(value) ? from - lineStart(t, from) : slot.col;
|
|
448
|
+
const [first, ...rest] = under && isFull(value) ?
|
|
449
|
+
items(u, value, col) :
|
|
450
|
+
block(u, slot.prefix, value, col);
|
|
451
|
+
// The text after the node on its last line stays, or after a block
|
|
452
|
+
// scalar's header unless the scalar's text moved into the new value. A
|
|
453
|
+
// block collection of several lines keeps none.
|
|
454
|
+
const header = headerTail(t, from);
|
|
455
|
+
const moved = u.pairs.some(([p]) => p.value === node && u.used.has(p));
|
|
456
|
+
const after = header !== undefined ? (moved ? '' : header) :
|
|
457
|
+
end === lineEnd(t, from) || !yaml.isCollection(node) || node.flow ?
|
|
458
|
+
t.slice(to, end) :
|
|
459
|
+
'';
|
|
460
|
+
if (under && !isFull(value)) {
|
|
461
|
+
// As a block scalar, text of several lines would take the text after it.
|
|
462
|
+
const type = typeof value === 'string' && value.includes('\n') ?
|
|
463
|
+
'QUOTE_DOUBLE' :
|
|
464
|
+
undefined;
|
|
465
|
+
u.edits.push([from, end, scalarText(u, value, slot.col, false, type) + after]);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (under) {
|
|
469
|
+
u.edits.push([from, end, [first.slice(col) + after, ...rest].join(u.nl)]);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const tail = from > keyLine ? t.slice(slot.after, keyLine) : after;
|
|
473
|
+
const head = first.slice(col + slot.prefix.length);
|
|
474
|
+
u.edits.push([slot.after, end, [head + tail, ...rest].join(u.nl)]);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* The lines of `prefix`, a key and `:` or a `-` at column `col`, and `value`
|
|
478
|
+
* after it, in the file's indentation. `key` is the key `prefix` writes. A
|
|
479
|
+
* value that moved keeps its text.
|
|
480
|
+
*/
|
|
481
|
+
function block(u, prefix, value, col, key) {
|
|
482
|
+
const pad = ' '.repeat(col);
|
|
483
|
+
const moved = prefix.endsWith(':') ? movedText(u, key, value, col) : undefined;
|
|
484
|
+
if (moved)
|
|
485
|
+
return [pad + prefix + moved[0], ...moved.slice(1)];
|
|
486
|
+
if (!isFull(value)) {
|
|
487
|
+
const s = scalarText(u, value, col, false);
|
|
488
|
+
// A block scalar's header is a line of its own, so that a comment after
|
|
489
|
+
// it stays out of the text.
|
|
490
|
+
const line = `${pad}${prefix} ${s}`;
|
|
491
|
+
return /^[|>]/.test(s) ? line.split(u.nl) : [line];
|
|
492
|
+
}
|
|
493
|
+
if (prefix === '-' && (0, loader_1.isRecord)(value)) {
|
|
494
|
+
const lines = items(u, value, col + u.itemIndent);
|
|
495
|
+
// In a file whose items start on the line after their '-', so do new ones.
|
|
496
|
+
if (u.itemAlone)
|
|
497
|
+
return [pad + '-', ...lines];
|
|
498
|
+
const [first, ...rest] = lines;
|
|
499
|
+
return [pad + '-' + first.slice(col + 1), ...rest];
|
|
500
|
+
}
|
|
501
|
+
const nested = prefix === '-' ? 2 : Array.isArray(value) ? u.seqIndent : u.step;
|
|
502
|
+
return [pad + prefix, ...items(u, value, col + nested)];
|
|
503
|
+
}
|
|
504
|
+
/** The lines of each item of the collection `value`, at column `col`. */
|
|
505
|
+
function items(u, value, col) {
|
|
506
|
+
release(u, value);
|
|
507
|
+
return entriesOf(value).flatMap(([k, v]) => {
|
|
508
|
+
const prefix = Array.isArray(value) ? '-' : `${scalarText(u, k, 0, false)}:`;
|
|
509
|
+
return block(u, prefix, v, col, String(k));
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Lets the entries of `value`, which is written entry by entry, move from the
|
|
514
|
+
* node `links` maps it to, unless another value keeps that node in place.
|
|
515
|
+
*/
|
|
516
|
+
function release(u, value) {
|
|
517
|
+
const node = u.links.get(value);
|
|
518
|
+
if (u.held.get(node) === value)
|
|
519
|
+
u.held.delete(node);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* The text of the input value that `value` moved from, for `key` at column
|
|
523
|
+
* `col`, or in a flow mapping when `col` is undefined: what follows the key's
|
|
524
|
+
* `:` on its line, then each line under it, re-indented. An object or array
|
|
525
|
+
* moves from the node `links` maps it to, unless another value keeps that
|
|
526
|
+
* node in place. A scalar under `key` moves from a
|
|
527
|
+
* pair with the same key and value whose text the new value does not keep.
|
|
528
|
+
* Undefined when nothing moved, or when the text would not read back as
|
|
529
|
+
* `value` at its new place, as when it holds an alias of an anchor outside it.
|
|
530
|
+
* A value that is or holds a block scalar that keeps its final line breaks,
|
|
531
|
+
* as `|+` does, is written fresh, since its text would take the blank lines
|
|
532
|
+
* after its new place.
|
|
533
|
+
*/
|
|
534
|
+
function movedText(u, key, value, col) {
|
|
535
|
+
const node = isObject(value) ? u.links.get(value) : undefined;
|
|
536
|
+
const found = u.pairs.find(([p, parent]) => !u.used.has(p) &&
|
|
537
|
+
(node ? p.value === node && u.held.get(node) === value :
|
|
538
|
+
key !== undefined && keyOf(p) === key && yaml.isScalar(p.value) &&
|
|
539
|
+
p.value.value === value &&
|
|
540
|
+
u.held.get(parent)?.[key] !==
|
|
541
|
+
value));
|
|
542
|
+
const p = found?.[0];
|
|
543
|
+
const v = p?.value;
|
|
544
|
+
if (!p || !v?.range || !yaml.isNode(p.key) || keeps(u, v))
|
|
545
|
+
return undefined;
|
|
546
|
+
const t = u.text;
|
|
547
|
+
const keyCol = p.key.range[0] - lineStart(t, p.key.range[0]);
|
|
548
|
+
const from = v.range[0];
|
|
549
|
+
// The comments after the value at its key's column or less are above the
|
|
550
|
+
// next key.
|
|
551
|
+
const to = blockEnd(t, v.range[1], keyCol);
|
|
552
|
+
const shift = (col ?? 0) - keyCol;
|
|
553
|
+
const own = t.slice(lineStart(t, from), from).trim() === '';
|
|
554
|
+
// A mapping or list on the lines under its key starts on the line after the
|
|
555
|
+
// key, so the comments above its first key or item move with it. The
|
|
556
|
+
// comments above a scalar under its key stay with the key.
|
|
557
|
+
const start = !own ? from :
|
|
558
|
+
yaml.isCollection(v) ? nextLine(t, p.key.range[1]) :
|
|
559
|
+
lineStart(t, from);
|
|
560
|
+
const stop = t.slice(lineStart(t, to), to).trim() ? to : lineStart(t, to);
|
|
561
|
+
const lines = t.slice(start, stop)
|
|
562
|
+
.replace(/\r?\n$/, '')
|
|
563
|
+
.split(/\r?\n/)
|
|
564
|
+
.map((l, i) => !own && i === 0 || l === '' ? l :
|
|
565
|
+
shift >= 0 ? ' '.repeat(shift) + l :
|
|
566
|
+
l.slice(Math.min(-shift, indentOf(l))));
|
|
567
|
+
// An empty value moves as nothing after the key's `:`.
|
|
568
|
+
const moved = own ? ['', ...lines] :
|
|
569
|
+
[lines[0] && ' ' + lines[0], ...lines.slice(1)];
|
|
570
|
+
const reads = col === undefined ?
|
|
571
|
+
moved.length === 1 && readsInFlow(moved[0].trim(), value) :
|
|
572
|
+
readsInBlock([' '.repeat(col) + '_:' + moved[0], ...moved.slice(1)].join('\n'), value);
|
|
573
|
+
if (!reads)
|
|
574
|
+
return undefined;
|
|
575
|
+
u.used.add(p);
|
|
576
|
+
return moved;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* `value` under `key` in a flow mapping. A value that moved keeps its text
|
|
580
|
+
* when that is one line that reads the same there.
|
|
581
|
+
*/
|
|
582
|
+
function flowValue(u, key, value) {
|
|
583
|
+
return movedText(u, key, value)?.[0].trim() ?? scalarText(u, value, 0, true);
|
|
584
|
+
}
|
|
585
|
+
/** Whether `text` reads as `value` in a flow collection. */
|
|
586
|
+
function readsInFlow(text, value) {
|
|
587
|
+
try {
|
|
588
|
+
return canonical(yaml.parse(`[${text}]`, yaml_options_1.YAML_OPTIONS)[0]) === canonical(value);
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Whether `text`, one key and its value, reads as that key holding `value`.
|
|
596
|
+
* An alias in it must have its anchor in it.
|
|
597
|
+
*/
|
|
598
|
+
function readsInBlock(text, value) {
|
|
599
|
+
try {
|
|
600
|
+
const read = yaml.parse(text, yaml_options_1.YAML_OPTIONS);
|
|
601
|
+
return (0, loader_1.isRecord)(read) && canonical(Object.values(read)[0]) === canonical(value);
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/** Whether the text of `node` holds a line break. */
|
|
608
|
+
function spansLines(u, node) {
|
|
609
|
+
return u.text.slice(node.range[0], node.range[1]).includes('\n');
|
|
610
|
+
}
|
|
611
|
+
/** Whether `node` is or holds a block scalar that keeps its final line breaks. */
|
|
612
|
+
function keeps(u, node) {
|
|
613
|
+
let found = false;
|
|
614
|
+
yaml.visit(node, { Scalar: (_, n) => void (found ||= keepsBreaks(u.text, n)) });
|
|
615
|
+
return found;
|
|
616
|
+
}
|
|
617
|
+
/** Whether `node` is a block scalar that keeps its final line breaks. */
|
|
618
|
+
function keepsBreaks(t, node) {
|
|
619
|
+
return yaml.isScalar(node) &&
|
|
620
|
+
/^[|>][0-9]*\+/.test(t.slice(node.range[0], lineEnd(t, node.range[0])));
|
|
621
|
+
}
|
|
622
|
+
/** The number of spaces and tabs that start `line`. */
|
|
623
|
+
function indentOf(line) {
|
|
624
|
+
return /^[ \t]*/.exec(line)[0].length;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* `value` written as a scalar or flow collection. Each line after the first
|
|
628
|
+
* that is not empty is indented under column `col`. In a flow collection, a
|
|
629
|
+
* string of several lines is one double-quoted line.
|
|
630
|
+
*/
|
|
631
|
+
function scalarText(u, value, col, flow, type) {
|
|
632
|
+
if (flow && isFull(value)) {
|
|
633
|
+
release(u, value);
|
|
634
|
+
const seq = Array.isArray(value);
|
|
635
|
+
const pad = seq ? u.pad.seq : u.pad.map;
|
|
636
|
+
const parts = entriesOf(value).map(([k, v]) => seq ?
|
|
637
|
+
scalarText(u, v, col, true) :
|
|
638
|
+
`${scalarText(u, k, 0, true)}: ${flowValue(u, String(k), v)}`);
|
|
639
|
+
return `${seq ? '[' : '{'}${pad}${parts.join(', ')}${pad}${seq ? ']' : '}'}`;
|
|
640
|
+
}
|
|
641
|
+
const lines = flow && typeof value === 'string' && value.includes('\n');
|
|
642
|
+
const node = isObject(value) ? value : Object.assign(new yaml.Scalar(value), { type: lines ? 'QUOTE_DOUBLE' : type });
|
|
643
|
+
const text = yaml.stringify(flow ? [node] : { _: node }, {
|
|
644
|
+
...yaml_options_1.YAML_OPTIONS,
|
|
645
|
+
compat: 'yaml-1.1',
|
|
646
|
+
indent: u.step,
|
|
647
|
+
lineWidth: 0,
|
|
648
|
+
doubleQuotedMinMultiLineLength: flow ? Infinity : undefined,
|
|
649
|
+
flowCollectionPadding: false,
|
|
650
|
+
collectionStyle: flow ? 'flow' : 'any',
|
|
651
|
+
});
|
|
652
|
+
// A block scalar that keeps its final line breaks would take the blank
|
|
653
|
+
// lines after it, so such a value is double-quoted.
|
|
654
|
+
if (!flow && /^_: [|>][0-9]*\+/.test(text)) {
|
|
655
|
+
return scalarText(u, value, col, flow, 'QUOTE_DOUBLE');
|
|
656
|
+
}
|
|
657
|
+
// A block scalar's indentation indicator, which yaml writes as 2, is the
|
|
658
|
+
// file's.
|
|
659
|
+
return (flow ? text.slice(1, -2) :
|
|
660
|
+
text.slice(3, -1).replace(/^([|>])2/, `$1${u.step}`))
|
|
661
|
+
.split('\n')
|
|
662
|
+
.map((line, i) => i > 0 && line ? ' '.repeat(col) + line : line)
|
|
663
|
+
.join(u.nl);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* The indentation of the file's first nested mapping, list and list item, and
|
|
667
|
+
* the space after the bracket of its first flow mapping and flow list whose
|
|
668
|
+
* first item is on the bracket's line.
|
|
669
|
+
*/
|
|
670
|
+
function indents(doc, text) {
|
|
671
|
+
const col = (p) => p - lineStart(text, p);
|
|
672
|
+
const found = {};
|
|
673
|
+
const pad = {};
|
|
674
|
+
let itemAlone;
|
|
675
|
+
yaml.visit(doc, {
|
|
676
|
+
Collection: (_, v) => {
|
|
677
|
+
const first = v.items[0];
|
|
678
|
+
const at = (yaml.isPair(first) ? first.key : first);
|
|
679
|
+
const gap = v.flow && v.range && at?.range ?
|
|
680
|
+
text.slice(v.range[0] + 1, at.range[0]) :
|
|
681
|
+
'\n';
|
|
682
|
+
if (!gap.includes('\n'))
|
|
683
|
+
pad[yaml.isMap(v) ? 'map' : 'seq'] ??= gap;
|
|
684
|
+
},
|
|
685
|
+
Pair: (_, { key, value: v }) => {
|
|
686
|
+
if (!yaml.isNode(key) || !yaml.isCollection(v) || v.flow ||
|
|
687
|
+
v.items.length === 0) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const first = v.items[0];
|
|
691
|
+
const at = yaml.isPair(first) ? first.key.range[0] :
|
|
692
|
+
dashes(v)[0];
|
|
693
|
+
found[yaml.isMap(v) ? 'step' : 'seqIndent'] ??=
|
|
694
|
+
col(at) - col(key.range[0]);
|
|
695
|
+
if (yaml.isMap(first) && !first.flow && first.items[0]) {
|
|
696
|
+
const firstKey = first.items[0].key.range[0];
|
|
697
|
+
found.itemIndent ??= col(firstKey) - col(at);
|
|
698
|
+
itemAlone ??= lineStart(text, firstKey) !== lineStart(text, at);
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
return {
|
|
703
|
+
step: 2, seqIndent: 2, itemIndent: 2, ...found,
|
|
704
|
+
itemAlone: itemAlone ?? false, pad: { map: '', seq: '', ...pad },
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
/** The offset of each `-` of a block list. */
|
|
708
|
+
function dashes(node) {
|
|
709
|
+
return (node.srcToken?.items ?? [])
|
|
710
|
+
.flatMap(it => it.start.filter(s => s.type === 'seq-item-ind'))
|
|
711
|
+
.map(s => s.offset);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Where the text of `node` starts and ends, and the text that a value written
|
|
715
|
+
* there starts with. An empty scalar, such as the value of `a: # c`, starts
|
|
716
|
+
* right after the `:`, `-`, anchor or tag before it, and also takes the spaces
|
|
717
|
+
* after that when no comment follows. A value written there starts with a
|
|
718
|
+
* space.
|
|
719
|
+
*/
|
|
720
|
+
function rangeOf(u, node) {
|
|
721
|
+
const [from, to] = node.range;
|
|
722
|
+
if (from !== to || !yaml.isScalar(node))
|
|
723
|
+
return [from, to, ''];
|
|
724
|
+
let p = from;
|
|
725
|
+
while (p > 0 && /[ \t]/.test(u.text[p - 1]))
|
|
726
|
+
p--;
|
|
727
|
+
return [p, u.text[from] === '#' ? p : from, ' '];
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* The text on its line after the header of the block scalar at `from`, or
|
|
731
|
+
* undefined when no block scalar starts there.
|
|
732
|
+
*/
|
|
733
|
+
function headerTail(t, from) {
|
|
734
|
+
return /^[|>][-+0-9]*(.*)/.exec(t.slice(from, lineEnd(t, from)))?.[1];
|
|
735
|
+
}
|
|
736
|
+
function lineStart(t, p) {
|
|
737
|
+
return t.lastIndexOf('\n', p - 1) + 1;
|
|
738
|
+
}
|
|
739
|
+
/** The offset where the text of the line holding `p` ends. */
|
|
740
|
+
function lineEnd(t, p) {
|
|
741
|
+
const i = t.indexOf('\n', p);
|
|
742
|
+
const e = i < 0 ? t.length : i;
|
|
743
|
+
return t[e - 1] === '\r' ? e - 1 : e;
|
|
744
|
+
}
|
|
745
|
+
/** The offset of the line after the one holding `p`. */
|
|
746
|
+
function nextLine(t, p) {
|
|
747
|
+
return t.indexOf('\n', p) + 1 || t.length;
|
|
748
|
+
}
|
|
749
|
+
/** The offset of the first line from `p` that is not blank, up to `limit`. */
|
|
750
|
+
function skipBlank(t, p, limit) {
|
|
751
|
+
while (p < limit && t.slice(p, nextLine(t, p)).trim() === '') {
|
|
752
|
+
p = nextLine(t, p);
|
|
753
|
+
}
|
|
754
|
+
return p;
|
|
755
|
+
}
|
|
756
|
+
function keyOf(pair) {
|
|
757
|
+
const key = yaml.isScalar(pair.key) ? pair.key.value : pair.key;
|
|
758
|
+
return key === null ? '' : String(key);
|
|
759
|
+
}
|
|
760
|
+
function entriesOf(value) {
|
|
761
|
+
return Array.isArray(value) ?
|
|
762
|
+
value.map((v, i) => [i, v]) :
|
|
763
|
+
Object.entries(value).filter(([, v]) => v !== undefined);
|
|
764
|
+
}
|
|
765
|
+
function isFull(value) {
|
|
766
|
+
return isObject(value) && entriesOf(value).length > 0;
|
|
767
|
+
}
|
|
768
|
+
function itemKey(value) {
|
|
769
|
+
return (0, loader_1.isRecord)(value) && typeof value.name === 'string' ?
|
|
770
|
+
`name:${value.name}` :
|
|
771
|
+
canonical(value);
|
|
772
|
+
}
|
|
773
|
+
/** `value` as JSON text with each mapping's keys sorted. */
|
|
774
|
+
function canonical(value) {
|
|
775
|
+
const sorted = (_, v) => (0, loader_1.isRecord)(v) ?
|
|
776
|
+
Object.fromEntries(Object.entries(v).sort(([a], [b]) => a < b ? -1 : 1)) :
|
|
777
|
+
v;
|
|
778
|
+
return JSON.stringify(value, sorted) ?? 'undefined';
|
|
779
|
+
}
|
|
780
|
+
function isObject(value) {
|
|
781
|
+
return typeof value === 'object' && value !== null;
|
|
782
|
+
}
|