Skip to content

Commit 43be015

Browse files
authored
Merge pull request #201 from bids-standard/enh/use_association_selectors
Use schema.meta.associations during buildAssociations.
2 parents 01f235e + 6105b71 commit 43be015

File tree

3 files changed

+143
-123
lines changed

3 files changed

+143
-123
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!--
2+
A new scriv changelog fragment.
3+
4+
Uncomment the section that is right (remove the HTML comment wrapper).
5+
For top level release notes, leave all the headers commented out.
6+
-->
7+
8+
<!--
9+
### Added
10+
11+
- A bullet item for the Added category.
12+
13+
-->
14+
### Changed
15+
16+
- Rely on `schema.meta.associations` to load context associations instead of relying on list maintained in validator.
17+
18+
<!--
19+
### Fixed
20+
21+
- A bullet item for the Fixed category.
22+
23+
-->
24+
<!--
25+
### Deprecated
26+
27+
- A bullet item for the Deprecated category.
28+
29+
-->
30+
<!--
31+
### Removed
32+
33+
- A bullet item for the Removed category.
34+
35+
-->
36+
<!--
37+
### Security
38+
39+
- A bullet item for the Security category.
40+
41+
-->
42+
<!--
43+
### Infrastructure
44+
45+
- A bullet item for the Infrastructure category.
46+
47+
-->

src/schema/associations.ts

Lines changed: 95 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ import type {
1010
Magnitude,
1111
Magnitude1,
1212
} from '@bids/schema/context'
13+
import type { Schema as MetaSchema } from '@bids/schema/metaschema'
14+
1315
import type { BIDSFile, FileTree } from '../types/filetree.ts'
1416
import type { BIDSContext } from './context.ts'
1517
import type { DatasetIssues } from '../issues/datasetIssues.ts'
1618
import type { readEntities } from './entities.ts'
1719
import { loadTSV } from '../files/tsv.ts'
1820
import { parseBvalBvec } from '../files/dwi.ts'
1921
import { walkBack } from '../files/inheritance.ts'
22+
import { evalCheck } from './applyRules.ts'
23+
import { expressionFunctions } from './expressionLanguage.ts'
2024

2125
// type AssociationsLookup = Record<keyof ContextAssociations, { extensions: string[], inherit: boolean, load: ... }
2226

@@ -32,158 +36,129 @@ import { walkBack } from '../files/inheritance.ts'
3236
* returning promises for now.
3337
*/
3438
const associationLookup = {
35-
events: {
36-
suffix: 'events',
37-
extensions: ['.tsv'],
38-
inherit: true,
39-
load: async (file: BIDSFile, options: { maxRows: number }): Promise<Events> => {
40-
const columns = await loadTSV(file, options.maxRows)
41-
.catch((e) => {
42-
return new Map()
43-
})
44-
return {
45-
path: file.path,
46-
onset: columns.get('onset') || [],
47-
}
48-
},
39+
events: async (file: BIDSFile, options: { maxRows: number }): Promise<Events> => {
40+
const columns = await loadTSV(file, options.maxRows)
41+
.catch((e) => {
42+
return new Map()
43+
})
44+
return {
45+
path: file.path,
46+
onset: columns.get('onset') || [],
47+
}
4948
},
50-
aslcontext: {
51-
suffix: 'aslcontext',
52-
extensions: ['.tsv'],
53-
inherit: true,
54-
load: async (
55-
file: BIDSFile,
56-
options: { maxRows: number },
57-
): Promise<Aslcontext> => {
58-
const columns = await loadTSV(file, options.maxRows)
59-
.catch((e) => {
60-
return new Map()
61-
})
62-
return {
63-
path: file.path,
64-
n_rows: columns.get('volume_type')?.length || 0,
65-
volume_type: columns.get('volume_type') || [],
66-
}
67-
},
49+
aslcontext: async (
50+
file: BIDSFile,
51+
options: { maxRows: number },
52+
): Promise<Aslcontext> => {
53+
const columns = await loadTSV(file, options.maxRows)
54+
.catch((e) => {
55+
return new Map()
56+
})
57+
return {
58+
path: file.path,
59+
n_rows: columns.get('volume_type')?.length || 0,
60+
volume_type: columns.get('volume_type') || [],
61+
}
6862
},
69-
m0scan: {
70-
suffix: 'm0scan',
71-
extensions: ['.nii', '.nii.gz'],
72-
inherit: false,
73-
load: (file: BIDSFile, options: any): Promise<M0Scan> => {
74-
return Promise.resolve({ path: file.path })
75-
},
63+
m0scan: (file: BIDSFile, options: any): Promise<M0Scan> => {
64+
return Promise.resolve({ path: file.path })
7665
},
77-
magnitude: {
78-
suffix: 'magnitude',
79-
extensions: ['.nii', '.nii.gz'],
80-
inherit: false,
81-
load: (file: BIDSFile, options: any): Promise<Magnitude> => {
82-
return Promise.resolve({ path: file.path })
83-
},
66+
magnitude: (file: BIDSFile, options: any): Promise<Magnitude> => {
67+
return Promise.resolve({ path: file.path })
8468
},
85-
magnitude1: {
86-
suffix: 'magnitude1',
87-
extensions: ['.nii', '.nii.gz'],
88-
inherit: false,
89-
load: (file: BIDSFile, options: any): Promise<Magnitude1> => {
90-
return Promise.resolve({ path: file.path })
91-
},
69+
magnitude1: (file: BIDSFile, options: any): Promise<Magnitude1> => {
70+
return Promise.resolve({ path: file.path })
9271
},
93-
bval: {
94-
suffix: 'dwi',
95-
extensions: ['.bval'],
96-
inherit: true,
97-
load: async (file: BIDSFile, options: any): Promise<Bval> => {
98-
const contents = await file.text()
99-
const rows = parseBvalBvec(contents)
100-
return {
101-
path: file.path,
102-
n_cols: rows ? rows[0].length : 0,
103-
n_rows: rows ? rows.length : 0,
104-
// @ts-expect-error values is expected to be a number[], coerce lazily
105-
values: rows[0],
106-
}
107-
},
72+
bval: async (file: BIDSFile, options: any): Promise<Bval> => {
73+
const contents = await file.text()
74+
const rows = parseBvalBvec(contents)
75+
return {
76+
path: file.path,
77+
n_cols: rows ? rows[0].length : 0,
78+
n_rows: rows ? rows.length : 0,
79+
// @ts-expect-error values is expected to be a number[], coerce lazily
80+
values: rows[0],
81+
}
10882
},
109-
bvec: {
110-
suffix: 'dwi',
111-
extensions: ['.bvec'],
112-
inherit: true,
113-
load: async (file: BIDSFile, options: any): Promise<Bvec> => {
114-
const contents = await file.text()
115-
const rows = parseBvalBvec(contents)
83+
bvec: async (file: BIDSFile, options: any): Promise<Bvec> => {
84+
const contents = await file.text()
85+
const rows = parseBvalBvec(contents)
11686

117-
if (rows.some((row) => row.length !== rows[0].length)) {
118-
throw { key: 'BVEC_ROW_LENGTH' }
119-
}
87+
if (rows.some((row) => row.length !== rows[0].length)) {
88+
throw { key: 'BVEC_ROW_LENGTH' }
89+
}
12090

121-
return {
122-
path: file.path,
123-
n_cols: rows ? rows[0].length : 0,
124-
n_rows: rows ? rows.length : 0,
125-
}
126-
},
91+
return {
92+
path: file.path,
93+
n_cols: rows ? rows[0].length : 0,
94+
n_rows: rows ? rows.length : 0,
95+
}
12796
},
128-
channels: {
129-
suffix: 'channels',
130-
extensions: ['.tsv'],
131-
inherit: true,
132-
load: async (file: BIDSFile, options: { maxRows: number }): Promise<Channels> => {
133-
const columns = await loadTSV(file, options.maxRows)
134-
.catch((e) => {
135-
return new Map()
136-
})
137-
return {
138-
path: file.path,
139-
type: columns.get('type'),
140-
short_channel: columns.get('short_channel'),
141-
sampling_frequency: columns.get('sampling_frequency'),
142-
}
143-
},
97+
channels: async (file: BIDSFile, options: { maxRows: number }): Promise<Channels> => {
98+
const columns = await loadTSV(file, options.maxRows)
99+
.catch((e) => {
100+
return new Map()
101+
})
102+
return {
103+
path: file.path,
104+
type: columns.get('type'),
105+
short_channel: columns.get('short_channel'),
106+
sampling_frequency: columns.get('sampling_frequency'),
107+
}
144108
},
145-
coordsystem: {
146-
suffix: 'coordsystem',
147-
extensions: ['.json'],
148-
inherit: true,
149-
load: (file: BIDSFile, options: any): Promise<Coordsystem> => {
150-
return Promise.resolve({ path: file.path })
151-
},
109+
coordsystem: (file: BIDSFile, options: any): Promise<Coordsystem> => {
110+
return Promise.resolve({ path: file.path })
152111
},
153112
}
154113

155114
export async function buildAssociations(
156-
source: BIDSFile,
157-
issues: DatasetIssues,
158-
maxRows: number = -1,
115+
context: BIDSContext,
159116
): Promise<Associations> {
160117
const associations: Associations = {}
161118

162-
for (const [key, value] of Object.entries(associationLookup)) {
163-
const { suffix, extensions, inherit, load } = value
119+
const schema: MetaSchema = context.dataset.schema as MetaSchema
120+
121+
Object.assign(context, expressionFunctions)
122+
// @ts-expect-error
123+
context.exists.bind(context)
124+
125+
for (const [key, rule] of Object.entries(schema.meta.associations)) {
126+
if (!rule.selectors!.every((x) => evalCheck(x, context))) {
127+
continue
128+
}
164129
let file
130+
let extension: string[] = []
131+
if (typeof rule.target.extension === 'string') {
132+
extension = [rule.target.extension]
133+
} else if (Array.isArray(rule.target.extension)) {
134+
extension = rule.target.extension
135+
}
165136
try {
166-
file = walkBack(source, inherit, extensions, suffix).next().value
137+
file = walkBack(context.file, rule.inherit, extension, rule.target.suffix).next().value
167138
} catch (error) {
168139
if (
169140
error && typeof error === 'object' && 'code' in error &&
170141
error.code === 'MULTIPLE_INHERITABLE_FILES'
171142
) {
172143
// @ts-expect-error
173-
issues.add(error)
144+
context.dataset.issues.add(error)
174145
break
175146
} else {
176147
throw error
177148
}
178149
}
179150

180151
if (file) {
181-
// @ts-expect-error Matching load return value to key is hard
182-
associations[key] = await load(file, { maxRows }).catch((error) => {
183-
if (error.key) {
184-
issues.add({ code: error.key, location: file.path })
185-
}
186-
})
152+
// @ts-expect-error
153+
const load = associationLookup[key]
154+
// @ts-expect-error
155+
associations[key] = await load(file, { maxRows: context.dataset.options?.maxRows }).catch(
156+
(error: any) => {
157+
if (key in error) {
158+
context.dataset.issues.add({ code: error.key, location: file.path })
159+
}
160+
},
161+
)
187162
}
188163
}
189164
return Promise.resolve(associations)

src/schema/context.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,7 @@ export class BIDSContext implements Context {
261261

262262
async loadAssociations(): Promise<void> {
263263
this.associations = await buildAssociations(
264-
this.file,
265-
this.dataset.issues,
266-
this.dataset.options?.maxRows,
264+
this,
267265
)
268266
return
269267
}

0 commit comments

Comments
 (0)