@shaclmate/cli 4.0.17 → 4.0.19

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/cli.js CHANGED
@@ -1,26 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import * as fs from "node:fs";
3
- import PrefixMap from "@rdfjs/prefix-map/PrefixMap.js";
4
- import { RdfDirectory, RdfFileSystemEntry } from "@rdfx/fs";
5
- import { AstJsonGenerator, Compiler, ShapesGraph, TsGenerator, ZodGenerator, } from "@shaclmate/compiler";
2
+ import fs from "node:fs";
3
+ import { AstJsonGenerator, Cx2Generator, TsGenerator, ZodGenerator, } from "@shaclmate/compiler";
6
4
  import { command, option, restPositionals, run, string, subcommands, } from "cmd-ts";
7
5
  import { ExistingPath } from "cmd-ts/dist/cjs/batteries/fs.js";
8
- import { DataFactory, Parser, Store, Writer } from "n3";
9
- import { pino } from "pino";
10
- import { EitherAsync } from "purify-ts";
11
- import SHACLValidator from "rdf-validate-shacl";
12
- import { shaclShaclDataset } from "./shaclShaclDataset.js";
6
+ import { generate } from "./commands/generate.js";
7
+ import { merge } from "./commands/merge.js";
8
+ import { validate } from "./commands/validate.js";
13
9
  const inputPaths = restPositionals({
14
10
  displayName: "inputPaths",
15
11
  description: "paths to RDF files or directories of RDF files containing SHACL shapes",
16
12
  type: ExistingPath,
17
13
  });
18
- const logger = pino({
19
- level: process.env["NODE_ENV"] === "development" ||
20
- process.env["NODE_ENV"] === "test"
21
- ? "debug"
22
- : "info",
23
- }, pino["destination"] ? pino.destination(2) : undefined);
24
14
  const outputFilePath = option({
25
15
  defaultValue: () => "",
26
16
  description: "path to a file to write output to; if not specified, write to stdout",
@@ -28,84 +18,10 @@ const outputFilePath = option({
28
18
  short: "o",
29
19
  type: string,
30
20
  });
31
- function generate({ generator, inputFiles, outputFilePath, }) {
32
- if (inputFiles.length === 0) {
33
- throw new Error("must specify at least one input shapes graph file path");
34
- }
35
- const inputParser = new Parser();
36
- const dataset = new Store();
37
- const iriPrefixes = [];
38
- for (const inputFile of inputFiles) {
39
- dataset.addQuads(inputParser.parse(fs.readFileSync(inputFile.path).toString(), null, (prefix, prefixNode) => {
40
- const existingIriPrefix = iriPrefixes.find((iriPrefix) => iriPrefix[0] === prefix || iriPrefix[1].equals(prefixNode));
41
- if (existingIriPrefix) {
42
- if (existingIriPrefix[0] !== prefix ||
43
- !existingIriPrefix[1].equals(prefixNode)) {
44
- logger.warn("conflicting prefix %s: %s", prefix, prefixNode.value);
45
- }
46
- return;
47
- }
48
- iriPrefixes.push([prefix, prefixNode]);
49
- }));
50
- }
51
- const prefixMap = new PrefixMap(iriPrefixes, { factory: DataFactory });
52
- {
53
- const validationReport = new SHACLValidator(shaclShaclDataset, {}).validate(dataset);
54
- if (!validationReport.conforms) {
55
- process.stderr.write("input is not valid SHACL:\n");
56
- const n3WriterPrefixes = {};
57
- for (const prefixEntry of prefixMap.entries()) {
58
- n3WriterPrefixes[prefixEntry[0]] = prefixEntry[1].value;
59
- }
60
- const n3Writer = new Writer({
61
- format: "text/turtle",
62
- prefixes: n3WriterPrefixes,
63
- });
64
- for (const quad of validationReport.dataset) {
65
- n3Writer.addQuad(quad);
66
- }
67
- n3Writer.end((_error, result) => process.stderr.write(result));
68
- return;
69
- }
70
- }
71
- ShapesGraph.builder()
72
- .parseDataset(dataset, { prefixMap })
73
- .map((_) => _.build())
74
- .chain((shapesGraph) => new Compiler({ generator }).compile(shapesGraph))
75
- .ifLeft((error) => {
76
- throw error;
77
- })
78
- .ifRight((output) => {
79
- if (outputFilePath.length === 0) {
80
- process.stdout.write(output);
81
- }
82
- else {
83
- fs.writeFileSync(outputFilePath, output);
84
- }
85
- });
86
- }
87
- async function resolveInputPaths(inputPaths) {
88
- return EitherAsync(async ({ liftEither }) => {
89
- const inputFiles = [];
90
- for (const inputPath of inputPaths) {
91
- const fileSystemEntry = await liftEither(await RdfFileSystemEntry.fromPath(inputPath));
92
- if (fileSystemEntry instanceof RdfDirectory) {
93
- for await (const inputFile of fileSystemEntry.files({
94
- recursive: true,
95
- })) {
96
- inputFiles.push(inputFile);
97
- }
98
- }
99
- else {
100
- inputFiles.push(fileSystemEntry);
101
- }
102
- }
103
- return inputFiles;
104
- });
105
- }
106
21
  run(subcommands({
107
22
  cmds: {
108
23
  generate: subcommands({
24
+ name: "generate",
109
25
  cmds: {
110
26
  "ast-json": command({
111
27
  name: "ast-json",
@@ -115,11 +31,35 @@ run(subcommands({
115
31
  outputFilePath,
116
32
  },
117
33
  handler: async ({ inputPaths, outputFilePath }) => {
118
- generate({
34
+ (await generate({
119
35
  generator: new AstJsonGenerator(),
120
- inputFiles: (await resolveInputPaths(inputPaths)).unsafeCoerce(),
36
+ inputPaths,
121
37
  outputFilePath,
122
- });
38
+ })).unsafeCoerce();
39
+ },
40
+ }),
41
+ cx2: command({
42
+ name: "cx2",
43
+ description: "generate Cytoscape Exchange Format Specification (Version 2)",
44
+ args: {
45
+ inputPaths,
46
+ outputFilePath,
47
+ visualPropertiesJsonFilePath: option({
48
+ description: "path to a file containing the visualProperties JSON object to include in the CX2 file",
49
+ long: "visual-properties-json-file-path",
50
+ type: ExistingPath,
51
+ }),
52
+ },
53
+ handler: async ({ inputPaths, outputFilePath, visualPropertiesJsonFilePath, }) => {
54
+ let visualProperties;
55
+ if (visualPropertiesJsonFilePath) {
56
+ visualProperties = JSON.parse((await fs.promises.readFile(visualPropertiesJsonFilePath)).toString("utf-8"));
57
+ }
58
+ (await generate({
59
+ generator: new Cx2Generator({ visualProperties }),
60
+ inputPaths,
61
+ outputFilePath,
62
+ })).unsafeCoerce();
123
63
  },
124
64
  }),
125
65
  ts: command({
@@ -130,11 +70,11 @@ run(subcommands({
130
70
  outputFilePath,
131
71
  },
132
72
  handler: async ({ inputPaths, outputFilePath }) => {
133
- generate({
73
+ (await generate({
134
74
  generator: new TsGenerator(),
135
- inputFiles: (await resolveInputPaths(inputPaths)).unsafeCoerce(),
75
+ inputPaths,
136
76
  outputFilePath,
137
- });
77
+ })).unsafeCoerce();
138
78
  },
139
79
  }),
140
80
  zod: command({
@@ -145,15 +85,50 @@ run(subcommands({
145
85
  outputFilePath,
146
86
  },
147
87
  handler: async ({ inputPaths, outputFilePath }) => {
148
- generate({
88
+ (await generate({
149
89
  generator: new ZodGenerator(),
150
- inputFiles: (await resolveInputPaths(inputPaths)).unsafeCoerce(),
90
+ inputPaths,
151
91
  outputFilePath,
152
- });
92
+ })).unsafeCoerce();
153
93
  },
154
94
  }),
155
95
  },
156
- name: "generate",
96
+ }),
97
+ merge: command({
98
+ name: "merge",
99
+ description: "merge one or more RDF files",
100
+ args: {
101
+ inputPaths,
102
+ outputFilePath,
103
+ },
104
+ handler: async ({ inputPaths, outputFilePath }) => {
105
+ (await merge({
106
+ inputPaths,
107
+ outputFilePath,
108
+ })).unsafeCoerce();
109
+ },
110
+ }),
111
+ validate: command({
112
+ name: "validate",
113
+ description: "validate a data graph with a shapes graph",
114
+ args: {
115
+ dataGraphPaths: restPositionals({
116
+ description: "path(s) to a file or directory of data graph files",
117
+ type: ExistingPath,
118
+ }),
119
+ shapesGraphPath: option({
120
+ description: "path to a file or directory of shapes graph files",
121
+ short: "s",
122
+ long: "--shapes-graph",
123
+ type: ExistingPath,
124
+ }),
125
+ },
126
+ handler: async ({ dataGraphPaths, shapesGraphPath }) => {
127
+ (await validate({
128
+ dataGraphPaths,
129
+ shapesGraphPaths: [shapesGraphPath],
130
+ })).unsafeCoerce();
131
+ },
157
132
  }),
158
133
  },
159
134
  description: "shaclmate command line interface",
@@ -0,0 +1,8 @@
1
+ import type { Generator } from "@shaclmate/compiler";
2
+ import { type Either } from "purify-ts";
3
+ export declare function generate({ generator, inputPaths, outputFilePath, }: {
4
+ generator: Generator;
5
+ inputPaths: readonly string[];
6
+ outputFilePath: string;
7
+ }): Promise<Either<Error, void>>;
8
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1,36 @@
1
+ import * as fs from "node:fs";
2
+ import Serializer from "@rdfjs/serializer-turtle";
3
+ import { Compiler, ShapesGraph } from "@shaclmate/compiler";
4
+ import { EitherAsync } from "purify-ts";
5
+ import SHACLValidator from "rdf-validate-shacl";
6
+ import { parseInputs } from "./parseInputs.js";
7
+ import { shaclShaclDataset } from "./shaclShaclDataset.js";
8
+ export async function generate({ generator, inputPaths, outputFilePath, }) {
9
+ return EitherAsync(async ({ liftEither }) => {
10
+ if (inputPaths.length === 0) {
11
+ throw new Error("must specify at least one input shapes graph file path");
12
+ }
13
+ const { dataset, prefixMap } = await liftEither(await parseInputs(inputPaths));
14
+ {
15
+ const validationReport = await new SHACLValidator(shaclShaclDataset, {}).validate(dataset);
16
+ if (!validationReport.conforms) {
17
+ process.stderr.write("input is not valid SHACL:\n");
18
+ process.stderr.write(new Serializer({
19
+ prefixes: prefixMap,
20
+ }).transform(validationReport.dataset));
21
+ return;
22
+ }
23
+ }
24
+ const output = await liftEither(ShapesGraph.builder()
25
+ .parseDataset(dataset, { prefixMap })
26
+ .map((_) => _.build())
27
+ .chain((shapesGraph) => new Compiler({ generator }).compile(shapesGraph)));
28
+ if (outputFilePath.length === 0) {
29
+ process.stdout.write(output);
30
+ }
31
+ else {
32
+ await fs.promises.writeFile(outputFilePath, output);
33
+ }
34
+ });
35
+ }
36
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1,6 @@
1
+ import { type Either } from "purify-ts";
2
+ export declare function merge({ inputPaths, outputFilePath, }: {
3
+ inputPaths: readonly string[];
4
+ outputFilePath: string;
5
+ }): Promise<Either<Error, void>>;
6
+ //# sourceMappingURL=merge.d.ts.map
@@ -0,0 +1,17 @@
1
+ import fs from "node:fs";
2
+ import Serializer from "@rdfjs/serializer-turtle";
3
+ import { EitherAsync } from "purify-ts";
4
+ import { parseInputs } from "./parseInputs.js";
5
+ export async function merge({ inputPaths, outputFilePath, }) {
6
+ return EitherAsync(async ({ liftEither }) => {
7
+ const { dataset, prefixMap } = await liftEither(await parseInputs(inputPaths));
8
+ const output = new Serializer({ prefixes: prefixMap }).transform(dataset);
9
+ if (outputFilePath.length === 0) {
10
+ process.stdout.write(output);
11
+ }
12
+ else {
13
+ await fs.promises.writeFile(outputFilePath, output);
14
+ }
15
+ });
16
+ }
17
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1,8 @@
1
+ import PrefixMap from "@rdfjs/prefix-map/PrefixMap.js";
2
+ import type { DatasetCore } from "@rdfjs/types";
3
+ import { Either } from "purify-ts";
4
+ export declare function parseInputs(inputPaths: readonly string[]): Promise<Either<Error, {
5
+ dataset: DatasetCore;
6
+ prefixMap: PrefixMap;
7
+ }>>;
8
+ //# sourceMappingURL=parseInputs.d.ts.map
@@ -0,0 +1,49 @@
1
+ import DataFactory from "@rdfjs/data-model";
2
+ import DatasetFactory from "@rdfjs/dataset";
3
+ import PrefixMap from "@rdfjs/prefix-map/PrefixMap.js";
4
+ import { RdfDirectory, RdfFileSystemEntry } from "@rdfx/fs";
5
+ import { Either, EitherAsync, Left } from "purify-ts";
6
+ import { logger } from "../logger.js";
7
+ export async function parseInputs(inputPaths) {
8
+ return EitherAsync(async ({ liftEither }) => {
9
+ const dataset = DatasetFactory.dataset();
10
+ const prefixMapInit = [];
11
+ for (const inputPath of inputPaths) {
12
+ const parseInputFileSystemEntry = async (inputFileSystemEntry) => {
13
+ if (inputFileSystemEntry instanceof RdfDirectory) {
14
+ for await (const file of inputFileSystemEntry.files({
15
+ recursive: true,
16
+ })) {
17
+ await parseInputFileSystemEntry(file);
18
+ }
19
+ return;
20
+ }
21
+ const inputFile = inputFileSystemEntry;
22
+ await liftEither(await new Promise((resolve) => {
23
+ const inputQuadStream = inputFile.parse();
24
+ inputQuadStream.on("data", (quad) => dataset.add(quad));
25
+ inputQuadStream.on("end", () => resolve(Either.of(null)));
26
+ inputQuadStream.on("error", (error) => resolve(Left(error)));
27
+ inputQuadStream.on("prefix", (prefix, prefixNode) => {
28
+ const existingPrefixMapEntry = prefixMapInit.find((prefixMapEntry) => prefixMapEntry[0] === prefix ||
29
+ prefixMapEntry[1].equals(prefixNode));
30
+ if (existingPrefixMapEntry) {
31
+ if (existingPrefixMapEntry[0] !== prefix ||
32
+ !existingPrefixMapEntry[1].equals(prefixNode)) {
33
+ logger.warn("conflicting prefix %s: %s", prefix, prefixNode.value);
34
+ }
35
+ return;
36
+ }
37
+ prefixMapInit.push([prefix, prefixNode]);
38
+ });
39
+ }));
40
+ };
41
+ await parseInputFileSystemEntry(await liftEither(await RdfFileSystemEntry.fromPath(inputPath)));
42
+ }
43
+ return {
44
+ dataset,
45
+ prefixMap: new PrefixMap(prefixMapInit, { factory: DataFactory }),
46
+ };
47
+ });
48
+ }
49
+ //# sourceMappingURL=parseInputs.js.map
@@ -0,0 +1,6 @@
1
+ import { type Either } from "purify-ts";
2
+ export declare function validate({ dataGraphPaths, shapesGraphPaths, }: {
3
+ dataGraphPaths: readonly string[];
4
+ shapesGraphPaths: readonly string[];
5
+ }): Promise<Either<Error, void>>;
6
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1,94 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import Serializer from "@rdfjs/serializer-turtle";
5
+ import { EitherAsync } from "purify-ts";
6
+ import SHACLValidator from "rdf-validate-shacl";
7
+ import * as tmp from "tmp-promise";
8
+ import which from "which";
9
+ import { logger } from "../logger.js";
10
+ import { parseInputs } from "./parseInputs.js";
11
+ function execFileStreaming(cmd, args) {
12
+ return new Promise((resolve) => {
13
+ const child = spawn(cmd, args, { stdio: "inherit" });
14
+ child.on("close", (code) => {
15
+ resolve(code);
16
+ });
17
+ });
18
+ }
19
+ export async function validate({ dataGraphPaths, shapesGraphPaths, }) {
20
+ return EitherAsync(async ({ liftEither }) => {
21
+ const { dataset: dataGraphDataset, prefixMap: dataGraphPrefixMap } = await liftEither(await parseInputs(dataGraphPaths));
22
+ if (dataGraphDataset.size === 0) {
23
+ throw new Error("data graph is empty!");
24
+ }
25
+ logger.info("data graph size: %d", dataGraphDataset.size);
26
+ const { dataset: shapesGraphDataset } = await liftEither(await parseInputs(shapesGraphPaths));
27
+ if (shapesGraphDataset.size === 0) {
28
+ throw new Error("shapes graph is empty!");
29
+ }
30
+ logger.info("shapes graph size: %d", shapesGraphDataset.size);
31
+ const serializer = new Serializer({
32
+ prefixes: dataGraphPrefixMap,
33
+ });
34
+ logger.info("validating with rdf-shacl-validate");
35
+ const validationReport = await new SHACLValidator(shapesGraphDataset, {}).validate(dataGraphDataset);
36
+ if (validationReport.conforms) {
37
+ logger.info("validated with rdf-shacl-validate: conforms");
38
+ }
39
+ else {
40
+ logger.info("validated with rdf-shacl-validate: does not conform");
41
+ process.stderr.write(serializer.transform(validationReport.dataset));
42
+ return;
43
+ }
44
+ const jenaShaclFilePath = await which("shacl", { nothrow: true });
45
+ const pyshaclFilePath = await which("pyshacl", { nothrow: true });
46
+ if (jenaShaclFilePath === null && pyshaclFilePath === null) {
47
+ logger.debug("neither Jena nor pyshacl found on PATH");
48
+ return;
49
+ }
50
+ await tmp.withDir(async ({ path: tmpDirectoryPath }) => {
51
+ const dataGraphFilePath = path.join(tmpDirectoryPath, "data.ttl");
52
+ logger.debug("writing data graph to %s", dataGraphFilePath);
53
+ await fs.writeFile(dataGraphFilePath, serializer.transform(dataGraphDataset));
54
+ logger.debug("wrote data graph to %s", dataGraphFilePath);
55
+ const shapesGraphFilePath = path.join(tmpDirectoryPath, "shapes.ttl");
56
+ logger.debug("writing shapes graph to %s", shapesGraphFilePath);
57
+ await fs.writeFile(shapesGraphFilePath, serializer.transform(shapesGraphDataset));
58
+ logger.debug("wrote shapes graph to %s", shapesGraphFilePath);
59
+ if (jenaShaclFilePath !== null) {
60
+ const args = [
61
+ "validate",
62
+ "--data",
63
+ dataGraphFilePath,
64
+ "--shapes",
65
+ shapesGraphFilePath,
66
+ ];
67
+ logger.info("validating with Jena (args=%s)", args);
68
+ const code = await execFileStreaming(jenaShaclFilePath, args);
69
+ logger.info("validated with Jena: %s", code === 0 ? "conforms" : "does not conform");
70
+ if (code !== 0) {
71
+ return;
72
+ }
73
+ }
74
+ else {
75
+ logger.info("Jena not found on PATH, skipping");
76
+ }
77
+ if (pyshaclFilePath !== null) {
78
+ const args = ["-s", shapesGraphFilePath, dataGraphFilePath];
79
+ logger.info("validating with pyshacl (args=%s)", args);
80
+ const code = await execFileStreaming(pyshaclFilePath, args);
81
+ logger.info("validated with pyshacl: %s", code === 0 ? "conforms" : "does not conform");
82
+ if (code !== 0) {
83
+ return;
84
+ }
85
+ }
86
+ else {
87
+ logger.info("pyshacl not found on PATH, skipping");
88
+ }
89
+ }, {
90
+ unsafeCleanup: true,
91
+ });
92
+ });
93
+ }
94
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1,2 @@
1
+ export declare const logger: import("pino").Logger<never>;
2
+ //# sourceMappingURL=logger.d.ts.map
package/dist/logger.js ADDED
@@ -0,0 +1,14 @@
1
+ import { pino } from "pino";
2
+ export const logger = pino({
3
+ level: process.env["NODE_ENV"] === "development" ||
4
+ process.env["NODE_ENV"] === "test"
5
+ ? "debug"
6
+ : "info",
7
+ transport: {
8
+ target: "pino-pretty",
9
+ options: {
10
+ colorize: true,
11
+ },
12
+ },
13
+ }, pino["destination"] ? pino.destination(2) : undefined);
14
+ //# sourceMappingURL=logger.js.map
package/package.json CHANGED
@@ -3,24 +3,34 @@
3
3
  "shaclmate": "dist/cli.js"
4
4
  },
5
5
  "dependencies": {
6
- "@shaclmate/compiler": "4.0.17",
6
+ "@shaclmate/compiler": "4.0.19",
7
+ "@rdfjs/data-model": "~2.1.1",
8
+ "@rdfjs/dataset": "~2.0.2",
7
9
  "@rdfjs/prefix-map": "~0.1.2",
8
- "@rdfjs/types": "~2.0.1",
10
+ "@rdfjs/serializer-turtle": "~1.1.5",
9
11
  "@rdfx/fs": "0.0.7",
10
- "@types/n3": "~1.26.0",
11
- "@types/rdfjs__prefix-map": "~0.1.5",
12
- "cmd-ts": "~0.13.0",
13
- "n3": "~1.26.0",
12
+ "cmd-ts": "~0.15.0",
14
13
  "pino": "~9.1.0",
14
+ "pino-pretty": "~13.1.2",
15
15
  "purify-ts": "~2.1.4",
16
- "rdf-validate-shacl": "0.5.8"
16
+ "rdf-validate-shacl": "0.6.5",
17
+ "tmp-promise": "~3.0.3",
18
+ "which": "~6.0.1"
17
19
  },
18
20
  "description": "Command line program to generate TypeScript code from SHACL shapes",
19
- "devDependencies": {},
21
+ "devDependencies": {
22
+ "@rdfjs/types": "~2.0.1",
23
+ "@types/rdfjs__data-model": "~2.0.9",
24
+ "@types/rdfjs__dataset": "~2.0.7",
25
+ "@types/rdfjs__prefix-map": "~0.1.5",
26
+ "@types/which": "~3.0.4"
27
+ },
20
28
  "files": [
21
29
  "README.md",
22
30
  "dist/*.d.ts",
23
- "dist/*.js"
31
+ "dist/*.js",
32
+ "dist/commands/*.d.ts",
33
+ "dist/commands/*.js"
24
34
  ],
25
35
  "homepage": "https://github.com/minorg/shaclmate",
26
36
  "keywords": [
@@ -46,5 +56,5 @@
46
56
  },
47
57
  "type": "module",
48
58
  "types": "./dist/index.d.ts",
49
- "version": "4.0.17"
59
+ "version": "4.0.19"
50
60
  }