From 829dc79e72166ff28516d7ffd8eb165c7aafec64 Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Thu, 22 Feb 2024 10:47:29 +0100 Subject: [PATCH 1/3] feat: simple key/value pairs --- .changeset/shaggy-pants-hammer.md | 5 ++++ .editorconfig | 6 ++++ README.md | 46 +++++++++++++++++++------------ arguments.ts | 20 ++++++++++++++ test/arguments-shorthand.ttl | 20 ++++++++++++++ test/arguments.test.js | 30 ++++++++++++++++++++ 6 files changed, 110 insertions(+), 17 deletions(-) create mode 100644 .changeset/shaggy-pants-hammer.md create mode 100644 .editorconfig create mode 100644 test/arguments-shorthand.ttl diff --git a/.changeset/shaggy-pants-hammer.md b/.changeset/shaggy-pants-hammer.md new file mode 100644 index 0000000..0d16f7f --- /dev/null +++ b/.changeset/shaggy-pants-hammer.md @@ -0,0 +1,5 @@ +--- +"rdf-loader-code": minor +--- + +Add simpler syntax for creating key/value pair arguments (closes #37, closes #38) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..88989c7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +indent_size = 2 +indent_style = space +insert_final_newline = true diff --git a/README.md b/README.md index 19fd596..57a10be 100644 --- a/README.md +++ b/README.md @@ -200,13 +200,13 @@ loading of an argument map. Such arguments are declared as name/value pairs. ```turtle @prefix code: . +@prefix arg: . - code:arguments [ - code:name "first"; code:value "foo" - ], [ - code:name "second"; code:value "bar" - ] . + code:arguments ([ + arg:first "foo" ; + arg:second "bar" ; + ]) . ``` Executing the code below against the above triples will return an object containing the values @@ -220,20 +220,32 @@ Executing the code below against the above triples will return an object contain ] ``` -To make this method consistent with the positional flavor, the object will actually be wrapped -in an array as presented above. +#### Mixed arguments + +Both methods can be sed together to represent any kind of function signature. + +For example, to call a function as below ```js -import rdf from '@zazuko/env' -import loadArguments from 'rdf-loader-code/arguments.js' -import registry from './registry.js' -import dataset from './dataset.js' +myFunction('foo', 42, { bar: { baz: 'baz' } }) +``` -const term = rdf.namedNode('urn:call:string#startsWith') +You would declare the argument list as follows: -const argumentsObject = loadArguments( - rdf.clownface({ term, dataset }), - { - loaderRegistry: registry - }) +```turtle +@prefix code: . +@prefix arg: . + + + code:arguments + ( + "foo" + 42 + [ + arg:bar + [ + arg:baz "baz" + ] + ] + ) . ``` diff --git a/arguments.ts b/arguments.ts index d92bda9..ffa627c 100644 --- a/arguments.ts +++ b/arguments.ts @@ -38,11 +38,31 @@ async function parseArguments(args: MultiPointer, options: ParseArgument): Promi return [argObject] } +const argumentPropPattern = new RegExp(`^${ns.code('argument#').value}(.+)$`) + async function parseArgument(arg: AnyPointer, { context, variables, basePath, loaderRegistry }: ParseArgument): Promise { if (!isGraphPointer(arg)) { throw new Error('Cannot load argument. Expected a single node or RDF List.') } + const keyValuePairs = await [...arg.dataset.match(arg.term)] + .reduce(async (previous: Promise<[string, unknown][]>, quad) => { + const isArgumentProp = quad.predicate.value.match(argumentPropPattern) + if (isArgumentProp) { + const entries = await previous + const key = isArgumentProp[1] + const value = await parseArgument(clownface({ dataset: arg.dataset, term: quad.object }), { context, variables, basePath, loaderRegistry }) + entries.push([key, value]) + return entries + } + + return previous + }, Promise.resolve([])) + + if (keyValuePairs.length > 0) { + return Object.fromEntries(keyValuePairs) + } + const code = await loaderRegistry.load(arg, { context, variables, basePath }) if (typeof code !== 'undefined') { diff --git a/test/arguments-shorthand.ttl b/test/arguments-shorthand.ttl new file mode 100644 index 0000000..47fc720 --- /dev/null +++ b/test/arguments-shorthand.ttl @@ -0,0 +1,20 @@ +@prefix arg: . +@prefix xsd: . +@prefix code: . + + code:arguments + ( + [ + arg:foo "foo" ; + arg:bar true ; + arg:baz 10 ; + ] + ) . + + + code:arguments + ( + [ + arg:foo [ arg:bar [ arg:baz "buzz" ] ] ; + ] + ) . diff --git a/test/arguments.test.js b/test/arguments.test.js index ef2338f..ed6c52c 100644 --- a/test/arguments.test.js +++ b/test/arguments.test.js @@ -129,4 +129,34 @@ describe('arguments loader', () => { } }) }) + + describe('shorthand syntax', () => { + it('load key-value pairs from a single blank node', async () => { + const term = rdf.namedNode('http://example.com/single') + const dataset = await loadDataset('./arguments-shorthand.ttl') + + const result = await loader({ term, dataset }, { loaderRegistry: dummyLoaderRegistry }) + + deepStrictEqual(result, [{ + foo: 'foo', + bar: true, + baz: 10, + }]) + }) + + it('load deep key-value pairs', async () => { + const term = rdf.namedNode('http://example.com/nested') + const dataset = await loadDataset('./arguments-shorthand.ttl') + + const result = await loader({ term, dataset }, { loaderRegistry: dummyLoaderRegistry }) + + deepStrictEqual(result, [{ + foo: { + bar: { + baz: 'buzz', + }, + }, + }]) + }) + }) }) From 0e7e26529ce8b559ed2ed232dd8f7df623a8d7f9 Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Thu, 22 Feb 2024 12:55:07 +0100 Subject: [PATCH 2/3] refactor: better async when loading args --- arguments.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/arguments.ts b/arguments.ts index ffa627c..1f035a4 100644 --- a/arguments.ts +++ b/arguments.ts @@ -45,22 +45,21 @@ async function parseArgument(arg: AnyPointer, { context, variables, basePath, lo throw new Error('Cannot load argument. Expected a single node or RDF List.') } - const keyValuePairs = await [...arg.dataset.match(arg.term)] - .reduce(async (previous: Promise<[string, unknown][]>, quad) => { + const loadingNamedArgs = [...arg.dataset.match(arg.term)] + .map(async quad => { const isArgumentProp = quad.predicate.value.match(argumentPropPattern) if (isArgumentProp) { - const entries = await previous const key = isArgumentProp[1] const value = await parseArgument(clownface({ dataset: arg.dataset, term: quad.object }), { context, variables, basePath, loaderRegistry }) - entries.push([key, value]) - return entries + return <[string, unknown]>[key, value] } + return [] + }) - return previous - }, Promise.resolve([])) + const argMap = Object.fromEntries(await Promise.all(loadingNamedArgs)) - if (keyValuePairs.length > 0) { - return Object.fromEntries(keyValuePairs) + if (Object.keys(argMap).length > 0) { + return argMap } const code = await loaderRegistry.load(arg, { context, variables, basePath }) From 11e9d2abaf838c9f02fed075363b7362885be778 Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Thu, 22 Feb 2024 13:03:02 +0100 Subject: [PATCH 3/3] fix: prevent `undefined` keys --- arguments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arguments.ts b/arguments.ts index 1f035a4..192db3a 100644 --- a/arguments.ts +++ b/arguments.ts @@ -56,7 +56,7 @@ async function parseArgument(arg: AnyPointer, { context, variables, basePath, lo return [] }) - const argMap = Object.fromEntries(await Promise.all(loadingNamedArgs)) + const argMap = Object.fromEntries((await Promise.all(loadingNamedArgs)).filter(([key]) => !!key)) if (Object.keys(argMap).length > 0) { return argMap