@tikoci/centrs 0.1.4 → 0.1.7
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/CHANGELOG.md +417 -1
- package/GLOSSARY.txt +40 -1
- package/README.md +16 -8
- package/commands/AGENTS.md +7 -4
- package/commands/api/AGENTS.md +13 -1
- package/commands/api/README.md +50 -10
- package/commands/api/examples.md +100 -2
- package/commands/devices/README.md +10 -1
- package/commands/devices/examples.md +33 -2
- package/commands/execute/README.md +68 -25
- package/commands/execute/examples.md +168 -7
- package/commands/explain/README.md +1206 -155
- package/commands/explain/examples.md +99 -3
- package/commands/mcp/README.md +9 -5
- package/commands/mcp/examples.md +2 -2
- package/commands/retrieve/examples.md +20 -0
- package/commands/terminal/examples.md +7 -1
- package/docs/CLI.md +3 -3
- package/docs/CONSTITUTION.md +83 -21
- package/docs/MATRIX.md +89 -52
- package/docs/errors/cdb/create-failed.md +20 -0
- package/docs/errors/cdb/file-exists.md +23 -0
- package/docs/errors/input/invalid-target.md +25 -3
- package/docs/errors/routeros/invalid-value.md +3 -2
- package/docs/errors/transport/cancel-unacknowledged.md +24 -0
- package/docs/errors/validation/syntax.md +29 -4
- package/package.json +13 -2
- package/src/api-fanout.ts +17 -4
- package/src/api.ts +346 -75
- package/src/cli/api.ts +1 -1
- package/src/cli/btest.ts +1 -1
- package/src/cli/devices.ts +16 -3
- package/src/cli/explain.ts +1 -1
- package/src/cli/terminal.ts +1 -1
- package/src/core/envelope.ts +53 -0
- package/src/core/error-catalog.ts +16 -1
- package/src/core/execute-command.ts +455 -0
- package/src/core/inspect.ts +58 -26
- package/src/core/routeros-errors.ts +66 -0
- package/src/core/version.ts +4 -0
- package/src/core/yaml.ts +86 -0
- package/src/data/winbox-cdb-write.ts +22 -8
- package/src/devices.ts +142 -28
- package/src/execute-fanout.ts +19 -5
- package/src/execute.ts +247 -295
- package/src/explain/arg-tokens.ts +62 -25
- package/src/explain/args.ts +398 -52
- package/src/explain/blocks.ts +54 -11
- package/src/explain/brace-tokens.ts +4 -3
- package/src/explain/catalog.ts +42 -44
- package/src/explain/coordinates.ts +159 -0
- package/src/explain/defects.ts +2 -1
- package/src/explain/escape-tokens.ts +77 -0
- package/src/explain/highlight-projection.ts +308 -0
- package/src/explain/menus.ts +9 -5
- package/src/explain/operator-tokens.ts +13 -7
- package/src/explain/operators.ts +27 -20
- package/src/explain/path-tokens.ts +1 -1
- package/src/explain/pathresolve.ts +358 -113
- package/src/explain/quoted-string.ts +110 -20
- package/src/explain/scope-brace.ts +175 -22
- package/src/explain/segment.ts +429 -21
- package/src/explain/separator.ts +187 -0
- package/src/explain/split-owner.ts +194 -0
- package/src/explain/string-tokens.ts +14 -4
- package/src/explain/symbol-values.ts +209 -173
- package/src/explain/symbols.ts +21 -1
- package/src/explain/transport.ts +17 -0
- package/src/explain/value-tokens.ts +15 -12
- package/src/explain/verbsplit.ts +165 -23
- package/src/explain/write.ts +47 -6
- package/src/explain.ts +522 -72
- package/src/index.ts +21 -2
- package/src/mcp/server.ts +2 -1
- package/src/mcp/tools.ts +1 -1
- package/src/offline-gate.ts +148 -0
- package/src/protocols/adapter.ts +12 -1
- package/src/protocols/mac-telnet-console.ts +6 -4
- package/src/protocols/native-api.ts +29 -3
- package/src/resolver/target.ts +32 -12
- package/src/retrieve-fanout.ts +1 -1
- package/src/retrieve.ts +18 -63
- package/src/settings.ts +2 -1
- package/src/transfer-fanout.ts +1 -1
- package/src/transfer.ts +1 -1
package/src/explain/blocks.ts
CHANGED
|
@@ -28,10 +28,16 @@
|
|
|
28
28
|
import {
|
|
29
29
|
DIRECTIVE_BODY,
|
|
30
30
|
HEAD_SCOPED_ARG_NAMES,
|
|
31
|
+
matchingBraceEnd,
|
|
31
32
|
SCOPE_ARG_NAMES,
|
|
32
33
|
scopeNameFromMasked,
|
|
33
34
|
} from "./scope-brace.ts";
|
|
34
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
documentRange,
|
|
37
|
+
type MaskedRange,
|
|
38
|
+
maskComments,
|
|
39
|
+
scanQuotedString,
|
|
40
|
+
} from "./segment.ts";
|
|
35
41
|
|
|
36
42
|
export { DIRECTIVE_BODY, HEAD_SCOPED_ARG_NAMES, SCOPE_ARG_NAMES };
|
|
37
43
|
/** One depth-0 scope block found in a statement: its name and raw body text. */
|
|
@@ -64,6 +70,22 @@ export function isScopeBrace(text: string, open: number): boolean {
|
|
|
64
70
|
return scopeNameAt(text, open) !== null;
|
|
65
71
|
}
|
|
66
72
|
|
|
73
|
+
/**
|
|
74
|
+
* {@link isScopeBrace} for a reader that already holds the document's mask,
|
|
75
|
+
* with `open` relative to `range` rather than to the whole document.
|
|
76
|
+
*
|
|
77
|
+
* The string form has to mask the text it is given, and a statement's text
|
|
78
|
+
* carries its whole nested `do={…}` subtree — so asking it this question once
|
|
79
|
+
* per brace re-derived a mask over that subtree once per enclosing level
|
|
80
|
+
* (#322). Same answer: the floor is what makes a ranged reading identical to
|
|
81
|
+
* the region read on its own (`scope-brace.ts` → `scopeNameFromMasked`).
|
|
82
|
+
*/
|
|
83
|
+
export function isScopeBraceIn(range: MaskedRange, open: number): boolean {
|
|
84
|
+
return (
|
|
85
|
+
scopeNameFromMasked(range.masked, range.start + open, range.start) !== null
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
67
89
|
/**
|
|
68
90
|
* The depth-0 scope `{…}` in a statement, with names and raw body text. A
|
|
69
91
|
* single left-to-right pass (no recursion): each scope's body is returned raw
|
|
@@ -71,29 +93,46 @@ export function isScopeBrace(text: string, open: number): boolean {
|
|
|
71
93
|
* values are skipped, not descended.
|
|
72
94
|
*/
|
|
73
95
|
export function scopeBlocks(text: string): ScopeBlock[] {
|
|
96
|
+
return scopeBlocksIn(documentRange(text));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The same scan over a RANGE of a document, reading the document's own mask.
|
|
101
|
+
*
|
|
102
|
+
* `ScopeBlock.start` stays relative to the range, which is what its contract
|
|
103
|
+
* already promises ("within the text `scopeBlocks` was given") and what a
|
|
104
|
+
* caller rebasing through `Loc` needs. Every other offset here is absolute.
|
|
105
|
+
*/
|
|
106
|
+
export function scopeBlocksIn(range: MaskedRange): ScopeBlock[] {
|
|
74
107
|
// Scan a comment-masked copy for structure so a `#`-comment `}`/`[` cannot
|
|
75
108
|
// truncate a body or shift depth; slice the ORIGINAL for the body text.
|
|
76
|
-
const masked =
|
|
109
|
+
const { text, masked, start: lo, end: hi } = range;
|
|
77
110
|
const blocks: ScopeBlock[] = [];
|
|
78
111
|
let depth = 0;
|
|
79
|
-
for (let i =
|
|
112
|
+
for (let i = lo; i < hi; i++) {
|
|
80
113
|
const c = masked[i];
|
|
81
114
|
if (c === '"') {
|
|
82
|
-
i = scanQuotedString(masked, i).end - 1;
|
|
115
|
+
i = scanQuotedString(masked, i, hi).end - 1;
|
|
83
116
|
continue;
|
|
84
117
|
}
|
|
85
118
|
if (c === "[" || c === "(") depth++;
|
|
86
119
|
else if (c === "]" || c === ")") {
|
|
87
120
|
if (depth > 0) depth--;
|
|
88
121
|
} else if (c === "{") {
|
|
89
|
-
const
|
|
122
|
+
const indexedEnd = range.coordinateIdentity
|
|
123
|
+
? matchingBraceEnd(masked, i)
|
|
124
|
+
: undefined;
|
|
125
|
+
const end =
|
|
126
|
+
indexedEnd !== undefined && indexedEnd < hi
|
|
127
|
+
? indexedEnd
|
|
128
|
+
: matchBraceInMasked(masked, i, hi);
|
|
90
129
|
if (depth === 0) {
|
|
91
|
-
const name = scopeNameFromMasked(masked, i);
|
|
130
|
+
const name = scopeNameFromMasked(masked, i, lo);
|
|
92
131
|
if (name !== null)
|
|
93
132
|
blocks.push({
|
|
94
133
|
name,
|
|
95
134
|
body: text.slice(i + 1, end),
|
|
96
|
-
start: i + 1,
|
|
135
|
+
start: i + 1 - lo,
|
|
97
136
|
});
|
|
98
137
|
}
|
|
99
138
|
i = end;
|
|
@@ -109,12 +148,16 @@ export function scopeBodies(text: string): string[] {
|
|
|
109
148
|
return scopeBlocks(text).map((b) => b.body);
|
|
110
149
|
}
|
|
111
150
|
|
|
112
|
-
function matchBraceInMasked(
|
|
151
|
+
function matchBraceInMasked(
|
|
152
|
+
masked: string,
|
|
153
|
+
open: number,
|
|
154
|
+
limit: number = masked.length,
|
|
155
|
+
): number {
|
|
113
156
|
let depth = 0;
|
|
114
|
-
for (let i = open; i <
|
|
157
|
+
for (let i = open; i < limit; i++) {
|
|
115
158
|
const c = masked[i];
|
|
116
159
|
if (c === '"') {
|
|
117
|
-
i = scanQuotedString(masked, i).end - 1;
|
|
160
|
+
i = scanQuotedString(masked, i, limit).end - 1;
|
|
118
161
|
continue;
|
|
119
162
|
}
|
|
120
163
|
if (c === "{") depth++;
|
|
@@ -123,7 +166,7 @@ function matchBraceInMasked(masked: string, open: number): number {
|
|
|
123
166
|
if (depth === 0) return i;
|
|
124
167
|
}
|
|
125
168
|
}
|
|
126
|
-
return
|
|
169
|
+
return limit;
|
|
127
170
|
}
|
|
128
171
|
|
|
129
172
|
/** Index of the `}` matching the `{` at `open`, honoring strings and comments. */
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* delimiters remain unclassified (container excluded to avoid overlap). Scope
|
|
6
6
|
* braces (`do={...}` etc.) are never array values. Both families therefore
|
|
7
7
|
* appear on the residual after the `value` and `string` fills, so this fill
|
|
8
|
-
* claims every remaining `{` / `}` byte there as
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* claims every remaining `{` / `}` byte there as `brace`. #264 B5 kept scope
|
|
9
|
+
* and array delimiters in one class: `structure.blocks[]` already locates every
|
|
10
|
+
* scope block, and an array container is the `data.values.occurrences[]` entry
|
|
11
|
+
* that is some other occurrence's `parent`, so the distinction is a join away.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import type { ExplainToken } from "../explain.ts";
|
package/src/explain/catalog.ts
CHANGED
|
@@ -9,23 +9,25 @@
|
|
|
9
9
|
* Union of two first-order sources (#228):
|
|
10
10
|
*
|
|
11
11
|
* 1. MikroTik's published CLI Reference (`https://manual.mikrotik.com/docs/cli-reference/`),
|
|
12
|
-
* 1,
|
|
12
|
+
* 1,055 pages, 1,062 entries — first-order about the definition
|
|
13
13
|
* structs and their build-time gates. Since #285 every page is a leaf whose
|
|
14
14
|
* slug is the CLI path, so no spelling has to be rewritten to be looked up.
|
|
15
|
-
* 2.
|
|
15
|
+
* 2. 6 pinned restraml `/console/inspect` trees
|
|
16
16
|
* (`https://tikoci.github.io/restraml/`) — first-order about the CLI surface:
|
|
17
17
|
*
|
|
18
18
|
* | Tree | Arch | RouterOS | Nodes |
|
|
19
19
|
* | ---- | ---- | -------- | ----- |
|
|
20
20
|
* | `7.10.2/extra/inspect.json` | x86 | 7.10.2 | 29,086 |
|
|
21
21
|
* | `7.16/extra/inspect.json` | x86 | 7.16 | 34,876 |
|
|
22
|
-
* | `7.
|
|
23
|
-
* | `7.
|
|
22
|
+
* | `7.24.4/extra/deep-inspect.x86.json` | x86 | 7.24.4 | 41,345 |
|
|
23
|
+
* | `7.24.4/extra/deep-inspect.arm64.json` | arm64 | 7.24.4 | 42,691 |
|
|
24
|
+
* | `7.25beta5/extra/deep-inspect.x86.json` | x86 | 7.25beta5 | 41,794 |
|
|
25
|
+
* | `7.25beta5/extra/deep-inspect.arm64.json` | arm64 | 7.25beta5 | 43,150 |
|
|
24
26
|
*
|
|
25
27
|
* | Kind | Total | `both` | `inspect` | `published` |
|
|
26
28
|
* | ---- | ----- | ------ | --------- | ----------- |
|
|
27
|
-
* | `menu` |
|
|
28
|
-
* | `command` | 450 |
|
|
29
|
+
* | `menu` | 554 | 445 | 65 | 44 |
|
|
30
|
+
* | `command` | 450 | 409 | 0 | 41 |
|
|
29
31
|
* | `settings` | 116 | 107 | 0 | 9 |
|
|
30
32
|
*
|
|
31
33
|
* Zero navigation-vs-command contradictions between the two sources. Generation
|
|
@@ -45,7 +47,7 @@
|
|
|
45
47
|
*
|
|
46
48
|
* **Gates conjoin down a path, so read them with {@link effectiveGates}, not
|
|
47
49
|
* row by row.** A row states only what the publication stated at that entry.
|
|
48
|
-
* Read row-wise,
|
|
50
|
+
* Read row-wise, 1 published-only path looks ungated; read with
|
|
49
51
|
* ancestry, the residue carrying no published explanation for its absence at
|
|
50
52
|
* all is 1.
|
|
51
53
|
*
|
|
@@ -87,7 +89,7 @@ export interface CatalogEntry {
|
|
|
87
89
|
* `path|kind|provenance|package|conditions|syscap` — with trailing empty
|
|
88
90
|
* columns trimmed. Lower-cased, slash-led and sorted by path.
|
|
89
91
|
*
|
|
90
|
-
* Text rather than ~1,
|
|
92
|
+
* Text rather than ~1,120 object literals so that one path is one line in a
|
|
91
93
|
* review diff, and so the formatter has nothing to re-wrap.
|
|
92
94
|
*/
|
|
93
95
|
const ROWS = `
|
|
@@ -152,7 +154,7 @@ const ROWS = `
|
|
|
152
154
|
/certificate/settings|settings|both
|
|
153
155
|
/certificate/sign|command|both
|
|
154
156
|
/certificate/sign-certificate-request|command|both
|
|
155
|
-
/console|menu|
|
|
157
|
+
/console|menu|inspect
|
|
156
158
|
/console/inspect|command|both
|
|
157
159
|
/console/settings|settings|both
|
|
158
160
|
/container|menu|both|container
|
|
@@ -239,7 +241,7 @@ const ROWS = `
|
|
|
239
241
|
/interface/bonding|menu|both
|
|
240
242
|
/interface/bonding/monitor|command|both
|
|
241
243
|
/interface/bonding/monitor-slaves|command|both
|
|
242
|
-
/interface/bridge|menu|both
|
|
244
|
+
/interface/bridge|menu|both
|
|
243
245
|
/interface/bridge/calea|menu|both
|
|
244
246
|
/interface/bridge/calea/reset-counters|command|both
|
|
245
247
|
/interface/bridge/calea/reset-counters-all|command|both
|
|
@@ -249,11 +251,6 @@ const ROWS = `
|
|
|
249
251
|
/interface/bridge/host|menu|both
|
|
250
252
|
/interface/bridge/mdb|menu|both
|
|
251
253
|
/interface/bridge/monitor|command|both
|
|
252
|
-
/interface/bridge/msrp|menu|published||MSRP_ENABLE
|
|
253
|
-
/interface/bridge/msrp/attributes|menu|published||MSRP_ENABLE
|
|
254
|
-
/interface/bridge/msrp/domain|menu|published||MSRP_ENABLE
|
|
255
|
-
/interface/bridge/msrp/domain/attributes|menu|published||MSRP_ENABLE
|
|
256
|
-
/interface/bridge/msrp/domain/monitor|command|published||MSRP_ENABLE
|
|
257
254
|
/interface/bridge/msti|menu|both
|
|
258
255
|
/interface/bridge/msti/monitor|command|both
|
|
259
256
|
/interface/bridge/nat|menu|both
|
|
@@ -280,14 +277,15 @@ const ROWS = `
|
|
|
280
277
|
/interface/dot1x/server/state|menu|both||!smips
|
|
281
278
|
/interface/eoip|menu|both
|
|
282
279
|
/interface/eoipv6|menu|both
|
|
283
|
-
/interface/ethernet|menu|both
|
|
280
|
+
/interface/ethernet|menu|both
|
|
284
281
|
/interface/ethernet/blink|command|both
|
|
285
282
|
/interface/ethernet/cable-test|command|both
|
|
286
|
-
/interface/ethernet/monitor|command|both
|
|
283
|
+
/interface/ethernet/monitor|command|both
|
|
287
284
|
/interface/ethernet/poe|menu|published|||(poe or poe-in)
|
|
288
285
|
/interface/ethernet/poe/monitor|command|published|||(poe or poe-in)
|
|
289
286
|
/interface/ethernet/poe/power-cycle|command|published|||(poe or poe-in)
|
|
290
287
|
/interface/ethernet/poe/settings|settings|published|||(poe or poe-in) and poesettings
|
|
288
|
+
/interface/ethernet/pon|menu|both
|
|
291
289
|
/interface/ethernet/reset-counters|command|both
|
|
292
290
|
/interface/ethernet/reset-mac-address|command|both
|
|
293
291
|
/interface/ethernet/switch|menu|both
|
|
@@ -329,6 +327,7 @@ const ROWS = `
|
|
|
329
327
|
/interface/ethernet/switch/reserved-fdb|menu|published|||musicswitch
|
|
330
328
|
/interface/ethernet/switch/reset-counters|command|both
|
|
331
329
|
/interface/ethernet/switch/rule|menu|both|||rbswitch
|
|
330
|
+
/interface/ethernet/switch/rule/reset-counters|command|both|||rbswitch
|
|
332
331
|
/interface/ethernet/switch/shaper|menu|published|||musicswitch
|
|
333
332
|
/interface/ethernet/switch/stats|settings|published||!smips|musicswitch
|
|
334
333
|
/interface/ethernet/switch/trunk|menu|published|||musicswitch
|
|
@@ -365,7 +364,7 @@ const ROWS = `
|
|
|
365
364
|
/interface/lte/monitor|command|both||!smips
|
|
366
365
|
/interface/lte/run-modem-update|command|published||!smips
|
|
367
366
|
/interface/lte/scan|command|both||!smips
|
|
368
|
-
/interface/lte/settings|settings|both||!smips
|
|
367
|
+
/interface/lte/settings|settings|both||!smips
|
|
369
368
|
/interface/lte/show-capabilities|command|both||!smips
|
|
370
369
|
/interface/macsec|menu|both||!smips
|
|
371
370
|
/interface/macsec/monitor|command|both||!smips
|
|
@@ -418,7 +417,7 @@ const ROWS = `
|
|
|
418
417
|
/interface/sstp-server/monitor|command|both
|
|
419
418
|
/interface/sstp-server/server|settings|both
|
|
420
419
|
/interface/veth|menu|both||!smips|container
|
|
421
|
-
/interface/vlan|menu|both
|
|
420
|
+
/interface/vlan|menu|both
|
|
422
421
|
/interface/vpls|menu|both||!smips
|
|
423
422
|
/interface/vpls/monitor|command|both||!smips
|
|
424
423
|
/interface/vrrp|menu|both
|
|
@@ -531,7 +530,7 @@ const ROWS = `
|
|
|
531
530
|
/interface/wireless/wds/monitor|command|both|wireless-rep
|
|
532
531
|
/interface/wireless/wps-client|command|both|wireless-rep
|
|
533
532
|
/interface/wireless/wps-push-button|command|both|wireless-rep
|
|
534
|
-
/interface/xfrm|menu|
|
|
533
|
+
/interface/xfrm|menu|both
|
|
535
534
|
/iot|menu|inspect
|
|
536
535
|
/iot/bluetooth|menu|both|iot
|
|
537
536
|
/iot/bluetooth/advertisers|menu|both|iot
|
|
@@ -602,7 +601,7 @@ const ROWS = `
|
|
|
602
601
|
/ip/cloud/advanced|settings|both
|
|
603
602
|
/ip/cloud/back-to-home-file|menu|both|||cloud-vpn
|
|
604
603
|
/ip/cloud/back-to-home-file/settings|settings|both|||cloud-vpn
|
|
605
|
-
/ip/cloud/back-to-home-file/settings/remove-certificate|command|
|
|
604
|
+
/ip/cloud/back-to-home-file/settings/remove-certificate|command|both|||cloud-vpn
|
|
606
605
|
/ip/cloud/back-to-home-user|menu|both|||cloud-vpn
|
|
607
606
|
/ip/cloud/back-to-home-user/show-client-config|command|both|||cloud-vpn
|
|
608
607
|
/ip/cloud/force-update|command|both
|
|
@@ -674,7 +673,7 @@ const ROWS = `
|
|
|
674
673
|
/ip/hotspot/walled-garden/ip|menu|both
|
|
675
674
|
/ip/hotspot/walled-garden/reset-counters|command|both
|
|
676
675
|
/ip/hotspot/walled-garden/reset-counters-all|command|both
|
|
677
|
-
/ip/ipsec|menu|
|
|
676
|
+
/ip/ipsec|menu|inspect
|
|
678
677
|
/ip/ipsec/active-peers|menu|both
|
|
679
678
|
/ip/ipsec/active-peers/kill-connections|command|both
|
|
680
679
|
/ip/ipsec/identity|menu|both
|
|
@@ -683,7 +682,6 @@ const ROWS = `
|
|
|
683
682
|
/ip/ipsec/key|menu|both
|
|
684
683
|
/ip/ipsec/key/psk|menu|both
|
|
685
684
|
/ip/ipsec/key/psk/generate|command|both
|
|
686
|
-
/ip/ipsec/key/qkd|menu|inspect
|
|
687
685
|
/ip/ipsec/key/rsa|menu|both
|
|
688
686
|
/ip/ipsec/key/rsa/export-pub-key|command|both
|
|
689
687
|
/ip/ipsec/key/rsa/generate-key|command|both
|
|
@@ -746,6 +744,7 @@ const ROWS = `
|
|
|
746
744
|
/ip/ssh|settings|both
|
|
747
745
|
/ip/ssh/export-host-key|command|both
|
|
748
746
|
/ip/ssh/import-host-key|command|both
|
|
747
|
+
/ip/ssh/known-hosts|menu|both
|
|
749
748
|
/ip/ssh/regenerate-host-key|command|both
|
|
750
749
|
/ip/tftp|menu|both
|
|
751
750
|
/ip/tftp/settings|settings|both
|
|
@@ -850,7 +849,7 @@ const ROWS = `
|
|
|
850
849
|
/password|command|both
|
|
851
850
|
/port|menu|both
|
|
852
851
|
/port/remote-access|menu|both
|
|
853
|
-
/ppp|menu|
|
|
852
|
+
/ppp|menu|inspect
|
|
854
853
|
/ppp/aaa|settings|both
|
|
855
854
|
/ppp/active|menu|both
|
|
856
855
|
/ppp/l2tp-secret|menu|both
|
|
@@ -874,12 +873,11 @@ const ROWS = `
|
|
|
874
873
|
/radius/monitor|command|both
|
|
875
874
|
/radius/reset-counters|command|both
|
|
876
875
|
/redo|command|both
|
|
877
|
-
/root|menu|published||CONSOLE_DEBUG
|
|
878
876
|
/root/terminal|menu|published
|
|
879
877
|
/routing|menu|inspect
|
|
880
|
-
/routing/bfd|menu|
|
|
878
|
+
/routing/bfd|menu|inspect
|
|
881
879
|
/routing/bfd/authentication|menu|published||BFD_AUTHENTICATION
|
|
882
|
-
/routing/bfd/configuration|menu|both
|
|
880
|
+
/routing/bfd/configuration|menu|both
|
|
883
881
|
/routing/bfd/session|menu|both
|
|
884
882
|
/routing/bgp|menu|both||!smips
|
|
885
883
|
/routing/bgp/advertisements|menu|both||!smips
|
|
@@ -897,7 +895,7 @@ const ROWS = `
|
|
|
897
895
|
/routing/bgp/vpn|menu|both||!smips
|
|
898
896
|
/routing/discourse|command|both
|
|
899
897
|
/routing/fantasy|menu|both
|
|
900
|
-
/routing/filter|menu|
|
|
898
|
+
/routing/filter|menu|inspect
|
|
901
899
|
/routing/filter/chain|menu|both
|
|
902
900
|
/routing/filter/community-ext-list|menu|both
|
|
903
901
|
/routing/filter/community-large-list|menu|both
|
|
@@ -913,7 +911,7 @@ const ROWS = `
|
|
|
913
911
|
/routing/igmp-proxy|settings|both
|
|
914
912
|
/routing/igmp-proxy/interface|menu|both
|
|
915
913
|
/routing/igmp-proxy/mfc|menu|both
|
|
916
|
-
/routing/isis|menu|
|
|
914
|
+
/routing/isis|menu|inspect
|
|
917
915
|
/routing/isis/instance|menu|both
|
|
918
916
|
/routing/isis/interface|menu|both
|
|
919
917
|
/routing/isis/interface-template|menu|both
|
|
@@ -921,7 +919,7 @@ const ROWS = `
|
|
|
921
919
|
/routing/isis/neighbor|menu|both
|
|
922
920
|
/routing/nexthop|menu|both
|
|
923
921
|
/routing/nexthop/dump-dot|command|both
|
|
924
|
-
/routing/ospf|menu|
|
|
922
|
+
/routing/ospf|menu|inspect
|
|
925
923
|
/routing/ospf/area|menu|both
|
|
926
924
|
/routing/ospf/area/range|menu|both
|
|
927
925
|
/routing/ospf/instance|menu|both
|
|
@@ -943,7 +941,7 @@ const ROWS = `
|
|
|
943
941
|
/routing/pimsm/static-rp|menu|both||!smips
|
|
944
942
|
/routing/pimsm/uib-g|menu|both||!smips
|
|
945
943
|
/routing/pimsm/uib-sg|menu|both||!smips
|
|
946
|
-
/routing/rip|menu|
|
|
944
|
+
/routing/rip|menu|inspect
|
|
947
945
|
/routing/rip/instance|menu|both
|
|
948
946
|
/routing/rip/interface|menu|both
|
|
949
947
|
/routing/rip/interface-template|menu|both
|
|
@@ -958,7 +956,7 @@ const ROWS = `
|
|
|
958
956
|
/routing/rpki/session|menu|both
|
|
959
957
|
/routing/rule|menu|both
|
|
960
958
|
/routing/settings|settings|both
|
|
961
|
-
/routing/stats|menu|
|
|
959
|
+
/routing/stats|menu|inspect
|
|
962
960
|
/routing/stats/memory|menu|both
|
|
963
961
|
/routing/stats/origin|menu|both
|
|
964
962
|
/routing/stats/pcap|menu|both||!smips
|
|
@@ -973,7 +971,7 @@ const ROWS = `
|
|
|
973
971
|
/snmp/send-trap|command|both
|
|
974
972
|
/special-login|menu|both
|
|
975
973
|
/system|menu|inspect
|
|
976
|
-
/system/backup|menu|
|
|
974
|
+
/system/backup|menu|inspect
|
|
977
975
|
/system/backup/cloud|menu|both
|
|
978
976
|
/system/backup/cloud/download-file|command|both
|
|
979
977
|
/system/backup/cloud/remove-file|command|both
|
|
@@ -993,11 +991,11 @@ const ROWS = `
|
|
|
993
991
|
/system/default-configuration/wps-sync-mode-script|settings|published|||wpssync
|
|
994
992
|
/system/device-mode|settings|both
|
|
995
993
|
/system/device-mode/update|command|both
|
|
996
|
-
/system/gps|settings|both|gps
|
|
994
|
+
/system/gps|settings|both|gps
|
|
997
995
|
/system/gps/monitor|command|both|gps
|
|
998
996
|
/system/hardware|menu|inspect
|
|
999
|
-
/system/health|menu|both
|
|
1000
|
-
/system/health/settings|settings|both||!i386
|
|
997
|
+
/system/health|menu|both
|
|
998
|
+
/system/health/settings|settings|both||!i386|health and health-settings
|
|
1001
999
|
/system/health/settings/detect-fans|command|both||!i386|health and health-settings
|
|
1002
1000
|
/system/history|menu|both
|
|
1003
1001
|
/system/identity|settings|both
|
|
@@ -1015,7 +1013,7 @@ const ROWS = `
|
|
|
1015
1013
|
/system/logging/action|menu|both
|
|
1016
1014
|
/system/logging/action/clear|command|both
|
|
1017
1015
|
/system/note|settings|both
|
|
1018
|
-
/system/ntp|menu|
|
|
1016
|
+
/system/ntp|menu|inspect
|
|
1019
1017
|
/system/ntp/client|settings|both
|
|
1020
1018
|
/system/ntp/client/reset-freq-drift|command|both
|
|
1021
1019
|
/system/ntp/client/servers|menu|both
|
|
@@ -1048,9 +1046,9 @@ const ROWS = `
|
|
|
1048
1046
|
/system/reboot|command|both
|
|
1049
1047
|
/system/regulatory|settings|both
|
|
1050
1048
|
/system/reset-configuration|command|both
|
|
1051
|
-
/system/resource|settings|both
|
|
1049
|
+
/system/resource|settings|both
|
|
1052
1050
|
/system/resource/cpu|menu|both
|
|
1053
|
-
/system/resource/hardware|menu|both||!powerpc, !smips
|
|
1051
|
+
/system/resource/hardware|menu|both||!powerpc, !smips
|
|
1054
1052
|
/system/resource/hardware/authorize|command|both||!powerpc, !smips
|
|
1055
1053
|
/system/resource/hardware/usb-power-reset|command|both||!powerpc, !smips, i386
|
|
1056
1054
|
/system/resource/hardware/usb-settings|settings|both||!powerpc, !smips
|
|
@@ -1060,10 +1058,10 @@ const ROWS = `
|
|
|
1060
1058
|
/system/resource/pci|menu|inspect
|
|
1061
1059
|
/system/resource/usb|menu|inspect
|
|
1062
1060
|
/system/resource/usb/settings|menu|inspect
|
|
1063
|
-
/system/routerboard|settings|both||!i386
|
|
1061
|
+
/system/routerboard|settings|both||!i386
|
|
1064
1062
|
/system/routerboard/mode-button|settings|both||!i386
|
|
1065
1063
|
/system/routerboard/reset-button|settings|both||!i386
|
|
1066
|
-
/system/routerboard/settings|settings|both||!i386
|
|
1064
|
+
/system/routerboard/settings|settings|both||!i386
|
|
1067
1065
|
/system/routerboard/settings/keep-frequency|command|published||!i386, mipsel
|
|
1068
1066
|
/system/routerboard/upgrade|command|both||!i386
|
|
1069
1067
|
/system/routerboard/usb|settings|both||!i386, !i386, !mipsel, !powerpc
|
|
@@ -1110,7 +1108,7 @@ const ROWS = `
|
|
|
1110
1108
|
/tool/dns-update|command|both
|
|
1111
1109
|
/tool/e-mail|settings|both
|
|
1112
1110
|
/tool/e-mail/send|command|both
|
|
1113
|
-
/tool/fetch|command|both
|
|
1111
|
+
/tool/fetch|command|both
|
|
1114
1112
|
/tool/flood-ping|command|both||!smips
|
|
1115
1113
|
/tool/graphing|settings|both
|
|
1116
1114
|
/tool/graphing/interface|menu|both
|
|
@@ -1161,7 +1159,7 @@ const ROWS = `
|
|
|
1161
1159
|
/tool/traffic-generator/quick|command|both
|
|
1162
1160
|
/tool/traffic-generator/raw-packet-template|menu|both
|
|
1163
1161
|
/tool/traffic-generator/start|command|both
|
|
1164
|
-
/tool/traffic-generator/stats|menu|
|
|
1162
|
+
/tool/traffic-generator/stats|menu|inspect
|
|
1165
1163
|
/tool/traffic-generator/stats/latency-distribution|menu|both
|
|
1166
1164
|
/tool/traffic-generator/stats/port|menu|both
|
|
1167
1165
|
/tool/traffic-generator/stats/raw|menu|both
|
|
@@ -1320,7 +1318,7 @@ export interface CatalogGate {
|
|
|
1320
1318
|
* parent's `syscap` applies even though the child entry states none: the gates
|
|
1321
1319
|
* up a path CONJOIN, they do not override.
|
|
1322
1320
|
*
|
|
1323
|
-
* Read row-wise,
|
|
1321
|
+
* Read row-wise, 1 published-only path looks ungated; read with
|
|
1324
1322
|
* ancestry the residue is 1. The gap between the two is the
|
|
1325
1323
|
* conjunction, and it is what a caller has to reproduce: a child's silence about
|
|
1326
1324
|
* a gate is not the absence of one. #228's finding — that a published-only path
|
|
@@ -87,9 +87,31 @@ export interface CoordinateAnalysis {
|
|
|
87
87
|
/** Pure-ASCII surface; `analyzed.length === originalU8.length`. */
|
|
88
88
|
analyzed: Uint8Array;
|
|
89
89
|
originalU8: Uint8Array;
|
|
90
|
+
/**
|
|
91
|
+
* One run per code point.
|
|
92
|
+
*
|
|
93
|
+
* On the {@link CoordinateAnalysis.ascii} path this array is built ON FIRST
|
|
94
|
+
* READ, not by `analyzeCoordinates`: every caller that only needs an offset
|
|
95
|
+
* or a position can answer from arithmetic there, and materializing one
|
|
96
|
+
* object per character was the single largest allocation in the analyzer
|
|
97
|
+
* (#322). Reading it is always sound — the contract is the array, and the
|
|
98
|
+
* lazy path builds the same one the eager path would have.
|
|
99
|
+
*/
|
|
90
100
|
runs: CharRun[];
|
|
91
101
|
/** Byte offset of each line's first character (index === 0-based line). */
|
|
92
102
|
lineStarts: number[];
|
|
103
|
+
/**
|
|
104
|
+
* True when every code point of `original` is ASCII, so `analyzed`,
|
|
105
|
+
* `originalU8` and `original` agree byte for byte and a byte offset IS a
|
|
106
|
+
* UTF-16 offset.
|
|
107
|
+
*
|
|
108
|
+
* This is the property the three coordinate spaces were defined to make
|
|
109
|
+
* checkable, and it is what lets a caller skip the mapping rather than
|
|
110
|
+
* re-derive an identity one. It is a FACT about the input, never a mode: the
|
|
111
|
+
* non-ASCII path below is unchanged and stays the only reading of a
|
|
112
|
+
* non-ASCII document.
|
|
113
|
+
*/
|
|
114
|
+
ascii: boolean;
|
|
93
115
|
}
|
|
94
116
|
|
|
95
117
|
/** UTF-8 byte length of a Unicode scalar value. */
|
|
@@ -114,12 +136,34 @@ function utf8Encode(cp: number): number[] {
|
|
|
114
136
|
];
|
|
115
137
|
}
|
|
116
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Any code point outside ASCII.
|
|
141
|
+
*
|
|
142
|
+
* `u`-mode, so the test is over CODE POINTS: an astral character is one match
|
|
143
|
+
* rather than two halves, and a lone surrogate — which is not a scalar value
|
|
144
|
+
* and therefore not ASCII either — matches as itself. That is the same
|
|
145
|
+
* population the walk below normalizes, so the two cannot disagree about what
|
|
146
|
+
* "all ASCII" means.
|
|
147
|
+
*/
|
|
148
|
+
const NON_ASCII = /[^\p{ASCII}]/u;
|
|
149
|
+
|
|
117
150
|
/**
|
|
118
151
|
* Build the coordinate analysis of `original`: its UTF-8 encoding, the
|
|
119
152
|
* byte-count-preserving `analyzed` surface, and one {@link CharRun} per code
|
|
120
153
|
* point locating it in all three spaces.
|
|
154
|
+
*
|
|
155
|
+
* An ALL-ASCII input takes the identity path. Nothing about the contract
|
|
156
|
+
* changes there — the three spaces coincide, which is what the
|
|
157
|
+
* byte-count-preserving rule above was chosen to guarantee — so the mapping is
|
|
158
|
+
* arithmetic and the per-character {@link CharRun} array is deferred to its
|
|
159
|
+
* first reader. The analyzer re-derived that map for every nested block body it
|
|
160
|
+
* descended into, and `pathresolve`'s own `locate` discards the coordinate
|
|
161
|
+
* facts of a body anyway (they are properties of the root input): on a 60 KiB
|
|
162
|
+
* document nested 64 deep it decoded 7.9 MiB and allocated a run object per
|
|
163
|
+
* byte of it, a quarter of total analysis time (#322).
|
|
121
164
|
*/
|
|
122
165
|
export function analyzeCoordinates(original: string): CoordinateAnalysis {
|
|
166
|
+
if (!NON_ASCII.test(original)) return asciiCoordinates(original);
|
|
123
167
|
const runs: CharRun[] = [];
|
|
124
168
|
const u8: number[] = [];
|
|
125
169
|
const analyzed: number[] = [];
|
|
@@ -175,9 +219,78 @@ export function analyzeCoordinates(original: string): CoordinateAnalysis {
|
|
|
175
219
|
originalU8: Uint8Array.from(u8),
|
|
176
220
|
runs,
|
|
177
221
|
lineStarts,
|
|
222
|
+
ascii: false,
|
|
178
223
|
};
|
|
179
224
|
}
|
|
180
225
|
|
|
226
|
+
/**
|
|
227
|
+
* The identity analysis of an all-ASCII input.
|
|
228
|
+
*
|
|
229
|
+
* One byte per code unit, so `analyzed`, `originalU8` and `original` are the
|
|
230
|
+
* same sequence and every offset is the same number in all three spaces. Only
|
|
231
|
+
* `lineStarts` needs a scan; `runs` is built on demand by {@link asciiRuns}
|
|
232
|
+
* because most callers never ask for it.
|
|
233
|
+
*/
|
|
234
|
+
function asciiCoordinates(original: string): CoordinateAnalysis {
|
|
235
|
+
const len = original.length;
|
|
236
|
+
const u8 = new Uint8Array(len);
|
|
237
|
+
const lineStarts = [0];
|
|
238
|
+
for (let i = 0; i < len; i++) {
|
|
239
|
+
const code = original.charCodeAt(i);
|
|
240
|
+
u8[i] = code;
|
|
241
|
+
if (code === 0x0a) lineStarts.push(i + 1);
|
|
242
|
+
}
|
|
243
|
+
let runs: CharRun[] | undefined;
|
|
244
|
+
return {
|
|
245
|
+
original,
|
|
246
|
+
// `analyzed` and `originalU8` are equal by definition here, but they stay
|
|
247
|
+
// separate arrays: they are separately typed, separately indexed surfaces
|
|
248
|
+
// and a shared buffer would let a write through one be read through the
|
|
249
|
+
// other.
|
|
250
|
+
analyzed: u8,
|
|
251
|
+
originalU8: u8.slice(),
|
|
252
|
+
get runs(): CharRun[] {
|
|
253
|
+
runs ??= asciiRuns(original, lineStarts);
|
|
254
|
+
return runs;
|
|
255
|
+
},
|
|
256
|
+
lineStarts,
|
|
257
|
+
ascii: true,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The per-character runs of an all-ASCII input, built on first read. */
|
|
262
|
+
function asciiRuns(original: string, lineStarts: readonly number[]): CharRun[] {
|
|
263
|
+
const runs: CharRun[] = new Array(original.length);
|
|
264
|
+
let line = 0;
|
|
265
|
+
for (let i = 0; i < original.length; i++) {
|
|
266
|
+
const codePoint = original.charCodeAt(i);
|
|
267
|
+
runs[i] = {
|
|
268
|
+
codePoint,
|
|
269
|
+
utf16Start: i,
|
|
270
|
+
utf16Len: 1,
|
|
271
|
+
byteStart: i,
|
|
272
|
+
byteLen: 1,
|
|
273
|
+
ascii: true,
|
|
274
|
+
line,
|
|
275
|
+
col: i - (lineStarts[line] as number),
|
|
276
|
+
};
|
|
277
|
+
if (codePoint === 0x0a) line++;
|
|
278
|
+
}
|
|
279
|
+
return runs;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** 0-based line containing `byte` — the last line start at or before it. */
|
|
283
|
+
function lineAtByte(lineStarts: readonly number[], byte: number): number {
|
|
284
|
+
let lo = 0;
|
|
285
|
+
let hi = lineStarts.length - 1;
|
|
286
|
+
while (lo < hi) {
|
|
287
|
+
const mid = (lo + hi + 1) >> 1;
|
|
288
|
+
if ((lineStarts[mid] as number) <= byte) lo = mid;
|
|
289
|
+
else hi = mid - 1;
|
|
290
|
+
}
|
|
291
|
+
return lo;
|
|
292
|
+
}
|
|
293
|
+
|
|
181
294
|
/**
|
|
182
295
|
* The {@link CharRun} whose byte range contains `byte`. Throws if `byte` is not
|
|
183
296
|
* inside any run (i.e. `byte < 0` or `byte >= analyzed.length`).
|
|
@@ -188,6 +301,21 @@ export function runAtByte(a: CoordinateAnalysis, byte: number): CharRun {
|
|
|
188
301
|
throw new Error(
|
|
189
302
|
`runAtByte: ${byte} is not a valid interior byte offset; expected an integer in [0, ${len})`,
|
|
190
303
|
);
|
|
304
|
+
// On the identity path the run IS the byte, so answer it rather than build
|
|
305
|
+
// (and then binary-search) an array of one object per character.
|
|
306
|
+
if (a.ascii) {
|
|
307
|
+
const line = lineAtByte(a.lineStarts, byte);
|
|
308
|
+
return {
|
|
309
|
+
codePoint: a.original.charCodeAt(byte),
|
|
310
|
+
utf16Start: byte,
|
|
311
|
+
utf16Len: 1,
|
|
312
|
+
byteStart: byte,
|
|
313
|
+
byteLen: 1,
|
|
314
|
+
ascii: true,
|
|
315
|
+
line,
|
|
316
|
+
col: byte - (a.lineStarts[line] as number),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
191
319
|
let lo = 0;
|
|
192
320
|
let hi = a.runs.length - 1;
|
|
193
321
|
while (lo <= hi) {
|
|
@@ -214,6 +342,12 @@ export function byteToPosition(a: CoordinateAnalysis, byte: number): Position {
|
|
|
214
342
|
`byteToPosition: ${byte} is not a valid cursor offset; expected an integer in [0, ${len}]`,
|
|
215
343
|
);
|
|
216
344
|
if (byte === len) {
|
|
345
|
+
// The identity path reads the same answer off `lineStarts`: a trailing
|
|
346
|
+
// newline has already pushed the line that the cursor sits at column 0 of.
|
|
347
|
+
if (a.ascii) {
|
|
348
|
+
const line = a.lineStarts.length - 1;
|
|
349
|
+
return { line, col: len - (a.lineStarts[line] as number) };
|
|
350
|
+
}
|
|
217
351
|
const last = a.runs.at(-1);
|
|
218
352
|
if (!last) return { line: 0, col: 0 };
|
|
219
353
|
if (last.codePoint === 0x0a) return { line: last.line + 1, col: 0 };
|
|
@@ -264,6 +398,9 @@ export function byteSpanToRange(
|
|
|
264
398
|
* as part of its `non-ascii` run like any other character.
|
|
265
399
|
*/
|
|
266
400
|
export function coordinateDefects(a: CoordinateAnalysis): Defect[] {
|
|
401
|
+
// Both classes are non-ASCII code points by definition (U+FEFF included), so
|
|
402
|
+
// an all-ASCII input has neither and the runs never need building.
|
|
403
|
+
if (a.ascii) return [];
|
|
267
404
|
const defects: Defect[] = [];
|
|
268
405
|
const first = a.runs[0];
|
|
269
406
|
const hasBom = first !== undefined && first.codePoint === BOM;
|
|
@@ -308,6 +445,28 @@ export function positionToByte(
|
|
|
308
445
|
throw new Error(
|
|
309
446
|
`positionToByte: (line ${line}, col ${col}) is not a valid position; expected non-negative integers`,
|
|
310
447
|
);
|
|
448
|
+
// On the identity path a position is arithmetic: the column IS the byte
|
|
449
|
+
// offset from the line start.
|
|
450
|
+
//
|
|
451
|
+
// The bound is the byte one PAST the line's last character, and on a line
|
|
452
|
+
// that ends in a newline that last character is the newline — so the bound
|
|
453
|
+
// is the next line's start, not one before it. That is the boundary the scan
|
|
454
|
+
// below reaches through `lastOnLine`, whose last run on the line is the
|
|
455
|
+
// newline itself; getting it wrong refused (line 0, col 2) of `a\n\nb`,
|
|
456
|
+
// which the scan answers with byte 2.
|
|
457
|
+
if (a.ascii) {
|
|
458
|
+
if (line >= a.lineStarts.length)
|
|
459
|
+
throw new Error(
|
|
460
|
+
`positionToByte: no character boundary at (line ${line}, col ${col})`,
|
|
461
|
+
);
|
|
462
|
+
const start = a.lineStarts[line] as number;
|
|
463
|
+
const end = (a.lineStarts[line + 1] ?? a.original.length) as number;
|
|
464
|
+
if (start + col > end)
|
|
465
|
+
throw new Error(
|
|
466
|
+
`positionToByte: no character boundary at (line ${line}, col ${col})`,
|
|
467
|
+
);
|
|
468
|
+
return start + col;
|
|
469
|
+
}
|
|
311
470
|
// Single pass: return an exact (line, col) hit immediately; otherwise keep
|
|
312
471
|
// the last run seen on the target line to resolve a just-past-the-end column.
|
|
313
472
|
let lastOnLine: CharRun | undefined;
|