diff --git a/.changeset/shaggy-grapes-joke.md b/.changeset/shaggy-grapes-joke.md new file mode 100644 index 0000000..ae9a92d --- /dev/null +++ b/.changeset/shaggy-grapes-joke.md @@ -0,0 +1,5 @@ +--- +"rdf-transform-graph-imports": minor +--- + +When importing local files, add support for `code:extension` to keep `code:imports` same in both local and web documents diff --git a/.editorconfig b/.editorconfig index 88989c7..d5251ad 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,3 +4,6 @@ root = true indent_size = 2 indent_style = space insert_final_newline = true + +[*.snap] +indent_style = tab diff --git a/README.md b/README.md index ba553cc..0d6dd85 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ PREFIX sh: [ # relative URIs are relative to the document itself - code:imports <./property/identifier> ; + code:imports <./property/identifier.ttl> ; code:imports ; ] . @@ -77,3 +77,22 @@ const dataset = await rdf.dataset().import(stream.pipe(imports(rdf, { basePath: '/path/to/shape.ttl' }))) ``` + +### Reusing imports for local and remote documents + +You may face the situation that you want to import the same file from a local file and a remote resource +but do not publish the extension in the remote resource's URL. In this case, you must add a `code:extension` +property to the import. + +```turtle +PREFIX code: + +[ + code:imports <./property/identifier> ; + code:extension "ttl" ; +] . +``` + +If the above is a local file, e.g. `/path/to/shape.ttl`, the import will be resolved as `/path/to/property/identifier.ttl`. + +If the above is a remote resource, e.g. `https://example.com/shape`, the import will be resolved as `https://example.com/property/identifier.ttl`. diff --git a/index.ts b/index.ts index 0b791f2..f297071 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1,5 @@ import through2 from 'through2' +import type { AnyPointer } from 'clownface' import type { Quad } from '@rdfjs/types' import { resolveImport } from './lib/path.js' import Environment from './lib/env.js' @@ -11,24 +12,37 @@ interface Options { function transform(env: Environment, { basePath }: Options = {}) { const code = env.namespace('https://code.described.at/') + const importStatements: AnyPointer = env.clownface() + return through2.obj(async function (quad: Quad, _, done) { - if (quad.predicate.equals(code.imports)) { - try { - const importTarget = resolveImport(quad.object, basePath) + if (quad.predicate.equals(code.imports) || quad.predicate.equals(code.extension)) { + importStatements.dataset.add(quad) + return done() + } + + done(null, quad) + }, async function (done) { + try { + const imports = importStatements.has(code.imports) + .map(Import => { + const importPath = Import.out(code.imports).term! + const extension = Import.out(code.extension).value + return resolveImport(importPath, { basePath, extension }) + }) + + for (const importTarget of imports) { const importStream = fetchImport(env, importTarget) .pipe(transform(env, { basePath: importTarget })) for await (const importedQuad of importStream) { this.push(importedQuad) } - - return done() - } catch (e) { - return done(e) } + } catch (e: unknown) { + this.destroy(new Error(`Failed to import: ${e}`)) } - done(null, quad) + done() }) } diff --git a/lib/env.ts b/lib/env.ts index b12a47d..b93e29c 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -2,6 +2,7 @@ import type { Environment as RdfjsEnvironment } from '@rdfjs/environment/Environ import type { NamespaceFactory } from '@rdfjs/namespace/Factory.js' import type FsUtilsFactory from '@zazuko/rdf-utils-fs/Factory.js' import type { FetchFactory } from '@rdfjs/fetch-lite/Factory.js' +import type ClownfaceFactory from 'clownface/Factory.js' -type Environment = RdfjsEnvironment +type Environment = RdfjsEnvironment export default Environment diff --git a/lib/path.ts b/lib/path.ts index ed87010..b6e5e3b 100644 --- a/lib/path.ts +++ b/lib/path.ts @@ -2,7 +2,12 @@ import * as url from 'url' import type { Term } from '@rdfjs/types' import isURI from 'is-uri' -export function resolveImport(importNode: Term, basePath: string | URL | undefined) { +interface Options { + basePath?: string | URL + extension?: string +} + +export function resolveImport(importNode: Term, { basePath, extension }: Options = {}) { if (importNode.termType !== 'NamedNode') { throw new Error(`Import target must be a NamedNode, got ${importNode.termType}`) } @@ -12,5 +17,7 @@ export function resolveImport(importNode: Term, basePath: string | URL | undefin } const base = typeof basePath === 'string' ? url.pathToFileURL(basePath) : basePath - return url.fileURLToPath(new URL(importNode.value + '.ttl', base)) + + const filePath = extension ? `${importNode.value}.${extension}` : importNode.value + return url.fileURLToPath(new URL(filePath, base)) } diff --git a/test/__snapshots__/index.test.ts.snap b/test/__snapshots__/index.test.ts.snap index 3b8eb55..ac768b3 100644 --- a/test/__snapshots__/index.test.ts.snap +++ b/test/__snapshots__/index.test.ts.snap @@ -4,17 +4,17 @@ exports[`rdf-merge-stream imports remote resources from files 1`] = ` "@prefix sh: . @prefix schema: . - sh:minCount 1 ; - sh:maxCount 1 . +<> a sh:NodeShape ; + sh:property . - sh:minLength 3 ; + sh:path schema:identifier ; + sh:minLength 3 ; sh:and ( - ) ; - sh:path schema:identifier . + ) . -<> a sh:NodeShape ; - sh:property . + sh:minCount 1 ; + sh:maxCount 1 . " `; @@ -23,17 +23,17 @@ exports[`rdf-merge-stream imports remote resources from http 1`] = ` "@prefix sh: . @prefix schema: . - sh:minCount 1 ; - sh:maxCount 1 . + a sh:NodeShape ; + sh:property . - sh:minLength 3 ; + sh:path schema:identifier ; + sh:minLength 3 ; sh:and ( - ) ; - sh:path schema:identifier . + ) . - a sh:NodeShape ; - sh:property . + sh:minCount 1 ; + sh:maxCount 1 . " `; @@ -42,17 +42,17 @@ exports[`rdf-merge-stream merges file stream by relative path 1`] = ` "@prefix sh: . @prefix schema: . - sh:minCount 1 ; - sh:maxCount 1 . + a sh:NodeShape ; + sh:property . - sh:minLength 3 ; + sh:path schema:identifier ; + sh:minLength 3 ; sh:and ( - ) ; - sh:path schema:identifier . + ) . - a sh:NodeShape ; - sh:property . + sh:minCount 1 ; + sh:maxCount 1 . " `; diff --git a/test/resources/import-relative.ttl b/test/resources/import-relative.ttl index 225a126..61ea9ea 100644 --- a/test/resources/import-relative.ttl +++ b/test/resources/import-relative.ttl @@ -5,6 +5,7 @@ PREFIX sh: [ code:imports <./property/identifier> ; + code:extension "ttl" ; ] . shape: diff --git a/test/resources/property/identifier.ttl b/test/resources/property/identifier.ttl index 234d08a..eb9f283 100644 --- a/test/resources/property/identifier.ttl +++ b/test/resources/property/identifier.ttl @@ -5,6 +5,7 @@ prefix code: [ code:imports <./shared> ; + code:extension "ttl" ; ] . property:identifier