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

feat: Validate CITATION.cff #2116

Merged
merged 8 commits into from
Aug 30, 2024
4 changes: 2 additions & 2 deletions bids-validator/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const flags = parse(Deno.args, {

const version = await getVersion()

let versionPlugin = {
const versionPlugin = {
name: 'version',
setup(build: esbuild.PluginBuild) {
build.onResolve({ filter: /\.git-meta\.json/ }, (args) => ({
Expand All @@ -46,7 +46,7 @@ const result = await esbuild.build({
format: 'esm',
entryPoints: [MAIN_ENTRY, CLI_ENTRY],
bundle: true,
outdir: path.join('dist','validator'),
outdir: path.join('dist', 'validator'),
minify: flags.minify,
target: ['chrome109', 'firefox109', 'safari16'],
plugins: [
Expand Down
17 changes: 9 additions & 8 deletions bids-validator/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,21 @@
]
},
"imports": {
"@ajv": "npm:[email protected]",
"@bids/schema": "jsr:@bids/[email protected]+7f1f6737",
"@cliffy/command": "jsr:@cliffy/[email protected]",
"@cliffy/table": "jsr:@cliffy/[email protected]",
"@hed/validator": "npm:[email protected]",
"@ignore": "npm:[email protected]",
"@libs/xml": "jsr:@libs/[email protected]",
"@mango/nifti": "npm:[email protected]",
"@std/assert": "jsr:@std/[email protected]",
"@std/fmt": "jsr:@std/[email protected]",
"@std/fs": "jsr:@std/[email protected]",
"@std/io": "jsr:@std/[email protected]",
"@std/log": "jsr:@std/[email protected]",
"@std/path": "jsr:@std/[email protected]",
"@ajv": "npm:[email protected]",
"@bids/schema": "jsr:@bids/[email protected]+a73c1b06",
"@cliffy/table": "jsr:@cliffy/[email protected]",
"@cliffy/command": "jsr:@cliffy/[email protected]",
"@hed/validator": "npm:[email protected]",
"@ignore": "npm:[email protected]",
"@mango/nifti": "npm:[email protected]",
"@libs/xml": "jsr:@libs/[email protected]"
"@std/yaml": "jsr:@std/yaml@^1.0.4"
},
"tasks": {
"test": "deno test -A src/tests/"
Expand Down
18 changes: 14 additions & 4 deletions bids-validator/src/issues/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,20 @@ export const bidsIssues: IssueDefinitionRecord = {
severity: 'error',
reason: 'A json sidecar file was found without a corresponding data file',
},
BLACKLISTED_MODALITY: {
severity: 'error',
reason: 'The modality in this file is blacklisted through validator configuration.',
},
CITATION_CFF_VALIDATION_ERROR: {
severity: 'error',
reason:
"The file does not pass validation using the citation.cff standard's schema." +
'https://github.com/citation-file-format/citation-file-format/blob/main/schema-guide.md'
},
FILE_READ: {
severity: 'error',
reason: 'We were unable to read this file.'
}
}

const hedIssues: IssueDefinitionRecord = {
Expand Down Expand Up @@ -191,10 +205,6 @@ const hedIssues: IssueDefinitionRecord = {
reason:
"You should define 'HEDVersion' for this file. If you don't provide this information, the HED validation will use the latest version available.",
},
BLACKLISTED_MODALITY: {
severity: 'error',
reason: 'The modality in this file is blacklisted through validator configuration.',
},
}

export const hedOldToNewLookup: Record<number, Partial<keyof IssueDefinitionRecord>> = {
Expand Down
2 changes: 1 addition & 1 deletion bids-validator/src/setup/loadSchema.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { assert, assertObjectMatch } from '@std/assert'
import { loadSchema } from './loadSchema.ts'

Deno.test('schema yaml loader', async (t) => {
Deno.test('schema loader', async (t) => {
await t.step('reads in top level files document', async () => {
const schemaDefs = await loadSchema()
// Look for some stable fields in top level files
Expand Down
2 changes: 2 additions & 0 deletions bids-validator/src/validators/bids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { sidecarWithoutDatafile, unusedStimulus } from './internal/unusedFile.ts
import { type BIDSContext, BIDSContextDataset } from '../schema/context.ts'
import type { parseOptions } from '../setup/options.ts'
import { hedValidate } from './hed.ts'
import { citationValidate } from './citation.ts'

/**
* Ordering of checks to apply
Expand All @@ -32,6 +33,7 @@ const perContextChecks: ContextCheckFunction[] = [
const perDSChecks: DSCheckFunction[] = [
unusedStimulus,
sidecarWithoutDatafile,
citationValidate,
]

/**
Expand Down
27 changes: 27 additions & 0 deletions bids-validator/src/validators/citation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { assert } from '@std/assert'
import { pathsToTree } from '../files/filetree.ts'
import { BIDSFileDeno } from '../files/deno.ts'
import { citationValidate } from './citation.ts'
import { BIDSContextDataset } from '../schema/context.ts'
import { GenericSchema } from '../types/schema.ts'
import { loadSchema } from '../setup/loadSchema.ts'

Deno.test('citation validation', async (t) => {
const schema = await loadSchema()
await t.step('no errors on the good citation.cff', async () => {
const tree = pathsToTree(['CITATION.cff'])
const dsContext = new BIDSContextDataset({ tree })
const file = new BIDSFileDeno('tests/data/citation', 'good.cff')
tree.files[0].text = () => file.text()
await citationValidate({} as GenericSchema, dsContext)
assert(dsContext.issues.size === 0)
})
await t.step('An error on the bad citation.cff', async () => {
const tree = pathsToTree(['CITATION.cff'])
const dsContext = new BIDSContextDataset({ tree })
const file = new BIDSFileDeno('tests/data/citation', 'bad.cff')
tree.files[0].text = () => file.text()
await citationValidate({} as GenericSchema, dsContext)
assert(dsContext.issues.get({ code: 'CITATION_CFF_VALIDATION_ERROR' }).length === 1)
})
})
39 changes: 39 additions & 0 deletions bids-validator/src/validators/citation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { GenericSchema } from '../types/schema.ts'
import type { BIDSFile, FileTree } from '../types/filetree.ts'
import type { BIDSContextDataset } from '../schema/context.ts'
import { schema as citationSchema } from '@bids/schema/citation'
import { compile } from './json.ts'
import type { DefinedError } from '@ajv'
import { parse } from '@std/yaml'

const citationFilename = 'CITATION.cff'

export async function citationValidate(
schema: GenericSchema,
dsContext: BIDSContextDataset,
) {
const citationFile = dsContext.tree.get(citationFilename)
if (!citationFile || 'directories' in citationFile) return
let citation: unknown = {}
try {
citation = parse(await citationFile.text())
} catch (error) {
dsContext.issues.add({
code: 'FILE_READ',
issueMessage: `Error from attempted read of file:\n${error}`,
location: citationFilename,
})
return

Check warning on line 26 in bids-validator/src/validators/citation.ts

View check run for this annotation

Codecov / codecov/patch

bids-validator/src/validators/citation.ts#L21-L26

Added lines #L21 - L26 were not covered by tests
}
const validate = compile(citationSchema)
if (!validate(citation)) {
for (const err of validate.errors as DefinedError[]) {
dsContext.issues.add({
code: 'CITATION_CFF_VALIDATION_ERROR',
subCode: err['instancePath'],
issueMessage: err['message'],
location: citationFilename,
})
}
}
}
15 changes: 15 additions & 0 deletions bids-validator/tests/data/citation/bad.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cff-version: 1.2.0
message: If you use this software, please cite it using these metadata.
title: My Research Software
abstract: This is my awesome research software. It does many things.
version: 0.11.2
date-released: "2021-07-18"
identifiers:
- description: This is the collection of archived snapshots of all versions of My Research Software
type: doi
value: "10.5281/zenodo.123456"
- description: This is the archived snapshot of version 0.11.2 of My Research Software
type: doi
value: "10.5281/zenodo.123457"
license: Apache-2.0
repository-code: "https://github.com/citation-file-format/my-research-software"
20 changes: 20 additions & 0 deletions bids-validator/tests/data/citation/good.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cff-version: 1.2.0
message: If you use this software, please cite it using these metadata.
title: My Research Software
abstract: This is my awesome research software. It does many things.
authors:
- family-names: Druskat
given-names: Stephan
orcid: "https://orcid.org/1234-5678-9101-1121"
- name: "The Research Software project"
version: 0.11.2
date-released: "2021-07-18"
identifiers:
- description: This is the collection of archived snapshots of all versions of My Research Software
type: doi
value: "10.5281/zenodo.123456"
- description: This is the archived snapshot of version 0.11.2 of My Research Software
type: doi
value: "10.5281/zenodo.123457"
license: Apache-2.0
repository-code: "https://github.com/citation-file-format/my-research-software"
Loading