@@ -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+
1315import type { BIDSFile , FileTree } from '../types/filetree.ts'
1416import type { BIDSContext } from './context.ts'
1517import type { DatasetIssues } from '../issues/datasetIssues.ts'
1618import type { readEntities } from './entities.ts'
1719import { loadTSV } from '../files/tsv.ts'
1820import { parseBvalBvec } from '../files/dwi.ts'
1921import { 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 */
3438const 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
155114export 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 )
0 commit comments