Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Env replace #25

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rdf-lens",
"version": "1.3.1",
"version": "1.3.3",
"main": "./dist/index.js",
"type": "module",
"exports": {
Expand Down
13 changes: 13 additions & 0 deletions src/lens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,19 @@ export class BasicLensM<C, T> extends BasicLens<C, T[]> {
return this.execute(c, states).filter(fn);
});
}

reduce<F>(
lens: BasicLens<[T, F], F>,
start: BasicLens<C, F>,
): BasicLens<C, F> {
return new BasicLens((c, _, states) => {
const st = this.and(start).map(([ts, f]) => {
return ts.reduce((acc, v) => lens.execute([v, acc], states), f);
});

return st.execute(c, states);
});
}
}

export function pred(pred?: Term): BasicLensM<Cont, Cont> {
Expand Down
1 change: 1 addition & 0 deletions src/ontology.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ export const RDFL = createUriAndTermNamespace(
"EnvVariable",
"envKey",
"envDefault",
"datatype",
);
89 changes: 80 additions & 9 deletions src/shacl.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { Quad, Term } from "@rdfjs/types";
import { RDF, XSD } from "@treecg/types";
import { Quad, Quad_Object, Term } from "@rdfjs/types";
import { PROV, RDF, XSD } from "@treecg/types";
import {
BasicLens,
BasicLensM,
Cont,
empty,
invPred,
match,
pred,
subject,
subjects,
unique,
} from "./lens";

import { DataFactory, NamedNode } from "rdf-data-factory";
import { RDFL, RDFS, SHACL } from "./ontology";

const { literal } = new DataFactory();
const { literal, quad } = new DataFactory();

export interface ShapeField {
name: string;
Expand Down Expand Up @@ -78,7 +80,6 @@ export function toLens(
.map((x) => {
const out = x.filter((x) => !!x);
if (maxCount < 2) {
// console.log(out, x);
return out[0];
} else {
return out;
Expand Down Expand Up @@ -288,8 +289,9 @@ function dataTypeToExtract(dataType: Term, t: Term): unknown {
if (dataType.equals(XSD.terms.dateTime)) return new Date(t.value);
if (dataType.equals(XSD.terms.custom("boolean"))) return t.value === "true";
if (dataType.equals(XSD.terms.custom("iri"))) return new NamedNode(t.value);
if (dataType.equals(XSD.terms.custom("anyURI")))
if (dataType.equals(XSD.terms.custom("anyURI"))) {
return new NamedNode(t.value);
}

return t;
}
Expand All @@ -302,7 +304,7 @@ type SubClasses = {
[clazz: string]: string;
};

function envLens(dataType: Term): BasicLens<Cont, unknown> {
function envLens(dataType?: Term): BasicLens<Cont, unknown> {
const checkType = pred(RDF.terms.type)
.thenSome(
new BasicLens(({ id }) => {
Expand All @@ -326,12 +328,18 @@ function envLens(dataType: Term): BasicLens<Cont, unknown> {
defaultValue: found?.id.value,
}));

const envDatatype = pred(RDFL.terms.datatype)
.one(undefined)
.map((found) => ({ dt: found?.id }));

return checkType
.and(envName, defaultValue)
.map(([_thing, { key }, { defaultValue }]) => {
.and(envName, defaultValue, envDatatype)
.map(([_thing, { key }, { defaultValue }, { dt }]) => {
const value = process.env[key] || defaultValue;
const thisDt = dataType || dt || XSD.terms.custom("literal");

if (value) {
return dataTypeToExtract(dataType, literal(value));
return dataTypeToExtract(thisDt, literal(value));
} else {
throw (
"Nothing set for ENV " +
Expand All @@ -342,6 +350,69 @@ function envLens(dataType: Term): BasicLens<Cont, unknown> {
});
}

export function sliced<T>(): BasicLens<T[], T[]> {
return new BasicLens((x) => x.slice());
}

function remove_cbd(quads: Quad[], subject: Term) {
const toRemoves = [subject];
while (toRemoves.length > 0) {
const toRemove = toRemoves.pop();

quads = quads.filter((q) => {
if (q.subject.equals(toRemove)) {
if (q.object.termType === "BlankNode") {
toRemoves.push(q.object);
}
return false;
} else {
return true;
}
});
}
return quads;
}

export function envReplace(): BasicLens<Quad[], Quad[]> {
const shouldReplace = empty<[Cont, Quad[]]>()
.map((x) => x[0])
.then(envLens().and(empty<Cont>().map((x) => x.id)))
.map(([value, id]) => ({
value,
id,
}));

const reduce: BasicLens<[Cont, Quad[]], Quad[]> = shouldReplace
.and(empty<[Cont, Quad[]]>().map((x) => x[1]))
.map(([{ value, id }, quads]) => {
return remove_cbd(
quads.map((q) => {
if (q.object.equals(id)) {
return quad(
q.subject,
q.predicate,
<Quad_Object>value,
q.graph,
);
} else {
return q;
}
}),
id,
);
});

const actualReplace = match(
undefined,
RDF.terms.type,
RDFL.terms.EnvVariable,
)
.thenAll(subject)
.reduce(reduce, empty<Quad[]>());

return sliced().then(actualReplace);
}

function extractLeaf(datatype: Term): BasicLens<Cont, unknown> {
return envLens(datatype).or(
empty<Cont>().map((item) => dataTypeToExtract(datatype, item.id)),
Expand Down
70 changes: 69 additions & 1 deletion test/shacl2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, test } from "vitest";
import { Quad } from "@rdfjs/types";
import { RDF } from "@treecg/types";
import { Parser } from "n3";
import { extractShapes } from "../src/shacl";
import { envReplace, extractShapes } from "../src/shacl";
import { RDFL } from "../src/ontology";
import { BasicLensM, Cont } from "../src";

Expand Down Expand Up @@ -564,4 +564,72 @@ ${prefixes}
expect(obj.str).toBe("true");
});
});

describe("Env replace works", () => {
const data = `
${prefixes}
<a> <b> <c>.
<1> js:test [
a rdfl:EnvVariable;
rdfl:envDefault js:generatedAtTime;
rdfl:envKey "key1"
], <c>.

<2> js:test [
a rdfl:EnvVariable;
rdfl:envDefault js:generatedAtTime;
rdfl:envKey "key2";
rdfl:datatype xsd:iri
].

<3> js:test [
a rdfl:EnvVariable;
rdfl:envDefault js:generatedAtTime;
rdfl:envKey "notset";
rdfl:datatype xsd:iri
].
`;

const quads = parseQuads(data);
const l1 = quads.length;

process.env["key1"] = "true";
process.env["key2"] = "42";

const lens = envReplace();
const newQuads = lens.execute(quads);

test("Didn't change original quads", () => {
expect(quads.length).toBe(l1);
});

test("Did change new quads", () => {
expect(newQuads.length).toBe(5);
});

test("Datatype defaults to literal", () => {
const q1 = newQuads.find((x) => x.subject.value == "1")!;
expect(q1.object.termType).toBe("Literal");
expect(q1.object.value).toBe("true");
});

test("Datatype iri happens", () => {
const q1 = newQuads.find((x) => x.subject.value == "2")!;
expect(q1.object.termType).toBe("NamedNode");
expect(q1.object.value).toBe("42");
});

test("Default value works", () => {
const q1 = newQuads.find((x) => x.subject.value == "3")!;
expect(q1.object.termType).toBe("NamedNode");
expect(q1.object.value).toBe(
"https://w3id.org/conn/js#generatedAtTime",
);
});

test("Doesnt fail when no env to replace", () => {
const newNewQuads = envReplace().execute(newQuads);
expect(newNewQuads.length).toBe(newQuads.length);
});
});
});
Loading