Skip to content

Commit 690a34a

Browse files
authored
Merge pull request #2116 from rwblair/feat/citationcff
feat: Validate CITATION.cff
2 parents e0ada04 + 0c1b20a commit 690a34a

File tree

9 files changed

+129
-15
lines changed

9 files changed

+129
-15
lines changed

bids-validator/build.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const flags = parse(Deno.args, {
2727

2828
const version = await getVersion()
2929

30-
let versionPlugin = {
30+
const versionPlugin = {
3131
name: 'version',
3232
setup(build: esbuild.PluginBuild) {
3333
build.onResolve({ filter: /\.git-meta\.json/ }, (args) => ({
@@ -46,7 +46,7 @@ const result = await esbuild.build({
4646
format: 'esm',
4747
entryPoints: [MAIN_ENTRY, CLI_ENTRY],
4848
bundle: true,
49-
outdir: path.join('dist','validator'),
49+
outdir: path.join('dist', 'validator'),
5050
minify: flags.minify,
5151
target: ['chrome109', 'firefox109', 'safari16'],
5252
plugins: [

bids-validator/deno.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,21 @@
2727
]
2828
},
2929
"imports": {
30+
"@ajv": "npm:[email protected]",
31+
"@bids/schema": "jsr:@bids/[email protected]+7f1f6737",
32+
"@cliffy/command": "jsr:@cliffy/[email protected]",
33+
"@cliffy/table": "jsr:@cliffy/[email protected]",
34+
"@hed/validator": "npm:[email protected]",
35+
"@ignore": "npm:[email protected]",
36+
"@libs/xml": "jsr:@libs/[email protected]",
37+
"@mango/nifti": "npm:[email protected]",
3038
"@std/assert": "jsr:@std/[email protected]",
3139
"@std/fmt": "jsr:@std/[email protected]",
3240
"@std/fs": "jsr:@std/[email protected]",
3341
"@std/io": "jsr:@std/[email protected]",
3442
"@std/log": "jsr:@std/[email protected]",
3543
"@std/path": "jsr:@std/[email protected]",
36-
"@ajv": "npm:[email protected]",
37-
"@bids/schema": "jsr:@bids/[email protected]+a73c1b06",
38-
"@cliffy/table": "jsr:@cliffy/[email protected]",
39-
"@cliffy/command": "jsr:@cliffy/[email protected]",
40-
"@hed/validator": "npm:[email protected]",
41-
"@ignore": "npm:[email protected]",
42-
"@mango/nifti": "npm:[email protected]",
43-
"@libs/xml": "jsr:@libs/[email protected]"
44+
"@std/yaml": "jsr:@std/yaml@^1.0.4"
4445
},
4546
"tasks": {
4647
"test": "deno test -A src/tests/"

bids-validator/src/issues/list.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,20 @@ export const bidsIssues: IssueDefinitionRecord = {
162162
severity: 'error',
163163
reason: 'A json sidecar file was found without a corresponding data file',
164164
},
165+
BLACKLISTED_MODALITY: {
166+
severity: 'error',
167+
reason: 'The modality in this file is blacklisted through validator configuration.',
168+
},
169+
CITATION_CFF_VALIDATION_ERROR: {
170+
severity: 'error',
171+
reason:
172+
"The file does not pass validation using the citation.cff standard's schema." +
173+
'https://github.com/citation-file-format/citation-file-format/blob/main/schema-guide.md'
174+
},
175+
FILE_READ: {
176+
severity: 'error',
177+
reason: 'We were unable to read this file.'
178+
}
165179
}
166180

167181
const hedIssues: IssueDefinitionRecord = {
@@ -191,10 +205,6 @@ const hedIssues: IssueDefinitionRecord = {
191205
reason:
192206
"You should define 'HEDVersion' for this file. If you don't provide this information, the HED validation will use the latest version available.",
193207
},
194-
BLACKLISTED_MODALITY: {
195-
severity: 'error',
196-
reason: 'The modality in this file is blacklisted through validator configuration.',
197-
},
198208
}
199209

200210
export const hedOldToNewLookup: Record<number, Partial<keyof IssueDefinitionRecord>> = {

bids-validator/src/setup/loadSchema.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { assert, assertObjectMatch } from '@std/assert'
22
import { loadSchema } from './loadSchema.ts'
33

4-
Deno.test('schema yaml loader', async (t) => {
4+
Deno.test('schema loader', async (t) => {
55
await t.step('reads in top level files document', async () => {
66
const schemaDefs = await loadSchema()
77
// Look for some stable fields in top level files

bids-validator/src/validators/bids.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { sidecarWithoutDatafile, unusedStimulus } from './internal/unusedFile.ts
1717
import { type BIDSContext, BIDSContextDataset } from '../schema/context.ts'
1818
import type { parseOptions } from '../setup/options.ts'
1919
import { hedValidate } from './hed.ts'
20+
import { citationValidate } from './citation.ts'
2021

2122
/**
2223
* Ordering of checks to apply
@@ -32,6 +33,7 @@ const perContextChecks: ContextCheckFunction[] = [
3233
const perDSChecks: DSCheckFunction[] = [
3334
unusedStimulus,
3435
sidecarWithoutDatafile,
36+
citationValidate,
3537
]
3638

3739
/**
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { assert } from '@std/assert'
2+
import { pathsToTree } from '../files/filetree.ts'
3+
import { BIDSFileDeno } from '../files/deno.ts'
4+
import { citationValidate } from './citation.ts'
5+
import { BIDSContextDataset } from '../schema/context.ts'
6+
import { GenericSchema } from '../types/schema.ts'
7+
import { loadSchema } from '../setup/loadSchema.ts'
8+
9+
Deno.test('citation validation', async (t) => {
10+
const schema = await loadSchema()
11+
await t.step('no errors on the good citation.cff', async () => {
12+
const tree = pathsToTree(['CITATION.cff'])
13+
const dsContext = new BIDSContextDataset({ tree })
14+
const file = new BIDSFileDeno('tests/data/citation', 'good.cff')
15+
tree.files[0].text = () => file.text()
16+
await citationValidate({} as GenericSchema, dsContext)
17+
assert(dsContext.issues.size === 0)
18+
})
19+
await t.step('An error on the bad citation.cff', async () => {
20+
const tree = pathsToTree(['CITATION.cff'])
21+
const dsContext = new BIDSContextDataset({ tree })
22+
const file = new BIDSFileDeno('tests/data/citation', 'bad.cff')
23+
tree.files[0].text = () => file.text()
24+
await citationValidate({} as GenericSchema, dsContext)
25+
assert(dsContext.issues.get({ code: 'CITATION_CFF_VALIDATION_ERROR' }).length === 1)
26+
})
27+
})
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { GenericSchema } from '../types/schema.ts'
2+
import type { BIDSFile, FileTree } from '../types/filetree.ts'
3+
import type { BIDSContextDataset } from '../schema/context.ts'
4+
import { schema as citationSchema } from '@bids/schema/citation'
5+
import { compile } from './json.ts'
6+
import type { DefinedError } from '@ajv'
7+
import { parse } from '@std/yaml'
8+
9+
const citationFilename = 'CITATION.cff'
10+
11+
export async function citationValidate(
12+
schema: GenericSchema,
13+
dsContext: BIDSContextDataset,
14+
) {
15+
const citationFile = dsContext.tree.get(citationFilename)
16+
if (!citationFile || 'directories' in citationFile) return
17+
let citation: unknown = {}
18+
try {
19+
citation = parse(await citationFile.text())
20+
} catch (error) {
21+
dsContext.issues.add({
22+
code: 'FILE_READ',
23+
issueMessage: `Error from attempted read of file:\n${error}`,
24+
location: citationFilename,
25+
})
26+
return
27+
}
28+
const validate = compile(citationSchema)
29+
if (!validate(citation)) {
30+
for (const err of validate.errors as DefinedError[]) {
31+
dsContext.issues.add({
32+
code: 'CITATION_CFF_VALIDATION_ERROR',
33+
subCode: err['instancePath'],
34+
issueMessage: err['message'],
35+
location: citationFilename,
36+
})
37+
}
38+
}
39+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
cff-version: 1.2.0
2+
message: If you use this software, please cite it using these metadata.
3+
title: My Research Software
4+
abstract: This is my awesome research software. It does many things.
5+
version: 0.11.2
6+
date-released: "2021-07-18"
7+
identifiers:
8+
- description: This is the collection of archived snapshots of all versions of My Research Software
9+
type: doi
10+
value: "10.5281/zenodo.123456"
11+
- description: This is the archived snapshot of version 0.11.2 of My Research Software
12+
type: doi
13+
value: "10.5281/zenodo.123457"
14+
license: Apache-2.0
15+
repository-code: "https://github.com/citation-file-format/my-research-software"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
cff-version: 1.2.0
2+
message: If you use this software, please cite it using these metadata.
3+
title: My Research Software
4+
abstract: This is my awesome research software. It does many things.
5+
authors:
6+
- family-names: Druskat
7+
given-names: Stephan
8+
orcid: "https://orcid.org/1234-5678-9101-1121"
9+
- name: "The Research Software project"
10+
version: 0.11.2
11+
date-released: "2021-07-18"
12+
identifiers:
13+
- description: This is the collection of archived snapshots of all versions of My Research Software
14+
type: doi
15+
value: "10.5281/zenodo.123456"
16+
- description: This is the archived snapshot of version 0.11.2 of My Research Software
17+
type: doi
18+
value: "10.5281/zenodo.123457"
19+
license: Apache-2.0
20+
repository-code: "https://github.com/citation-file-format/my-research-software"

0 commit comments

Comments
 (0)