@graphty/graph-io 0.0.0 → 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 +21 -0
- package/README.md +250 -28
- package/dist/chunks/children-CL3Cy0ez.js +238 -0
- package/dist/chunks/children-CL3Cy0ez.js.map +1 -0
- package/dist/chunks/escape-DyI8JofU.js +938 -0
- package/dist/chunks/escape-DyI8JofU.js.map +1 -0
- package/dist/chunks/importer-CQnJuWJw.js +2987 -0
- package/dist/chunks/importer-CQnJuWJw.js.map +1 -0
- package/dist/chunks/importer-CpCpfbxr.js +2015 -0
- package/dist/chunks/importer-CpCpfbxr.js.map +1 -0
- package/dist/chunks/importer-DbnGYr3_.js +2342 -0
- package/dist/chunks/importer-DbnGYr3_.js.map +1 -0
- package/dist/chunks/importer-GozH8DkN.js +3050 -0
- package/dist/chunks/importer-GozH8DkN.js.map +1 -0
- package/dist/chunks/records-CGpxszm1.js +605 -0
- package/dist/chunks/records-CGpxszm1.js.map +1 -0
- package/dist/chunks/text-CajMdVFy.js +189 -0
- package/dist/chunks/text-CajMdVFy.js.map +1 -0
- package/dist/chunks/writer-DxSKC7TL.js +2842 -0
- package/dist/chunks/writer-DxSKC7TL.js.map +1 -0
- package/dist/csv.d.ts +1 -0
- package/dist/csv.js +1702 -0
- package/dist/csv.js.map +1 -0
- package/dist/dot.d.ts +1 -0
- package/dist/dot.js +8 -0
- package/dist/dot.js.map +1 -0
- package/dist/gexf.d.ts +1 -0
- package/dist/gexf.js +3466 -0
- package/dist/gexf.js.map +1 -0
- package/dist/gml.d.ts +1 -0
- package/dist/gml.js +2647 -0
- package/dist/gml.js.map +1 -0
- package/dist/graph-io.d.ts +1 -0
- package/dist/graph-io.js +790 -0
- package/dist/graph-io.js.map +1 -0
- package/dist/graphml.d.ts +1 -0
- package/dist/graphml.js +8 -0
- package/dist/graphml.js.map +1 -0
- package/dist/json.d.ts +1 -0
- package/dist/json.js +11 -0
- package/dist/json.js.map +1 -0
- package/dist/neo4j.d.ts +1 -0
- package/dist/neo4j.js +2046 -0
- package/dist/neo4j.js.map +1 -0
- package/dist/pajek.d.ts +1 -0
- package/dist/pajek.js +8 -0
- package/dist/pajek.js.map +1 -0
- package/dist/src/children.d.ts +134 -0
- package/dist/src/children.d.ts.map +1 -0
- package/dist/src/children.js +274 -0
- package/dist/src/children.js.map +1 -0
- package/dist/src/common/attributes.d.ts +229 -0
- package/dist/src/common/attributes.d.ts.map +1 -0
- package/dist/src/common/attributes.js +368 -0
- package/dist/src/common/attributes.js.map +1 -0
- package/dist/src/common/codes.d.ts +105 -0
- package/dist/src/common/codes.d.ts.map +1 -0
- package/dist/src/common/codes.js +107 -0
- package/dist/src/common/codes.js.map +1 -0
- package/dist/src/common/declared-types.d.ts +84 -0
- package/dist/src/common/declared-types.d.ts.map +1 -0
- package/dist/src/common/declared-types.js +326 -0
- package/dist/src/common/declared-types.js.map +1 -0
- package/dist/src/common/direction.d.ts +206 -0
- package/dist/src/common/direction.d.ts.map +1 -0
- package/dist/src/common/direction.js +370 -0
- package/dist/src/common/direction.js.map +1 -0
- package/dist/src/common/escape.d.ts +92 -0
- package/dist/src/common/escape.d.ts.map +1 -0
- package/dist/src/common/escape.js +212 -0
- package/dist/src/common/escape.js.map +1 -0
- package/dist/src/common/export.d.ts +249 -0
- package/dist/src/common/export.d.ts.map +1 -0
- package/dist/src/common/export.js +594 -0
- package/dist/src/common/export.js.map +1 -0
- package/dist/src/common/format.d.ts +59 -0
- package/dist/src/common/format.d.ts.map +1 -0
- package/dist/src/common/format.js +106 -0
- package/dist/src/common/format.js.map +1 -0
- package/dist/src/common/ids.d.ts +83 -0
- package/dist/src/common/ids.d.ts.map +1 -0
- package/dist/src/common/ids.js +158 -0
- package/dist/src/common/ids.js.map +1 -0
- package/dist/src/common/input.d.ts +100 -0
- package/dist/src/common/input.d.ts.map +1 -0
- package/dist/src/common/input.js +335 -0
- package/dist/src/common/input.js.map +1 -0
- package/dist/src/common/lists.d.ts +34 -0
- package/dist/src/common/lists.d.ts.map +1 -0
- package/dist/src/common/lists.js +185 -0
- package/dist/src/common/lists.js.map +1 -0
- package/dist/src/common/options.d.ts +108 -0
- package/dist/src/common/options.d.ts.map +1 -0
- package/dist/src/common/options.js +265 -0
- package/dist/src/common/options.js.map +1 -0
- package/dist/src/common/report.d.ts +187 -0
- package/dist/src/common/report.d.ts.map +1 -0
- package/dist/src/common/report.js +274 -0
- package/dist/src/common/report.js.map +1 -0
- package/dist/src/common/temporal.d.ts +71 -0
- package/dist/src/common/temporal.d.ts.map +1 -0
- package/dist/src/common/temporal.js +266 -0
- package/dist/src/common/temporal.js.map +1 -0
- package/dist/src/common/text.d.ts +104 -0
- package/dist/src/common/text.d.ts.map +1 -0
- package/dist/src/common/text.js +255 -0
- package/dist/src/common/text.js.map +1 -0
- package/dist/src/common/weights.d.ts +77 -0
- package/dist/src/common/weights.d.ts.map +1 -0
- package/dist/src/common/weights.js +156 -0
- package/dist/src/common/weights.js.map +1 -0
- package/dist/src/common/writer.d.ts +51 -0
- package/dist/src/common/writer.d.ts.map +1 -0
- package/dist/src/common/writer.js +108 -0
- package/dist/src/common/writer.js.map +1 -0
- package/dist/src/common/xml.d.ts +245 -0
- package/dist/src/common/xml.d.ts.map +1 -0
- package/dist/src/common/xml.js +942 -0
- package/dist/src/common/xml.js.map +1 -0
- package/dist/src/formats/csv/exporter.d.ts +70 -0
- package/dist/src/formats/csv/exporter.d.ts.map +1 -0
- package/dist/src/formats/csv/exporter.js +682 -0
- package/dist/src/formats/csv/exporter.js.map +1 -0
- package/dist/src/formats/csv/header.d.ts +66 -0
- package/dist/src/formats/csv/header.d.ts.map +1 -0
- package/dist/src/formats/csv/header.js +152 -0
- package/dist/src/formats/csv/header.js.map +1 -0
- package/dist/src/formats/csv/importer.d.ts +82 -0
- package/dist/src/formats/csv/importer.d.ts.map +1 -0
- package/dist/src/formats/csv/importer.js +849 -0
- package/dist/src/formats/csv/importer.js.map +1 -0
- package/dist/src/formats/csv/index.d.ts +60 -0
- package/dist/src/formats/csv/index.d.ts.map +1 -0
- package/dist/src/formats/csv/index.js +63 -0
- package/dist/src/formats/csv/index.js.map +1 -0
- package/dist/src/formats/csv/records.d.ts +188 -0
- package/dist/src/formats/csv/records.d.ts.map +1 -0
- package/dist/src/formats/csv/records.js +702 -0
- package/dist/src/formats/csv/records.js.map +1 -0
- package/dist/src/formats/csv/values.d.ts +105 -0
- package/dist/src/formats/csv/values.d.ts.map +1 -0
- package/dist/src/formats/csv/values.js +192 -0
- package/dist/src/formats/csv/values.js.map +1 -0
- package/dist/src/formats/dot/exporter.d.ts +52 -0
- package/dist/src/formats/dot/exporter.d.ts.map +1 -0
- package/dist/src/formats/dot/exporter.js +836 -0
- package/dist/src/formats/dot/exporter.js.map +1 -0
- package/dist/src/formats/dot/importer.d.ts +102 -0
- package/dist/src/formats/dot/importer.d.ts.map +1 -0
- package/dist/src/formats/dot/importer.js +1291 -0
- package/dist/src/formats/dot/importer.js.map +1 -0
- package/dist/src/formats/dot/index.d.ts +7 -0
- package/dist/src/formats/dot/index.d.ts.map +1 -0
- package/dist/src/formats/dot/index.js +7 -0
- package/dist/src/formats/dot/index.js.map +1 -0
- package/dist/src/formats/dot/names.d.ts +29 -0
- package/dist/src/formats/dot/names.d.ts.map +1 -0
- package/dist/src/formats/dot/names.js +28 -0
- package/dist/src/formats/dot/names.js.map +1 -0
- package/dist/src/formats/dot/tokenizer.d.ts +114 -0
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -0
- package/dist/src/formats/dot/tokenizer.js +341 -0
- package/dist/src/formats/dot/tokenizer.js.map +1 -0
- package/dist/src/formats/gexf/exporter.d.ts +56 -0
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -0
- package/dist/src/formats/gexf/exporter.js +1395 -0
- package/dist/src/formats/gexf/exporter.js.map +1 -0
- package/dist/src/formats/gexf/importer.d.ts +73 -0
- package/dist/src/formats/gexf/importer.d.ts.map +1 -0
- package/dist/src/formats/gexf/importer.js +1880 -0
- package/dist/src/formats/gexf/importer.js.map +1 -0
- package/dist/src/formats/gexf/index.d.ts +96 -0
- package/dist/src/formats/gexf/index.d.ts.map +1 -0
- package/dist/src/formats/gexf/index.js +97 -0
- package/dist/src/formats/gexf/index.js.map +1 -0
- package/dist/src/formats/gexf/schema.d.ts +135 -0
- package/dist/src/formats/gexf/schema.d.ts.map +1 -0
- package/dist/src/formats/gexf/schema.js +323 -0
- package/dist/src/formats/gexf/schema.js.map +1 -0
- package/dist/src/formats/gml/exporter.d.ts +69 -0
- package/dist/src/formats/gml/exporter.d.ts.map +1 -0
- package/dist/src/formats/gml/exporter.js +1093 -0
- package/dist/src/formats/gml/exporter.js.map +1 -0
- package/dist/src/formats/gml/importer.d.ts +66 -0
- package/dist/src/formats/gml/importer.d.ts.map +1 -0
- package/dist/src/formats/gml/importer.js +1331 -0
- package/dist/src/formats/gml/importer.js.map +1 -0
- package/dist/src/formats/gml/index.d.ts +85 -0
- package/dist/src/formats/gml/index.d.ts.map +1 -0
- package/dist/src/formats/gml/index.js +88 -0
- package/dist/src/formats/gml/index.js.map +1 -0
- package/dist/src/formats/gml/syntax.d.ts +186 -0
- package/dist/src/formats/gml/syntax.d.ts.map +1 -0
- package/dist/src/formats/gml/syntax.js +467 -0
- package/dist/src/formats/gml/syntax.js.map +1 -0
- package/dist/src/formats/graphml/constants.d.ts +169 -0
- package/dist/src/formats/graphml/constants.d.ts.map +1 -0
- package/dist/src/formats/graphml/constants.js +165 -0
- package/dist/src/formats/graphml/constants.js.map +1 -0
- package/dist/src/formats/graphml/exporter.d.ts +34 -0
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -0
- package/dist/src/formats/graphml/exporter.js +1176 -0
- package/dist/src/formats/graphml/exporter.js.map +1 -0
- package/dist/src/formats/graphml/importer.d.ts +31 -0
- package/dist/src/formats/graphml/importer.d.ts.map +1 -0
- package/dist/src/formats/graphml/importer.js +1607 -0
- package/dist/src/formats/graphml/importer.js.map +1 -0
- package/dist/src/formats/graphml/index.d.ts +8 -0
- package/dist/src/formats/graphml/index.d.ts.map +1 -0
- package/dist/src/formats/graphml/index.js +8 -0
- package/dist/src/formats/graphml/index.js.map +1 -0
- package/dist/src/formats/graphml/tree.d.ts +72 -0
- package/dist/src/formats/graphml/tree.d.ts.map +1 -0
- package/dist/src/formats/graphml/tree.js +290 -0
- package/dist/src/formats/graphml/tree.js.map +1 -0
- package/dist/src/formats/json/dialect.d.ts +125 -0
- package/dist/src/formats/json/dialect.d.ts.map +1 -0
- package/dist/src/formats/json/dialect.js +262 -0
- package/dist/src/formats/json/dialect.js.map +1 -0
- package/dist/src/formats/json/exporter.d.ts +89 -0
- package/dist/src/formats/json/exporter.d.ts.map +1 -0
- package/dist/src/formats/json/exporter.js +1358 -0
- package/dist/src/formats/json/exporter.js.map +1 -0
- package/dist/src/formats/json/importer.d.ts +108 -0
- package/dist/src/formats/json/importer.d.ts.map +1 -0
- package/dist/src/formats/json/importer.js +1838 -0
- package/dist/src/formats/json/importer.js.map +1 -0
- package/dist/src/formats/json/index.d.ts +8 -0
- package/dist/src/formats/json/index.d.ts.map +1 -0
- package/dist/src/formats/json/index.js +8 -0
- package/dist/src/formats/json/index.js.map +1 -0
- package/dist/src/formats/neo4j/exporter.d.ts +68 -0
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -0
- package/dist/src/formats/neo4j/exporter.js +1055 -0
- package/dist/src/formats/neo4j/exporter.js.map +1 -0
- package/dist/src/formats/neo4j/header.d.ts +52 -0
- package/dist/src/formats/neo4j/header.d.ts.map +1 -0
- package/dist/src/formats/neo4j/header.js +131 -0
- package/dist/src/formats/neo4j/header.js.map +1 -0
- package/dist/src/formats/neo4j/importer.d.ts +73 -0
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -0
- package/dist/src/formats/neo4j/importer.js +932 -0
- package/dist/src/formats/neo4j/importer.js.map +1 -0
- package/dist/src/formats/neo4j/index.d.ts +79 -0
- package/dist/src/formats/neo4j/index.d.ts.map +1 -0
- package/dist/src/formats/neo4j/index.js +83 -0
- package/dist/src/formats/neo4j/index.js.map +1 -0
- package/dist/src/formats/pajek/exporter.d.ts +58 -0
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -0
- package/dist/src/formats/pajek/exporter.js +825 -0
- package/dist/src/formats/pajek/exporter.js.map +1 -0
- package/dist/src/formats/pajek/importer.d.ts +88 -0
- package/dist/src/formats/pajek/importer.d.ts.map +1 -0
- package/dist/src/formats/pajek/importer.js +1047 -0
- package/dist/src/formats/pajek/importer.js.map +1 -0
- package/dist/src/formats/pajek/index.d.ts +7 -0
- package/dist/src/formats/pajek/index.d.ts.map +1 -0
- package/dist/src/formats/pajek/index.js +7 -0
- package/dist/src/formats/pajek/index.js.map +1 -0
- package/dist/src/formats/pajek/syntax.d.ts +112 -0
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -0
- package/dist/src/formats/pajek/syntax.js +269 -0
- package/dist/src/formats/pajek/syntax.js.map +1 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/registry.d.ts +207 -0
- package/dist/src/registry.d.ts.map +1 -0
- package/dist/src/registry.js +481 -0
- package/dist/src/registry.js.map +1 -0
- package/dist/src/sniff.d.ts +104 -0
- package/dist/src/sniff.d.ts.map +1 -0
- package/dist/src/sniff.js +357 -0
- package/dist/src/sniff.js.map +1 -0
- package/dist/src/types.d.ts +238 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +29 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +122 -7
- package/src/children.ts +335 -0
- package/src/common/attributes.ts +520 -0
- package/src/common/codes.ts +153 -0
- package/src/common/declared-types.ts +374 -0
- package/src/common/direction.ts +518 -0
- package/src/common/escape.ts +231 -0
- package/src/common/export.ts +817 -0
- package/src/common/format.ts +111 -0
- package/src/common/ids.ts +176 -0
- package/src/common/input.ts +378 -0
- package/src/common/lists.ts +196 -0
- package/src/common/options.ts +377 -0
- package/src/common/report.ts +352 -0
- package/src/common/temporal.ts +302 -0
- package/src/common/text.ts +294 -0
- package/src/common/weights.ts +202 -0
- package/src/common/writer.ts +123 -0
- package/src/common/xml.ts +1053 -0
- package/src/formats/csv/exporter.ts +894 -0
- package/src/formats/csv/header.ts +172 -0
- package/src/formats/csv/importer.ts +1104 -0
- package/src/formats/csv/index.ts +88 -0
- package/src/formats/csv/records.ts +813 -0
- package/src/formats/csv/values.ts +224 -0
- package/src/formats/dot/exporter.ts +1014 -0
- package/src/formats/dot/importer.ts +1549 -0
- package/src/formats/dot/index.ts +7 -0
- package/src/formats/dot/names.ts +40 -0
- package/src/formats/dot/tokenizer.ts +384 -0
- package/src/formats/gexf/exporter.ts +1696 -0
- package/src/formats/gexf/importer.ts +2333 -0
- package/src/formats/gexf/index.ts +142 -0
- package/src/formats/gexf/schema.ts +361 -0
- package/src/formats/gml/exporter.ts +1404 -0
- package/src/formats/gml/importer.ts +1591 -0
- package/src/formats/gml/index.ts +128 -0
- package/src/formats/gml/syntax.ts +545 -0
- package/src/formats/graphml/constants.ts +225 -0
- package/src/formats/graphml/exporter.ts +1458 -0
- package/src/formats/graphml/importer.ts +2027 -0
- package/src/formats/graphml/index.ts +8 -0
- package/src/formats/graphml/tree.ts +318 -0
- package/src/formats/json/dialect.ts +317 -0
- package/src/formats/json/exporter.ts +1616 -0
- package/src/formats/json/importer.ts +2271 -0
- package/src/formats/json/index.ts +8 -0
- package/src/formats/neo4j/exporter.ts +1287 -0
- package/src/formats/neo4j/header.ts +156 -0
- package/src/formats/neo4j/importer.ts +1220 -0
- package/src/formats/neo4j/index.ts +116 -0
- package/src/formats/pajek/exporter.ts +1000 -0
- package/src/formats/pajek/importer.ts +1311 -0
- package/src/formats/pajek/index.ts +7 -0
- package/src/formats/pajek/syntax.ts +307 -0
- package/src/index.ts +244 -0
- package/src/registry.ts +617 -0
- package/src/sniff.ts +397 -0
- package/src/types.ts +262 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List value syntaxes of the text formats (design section 5.1, research note 07 section 2.1):
|
|
3
|
+
* GEXF 1.3 bracket lists `[1, 2, 3]` / `[foo, 'bar baz']`, GEXF 1.2 `liststring` values separated
|
|
4
|
+
* by `|`, `,` or `;` ("an unsafe type"), Gephi and Neo4j `;`-separated arrays. The importer splits
|
|
5
|
+
* the text into item texts with splitListText() and parses each item by the declared item type.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { GraphFormatError } from "@graphty/graph-format";
|
|
9
|
+
|
|
10
|
+
/** The list syntaxes an importer or exporter names. */
|
|
11
|
+
export type ListSyntax = "gexf" | "brackets" | "pipe" | "comma" | "semicolon";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Split a list value into its item texts.
|
|
15
|
+
*
|
|
16
|
+
* - "gexf": a bracketed text `[a, b]` is parsed with single or double quotes around items that
|
|
17
|
+
* contain commas; otherwise the 1.2 rule applies: the text is split on `|` when it contains one,
|
|
18
|
+
* else on `,` when it contains one, else on `;` when it contains one, else it is one item.
|
|
19
|
+
* - "brackets": bracketed only; an unbracketed text is one item.
|
|
20
|
+
* - "pipe" / "comma" / "semicolon": split on that separator.
|
|
21
|
+
*
|
|
22
|
+
* Items are trimmed; an empty text (or `[]`) is an empty list; an empty item between separators
|
|
23
|
+
* is kept as an empty string.
|
|
24
|
+
* @param text - the value text
|
|
25
|
+
* @param syntax - the syntax
|
|
26
|
+
* @returns the item texts
|
|
27
|
+
*/
|
|
28
|
+
export function splitListText(text: string, syntax: ListSyntax): string[] {
|
|
29
|
+
const trimmed = text.trim();
|
|
30
|
+
if (trimmed.length === 0) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
switch (syntax) {
|
|
34
|
+
case "gexf": {
|
|
35
|
+
if (isBracketed(trimmed)) {
|
|
36
|
+
return splitBracketed(trimmed);
|
|
37
|
+
}
|
|
38
|
+
const separator = firstSeparator(trimmed, ["|", ",", ";"]);
|
|
39
|
+
return separator === null ? [trimmed] : splitOn(trimmed, separator);
|
|
40
|
+
}
|
|
41
|
+
case "brackets":
|
|
42
|
+
return isBracketed(trimmed) ? splitBracketed(trimmed) : [trimmed];
|
|
43
|
+
case "pipe":
|
|
44
|
+
return splitOn(trimmed, "|");
|
|
45
|
+
case "comma":
|
|
46
|
+
return splitOn(trimmed, ",");
|
|
47
|
+
case "semicolon":
|
|
48
|
+
return splitOn(trimmed, ";");
|
|
49
|
+
default: {
|
|
50
|
+
const name: string = syntax;
|
|
51
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown list syntax ${name}`, {
|
|
52
|
+
option: "listSyntax",
|
|
53
|
+
found: name,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Join item texts into a list value of one syntax, the inverse of splitListText() for the exporters.
|
|
61
|
+
* Bracket syntaxes quote an item that contains a comma, a bracket or a quote (with double quotes,
|
|
62
|
+
* a double quote inside doubled); separator syntaxes cannot escape and leave items as they are.
|
|
63
|
+
* @param items - the item texts
|
|
64
|
+
* @param syntax - the syntax; "gexf" writes the 1.3 bracket form
|
|
65
|
+
* @returns the list text
|
|
66
|
+
*/
|
|
67
|
+
export function joinListText(items: readonly string[], syntax: ListSyntax): string {
|
|
68
|
+
switch (syntax) {
|
|
69
|
+
case "gexf":
|
|
70
|
+
case "brackets":
|
|
71
|
+
return `[${items.map(quoteBracketItem).join(", ")}]`;
|
|
72
|
+
case "pipe":
|
|
73
|
+
return items.join("|");
|
|
74
|
+
case "comma":
|
|
75
|
+
return items.join(",");
|
|
76
|
+
case "semicolon":
|
|
77
|
+
return items.join(";");
|
|
78
|
+
default: {
|
|
79
|
+
const name: string = syntax;
|
|
80
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown list syntax ${name}`, {
|
|
81
|
+
option: "listSyntax",
|
|
82
|
+
found: name,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Whether a trimmed text is `[...]`.
|
|
90
|
+
* @param text - the trimmed text
|
|
91
|
+
* @returns true when bracketed
|
|
92
|
+
*/
|
|
93
|
+
function isBracketed(text: string): boolean {
|
|
94
|
+
return text.length >= 2 && text.startsWith("[") && text.endsWith("]");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The first of several separators that occurs in a text.
|
|
99
|
+
* @param text - the text
|
|
100
|
+
* @param candidates - separators in priority order
|
|
101
|
+
* @returns the first candidate found, or null
|
|
102
|
+
*/
|
|
103
|
+
function firstSeparator(text: string, candidates: readonly string[]): string | null {
|
|
104
|
+
for (const candidate of candidates) {
|
|
105
|
+
if (text.includes(candidate)) {
|
|
106
|
+
return candidate;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Split on a separator and trim every item.
|
|
114
|
+
* @param text - the text
|
|
115
|
+
* @param separator - the separator
|
|
116
|
+
* @returns the trimmed items
|
|
117
|
+
*/
|
|
118
|
+
function splitOn(text: string, separator: string): string[] {
|
|
119
|
+
return text.split(separator).map((item) => item.trim());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parse the inside of a bracketed list: comma-separated items, each optionally wrapped in single
|
|
124
|
+
* or double quotes (a doubled quote inside a quoted item is one quote).
|
|
125
|
+
* @param text - the bracketed text
|
|
126
|
+
* @returns the items
|
|
127
|
+
*/
|
|
128
|
+
function splitBracketed(text: string): string[] {
|
|
129
|
+
const inner = text.slice(1, -1);
|
|
130
|
+
const items: string[] = [];
|
|
131
|
+
const n = inner.length;
|
|
132
|
+
if (inner.trim().length === 0) {
|
|
133
|
+
return items;
|
|
134
|
+
}
|
|
135
|
+
let i = 0;
|
|
136
|
+
for (;;) {
|
|
137
|
+
while (i < n && isSpace(inner.charCodeAt(i))) {
|
|
138
|
+
i++;
|
|
139
|
+
}
|
|
140
|
+
let item = "";
|
|
141
|
+
if (i < n && (inner[i] === '"' || inner[i] === "'")) {
|
|
142
|
+
const quote = inner[i];
|
|
143
|
+
i++;
|
|
144
|
+
while (i < n) {
|
|
145
|
+
if (inner[i] === quote) {
|
|
146
|
+
if (inner[i + 1] === quote) {
|
|
147
|
+
item += quote;
|
|
148
|
+
i += 2;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
i++;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
item += inner[i];
|
|
155
|
+
i++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// unquoted text, or text after a closing quote that is not a separator, runs to the next comma
|
|
159
|
+
let end = inner.indexOf(",", i);
|
|
160
|
+
if (end < 0) {
|
|
161
|
+
end = n;
|
|
162
|
+
}
|
|
163
|
+
item += inner.slice(i, end).trim();
|
|
164
|
+
items.push(item);
|
|
165
|
+
i = end;
|
|
166
|
+
if (i >= n) {
|
|
167
|
+
return items;
|
|
168
|
+
}
|
|
169
|
+
i++;
|
|
170
|
+
if (inner.slice(i).trim().length === 0) {
|
|
171
|
+
items.push("");
|
|
172
|
+
return items;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Whether a char code is ASCII whitespace.
|
|
179
|
+
* @param c - the char code
|
|
180
|
+
* @returns true for space, tab, CR, LF
|
|
181
|
+
*/
|
|
182
|
+
function isSpace(c: number): boolean {
|
|
183
|
+
return c === 32 || c === 9 || c === 13 || c === 10;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Quote one item for a bracket list when it needs it.
|
|
188
|
+
* @param item - the item text
|
|
189
|
+
* @returns the item, quoted when it contains a comma, a bracket, a quote or surrounding whitespace
|
|
190
|
+
*/
|
|
191
|
+
function quoteBracketItem(item: string): string {
|
|
192
|
+
if (item.length === 0 || /[,[\]"']/.test(item) || item !== item.trim()) {
|
|
193
|
+
return `"${item.replace(/"/g, '""')}"`;
|
|
194
|
+
}
|
|
195
|
+
return item;
|
|
196
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Option normalisation for importers and exporters (design sections 8.4 and 8.5): every common
|
|
3
|
+
* option resolved to its documented default, enum values checked (E_UNSUPPORTED, the core's
|
|
4
|
+
* convention for an option outside its set), and the per-format defaults (`ids`, `defaultDirected`,
|
|
5
|
+
* `weightFrom`, `addMissingNodes`) supplied by the importer that calls resolveImportOptions().
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type DuplicatePolicy, GraphFormatError, type GraphSink, type IdCoercion } from "@graphty/graph-format";
|
|
9
|
+
|
|
10
|
+
import { type CommonExportOptions, type CommonImportOptions } from "../types.js";
|
|
11
|
+
import { OPTION_IGNORED_CODE, SINK_OPTION_CODE } from "./codes.js";
|
|
12
|
+
import { type ImportReportBuilder } from "./report.js";
|
|
13
|
+
|
|
14
|
+
/** The defaults an importer supplies for the options whose default is per format (design section 8.4). */
|
|
15
|
+
export interface ImportFormatDefaults {
|
|
16
|
+
/** "canonical" for text-cell formats, "keep" for JSON. */
|
|
17
|
+
readonly ids: IdCoercion;
|
|
18
|
+
/** The direction assumed when the file declares none. */
|
|
19
|
+
readonly defaultDirected: boolean;
|
|
20
|
+
/** The attribute that becomes the weight ("weight", GML "value"), or null for an unweighted format. */
|
|
21
|
+
readonly weightFrom: string | null;
|
|
22
|
+
/** Whether edges may reference undeclared nodes; true unless the format says otherwise (GEXF: false). */
|
|
23
|
+
readonly addMissingNodes?: boolean | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* CommonImportOptions with every field present (design section 8.4 defaults applied).
|
|
28
|
+
* Consumed by the per-format importers and exporters under src/formats.
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
export interface ResolvedImportOptions {
|
|
32
|
+
/** The id coercion rule. */
|
|
33
|
+
readonly ids: IdCoercion;
|
|
34
|
+
/** Which field becomes the node id. */
|
|
35
|
+
readonly nodeIdFrom: "id" | "label" | "index";
|
|
36
|
+
/** Whether an edge may reference an undeclared node. */
|
|
37
|
+
readonly addMissingNodes: boolean;
|
|
38
|
+
/** The builder's duplicate-edge policy seed. */
|
|
39
|
+
readonly duplicateEdges: DuplicatePolicy;
|
|
40
|
+
/** The builder's self-loop policy seed. */
|
|
41
|
+
readonly selfLoops: "keep" | "drop" | "error";
|
|
42
|
+
/** The mixed-direction policy. */
|
|
43
|
+
readonly onMixedDirection: "expand" | "directed" | "undirected" | "error";
|
|
44
|
+
/** The direction assumed when the file declares none. */
|
|
45
|
+
readonly defaultDirected: boolean;
|
|
46
|
+
/** The weight attribute, or null for unweighted. */
|
|
47
|
+
readonly weightFrom: string | null;
|
|
48
|
+
/** The weight staging precision. */
|
|
49
|
+
readonly weightDtype: "f32" | "f64";
|
|
50
|
+
/** How declared long columns are stored. */
|
|
51
|
+
readonly long: "f64" | "string";
|
|
52
|
+
/** Whether mangled ids are restored. */
|
|
53
|
+
readonly restoreMangledIds: boolean;
|
|
54
|
+
/** The hyperedge policy. */
|
|
55
|
+
readonly hyperedges: "error" | "skip" | "star" | "clique";
|
|
56
|
+
/** Errors tolerated before aborting. */
|
|
57
|
+
readonly errorLimit: number;
|
|
58
|
+
/** The cancellation signal, or null. */
|
|
59
|
+
readonly signal: AbortSignal | null;
|
|
60
|
+
/** The progress callback, or null. */
|
|
61
|
+
readonly onProgress: ((bytesDone: number, bytesTotal?: number) => void) | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** CommonExportOptions with every field present (design section 8.5 defaults applied). */
|
|
65
|
+
export interface ResolvedExportOptions {
|
|
66
|
+
/** "error" never renames a node; "mangle" rewrites and keeps the original. */
|
|
67
|
+
readonly sanitizeIds: "error" | "mangle";
|
|
68
|
+
/** What a format without mixed-direction support does with a mixed snapshot. */
|
|
69
|
+
readonly onMixedDirection: "error" | "directed" | "undirected";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const ID_COERCIONS: ReadonlySet<string> = new Set(["keep", "canonical", "string", "number"]);
|
|
73
|
+
const NODE_ID_SOURCES: ReadonlySet<string> = new Set(["id", "label", "index"]);
|
|
74
|
+
const DUPLICATE_POLICIES: ReadonlySet<string> = new Set(["keep", "error", "first", "last", "sum", "min", "max"]);
|
|
75
|
+
const SELF_LOOP_POLICIES: ReadonlySet<string> = new Set(["keep", "drop", "error"]);
|
|
76
|
+
const IMPORT_MIXED_POLICIES: ReadonlySet<string> = new Set(["expand", "directed", "undirected", "error"]);
|
|
77
|
+
const EXPORT_MIXED_POLICIES: ReadonlySet<string> = new Set(["error", "directed", "undirected"]);
|
|
78
|
+
const WEIGHT_DTYPES: ReadonlySet<string> = new Set(["f32", "f64"]);
|
|
79
|
+
const LONG_MODES: ReadonlySet<string> = new Set(["f64", "string"]);
|
|
80
|
+
const HYPEREDGE_POLICIES: ReadonlySet<string> = new Set(["error", "skip", "star", "clique"]);
|
|
81
|
+
const SANITIZE_MODES: ReadonlySet<string> = new Set(["error", "mangle"]);
|
|
82
|
+
|
|
83
|
+
/** The default error limit of design section 8.4. */
|
|
84
|
+
export const DEFAULT_ERROR_LIMIT = 100;
|
|
85
|
+
|
|
86
|
+
export { SINK_OPTION_CODE };
|
|
87
|
+
|
|
88
|
+
/** The builder-policy options a sink exposes read-only as `sink.options` (design section 8.4). */
|
|
89
|
+
const SINK_OPTION_NAMES = ["addMissingNodes", "duplicateEdges", "selfLoops", "weightDtype"] as const;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Report every builder-policy option the caller explicitly requested that the sink does not use
|
|
93
|
+
* (design section 8.4 precedence), one `W_SINK_OPTION` warning per option with the option name as
|
|
94
|
+
* the element. Options left undefined are never reported: they are defaults, not requests. On the
|
|
95
|
+
* registry's builder, which is seeded from the same options, nothing is ever reported.
|
|
96
|
+
* @param sink - the sink the importer pushes into
|
|
97
|
+
* @param options - the caller's raw options, possibly undefined
|
|
98
|
+
* @param report - the report to record into
|
|
99
|
+
* @param enforcesMissingNodes - true when the importer applies `addMissingNodes: false` itself (it
|
|
100
|
+
* refuses unknown endpoints before the sink sees them), so that request is honoured on any sink and
|
|
101
|
+
* only `addMissingNodes: true` against a refusing sink is reported
|
|
102
|
+
* @returns the number of warnings recorded
|
|
103
|
+
*/
|
|
104
|
+
export function reportSinkOptions(
|
|
105
|
+
sink: GraphSink,
|
|
106
|
+
options: CommonImportOptions | undefined,
|
|
107
|
+
report: ImportReportBuilder,
|
|
108
|
+
enforcesMissingNodes = false,
|
|
109
|
+
): number {
|
|
110
|
+
if (options === undefined) {
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
let recorded = 0;
|
|
114
|
+
for (const name of SINK_OPTION_NAMES) {
|
|
115
|
+
const wanted: unknown = options[name];
|
|
116
|
+
const actual: unknown = sink.options[name];
|
|
117
|
+
if (wanted === undefined || wanted === actual) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (name === "addMissingNodes" && enforcesMissingNodes && wanted === false) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
report.warning(
|
|
124
|
+
"coercion",
|
|
125
|
+
SINK_OPTION_CODE,
|
|
126
|
+
`option ${name}: ${JSON.stringify(wanted)} requested but the sink uses ${JSON.stringify(actual)}; the sink's setting applies`,
|
|
127
|
+
{ element: name },
|
|
128
|
+
);
|
|
129
|
+
recorded++;
|
|
130
|
+
}
|
|
131
|
+
return recorded;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The common options a format may leave unused; `signal`, `onProgress` and `errorLimit` apply everywhere. */
|
|
135
|
+
const IGNORABLE_OPTION_NAMES = [
|
|
136
|
+
"ids",
|
|
137
|
+
"nodeIdFrom",
|
|
138
|
+
"addMissingNodes",
|
|
139
|
+
"duplicateEdges",
|
|
140
|
+
"selfLoops",
|
|
141
|
+
"onMixedDirection",
|
|
142
|
+
"defaultDirected",
|
|
143
|
+
"weightFrom",
|
|
144
|
+
"weightDtype",
|
|
145
|
+
"long",
|
|
146
|
+
"restoreMangledIds",
|
|
147
|
+
"hyperedges",
|
|
148
|
+
] as const;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Report every common option the caller set to a non-default value that the format has no use
|
|
152
|
+
* for (design section 8.4: "the importer reports every option it could not honour"): one
|
|
153
|
+
* `W_OPTION_IGNORED` warning (category `unsupported`) per option, the option name as the element.
|
|
154
|
+
* The builder-policy options are reportSinkOptions()'s and are skipped here.
|
|
155
|
+
* @param options - the caller's raw options, possibly undefined
|
|
156
|
+
* @param report - the report to record into
|
|
157
|
+
* @param used - the common option names the importer reads
|
|
158
|
+
* @returns the number of warnings recorded
|
|
159
|
+
*/
|
|
160
|
+
export function reportUnusedOptions(
|
|
161
|
+
options: CommonImportOptions | undefined,
|
|
162
|
+
report: ImportReportBuilder,
|
|
163
|
+
used: ReadonlySet<keyof CommonImportOptions>,
|
|
164
|
+
): number {
|
|
165
|
+
if (options === undefined) {
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
let recorded = 0;
|
|
169
|
+
for (const name of IGNORABLE_OPTION_NAMES) {
|
|
170
|
+
if (used.has(name) || (SINK_OPTION_NAMES as readonly string[]).includes(name)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const value: unknown = options[name];
|
|
174
|
+
if (value === undefined) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
report.warning(
|
|
178
|
+
"unsupported",
|
|
179
|
+
OPTION_IGNORED_CODE,
|
|
180
|
+
`option ${name}: ${JSON.stringify(value)} has no effect on the ${report.format} importer`,
|
|
181
|
+
{ element: name },
|
|
182
|
+
);
|
|
183
|
+
recorded++;
|
|
184
|
+
}
|
|
185
|
+
return recorded;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Apply the design section 8.4 defaults to an importer's common options and check every enum
|
|
190
|
+
* value. Format-specific options in the same object are ignored here.
|
|
191
|
+
* @param options - the caller's options, possibly undefined
|
|
192
|
+
* @param defaults - the importer's per-format defaults
|
|
193
|
+
* @returns the resolved options; E_UNSUPPORTED for a value outside its set
|
|
194
|
+
*/
|
|
195
|
+
export function resolveImportOptions(
|
|
196
|
+
options: CommonImportOptions | undefined,
|
|
197
|
+
defaults: ImportFormatDefaults,
|
|
198
|
+
): ResolvedImportOptions {
|
|
199
|
+
const o: CommonImportOptions = options ?? {};
|
|
200
|
+
return Object.freeze({
|
|
201
|
+
ids: enumOption("ids", o.ids, defaults.ids, ID_COERCIONS),
|
|
202
|
+
nodeIdFrom: enumOption("nodeIdFrom", o.nodeIdFrom, "id", NODE_ID_SOURCES),
|
|
203
|
+
addMissingNodes: booleanOption("addMissingNodes", o.addMissingNodes, defaults.addMissingNodes ?? true),
|
|
204
|
+
duplicateEdges: enumOption("duplicateEdges", o.duplicateEdges, "keep", DUPLICATE_POLICIES),
|
|
205
|
+
selfLoops: enumOption("selfLoops", o.selfLoops, "keep", SELF_LOOP_POLICIES),
|
|
206
|
+
onMixedDirection: enumOption("onMixedDirection", o.onMixedDirection, "expand", IMPORT_MIXED_POLICIES),
|
|
207
|
+
defaultDirected: booleanOption("defaultDirected", o.defaultDirected, defaults.defaultDirected),
|
|
208
|
+
weightFrom: weightFromOption(o.weightFrom, defaults.weightFrom),
|
|
209
|
+
weightDtype: enumOption("weightDtype", o.weightDtype, "f64", WEIGHT_DTYPES),
|
|
210
|
+
long: enumOption("long", o.long, "f64", LONG_MODES),
|
|
211
|
+
restoreMangledIds: booleanOption("restoreMangledIds", o.restoreMangledIds, true),
|
|
212
|
+
hyperedges: enumOption("hyperedges", o.hyperedges, "skip", HYPEREDGE_POLICIES),
|
|
213
|
+
errorLimit: errorLimitOption(o.errorLimit),
|
|
214
|
+
signal: signalOption(o.signal),
|
|
215
|
+
onProgress: progressOption(o.onProgress),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Apply the design section 8.5 defaults to an exporter's common options and check the enum values.
|
|
221
|
+
* @param options - the caller's options, possibly undefined
|
|
222
|
+
* @returns the resolved options; E_UNSUPPORTED for a value outside its set
|
|
223
|
+
*/
|
|
224
|
+
export function resolveExportOptions(options: CommonExportOptions | undefined): ResolvedExportOptions {
|
|
225
|
+
const o: CommonExportOptions = options ?? {};
|
|
226
|
+
return Object.freeze({
|
|
227
|
+
sanitizeIds: enumOption("sanitizeIds", o.sanitizeIds, "error", SANITIZE_MODES),
|
|
228
|
+
onMixedDirection: enumOption("onMixedDirection", o.onMixedDirection, "error", EXPORT_MIXED_POLICIES),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Resolve one enum-valued option.
|
|
234
|
+
* @param name - the option name, for the error
|
|
235
|
+
* @param value - the caller's value
|
|
236
|
+
* @param fallback - the default
|
|
237
|
+
* @param allowed - the accepted values
|
|
238
|
+
* @returns the value or the default; E_UNSUPPORTED when outside the set
|
|
239
|
+
*/
|
|
240
|
+
function enumOption<T extends string>(name: string, value: unknown, fallback: T, allowed: ReadonlySet<string>): T {
|
|
241
|
+
if (value === undefined) {
|
|
242
|
+
return fallback;
|
|
243
|
+
}
|
|
244
|
+
if (typeof value !== "string" || !allowed.has(value)) {
|
|
245
|
+
throw new GraphFormatError(
|
|
246
|
+
"E_UNSUPPORTED",
|
|
247
|
+
`option ${name}: ${describe(value)} is not one of ${list(allowed)}`,
|
|
248
|
+
{
|
|
249
|
+
option: name,
|
|
250
|
+
found: value,
|
|
251
|
+
supported: [...allowed],
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return value as T;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Resolve one boolean option.
|
|
260
|
+
* @param name - the option name, for the error
|
|
261
|
+
* @param value - the caller's value
|
|
262
|
+
* @param fallback - the default
|
|
263
|
+
* @returns the value or the default; E_UNSUPPORTED when not a boolean
|
|
264
|
+
*/
|
|
265
|
+
function booleanOption(name: string, value: unknown, fallback: boolean): boolean {
|
|
266
|
+
if (value === undefined) {
|
|
267
|
+
return fallback;
|
|
268
|
+
}
|
|
269
|
+
if (typeof value !== "boolean") {
|
|
270
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option ${name}: ${describe(value)} is not a boolean`, {
|
|
271
|
+
option: name,
|
|
272
|
+
found: value,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Resolve the weightFrom option: a non-empty attribute name, null for unweighted, or the format default.
|
|
280
|
+
* @param value - the caller's value
|
|
281
|
+
* @param fallback - the format default
|
|
282
|
+
* @returns the resolved value; E_UNSUPPORTED for anything else
|
|
283
|
+
*/
|
|
284
|
+
function weightFromOption(value: unknown, fallback: string | null): string | null {
|
|
285
|
+
if (value === undefined) {
|
|
286
|
+
return fallback;
|
|
287
|
+
}
|
|
288
|
+
if (value === null || (typeof value === "string" && value.length > 0)) {
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
throw new GraphFormatError(
|
|
292
|
+
"E_UNSUPPORTED",
|
|
293
|
+
`option weightFrom: ${describe(value)} is not an attribute name or null`,
|
|
294
|
+
{ option: "weightFrom", found: value },
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Resolve the error limit: a non-negative integer or Infinity.
|
|
300
|
+
* @param value - the caller's value
|
|
301
|
+
* @returns the limit or the default
|
|
302
|
+
*/
|
|
303
|
+
function errorLimitOption(value: unknown): number {
|
|
304
|
+
if (value === undefined) {
|
|
305
|
+
return DEFAULT_ERROR_LIMIT;
|
|
306
|
+
}
|
|
307
|
+
if (typeof value === "number" && value >= 0 && (Number.isInteger(value) || value === Infinity)) {
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
throw new GraphFormatError(
|
|
311
|
+
"E_UNSUPPORTED",
|
|
312
|
+
`option errorLimit: ${describe(value)} is not a non-negative integer or Infinity`,
|
|
313
|
+
{ option: "errorLimit", found: value },
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Resolve the signal option by duck type, so a signal from another realm is accepted.
|
|
319
|
+
* @param value - the caller's value
|
|
320
|
+
* @returns the signal or null
|
|
321
|
+
*/
|
|
322
|
+
function signalOption(value: unknown): AbortSignal | null {
|
|
323
|
+
if (value === undefined || value === null) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
if (typeof value === "object" && typeof (value as { aborted?: unknown }).aborted === "boolean") {
|
|
327
|
+
return value as AbortSignal;
|
|
328
|
+
}
|
|
329
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option signal: ${describe(value)} is not an AbortSignal`, {
|
|
330
|
+
option: "signal",
|
|
331
|
+
found: typeof value,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Resolve the progress callback option.
|
|
337
|
+
* @param value - the caller's value
|
|
338
|
+
* @returns the callback or null
|
|
339
|
+
*/
|
|
340
|
+
function progressOption(value: unknown): ((bytesDone: number, bytesTotal?: number) => void) | null {
|
|
341
|
+
if (value === undefined || value === null) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
if (typeof value === "function") {
|
|
345
|
+
return value as (bytesDone: number, bytesTotal?: number) => void;
|
|
346
|
+
}
|
|
347
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option onProgress: ${describe(value)} is not a function`, {
|
|
348
|
+
option: "onProgress",
|
|
349
|
+
found: typeof value,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* A short description of an option value for an error message.
|
|
355
|
+
* @param value - the value
|
|
356
|
+
* @returns the JSON text of a primitive, or the type name otherwise
|
|
357
|
+
*/
|
|
358
|
+
function describe(value: unknown): string {
|
|
359
|
+
switch (typeof value) {
|
|
360
|
+
case "string":
|
|
361
|
+
return JSON.stringify(value);
|
|
362
|
+
case "number":
|
|
363
|
+
case "boolean":
|
|
364
|
+
return String(value);
|
|
365
|
+
default:
|
|
366
|
+
return value === null ? "null" : typeof value;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* The accepted values of an enum option, for an error message.
|
|
372
|
+
* @param allowed - the set
|
|
373
|
+
* @returns the quoted values joined by commas
|
|
374
|
+
*/
|
|
375
|
+
function list(allowed: ReadonlySet<string>): string {
|
|
376
|
+
return [...allowed].map((v) => JSON.stringify(v)).join(", ");
|
|
377
|
+
}
|