keepset 0.16.1 → 0.18.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/dist/{bundled-DPEAZdj9.js → bundled-BiuG_xLd.js} +539 -5373
- package/dist/{categories-BYezdHCk.d.ts → categories-CiJbssql.d.ts} +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node/fetch-dataset.d.ts +1 -1
- package/dist/node/fetch-dataset.js +211 -81
- package/dist-cli/keepset.mjs +1 -1
- package/package.json +1 -1
- package/schemas/entry.schema.json +1 -1
- package/schemas/manifest.schema.json +1 -1
|
@@ -153,6 +153,7 @@ declare const ParserSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
153
153
|
column: z.ZodString;
|
|
154
154
|
factor: z.ZodNumber;
|
|
155
155
|
}, z.core.$strict>>;
|
|
156
|
+
columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
156
157
|
}, z.core.$strict>], "format">;
|
|
157
158
|
type Parser = z.infer<typeof ParserSchema>;
|
|
158
159
|
/** How to obtain the dataset: a fetchable recipe naming the source (by index into `sources`), the archive layout and the parser, or a statement that no declarative recipe exists yet and why. */
|
|
@@ -237,6 +238,7 @@ declare const RecipeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
237
238
|
column: z.ZodString;
|
|
238
239
|
factor: z.ZodNumber;
|
|
239
240
|
}, z.core.$strict>>;
|
|
241
|
+
columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
240
242
|
}, z.core.$strict>], "format">;
|
|
241
243
|
}, z.core.$strict>, z.ZodObject<{
|
|
242
244
|
status: z.ZodLiteral<"not-fetchable">;
|
|
@@ -375,6 +377,7 @@ declare const EntrySchema: z.ZodObject<{
|
|
|
375
377
|
column: z.ZodString;
|
|
376
378
|
factor: z.ZodNumber;
|
|
377
379
|
}, z.core.$strict>>;
|
|
380
|
+
columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
378
381
|
}, z.core.$strict>], "format">;
|
|
379
382
|
}, z.core.$strict>, z.ZodObject<{
|
|
380
383
|
status: z.ZodLiteral<"not-fetchable">;
|
|
@@ -464,6 +467,7 @@ declare const EntrySchema: z.ZodObject<{
|
|
|
464
467
|
column: string;
|
|
465
468
|
factor: number;
|
|
466
469
|
}[];
|
|
470
|
+
columns?: string[] | undefined;
|
|
467
471
|
} | {
|
|
468
472
|
format: "matrix-market";
|
|
469
473
|
encoding: "utf-8" | "latin1";
|
|
@@ -702,6 +706,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
|
|
|
702
706
|
column: z.ZodString;
|
|
703
707
|
factor: z.ZodNumber;
|
|
704
708
|
}, z.core.$strict>>;
|
|
709
|
+
columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
705
710
|
}, z.core.$strict>], "format">;
|
|
706
711
|
}, z.core.$strict>, z.ZodObject<{
|
|
707
712
|
status: z.ZodLiteral<"not-fetchable">;
|
|
@@ -791,6 +796,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
|
|
|
791
796
|
column: string;
|
|
792
797
|
factor: number;
|
|
793
798
|
}[];
|
|
799
|
+
columns?: string[] | undefined;
|
|
794
800
|
} | {
|
|
795
801
|
format: "matrix-market";
|
|
796
802
|
encoding: "utf-8" | "latin1";
|
|
@@ -908,6 +914,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
|
|
|
908
914
|
column: string;
|
|
909
915
|
factor: number;
|
|
910
916
|
}[];
|
|
917
|
+
columns?: string[] | undefined;
|
|
911
918
|
} | {
|
|
912
919
|
format: "matrix-market";
|
|
913
920
|
encoding: "utf-8" | "latin1";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as Parser, A as LineageSchema, B as Citation, C as CatalogueIndex, D as NamespaceNode, E as DatasetQuery, F as AliasSchema, G as Entry, H as ColumnNameSchema, I as Archive, J as IdSchema, K as EntrySchema, L as ArchiveMember, M as ManifestSchema, N as SemVerSchema, O as createCatalogue, P as parseManifest, Q as NamespaceSchema, R as ArchiveMemberSchema, S as Catalogue, T as Dataset, U as CrosswalkReference, V as CitationSchema, W as CrosswalkReferenceSchema, X as LicenceSchema, Y as Licence, Z as Namespace, _ as GraphEdgeSchema, a as CategoryTargets, at as Snapshot, b as GraphV1, c as getCategory, ct as SourceSchema, d as listCategories, dt as waybackUrl, et as ParserSchema, f as OrNode, g as GraphEdge, h as OrV1Schema, i as CategoryTarget, it as RelativePathSchema, j as Manifest, k as Lineage, l as getTarget, lt as WaybackTimestampSchema, m as OrV1, n as CategoryId, nt as Recipe, o as CountedKey, ot as SnapshotSchema, p as OrNodeSchema, q as HttpUrlSchema, r as CategoryIdSchema, rt as RecipeSchema, s as countTarget, st as Source, t as Category, tt as RESERVED_META_KEYS, u as kindOf, ut as parseEntry, v as GraphNode, w as CatalogueIndexSchema, x as GraphV1Schema, y as GraphNodeSchema, z as ArchiveSchema } from "./categories-
|
|
1
|
+
import { $ as Parser, A as LineageSchema, B as Citation, C as CatalogueIndex, D as NamespaceNode, E as DatasetQuery, F as AliasSchema, G as Entry, H as ColumnNameSchema, I as Archive, J as IdSchema, K as EntrySchema, L as ArchiveMember, M as ManifestSchema, N as SemVerSchema, O as createCatalogue, P as parseManifest, Q as NamespaceSchema, R as ArchiveMemberSchema, S as Catalogue, T as Dataset, U as CrosswalkReference, V as CitationSchema, W as CrosswalkReferenceSchema, X as LicenceSchema, Y as Licence, Z as Namespace, _ as GraphEdgeSchema, a as CategoryTargets, at as Snapshot, b as GraphV1, c as getCategory, ct as SourceSchema, d as listCategories, dt as waybackUrl, et as ParserSchema, f as OrNode, g as GraphEdge, h as OrV1Schema, i as CategoryTarget, it as RelativePathSchema, j as Manifest, k as Lineage, l as getTarget, lt as WaybackTimestampSchema, m as OrV1, n as CategoryId, nt as Recipe, o as CountedKey, ot as SnapshotSchema, p as OrNodeSchema, q as HttpUrlSchema, r as CategoryIdSchema, rt as RecipeSchema, s as countTarget, st as Source, t as Category, tt as RESERVED_META_KEYS, u as kindOf, ut as parseEntry, v as GraphNode, w as CatalogueIndexSchema, x as GraphV1Schema, y as GraphNodeSchema, z as ArchiveSchema } from "./categories-CiJbssql.js";
|
|
2
2
|
import { Crosswalk } from "twindex";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/errors.d.ts
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as NativeParseError, A as toIssues, B as kindOf, C as RESERVED_META_KEYS, D as SourceSchema, E as SnapshotSchema, G as GraphNodeSchema, H as OrNodeSchema, I as CategoryIdSchema, J as ArchiveError, K as GraphV1Schema, L as countTarget, M as NAME_SEPARATOR, N as groupsOf, O as WaybackTimestampSchema, P as namesOf, Q as KeepsetError, R as getCategory, S as ParserSchema, T as RelativePathSchema, U as OrV1Schema, V as listCategories, W as GraphEdgeSchema, X as DatasetAmbiguousError, Y as CatalogueSyntaxError, Z as DatasetNotFoundError, _ as EntrySchema, a as CatalogueIndexSchema, b as LicenceSchema, c as ManifestSchema, d as AliasSchema, et as RecipeError, f as ArchiveMemberSchema, g as CrosswalkReferenceSchema, h as ColumnNameSchema, i as listNamespaces, j as waybackUrl, k as parseEntry, l as SemVerSchema, m as CitationSchema, n as getManifest, o as createCatalogue, p as ArchiveSchema, q as JsonScalarSchema, r as listDatasets, s as LineageSchema, t as getDataset, tt as UnknownCategoryError, u as parseManifest, v as HttpUrlSchema, w as RecipeSchema, x as NamespaceSchema, y as IdSchema, z as getTarget } from "./bundled-
|
|
1
|
+
import { $ as NativeParseError, A as toIssues, B as kindOf, C as RESERVED_META_KEYS, D as SourceSchema, E as SnapshotSchema, G as GraphNodeSchema, H as OrNodeSchema, I as CategoryIdSchema, J as ArchiveError, K as GraphV1Schema, L as countTarget, M as NAME_SEPARATOR, N as groupsOf, O as WaybackTimestampSchema, P as namesOf, Q as KeepsetError, R as getCategory, S as ParserSchema, T as RelativePathSchema, U as OrV1Schema, V as listCategories, W as GraphEdgeSchema, X as DatasetAmbiguousError, Y as CatalogueSyntaxError, Z as DatasetNotFoundError, _ as EntrySchema, a as CatalogueIndexSchema, b as LicenceSchema, c as ManifestSchema, d as AliasSchema, et as RecipeError, f as ArchiveMemberSchema, g as CrosswalkReferenceSchema, h as ColumnNameSchema, i as listNamespaces, j as waybackUrl, k as parseEntry, l as SemVerSchema, m as CitationSchema, n as getManifest, o as createCatalogue, p as ArchiveSchema, q as JsonScalarSchema, r as listDatasets, s as LineageSchema, t as getDataset, tt as UnknownCategoryError, u as parseManifest, v as HttpUrlSchema, w as RecipeSchema, x as NamespaceSchema, y as IdSchema, z as getTarget } from "./bundled-BiuG_xLd.js";
|
|
2
2
|
import { validateCrosswalk } from "twindex";
|
|
3
3
|
//#region src/serialise.ts
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as Entry, T as Dataset, at as Snapshot, i as CategoryTarget, st as Source } from "../categories-
|
|
1
|
+
import { G as Entry, T as Dataset, at as Snapshot, i as CategoryTarget, st as Source } from "../categories-CiJbssql.js";
|
|
2
2
|
//#region src/node/fetch-dataset.d.ts
|
|
3
3
|
/** The network port a recipe downloads through: `downloadRaw` by default, which returns the bytes exactly as served, or a stand-in under test. */
|
|
4
4
|
export type FetchPort = (url: string, init: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as NativeParseError, F as getOwn, J as ArchiveError, L as countTarget, Q as KeepsetError, et as RecipeError, t as getDataset, z as getTarget } from "../bundled-
|
|
1
|
+
import { $ as NativeParseError, F as getOwn, J as ArchiveError, L as countTarget, Q as KeepsetError, et as RecipeError, t as getDataset, z as getTarget } from "../bundled-BiuG_xLd.js";
|
|
2
2
|
import { RoundTripMismatchError, assertRoundTrip, encode } from "twindex";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { gunzipSync } from "node:zlib";
|
|
@@ -388,76 +388,164 @@ function columnNames(cells, idColumn, line) {
|
|
|
388
388
|
}
|
|
389
389
|
return names;
|
|
390
390
|
}
|
|
391
|
+
/** The code unit of each character the scanner stops at. */
|
|
392
|
+
const QUOTE = "\"".charCodeAt(0);
|
|
393
|
+
const LINE_FEED = "\n".charCodeAt(0);
|
|
394
|
+
const CARRIAGE_RETURN = "\r".charCodeAt(0);
|
|
395
|
+
/** Scans a table file's text as it arrives in pieces, holding what a record in progress needs between them: the fields and field so far, whether a quoted field is open, and the line the record began on and the line the scan has reached. */
|
|
396
|
+
var RecordScanner = class {
|
|
397
|
+
delimiterCode;
|
|
398
|
+
fields = [];
|
|
399
|
+
field = "";
|
|
400
|
+
quoted = false;
|
|
401
|
+
started;
|
|
402
|
+
line;
|
|
403
|
+
constructor(delimiterCode, firstLine) {
|
|
404
|
+
this.delimiterCode = delimiterCode;
|
|
405
|
+
this.started = firstLine;
|
|
406
|
+
this.line = firstLine;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Scans text, returning the records it completes.
|
|
410
|
+
* @param text - The unscanned text.
|
|
411
|
+
* @param final - Whether no more text follows, so a character that needs the next one to be understood can be taken as it stands.
|
|
412
|
+
* @returns The records the text completes, and the unscanned end of it, which starts at a character that needs the next one when more text may follow.
|
|
413
|
+
* @throws NativeParseError when a quote opens inside an unquoted field.
|
|
414
|
+
*/
|
|
415
|
+
scan(text, final) {
|
|
416
|
+
const completed = [];
|
|
417
|
+
let index = 0;
|
|
418
|
+
while (index < text.length) {
|
|
419
|
+
if (this.quoted) {
|
|
420
|
+
const close = text.indexOf("\"", index);
|
|
421
|
+
const end = close === -1 ? text.length : close;
|
|
422
|
+
const run = text.slice(index, end);
|
|
423
|
+
for (let at = run.indexOf("\n"); at !== -1; at = run.indexOf("\n", at + 1)) this.line += 1;
|
|
424
|
+
this.field += run;
|
|
425
|
+
index = end;
|
|
426
|
+
if (close === -1) break;
|
|
427
|
+
if (index + 1 >= text.length && !final) return {
|
|
428
|
+
completed,
|
|
429
|
+
carry: text.slice(index)
|
|
430
|
+
};
|
|
431
|
+
if (text.charCodeAt(index + 1) === QUOTE) {
|
|
432
|
+
this.field += "\"";
|
|
433
|
+
index += 2;
|
|
434
|
+
} else {
|
|
435
|
+
this.quoted = false;
|
|
436
|
+
index += 1;
|
|
437
|
+
}
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const code = text.charCodeAt(index);
|
|
441
|
+
if (code === QUOTE) {
|
|
442
|
+
if (this.field !== "") throw new NativeParseError("csv-table", this.line, "a quote opens inside an unquoted field");
|
|
443
|
+
this.quoted = true;
|
|
444
|
+
index += 1;
|
|
445
|
+
} else if (code === this.delimiterCode) {
|
|
446
|
+
this.fields.push(this.field);
|
|
447
|
+
this.field = "";
|
|
448
|
+
index += 1;
|
|
449
|
+
} else if (code === LINE_FEED || code === CARRIAGE_RETURN) {
|
|
450
|
+
if (code === CARRIAGE_RETURN && index + 1 >= text.length && !final) return {
|
|
451
|
+
completed,
|
|
452
|
+
carry: text.slice(index)
|
|
453
|
+
};
|
|
454
|
+
index += code === CARRIAGE_RETURN && text.charCodeAt(index + 1) === LINE_FEED ? 2 : 1;
|
|
455
|
+
this.line += 1;
|
|
456
|
+
completed.push(this.endRecord());
|
|
457
|
+
} else {
|
|
458
|
+
let next = index + 1;
|
|
459
|
+
while (next < text.length) {
|
|
460
|
+
const following = text.charCodeAt(next);
|
|
461
|
+
if (following === QUOTE || following === this.delimiterCode || following === LINE_FEED || following === CARRIAGE_RETURN) break;
|
|
462
|
+
next += 1;
|
|
463
|
+
}
|
|
464
|
+
this.field += text.slice(index, next);
|
|
465
|
+
index = next;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
completed,
|
|
470
|
+
carry: ""
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* The record left unfinished when the text ends without a line break, if any.
|
|
475
|
+
* @returns The record, or undefined when the text ended on a line break.
|
|
476
|
+
* @throws NativeParseError when a quoted field is never closed.
|
|
477
|
+
*/
|
|
478
|
+
finish() {
|
|
479
|
+
if (this.quoted) throw new NativeParseError("csv-table", this.started, "a quoted field is never closed");
|
|
480
|
+
return this.field !== "" || this.fields.length > 0 ? this.endRecord() : void 0;
|
|
481
|
+
}
|
|
482
|
+
endRecord() {
|
|
483
|
+
this.fields.push(this.field);
|
|
484
|
+
const record = {
|
|
485
|
+
line: this.started,
|
|
486
|
+
fields: this.fields
|
|
487
|
+
};
|
|
488
|
+
this.fields = [];
|
|
489
|
+
this.field = "";
|
|
490
|
+
this.started = this.line;
|
|
491
|
+
return record;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
391
494
|
/**
|
|
392
|
-
* Splits text into records and fields by RFC 4180: a field may be quoted, a quoted field may hold the delimiter, line breaks and doubled quotes, and a record ends at an unquoted LF, CRLF or CR.
|
|
393
|
-
* @param
|
|
495
|
+
* Splits text into records and fields by RFC 4180 as the text arrives in pieces, one piece's records at a time, so neither the whole file nor every cell is held at once: a field may be quoted, a quoted field may hold the delimiter, line breaks and doubled quotes, and a record ends at an unquoted LF, CRLF or CR. A piece may end anywhere, even between the two quotes of a doubled quote or between a CR and its LF.
|
|
496
|
+
* @param pieces - The file text in order, after the lines the recipe skips.
|
|
394
497
|
* @param delimiter - The field delimiter.
|
|
395
498
|
* @param firstLine - The 1-based line of the file the text starts on.
|
|
396
|
-
* @returns The records, blank lines included as a record of one empty field.
|
|
499
|
+
* @returns The records in order, blank lines included as a record of one empty field.
|
|
397
500
|
* @throws NativeParseError when a quoted field is never closed or a quote opens inside an unquoted field.
|
|
398
501
|
*/
|
|
399
|
-
function records(
|
|
400
|
-
const
|
|
401
|
-
let
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
let index = 0;
|
|
407
|
-
const endRecord = () => {
|
|
408
|
-
fields.push(field);
|
|
409
|
-
out.push({
|
|
410
|
-
line: started,
|
|
411
|
-
fields
|
|
412
|
-
});
|
|
413
|
-
fields = [];
|
|
414
|
-
field = "";
|
|
415
|
-
started = line;
|
|
416
|
-
};
|
|
417
|
-
while (index < text.length) {
|
|
418
|
-
const char = text.charAt(index);
|
|
419
|
-
if (quoted) {
|
|
420
|
-
if (char === "\"") {
|
|
421
|
-
if (text.charAt(index + 1) === "\"") {
|
|
422
|
-
field += "\"";
|
|
423
|
-
index += 1;
|
|
424
|
-
} else quoted = false;
|
|
425
|
-
} else {
|
|
426
|
-
if (char === "\n") line += 1;
|
|
427
|
-
field += char;
|
|
428
|
-
}
|
|
429
|
-
} else if (char === "\"") {
|
|
430
|
-
if (field !== "") throw new NativeParseError("csv-table", line, "a quote opens inside an unquoted field");
|
|
431
|
-
quoted = true;
|
|
432
|
-
} else if (char === delimiter) {
|
|
433
|
-
fields.push(field);
|
|
434
|
-
field = "";
|
|
435
|
-
} else if (char === "\n" || char === "\r") {
|
|
436
|
-
if (char === "\r" && text.charAt(index + 1) === "\n") index += 1;
|
|
437
|
-
line += 1;
|
|
438
|
-
endRecord();
|
|
439
|
-
} else field += char;
|
|
440
|
-
index += 1;
|
|
502
|
+
function* records(pieces, delimiter, firstLine) {
|
|
503
|
+
const scanner = new RecordScanner(delimiter.charCodeAt(0), firstLine);
|
|
504
|
+
let carry = "";
|
|
505
|
+
for (const piece of pieces) {
|
|
506
|
+
const scanned = scanner.scan(carry + piece, false);
|
|
507
|
+
carry = scanned.carry;
|
|
508
|
+
yield* scanned.completed;
|
|
441
509
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
510
|
+
yield* scanner.scan(carry, true).completed;
|
|
511
|
+
const last = scanner.finish();
|
|
512
|
+
if (last !== void 0) yield last;
|
|
445
513
|
}
|
|
446
514
|
/**
|
|
447
|
-
* Drops the first lines of a file.
|
|
448
|
-
* @param
|
|
515
|
+
* Drops the first physical lines of a file given in pieces.
|
|
516
|
+
* @param pieces - The file text in order.
|
|
449
517
|
* @param count - How many physical lines to drop.
|
|
450
|
-
* @returns The rest of the text.
|
|
518
|
+
* @returns The rest of the text, in pieces.
|
|
451
519
|
* @throws NativeParseError when the file has fewer lines than that.
|
|
452
520
|
*/
|
|
453
|
-
function afterLines(
|
|
454
|
-
let
|
|
455
|
-
for (
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
521
|
+
function* afterLines(pieces, count) {
|
|
522
|
+
let remaining = count;
|
|
523
|
+
for (const piece of pieces) {
|
|
524
|
+
let rest = piece;
|
|
525
|
+
while (remaining > 0) {
|
|
526
|
+
const next = rest.indexOf("\n");
|
|
527
|
+
if (next === -1) {
|
|
528
|
+
rest = "";
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
rest = rest.slice(next + 1);
|
|
532
|
+
remaining -= 1;
|
|
533
|
+
}
|
|
534
|
+
if (remaining === 0 && rest !== "") yield rest;
|
|
535
|
+
}
|
|
536
|
+
if (remaining > 0) throw new NativeParseError("csv-table", count - remaining + 1, `the file has fewer than ${String(count)} lines to skip before the header`);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* The text of a file as its pieces, with a leading byte order mark removed.
|
|
540
|
+
* @param text - The file text, or its pieces in order.
|
|
541
|
+
* @returns The pieces.
|
|
542
|
+
*/
|
|
543
|
+
function* withoutByteOrderMark(text) {
|
|
544
|
+
let first = true;
|
|
545
|
+
for (const piece of typeof text === "string" ? [text] : text) {
|
|
546
|
+
yield first && piece.startsWith(BYTE_ORDER_MARK) ? piece.slice(1) : piece;
|
|
547
|
+
first = false;
|
|
459
548
|
}
|
|
460
|
-
return text.slice(index);
|
|
461
549
|
}
|
|
462
550
|
/**
|
|
463
551
|
* Reads a numeric cell.
|
|
@@ -474,44 +562,59 @@ function numeric$1(cell, factor, column, line) {
|
|
|
474
562
|
return Number(cell) * factor;
|
|
475
563
|
}
|
|
476
564
|
/**
|
|
477
|
-
* The records of a table file after the lines the recipe skips and without its blank lines: the header first, then the rows.
|
|
478
|
-
* @param text - The file text, which may begin with a byte order mark.
|
|
565
|
+
* The records of a table file after the lines the recipe skips and without its blank lines: the header first, then the rows, one at a time.
|
|
566
|
+
* @param text - The file text, or its pieces in order, which may begin with a byte order mark.
|
|
479
567
|
* @param options - The recipe's table parser, of which the delimiter and the lines to skip are read.
|
|
480
|
-
* @returns
|
|
568
|
+
* @returns Each record.
|
|
481
569
|
* @throws NativeParseError when a quote is never closed or opens inside an unquoted field, or the file has fewer lines than the recipe skips.
|
|
482
570
|
*/
|
|
483
|
-
function tableRecords(text, options) {
|
|
484
|
-
|
|
571
|
+
function* tableRecords(text, options) {
|
|
572
|
+
const body = afterLines(withoutByteOrderMark(text), options.skipLines);
|
|
573
|
+
for (const record of records(body, DELIMITERS[options.delimiter], options.skipLines + 1)) if (record.fields.length > 1 || record.fields[0]?.trim() !== "") yield record;
|
|
485
574
|
}
|
|
486
575
|
/**
|
|
487
|
-
* Parses a delimited table into rows: the first record after `skipLines` lines is the header, each later record is a row, and every row carries every column but the id column, an empty cell being null. Cells are trimmed. A column the recipe declares numeric is read as a decimal number times its factor and any other column stays a string. `id` is the row's id, so a column of that name which is not the id column is named by its position. A row's id is the cell of the recipe's id column, which is then not also a column, or its 1-based row number when the recipe names none. Blank lines are skipped; a row with a different number of fields than the header, two columns with the same name, and an id cell that is empty are errors.
|
|
488
|
-
* @param text - The file text.
|
|
576
|
+
* Parses a delimited table into rows: the first record after `skipLines` lines is the header, each later record is a row, and every row carries every column but the id column (or, when the recipe lists `columns`, exactly those, so a wide table is not held cell for cell), an empty cell being null. Cells are trimmed. A column the recipe declares numeric is read as a decimal number times its factor and any other column stays a string. `id` is the row's id, so a column of that name which is not the id column is named by its position. A row's id is the cell of the recipe's id column, which is then not also a column, or its 1-based row number when the recipe names none. Blank lines are skipped; a row with a different number of fields than the header, two columns with the same name, and an id cell that is empty are errors.
|
|
577
|
+
* @param text - The file text, or its pieces in order.
|
|
489
578
|
* @param options - The recipe's table parser.
|
|
490
579
|
* @returns The rows, in file order.
|
|
491
|
-
* @throws NativeParseError naming the line of the first problem.
|
|
580
|
+
* @throws NativeParseError naming the line of the first problem, including a listed or numeric column the header lacks or a numeric column that is not listed.
|
|
492
581
|
*/
|
|
493
582
|
function parseCsvTable(text, options) {
|
|
494
|
-
const
|
|
583
|
+
const table = tableRecords(text, options);
|
|
584
|
+
const { value: header } = table.next();
|
|
495
585
|
if (header === void 0) throw new NativeParseError("csv-table", options.skipLines + 1, "the file has no header row");
|
|
496
586
|
const names = columnNames(header.fields, options.idColumn, header.line);
|
|
497
|
-
const { idColumn, numericColumns } = options;
|
|
498
|
-
|
|
587
|
+
const { idColumn, numericColumns, columns } = options;
|
|
588
|
+
const wanted = new Set(columns ?? names.filter((name) => name !== idColumn));
|
|
589
|
+
for (const named of [
|
|
590
|
+
...idColumn === null ? [] : [idColumn],
|
|
591
|
+
...numericColumns.map((entry) => entry.column),
|
|
592
|
+
...columns ?? []
|
|
593
|
+
]) if (!names.includes(named)) throw new NativeParseError("csv-table", header.line, `the recipe names the column ${named}, which the header does not have`);
|
|
594
|
+
if (idColumn !== null && wanted.has(idColumn)) throw new NativeParseError("csv-table", header.line, `the id column ${idColumn} is the row's id, so it is not listed among the columns`);
|
|
595
|
+
for (const { column } of numericColumns) if (!wanted.has(column)) throw new NativeParseError("csv-table", header.line, `the column ${column} is declared numeric but is not among the columns kept`);
|
|
499
596
|
const factors = new Map(numericColumns.map((entry) => [entry.column, entry.factor]));
|
|
500
|
-
|
|
597
|
+
const idPosition = idColumn === null ? -1 : names.indexOf(idColumn);
|
|
598
|
+
const kept = names.flatMap((name, position) => name !== idColumn && wanted.has(name) ? [{
|
|
599
|
+
name,
|
|
600
|
+
position,
|
|
601
|
+
factor: factors.get(name)
|
|
602
|
+
}] : []);
|
|
603
|
+
const nodes = [];
|
|
604
|
+
for (const record of table) {
|
|
501
605
|
if (record.fields.length !== names.length) throw new NativeParseError("csv-table", record.line, `the row has ${String(record.fields.length)} fields, the header has ${String(names.length)}`);
|
|
502
|
-
const
|
|
503
|
-
const id = idColumn === null ? String(rowIndex + 1) : cells.get(idColumn) ?? "";
|
|
606
|
+
const id = idColumn === null ? String(nodes.length + 1) : (record.fields[idPosition] ?? "").trim();
|
|
504
607
|
if (id === "") throw new NativeParseError("csv-table", record.line, `the id column ${idColumn ?? ""} is empty`);
|
|
505
|
-
const
|
|
506
|
-
const cell =
|
|
507
|
-
const factor = factors.get(name);
|
|
608
|
+
const cells = kept.map(({ name, position, factor }) => {
|
|
609
|
+
const cell = (record.fields[position] ?? "").trim();
|
|
508
610
|
return [name, factor === void 0 ? cell === "" ? null : cell : numeric$1(cell, factor, name, record.line)];
|
|
509
611
|
});
|
|
510
|
-
|
|
511
|
-
...Object.fromEntries(
|
|
612
|
+
nodes.push({
|
|
613
|
+
...Object.fromEntries(cells),
|
|
512
614
|
id
|
|
513
|
-
};
|
|
514
|
-
}
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
return { nodes };
|
|
515
618
|
}
|
|
516
619
|
//#endregion
|
|
517
620
|
//#region src/parsers/pajek.ts
|
|
@@ -647,12 +750,39 @@ function decode(bytes, parser) {
|
|
|
647
750
|
throw new NativeParseError(parser.format, 1, `the file is not valid utf-8 (${error instanceof Error ? error.message : String(error)}); a recipe for a latin1 file states encoding latin1`);
|
|
648
751
|
}
|
|
649
752
|
}
|
|
753
|
+
const KIBIBYTE = 1024;
|
|
754
|
+
/** Bytes decoded at a time for a format that is read in pieces. The decoder holds only a piece's text at once, so this bounds what a large table costs beyond its bytes; it is large enough that the per-piece work is small beside the piece. */
|
|
755
|
+
const DECODE_PIECE_BYTES = 8 * KIBIBYTE * KIBIBYTE;
|
|
756
|
+
/**
|
|
757
|
+
* Decodes a file's bytes in the recipe's encoding a piece at a time, as strictly as `decode`, so a file whose text would not fit in one string can still be read.
|
|
758
|
+
* @param bytes - The unpacked file.
|
|
759
|
+
* @param parser - The recipe's parser, which names the encoding.
|
|
760
|
+
* @param pieceBytes - How many bytes to decode at a time.
|
|
761
|
+
* @returns The text of each piece, in order; a character that spans two pieces is yielded whole with the later one.
|
|
762
|
+
* @throws NativeParseError when the bytes are not valid in the stated encoding.
|
|
763
|
+
*/
|
|
764
|
+
function* decodePieces(bytes, parser, pieceBytes = DECODE_PIECE_BYTES) {
|
|
765
|
+
const decoder = parser.encoding === "latin1" ? void 0 : new TextDecoder("utf-8", { fatal: true });
|
|
766
|
+
try {
|
|
767
|
+
for (let start = 0; start < bytes.length; start += pieceBytes) {
|
|
768
|
+
const piece = bytes.subarray(start, start + pieceBytes);
|
|
769
|
+
yield decoder === void 0 ? decode(piece, parser) : decoder.decode(piece, { stream: true });
|
|
770
|
+
}
|
|
771
|
+
if (decoder !== void 0) {
|
|
772
|
+
const rest = decoder.decode();
|
|
773
|
+
if (rest !== "") yield rest;
|
|
774
|
+
}
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (error instanceof NativeParseError) throw error;
|
|
777
|
+
throw new NativeParseError(parser.format, 1, `the file is not valid utf-8 (${error instanceof Error ? error.message : String(error)}); a recipe for a latin1 file states encoding latin1`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
650
780
|
function parseNative(bytes, parser) {
|
|
781
|
+
if (parser.format === "csv-table") return parseCsvTable(decodePieces(bytes, parser), parser);
|
|
651
782
|
const text = decode(bytes, parser);
|
|
652
783
|
if (parser.format === "edge-list") return parseEdgeList(text, parser);
|
|
653
784
|
if (parser.format === "matrix-market") return parseMatrixMarket(text);
|
|
654
785
|
if (parser.format === "gml") return parseGml(text);
|
|
655
|
-
if (parser.format === "csv-table") return parseCsvTable(text, parser);
|
|
656
786
|
return parsePajek(text);
|
|
657
787
|
}
|
|
658
788
|
/** The ustar header layout (POSIX.1-1988, pax(1) "ustar Interchange Format"): each field's byte offset and length. */
|
package/dist-cli/keepset.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { KeepsetError, NAME_SEPARATOR, countTarget, getDataset, listDatasets, li
|
|
|
4
4
|
import { fetchDataset, roundTripDataset } from "keepset/node";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
//#region package.json
|
|
7
|
-
var version = "0.
|
|
7
|
+
var version = "0.18.0";
|
|
8
8
|
const usage = `Usage: keepset <command> [options]
|
|
9
9
|
|
|
10
10
|
Commands:
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$defs":{"Crosswalk":{"$ref":"https://cdn.jsdelivr.net/npm/twindex@0.1.4/schemas/twindex.schema.json"},"Entry":{"additionalProperties":false,"properties":{"aliases":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*(?:\\/[A-Za-z0-9][A-Za-z0-9._-]*)*$","type":"string"},"minItems":1,"type":"array"},"category":{"enum":["graph/v1","or/v1"],"type":"string"},"citations":{"items":{"additionalProperties":false,"properties":{"doi":{"pattern":"^10\\.\\d{4,9}\\/\\S+$","type":"string"},"text":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["text"],"type":"object"},"type":"array"},"crosswalk":{"anyOf":[{"$ref":"#/$defs/Crosswalk"},{"additionalProperties":false,"properties":{"file":{"type":"string"}},"required":["file"],"type":"object"}]},"description":{"minLength":1,"type":"string"},"domain":{"minLength":1,"type":"string"},"expectedCounts":{"additionalProperties":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"licence":{"oneOf":[{"additionalProperties":false,"properties":{"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*$","type":"string"},"kind":{"const":"spdx","type":"string"}},"required":["kind","id"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"custom","type":"string"},"name":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["kind","name","url"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"unrecorded","type":"string"}},"required":["kind"],"type":"object"}]},"name":{"minLength":1,"type":"string"},"namespace":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"properties":{"additionalProperties":{"type":["string","number","boolean","null"]},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"recipe":{"oneOf":[{"additionalProperties":false,"properties":{"archive":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"none","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"gzip","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"zip","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"tar","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"tar.gz","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"}]},"parser":{"oneOf":[{"additionalProperties":false,"properties":{"commentPrefixes":{"items":{"minLength":1,"type":"string"},"type":"array"},"delimiter":{"enum":["detect","whitespace","tab","comma","space"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"edge-list","type":"string"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"sourceColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"targetColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","commentPrefixes","sourceColumn","targetColumn"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"matrix-market","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"gml","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"pajek","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"delimiter":{"enum":["comma","tab","semicolon"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"csv-table","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","idColumn","numericColumns"],"type":"object"}]},"source":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"status":{"const":"fetchable","type":"string"}},"required":["status","source","archive","parser"],"type":"object"},{"additionalProperties":false,"properties":{"reason":{"minLength":1,"type":"string"},"status":{"const":"not-fetchable","type":"string"}},"required":["status","reason"],"type":"object"}]},"sources":{"items":{"additionalProperties":false,"properties":{"retrieved":{"format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","type":"string"},"snapshot":{"additionalProperties":false,"properties":{"timestamp":{"pattern":"^\\d{8}(?:\\d{6})?$","type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","timestamp"],"type":"object"},"status":{"enum":["live","dead"],"type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","retrieved","status"],"type":"object"},"minItems":1,"type":"array"},"tags":{"items":{"minLength":1,"type":"string"},"type":"array"}},"required":["id","name","tags","category","sources","licence","citations","recipe","crosswalk","expectedCounts","properties"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.
|
|
1
|
+
{"$defs":{"Crosswalk":{"$ref":"https://cdn.jsdelivr.net/npm/twindex@0.1.4/schemas/twindex.schema.json"},"Entry":{"additionalProperties":false,"properties":{"aliases":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*(?:\\/[A-Za-z0-9][A-Za-z0-9._-]*)*$","type":"string"},"minItems":1,"type":"array"},"category":{"enum":["graph/v1","or/v1"],"type":"string"},"citations":{"items":{"additionalProperties":false,"properties":{"doi":{"pattern":"^10\\.\\d{4,9}\\/\\S+$","type":"string"},"text":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["text"],"type":"object"},"type":"array"},"crosswalk":{"anyOf":[{"$ref":"#/$defs/Crosswalk"},{"additionalProperties":false,"properties":{"file":{"type":"string"}},"required":["file"],"type":"object"}]},"description":{"minLength":1,"type":"string"},"domain":{"minLength":1,"type":"string"},"expectedCounts":{"additionalProperties":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"licence":{"oneOf":[{"additionalProperties":false,"properties":{"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*$","type":"string"},"kind":{"const":"spdx","type":"string"}},"required":["kind","id"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"custom","type":"string"},"name":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["kind","name","url"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"unrecorded","type":"string"}},"required":["kind"],"type":"object"}]},"name":{"minLength":1,"type":"string"},"namespace":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"properties":{"additionalProperties":{"type":["string","number","boolean","null"]},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"recipe":{"oneOf":[{"additionalProperties":false,"properties":{"archive":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"none","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"gzip","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"zip","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"tar","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"tar.gz","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"}]},"parser":{"oneOf":[{"additionalProperties":false,"properties":{"commentPrefixes":{"items":{"minLength":1,"type":"string"},"type":"array"},"delimiter":{"enum":["detect","whitespace","tab","comma","space"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"edge-list","type":"string"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"sourceColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"targetColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","commentPrefixes","sourceColumn","targetColumn"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"matrix-market","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"gml","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"pajek","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"columns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"type":"array"},"delimiter":{"enum":["comma","tab","semicolon"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"csv-table","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","idColumn","numericColumns"],"type":"object"}]},"source":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"status":{"const":"fetchable","type":"string"}},"required":["status","source","archive","parser"],"type":"object"},{"additionalProperties":false,"properties":{"reason":{"minLength":1,"type":"string"},"status":{"const":"not-fetchable","type":"string"}},"required":["status","reason"],"type":"object"}]},"sources":{"items":{"additionalProperties":false,"properties":{"retrieved":{"format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","type":"string"},"snapshot":{"additionalProperties":false,"properties":{"timestamp":{"pattern":"^\\d{8}(?:\\d{6})?$","type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","timestamp"],"type":"object"},"status":{"enum":["live","dead"],"type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","retrieved","status"],"type":"object"},"minItems":1,"type":"array"},"tags":{"items":{"minLength":1,"type":"string"},"type":"array"}},"required":["id","name","tags","category","sources","licence","citations","recipe","crosswalk","expectedCounts","properties"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.18.0/schemas/entry.schema.json","$ref":"#/$defs/Entry","$schema":"https://json-schema.org/draft/2020-12/schema"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$defs":{"Manifest":{"additionalProperties":false,"properties":{"description":{"minLength":1,"type":"string"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"lineage":{"additionalProperties":false,"properties":{"catalogue":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$","type":"string"}},"required":["catalogue","version","repository"],"type":"object"},"name":{"minLength":1,"type":"string"},"namespaceAliases":{"additionalProperties":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"propertyNames":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"type":"object"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$","type":"string"}},"required":["id","name","description","version","repository"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.
|
|
1
|
+
{"$defs":{"Manifest":{"additionalProperties":false,"properties":{"description":{"minLength":1,"type":"string"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"lineage":{"additionalProperties":false,"properties":{"catalogue":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$","type":"string"}},"required":["catalogue","version","repository"],"type":"object"},"name":{"minLength":1,"type":"string"},"namespaceAliases":{"additionalProperties":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"propertyNames":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"type":"object"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$","type":"string"}},"required":["id","name","description","version","repository"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.18.0/schemas/manifest.schema.json","$ref":"#/$defs/Manifest","$schema":"https://json-schema.org/draft/2020-12/schema"}
|