bids-validator-deno 2.1.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bids-validator-deno might be problematic. Click here for more details.
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/PKG-INFO +1 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/deno.json +19 -14
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/pyproject.toml +1 -1
- bids_validator_deno-2.2.0/src/files/access.test.ts +40 -0
- bids_validator_deno-2.2.0/src/files/access.ts +35 -0
- bids_validator_deno-2.2.0/src/files/browser.ts +34 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/deno.test.ts +1 -1
- bids_validator_deno-2.2.0/src/files/deno.ts +99 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/filetree.test.ts +21 -3
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/filetree.ts +1 -24
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/gzip.test.ts +3 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/gzip.ts +2 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/inheritance.test.ts +19 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/inheritance.ts +1 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/json.test.ts +13 -5
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/json.ts +6 -2
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/nifti.test.ts +4 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/nifti.ts +4 -2
- bids_validator_deno-2.2.0/src/files/openers.ts +128 -0
- bids_validator_deno-2.2.0/src/files/repo.ts +141 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/streams.ts +15 -5
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/tiff.test.ts +3 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/tiff.ts +2 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/tsv.test.ts +54 -32
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/tsv.ts +8 -4
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/issues/datasetIssues.test.ts +1 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/issues/list.ts +6 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/main.ts +8 -3
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/applyRules.test.ts +1 -46
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/applyRules.ts +40 -34
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/associations.ts +4 -2
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/context.ts +16 -9
- bids_validator_deno-2.2.0/src/schema/datatypes.test.ts +57 -0
- bids_validator_deno-2.2.0/src/schema/datatypes.ts +30 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/expressionLanguage.test.ts +95 -2
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/expressionLanguage.ts +53 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/fixtures.test.ts +1 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/tables.test.ts +68 -38
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/tables.ts +78 -99
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/walk.test.ts +1 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/walk.ts +23 -14
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/loadSchema.ts +5 -5
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/options.test.ts +1 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/options.ts +16 -2
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/hed-integration.test.ts +4 -4
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/nifti_rules.test.ts +14 -14
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/regression.test.ts +4 -4
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/simple-dataset.ts +1 -1
- bids_validator_deno-2.2.0/src/tests/utils.ts +20 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/filetree.ts +67 -19
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/output.ts +3 -2
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/bids.test.ts +1 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/bids.ts +2 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/citation.test.ts +1 -1
- bids_validator_deno-2.2.0/src/validators/filenameCase.test.ts +32 -0
- bids_validator_deno-2.2.0/src/validators/filenameCase.ts +16 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/filenameIdentify.test.ts +1 -19
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/filenameIdentify.ts +0 -25
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/filenameValidate.test.ts +1 -1
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/validateFiles.test.ts +15 -13
- bids_validator_deno-2.1.0/src/files/browser.ts +0 -65
- bids_validator_deno-2.1.0/src/files/deno.ts +0 -158
- bids_validator_deno-2.1.0/src/schema/modalities.ts +0 -16
- bids_validator_deno-2.1.0/src/tests/utils.ts +0 -12
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/LICENSE +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/README.md +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/pdm_build.py +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/.git-meta.json +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/bids-validator.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/browser.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/dwi.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/dwi.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/ignore.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/ignore.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/streams.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/issues/datasetIssues.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/issues/list.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/context.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/entities.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/entities.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/loadSchema.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/requestPermissions.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/summary/collectSubjectMetadata.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/summary/summary.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/summary/summary.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/README.md +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/bom-utf16.tsv +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/bom-utf8.json +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/generate-filenames.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/bids_examples.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/common.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/derivatives.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/empty_files.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/valid_dataset.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/valid_filenames.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/valid_headers.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/nullReadBytes.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/schema-expression-language.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/check.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/columns.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/columns.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/issues.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/schema.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/validation-result.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/errors.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/logger.test.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/logger.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/memoize.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/objectPathHandler.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/citation.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/filenameValidate.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/hed.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/internal/emptyFile.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/internal/unusedFile.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/json.ts +0 -0
- {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/version.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bids/validator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": "./src/bids-validator.ts",
|
|
6
6
|
"./main": "./src/main.ts",
|
|
@@ -27,22 +27,24 @@
|
|
|
27
27
|
]
|
|
28
28
|
},
|
|
29
29
|
"imports": {
|
|
30
|
-
"@ajv": "npm:ajv
|
|
30
|
+
"@ajv": "npm:ajv@^8.17.1",
|
|
31
31
|
"@bids/schema": "jsr:@bids/schema@~1.1.0",
|
|
32
32
|
"@cliffy/command": "jsr:@effigies/cliffy-command@1.0.0-dev.8",
|
|
33
33
|
"@cliffy/table": "jsr:@effigies/cliffy-table@1.0.0-dev.5",
|
|
34
|
-
"@hed/validator": "npm:hed-validator
|
|
35
|
-
"@ignore": "npm:ignore
|
|
36
|
-
"@libs/xml": "jsr:@libs/xml
|
|
37
|
-
"@mango/nifti": "npm:@bids/nifti-reader-js
|
|
38
|
-
"@std/assert": "jsr:@std/assert
|
|
39
|
-
"@std/fmt": "jsr:@std/fmt
|
|
40
|
-
"@std/fs": "jsr:@std/fs
|
|
41
|
-
"@std/io": "jsr:@std/io
|
|
42
|
-
"@std/log": "jsr:@std/log
|
|
43
|
-
"@std/path": "jsr:@std/path
|
|
44
|
-
"@std/streams": "jsr:@std/streams
|
|
45
|
-
"@std/yaml": "jsr:@std/yaml@^1.0.
|
|
34
|
+
"@hed/validator": "npm:hed-validator@~4.1.4",
|
|
35
|
+
"@ignore": "npm:ignore@^7.0.5",
|
|
36
|
+
"@libs/xml": "jsr:@libs/xml@^6.0.8",
|
|
37
|
+
"@mango/nifti": "npm:@bids/nifti-reader-js@^0.6.9",
|
|
38
|
+
"@std/assert": "jsr:@std/assert@^1.0.14",
|
|
39
|
+
"@std/fmt": "jsr:@std/fmt@^1.0.8",
|
|
40
|
+
"@std/fs": "jsr:@std/fs@^1.0.19",
|
|
41
|
+
"@std/io": "jsr:@std/io@^0.225.2",
|
|
42
|
+
"@std/log": "jsr:@std/log@^0.224.14",
|
|
43
|
+
"@std/path": "jsr:@std/path@^1.1.2",
|
|
44
|
+
"@std/streams": "jsr:@std/streams@^1.0.12",
|
|
45
|
+
"@std/yaml": "jsr:@std/yaml@^1.0.9",
|
|
46
|
+
"isomorphic-git": "npm:isomorphic-git@^1.27.1",
|
|
47
|
+
"hash-wasm": "npm:hash-wasm@^4.12.0"
|
|
46
48
|
},
|
|
47
49
|
"tasks": {
|
|
48
50
|
"test": "deno test -A src/"
|
|
@@ -54,6 +56,9 @@
|
|
|
54
56
|
"proseWrap": "preserve",
|
|
55
57
|
"include": [
|
|
56
58
|
"src/"
|
|
59
|
+
],
|
|
60
|
+
"exclude": [
|
|
61
|
+
"src/tests/bom-utf8.json"
|
|
57
62
|
]
|
|
58
63
|
}
|
|
59
64
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { assert, assertArrayIncludes, assertObjectMatch } from '@std/assert'
|
|
2
|
+
import { basename, dirname } from '@std/path'
|
|
3
|
+
import { BIDSFileDeno } from './deno.ts'
|
|
4
|
+
|
|
5
|
+
export function testAsyncFileAccess(
|
|
6
|
+
name: string,
|
|
7
|
+
fn: (file: BIDSFileDeno, ...args: any[]) => Promise<any>,
|
|
8
|
+
...args: any[]
|
|
9
|
+
) {
|
|
10
|
+
Deno.test({
|
|
11
|
+
name,
|
|
12
|
+
ignore: Deno.build.os === 'windows',
|
|
13
|
+
async fn(t) {
|
|
14
|
+
await t.step('Dangling symlink', async () => {
|
|
15
|
+
const file = new BIDSFileDeno('tests/data', '/broken-symlink')
|
|
16
|
+
try {
|
|
17
|
+
await fn(file, ...args)
|
|
18
|
+
assert(false, 'Expected error')
|
|
19
|
+
} catch (e: any) {
|
|
20
|
+
assertObjectMatch(e, {
|
|
21
|
+
code: 'FILE_READ',
|
|
22
|
+
location: '/broken-symlink',
|
|
23
|
+
})
|
|
24
|
+
assertArrayIncludes(['NotFound', 'FilesystemLoop'], [e.subCode])
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
await t.step('Insufficient permissions', async () => {
|
|
28
|
+
const tmpfile = await Deno.makeTempFile()
|
|
29
|
+
await Deno.chmod(tmpfile, 0o000)
|
|
30
|
+
const file = new BIDSFileDeno('', tmpfile)
|
|
31
|
+
try {
|
|
32
|
+
await fn(file, ...args)
|
|
33
|
+
assert(false, 'Expected error')
|
|
34
|
+
} catch (e: any) {
|
|
35
|
+
assertObjectMatch(e, { code: 'FILE_READ', subCode: 'PermissionDenied' })
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type BIDSFile } from '../types/filetree.ts'
|
|
2
|
+
import { type Issue } from '../types/issues.ts'
|
|
3
|
+
|
|
4
|
+
function IOErrorToIssue(err: { code: string; name: string }): Issue {
|
|
5
|
+
const subcode = err.name
|
|
6
|
+
let issueMessage: string | undefined = undefined
|
|
7
|
+
if (err.code === 'ENOENT' || err.code === 'ELOOP') {
|
|
8
|
+
issueMessage = 'Possible dangling symbolic link'
|
|
9
|
+
}
|
|
10
|
+
return { code: 'FILE_READ', subCode: err.name, issueMessage }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function openStream(
|
|
14
|
+
file: BIDSFile,
|
|
15
|
+
): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
|
|
16
|
+
return file.stream().catch((err: any) => {
|
|
17
|
+
throw { location: file.path, ...IOErrorToIssue(err) }
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function readBytes(
|
|
22
|
+
file: BIDSFile,
|
|
23
|
+
size: number,
|
|
24
|
+
offset = 0,
|
|
25
|
+
): Promise<Uint8Array<ArrayBuffer>> {
|
|
26
|
+
return file.readBytes(size, offset).catch((err: any) => {
|
|
27
|
+
throw { location: file.path, ...IOErrorToIssue(err) }
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function readText(file: BIDSFile): Promise<string> {
|
|
32
|
+
return file.text().catch((err: any) => {
|
|
33
|
+
throw { location: file.path, ...IOErrorToIssue(err) }
|
|
34
|
+
})
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BIDSFile, FileTree } from '../types/filetree.ts'
|
|
2
|
+
import { filesToTree } from './filetree.ts'
|
|
3
|
+
import { FileIgnoreRules, readBidsIgnore } from './ignore.ts'
|
|
4
|
+
import { BrowserFileOpener } from './openers.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Browser implement of BIDSFile wrapping native File/FileList types
|
|
8
|
+
*/
|
|
9
|
+
export class BIDSFileBrowser extends BIDSFile {
|
|
10
|
+
constructor(file: File, ignore: FileIgnoreRules, parent?: FileTree) {
|
|
11
|
+
const relativePath = file.webkitRelativePath
|
|
12
|
+
const prefixLength = relativePath.indexOf('/')
|
|
13
|
+
const opener = new BrowserFileOpener(file)
|
|
14
|
+
super(relativePath.substring(prefixLength), opener, ignore, parent)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Convert from FileList (created with webkitDirectory: true) to FileTree for validator use
|
|
20
|
+
*/
|
|
21
|
+
export async function fileListToTree(files: File[]): Promise<FileTree> {
|
|
22
|
+
const ignore = new FileIgnoreRules([])
|
|
23
|
+
const root = new FileTree('/', '/', undefined)
|
|
24
|
+
const tree = filesToTree(files.map((f) => new BIDSFileBrowser(f, ignore, root)), ignore)
|
|
25
|
+
const bidsignore = tree.get('.bidsignore')
|
|
26
|
+
if (bidsignore) {
|
|
27
|
+
try {
|
|
28
|
+
ignore.add(await readBidsIgnore(bidsignore as BIDSFile))
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.log(`Failed to read '.bidsignore' file with the following error:\n${err}`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return tree
|
|
34
|
+
}
|
|
@@ -31,7 +31,7 @@ Deno.test('Deno implementation of BIDSFile', async (t) => {
|
|
|
31
31
|
})
|
|
32
32
|
await t.step('can be read as ReadableStream', async () => {
|
|
33
33
|
const file = new BIDSFileDeno(testDir, testFilename, ignore)
|
|
34
|
-
const stream = file.stream
|
|
34
|
+
const stream = await file.stream()
|
|
35
35
|
const streamReader = stream.getReader()
|
|
36
36
|
const denoReader = readerFromStreamReader(streamReader)
|
|
37
37
|
const fileBuffer = await readAll(denoReader)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deno specific implementation for reading files
|
|
3
|
+
*/
|
|
4
|
+
import { basename, join } from '@std/path'
|
|
5
|
+
import * as posix from '@std/path/posix'
|
|
6
|
+
import { BIDSFile, type FileOpener, FileTree } from '../types/filetree.ts'
|
|
7
|
+
import { requestReadPermission } from '../setup/requestPermissions.ts'
|
|
8
|
+
import { FileIgnoreRules, readBidsIgnore } from './ignore.ts'
|
|
9
|
+
import { FsFileOpener, HTTPOpener } from './openers.ts'
|
|
10
|
+
import { resolveAnnexedFile } from './repo.ts'
|
|
11
|
+
import fs from 'node:fs'
|
|
12
|
+
|
|
13
|
+
export class BIDSFileDeno extends BIDSFile {
|
|
14
|
+
constructor(datasetPath: string, path: string, ignore?: FileIgnoreRules, parent?: FileTree) {
|
|
15
|
+
super(path, new FsFileOpener(datasetPath, path), ignore, parent)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ReadFileTreeOptions = {
|
|
20
|
+
rootPath: string
|
|
21
|
+
relativePath: string
|
|
22
|
+
ignore: FileIgnoreRules
|
|
23
|
+
prune: FileIgnoreRules
|
|
24
|
+
parent?: FileTree
|
|
25
|
+
preferredRemote?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function _readFileTree({
|
|
29
|
+
rootPath,
|
|
30
|
+
relativePath,
|
|
31
|
+
ignore,
|
|
32
|
+
prune,
|
|
33
|
+
parent,
|
|
34
|
+
preferredRemote,
|
|
35
|
+
}: ReadFileTreeOptions): Promise<FileTree> {
|
|
36
|
+
await requestReadPermission()
|
|
37
|
+
const name = basename(relativePath)
|
|
38
|
+
const tree = new FileTree(relativePath, name, parent, ignore)
|
|
39
|
+
|
|
40
|
+
// Opaque cache for passing to git operations
|
|
41
|
+
const cache = {}
|
|
42
|
+
|
|
43
|
+
for await (const dirEntry of Deno.readDir(join(rootPath, relativePath))) {
|
|
44
|
+
const thisPath = posix.join(relativePath, dirEntry.name)
|
|
45
|
+
if (prune.test(thisPath)) {
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
if (dirEntry.isFile || dirEntry.isSymlink) {
|
|
49
|
+
let opener: FileOpener
|
|
50
|
+
const fullPath = join(rootPath, thisPath)
|
|
51
|
+
try {
|
|
52
|
+
const fileInfo = await Deno.stat(fullPath)
|
|
53
|
+
opener = new FsFileOpener(rootPath, thisPath, fileInfo)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
try {
|
|
56
|
+
const { url, size } = await resolveAnnexedFile(fullPath, preferredRemote, { cache, fs })
|
|
57
|
+
opener = new HTTPOpener(url, size)
|
|
58
|
+
} catch (_) {
|
|
59
|
+
throw error
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
tree.files.push(new BIDSFile(thisPath, opener, ignore, tree))
|
|
63
|
+
}
|
|
64
|
+
if (dirEntry.isDirectory) {
|
|
65
|
+
const dirTree = await _readFileTree({
|
|
66
|
+
rootPath,
|
|
67
|
+
relativePath: thisPath,
|
|
68
|
+
ignore,
|
|
69
|
+
prune,
|
|
70
|
+
parent: tree,
|
|
71
|
+
preferredRemote,
|
|
72
|
+
})
|
|
73
|
+
tree.directories.push(dirTree)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return tree
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Read in the target directory structure and return a FileTree
|
|
81
|
+
*/
|
|
82
|
+
export async function readFileTree(
|
|
83
|
+
rootPath: string,
|
|
84
|
+
prune?: FileIgnoreRules,
|
|
85
|
+
preferredRemote?: string,
|
|
86
|
+
): Promise<FileTree> {
|
|
87
|
+
prune ??= new FileIgnoreRules([], false)
|
|
88
|
+
const ignore = new FileIgnoreRules([])
|
|
89
|
+
const tree = await _readFileTree({rootPath, relativePath: '/', ignore, prune, preferredRemote})
|
|
90
|
+
const bidsignore = tree.get('.bidsignore')
|
|
91
|
+
if (bidsignore) {
|
|
92
|
+
try {
|
|
93
|
+
ignore.add(await readBidsIgnore(bidsignore as BIDSFile))
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.log(`Failed to read '.bidsignore' file with the following error:\n${err}`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return tree
|
|
99
|
+
}
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { assert, assertEquals } from '@std/assert'
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { filesToTree
|
|
2
|
+
import { FileIgnoreRules } from './ignore.ts'
|
|
3
|
+
import { BIDSFile, type FileOpener, type FileTree } from '../types/filetree.ts'
|
|
4
|
+
import { filesToTree } from './filetree.ts'
|
|
5
|
+
import { asyncStreamFromString } from '../tests/utils.ts'
|
|
6
|
+
|
|
7
|
+
class NullFileOpener implements FileOpener {
|
|
8
|
+
size = 0
|
|
9
|
+
stream = () => asyncStreamFromString('')
|
|
10
|
+
text = () => Promise.resolve('')
|
|
11
|
+
readBytes = async (size: number, offset?: number) => new Uint8Array()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function pathToFile(path: string, ignored: boolean = false): BIDSFile {
|
|
15
|
+
const name = path.split('/').pop() as string
|
|
16
|
+
return new BIDSFile(path, new NullFileOpener(), ignored)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function pathsToTree(paths: string[], ignore?: string[]): FileTree {
|
|
20
|
+
const ignoreRules = new FileIgnoreRules(ignore ?? [])
|
|
21
|
+
return filesToTree(paths.map((path) => pathToFile(path, ignoreRules.test(path))))
|
|
22
|
+
}
|
|
5
23
|
|
|
6
24
|
Deno.test('FileTree generation', async (t) => {
|
|
7
25
|
await t.step('converts a basic list', async () => {
|
|
@@ -1,31 +1,8 @@
|
|
|
1
1
|
import { parse, SEPARATOR_PATTERN } from '@std/path'
|
|
2
2
|
import * as posix from '@std/path/posix'
|
|
3
|
-
import {
|
|
3
|
+
import { BIDSFile, FileTree } from '../types/filetree.ts'
|
|
4
4
|
import { FileIgnoreRules } from './ignore.ts'
|
|
5
5
|
|
|
6
|
-
const nullFile = {
|
|
7
|
-
size: 0,
|
|
8
|
-
stream: new ReadableStream({
|
|
9
|
-
start(controller) {
|
|
10
|
-
controller.close()
|
|
11
|
-
},
|
|
12
|
-
}),
|
|
13
|
-
text: () => Promise.resolve(''),
|
|
14
|
-
readBytes: async (size: number, offset?: number) => new Uint8Array(),
|
|
15
|
-
parent: new FileTree('', '/'),
|
|
16
|
-
viewed: false,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function pathToFile(path: string, ignored: boolean = false): BIDSFile {
|
|
20
|
-
const name = path.split('/').pop() as string
|
|
21
|
-
return { name, path, ignored, ...nullFile }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function pathsToTree(paths: string[], ignore?: string[]): FileTree {
|
|
25
|
-
const ignoreRules = new FileIgnoreRules(ignore ?? [])
|
|
26
|
-
return filesToTree(paths.map((path) => pathToFile(path, ignoreRules.test(path))))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
6
|
export function filesToTree(fileList: BIDSFile[], ignore?: FileIgnoreRules): FileTree {
|
|
30
7
|
ignore = ignore ?? new FileIgnoreRules([])
|
|
31
8
|
const tree: FileTree = new FileTree('/', '/')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assert, assertObjectMatch } from '@std/assert'
|
|
2
2
|
import { parseGzip } from './gzip.ts'
|
|
3
3
|
import { BIDSFileDeno } from './deno.ts'
|
|
4
|
+
import { testAsyncFileAccess } from './access.test.ts'
|
|
4
5
|
|
|
5
6
|
Deno.test('parseGzip', async (t) => {
|
|
6
7
|
await t.step('parses anonymized file', async () => {
|
|
@@ -40,3 +41,5 @@ Deno.test('parseGzip', async (t) => {
|
|
|
40
41
|
assert(!gzip)
|
|
41
42
|
})
|
|
42
43
|
})
|
|
44
|
+
|
|
45
|
+
testAsyncFileAccess('Test file access errors for parseGzip', parseGzip)
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Gzip } from '@bids/schema/context'
|
|
6
6
|
import type { BIDSFile } from '../types/filetree.ts'
|
|
7
|
+
import { readBytes } from './access.ts'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Parse a gzip header from a file
|
|
@@ -19,7 +20,7 @@ export async function parseGzip(
|
|
|
19
20
|
file: BIDSFile,
|
|
20
21
|
maxBytes: number = 512,
|
|
21
22
|
): Promise<Gzip | undefined> {
|
|
22
|
-
const buf = await
|
|
23
|
+
const buf = await readBytes(file, maxBytes)
|
|
23
24
|
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)
|
|
24
25
|
if (view.byteLength < 2 || view.getUint16(0, false) !== 0x1f8b) return undefined
|
|
25
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assert, assertEquals, assertThrows } from '@std/assert'
|
|
2
2
|
import type { BIDSFile } from '../types/filetree.ts'
|
|
3
|
-
import { pathsToTree } from './filetree.ts'
|
|
3
|
+
import { pathsToTree } from './filetree.test.ts'
|
|
4
4
|
import { walkBack } from './inheritance.ts'
|
|
5
5
|
|
|
6
6
|
Deno.test('walkback inheritance tests', async (t) => {
|
|
@@ -72,4 +72,22 @@ Deno.test('walkback inheritance tests', async (t) => {
|
|
|
72
72
|
assertEquals(rootElectrodes.path, '/space-talairach_electrodes.tsv')
|
|
73
73
|
},
|
|
74
74
|
)
|
|
75
|
+
await t.step(
|
|
76
|
+
'The presence of target entities does not trigger exact match logic',
|
|
77
|
+
async () => {
|
|
78
|
+
const rootFileTree = pathsToTree([
|
|
79
|
+
'/sub-01/ieeg/sub-01_task-rest_ieeg.edf',
|
|
80
|
+
'/sub-01/ieeg/sub-01_task-rest_space-anat_electrodes.tsv',
|
|
81
|
+
'/sub-01/ieeg/sub-01_task-rest_space-MNI_electrodes.tsv',
|
|
82
|
+
])
|
|
83
|
+
const dataFile = rootFileTree.get('sub-01/ieeg/sub-01_task-rest_ieeg.edf') as BIDSFile
|
|
84
|
+
const electrodes = walkBack(dataFile, true, ['.tsv'], 'electrodes', ['space'])
|
|
85
|
+
const localElectrodes: BIDSFile[] = electrodes.next().value
|
|
86
|
+
assert(Array.isArray(localElectrodes))
|
|
87
|
+
assertEquals(localElectrodes.map((f) => f.path), [
|
|
88
|
+
'/sub-01/ieeg/sub-01_task-rest_space-anat_electrodes.tsv',
|
|
89
|
+
'/sub-01/ieeg/sub-01_task-rest_space-MNI_electrodes.tsv',
|
|
90
|
+
])
|
|
91
|
+
},
|
|
92
|
+
)
|
|
75
93
|
})
|
|
@@ -50,7 +50,7 @@ export function* walkBack<T extends string[]>(
|
|
|
50
50
|
if (candidates.length > 1) {
|
|
51
51
|
const exactMatch = candidates.find((file) => {
|
|
52
52
|
const { entities } = readEntities(file.name)
|
|
53
|
-
return Object.keys(sourceParts.entities).every((entity) =>
|
|
53
|
+
return [...Object.keys(sourceParts.entities), ...(targetEntities ?? [])].every((entity) =>
|
|
54
54
|
entities[entity] === sourceParts.entities[entity]
|
|
55
55
|
)
|
|
56
56
|
})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type assert, assertObjectMatch } from '@std/assert'
|
|
2
2
|
import type { BIDSFile } from '../types/filetree.ts'
|
|
3
3
|
import type { FileIgnoreRules } from './ignore.ts'
|
|
4
|
+
import { testAsyncFileAccess } from './access.test.ts'
|
|
4
5
|
|
|
6
|
+
import { pathsToTree } from '../files/filetree.test.ts'
|
|
5
7
|
import { loadJSON } from './json.ts'
|
|
6
8
|
|
|
7
9
|
function encodeUTF16(text: string) {
|
|
@@ -17,9 +19,12 @@ function encodeUTF16(text: string) {
|
|
|
17
19
|
return buffer
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
function makeFile(text: string, encoding: string): BIDSFile {
|
|
22
|
+
function makeFile(path: string, text: string, encoding: string): BIDSFile {
|
|
21
23
|
const bytes = encoding === 'utf-8' ? new TextEncoder().encode(text) : encodeUTF16(text)
|
|
24
|
+
const file = pathsToTree([path]).get(path) as BIDSFile
|
|
22
25
|
return {
|
|
26
|
+
path: file.path,
|
|
27
|
+
parent: file.parent,
|
|
23
28
|
readBytes: async (size: number) => {
|
|
24
29
|
return new Uint8Array(bytes)
|
|
25
30
|
},
|
|
@@ -29,13 +34,13 @@ function makeFile(text: string, encoding: string): BIDSFile {
|
|
|
29
34
|
|
|
30
35
|
Deno.test('Test JSON error conditions', async (t) => {
|
|
31
36
|
await t.step('Load valid JSON', async () => {
|
|
32
|
-
const JSONfile = makeFile('{"a": 1}', 'utf-8')
|
|
37
|
+
const JSONfile = makeFile('/valid-contents.json', '{"a": 1}', 'utf-8')
|
|
33
38
|
const result = await loadJSON(JSONfile)
|
|
34
39
|
assertObjectMatch(result, { a: 1 })
|
|
35
40
|
})
|
|
36
41
|
|
|
37
42
|
await t.step('Error on BOM', async () => {
|
|
38
|
-
const BOMfile = makeFile('\uFEFF{"a": 1}', 'utf-8')
|
|
43
|
+
const BOMfile = makeFile('/BOM.json', '\uFEFF{"a": 1}', 'utf-8')
|
|
39
44
|
let error: any = undefined
|
|
40
45
|
await loadJSON(BOMfile).catch((e) => {
|
|
41
46
|
error = e
|
|
@@ -44,7 +49,7 @@ Deno.test('Test JSON error conditions', async (t) => {
|
|
|
44
49
|
})
|
|
45
50
|
|
|
46
51
|
await t.step('Error on UTF-16', async () => {
|
|
47
|
-
const UTF16file = makeFile('{"a": 1}', 'utf-16')
|
|
52
|
+
const UTF16file = makeFile('/utf16.json', '{"a": 1}', 'utf-16')
|
|
48
53
|
let error: any = undefined
|
|
49
54
|
await loadJSON(UTF16file).catch((e) => {
|
|
50
55
|
error = e
|
|
@@ -53,11 +58,14 @@ Deno.test('Test JSON error conditions', async (t) => {
|
|
|
53
58
|
})
|
|
54
59
|
|
|
55
60
|
await t.step('Error on invalid JSON syntax', async () => {
|
|
56
|
-
const badJSON = makeFile('{"a": 1]', 'utf-8')
|
|
61
|
+
const badJSON = makeFile('/bad-syntax.json', '{"a": 1]', 'utf-8')
|
|
57
62
|
let error: any = undefined
|
|
58
63
|
await loadJSON(badJSON).catch((e) => {
|
|
59
64
|
error = e
|
|
60
65
|
})
|
|
61
66
|
assertObjectMatch(error, { code: 'JSON_INVALID' })
|
|
62
67
|
})
|
|
68
|
+
loadJSON.cache.clear()
|
|
63
69
|
})
|
|
70
|
+
|
|
71
|
+
testAsyncFileAccess('Test file access errors for loadJSON', loadJSON)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { filememoizeAsync } from '../utils/memoize.ts'
|
|
1
2
|
import type { BIDSFile } from '../types/filetree.ts'
|
|
3
|
+
import { readBytes } from './access.ts'
|
|
2
4
|
|
|
3
5
|
async function readJSONText(file: BIDSFile): Promise<string> {
|
|
4
6
|
// Read JSON text from a file
|
|
@@ -6,7 +8,7 @@ async function readJSONText(file: BIDSFile): Promise<string> {
|
|
|
6
8
|
const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true })
|
|
7
9
|
// Streaming TextDecoders are buggy in Deno and Chrome, so read the
|
|
8
10
|
// entire file into memory before decoding and parsing
|
|
9
|
-
const data = await
|
|
11
|
+
const data = await readBytes(file, file.size)
|
|
10
12
|
try {
|
|
11
13
|
const text = decoder.decode(data)
|
|
12
14
|
if (text.startsWith('\uFEFF')) {
|
|
@@ -20,7 +22,7 @@ async function readJSONText(file: BIDSFile): Promise<string> {
|
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
async function _loadJSON(file: BIDSFile): Promise<Record<string, unknown>> {
|
|
24
26
|
const text = await readJSONText(file) // Raise encoding errors
|
|
25
27
|
let parsedText
|
|
26
28
|
try {
|
|
@@ -36,3 +38,5 @@ export async function loadJSON(file: BIDSFile): Promise<Record<string, unknown>>
|
|
|
36
38
|
}
|
|
37
39
|
return parsedText
|
|
38
40
|
}
|
|
41
|
+
|
|
42
|
+
export const loadJSON = filememoizeAsync(_loadJSON)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { assert, assertEquals, assertObjectMatch } from '@std/assert'
|
|
2
2
|
import { FileIgnoreRules } from './ignore.ts'
|
|
3
3
|
import { BIDSFileDeno } from './deno.ts'
|
|
4
|
+
import { testAsyncFileAccess } from './access.test.ts'
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import { axisCodes, loadHeader } from './nifti.ts'
|
|
6
7
|
|
|
7
8
|
Deno.test('Test loading nifti header', async (t) => {
|
|
8
9
|
const ignore = new FileIgnoreRules([])
|
|
@@ -96,3 +97,5 @@ Deno.test('Test extracting axis codes', async (t) => {
|
|
|
96
97
|
assertEquals(axisCodes(affine), ['A', 'S', 'R'])
|
|
97
98
|
})
|
|
98
99
|
})
|
|
100
|
+
|
|
101
|
+
testAsyncFileAccess('Test file access errors for loadHeader', loadHeader)
|
|
@@ -2,6 +2,7 @@ import { isCompressed, isNIFTI1, isNIFTI2, NIFTI1, NIFTI2 } from '@mango/nifti'
|
|
|
2
2
|
import type { BIDSFile } from '../types/filetree.ts'
|
|
3
3
|
import { logger } from '../utils/logger.ts'
|
|
4
4
|
import type { NiftiHeader } from '@bids/schema/context'
|
|
5
|
+
import { readBytes } from './access.ts'
|
|
5
6
|
|
|
6
7
|
async function extract(buffer: Uint8Array, nbytes: number): Promise<Uint8Array<ArrayBuffer>> {
|
|
7
8
|
// The fflate decompression that is used in nifti-reader does not like
|
|
@@ -32,8 +33,8 @@ async function extract(buffer: Uint8Array, nbytes: number): Promise<Uint8Array<A
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export async function loadHeader(file: BIDSFile): Promise<NiftiHeader> {
|
|
36
|
+
const buf = await readBytes(file, 1024)
|
|
35
37
|
try {
|
|
36
|
-
const buf = await file.readBytes(1024)
|
|
37
38
|
const data = isCompressed(buf.buffer) ? await extract(buf, 540) : buf.slice(0, 540)
|
|
38
39
|
let header
|
|
39
40
|
if (isNIFTI1(data.buffer)) {
|
|
@@ -138,7 +139,8 @@ export function axisCodes(affine: number[][]): string[] {
|
|
|
138
139
|
|
|
139
140
|
// Orthogonalize cosZ with respect to cosX and orthY
|
|
140
141
|
const orthZ = sub(
|
|
141
|
-
cosZ,
|
|
142
|
+
cosZ,
|
|
143
|
+
add(scale(cosX, dot(cosX, cosZ)), scale(orthY, dot(orthY, cosZ))),
|
|
142
144
|
)
|
|
143
145
|
|
|
144
146
|
const basis = [cosX, orthY, orthZ]
|