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.

Files changed (116) hide show
  1. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/PKG-INFO +1 -1
  2. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/deno.json +19 -14
  3. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/pyproject.toml +1 -1
  4. bids_validator_deno-2.2.0/src/files/access.test.ts +40 -0
  5. bids_validator_deno-2.2.0/src/files/access.ts +35 -0
  6. bids_validator_deno-2.2.0/src/files/browser.ts +34 -0
  7. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/deno.test.ts +1 -1
  8. bids_validator_deno-2.2.0/src/files/deno.ts +99 -0
  9. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/filetree.test.ts +21 -3
  10. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/filetree.ts +1 -24
  11. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/gzip.test.ts +3 -0
  12. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/gzip.ts +2 -1
  13. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/inheritance.test.ts +19 -1
  14. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/inheritance.ts +1 -1
  15. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/json.test.ts +13 -5
  16. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/json.ts +6 -2
  17. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/nifti.test.ts +4 -1
  18. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/nifti.ts +4 -2
  19. bids_validator_deno-2.2.0/src/files/openers.ts +128 -0
  20. bids_validator_deno-2.2.0/src/files/repo.ts +141 -0
  21. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/streams.ts +15 -5
  22. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/tiff.test.ts +3 -0
  23. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/tiff.ts +2 -1
  24. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/tsv.test.ts +54 -32
  25. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/tsv.ts +8 -4
  26. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/issues/datasetIssues.test.ts +1 -1
  27. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/issues/list.ts +6 -1
  28. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/main.ts +8 -3
  29. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/applyRules.test.ts +1 -46
  30. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/applyRules.ts +40 -34
  31. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/associations.ts +4 -2
  32. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/context.ts +16 -9
  33. bids_validator_deno-2.2.0/src/schema/datatypes.test.ts +57 -0
  34. bids_validator_deno-2.2.0/src/schema/datatypes.ts +30 -0
  35. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/expressionLanguage.test.ts +95 -2
  36. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/expressionLanguage.ts +53 -0
  37. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/fixtures.test.ts +1 -1
  38. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/tables.test.ts +68 -38
  39. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/tables.ts +78 -99
  40. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/walk.test.ts +1 -1
  41. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/walk.ts +23 -14
  42. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/loadSchema.ts +5 -5
  43. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/options.test.ts +1 -0
  44. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/options.ts +16 -2
  45. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/hed-integration.test.ts +4 -4
  46. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/nifti_rules.test.ts +14 -14
  47. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/regression.test.ts +4 -4
  48. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/simple-dataset.ts +1 -1
  49. bids_validator_deno-2.2.0/src/tests/utils.ts +20 -0
  50. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/filetree.ts +67 -19
  51. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/output.ts +3 -2
  52. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/bids.test.ts +1 -1
  53. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/bids.ts +2 -0
  54. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/citation.test.ts +1 -1
  55. bids_validator_deno-2.2.0/src/validators/filenameCase.test.ts +32 -0
  56. bids_validator_deno-2.2.0/src/validators/filenameCase.ts +16 -0
  57. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/filenameIdentify.test.ts +1 -19
  58. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/filenameIdentify.ts +0 -25
  59. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/filenameValidate.test.ts +1 -1
  60. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/validateFiles.test.ts +15 -13
  61. bids_validator_deno-2.1.0/src/files/browser.ts +0 -65
  62. bids_validator_deno-2.1.0/src/files/deno.ts +0 -158
  63. bids_validator_deno-2.1.0/src/schema/modalities.ts +0 -16
  64. bids_validator_deno-2.1.0/src/tests/utils.ts +0 -12
  65. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/LICENSE +0 -0
  66. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/README.md +0 -0
  67. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/pdm_build.py +0 -0
  68. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/.git-meta.json +0 -0
  69. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/bids-validator.ts +0 -0
  70. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/browser.test.ts +0 -0
  71. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/dwi.test.ts +0 -0
  72. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/dwi.ts +0 -0
  73. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/ignore.test.ts +0 -0
  74. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/ignore.ts +0 -0
  75. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/files/streams.test.ts +0 -0
  76. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/issues/datasetIssues.ts +0 -0
  77. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/issues/list.test.ts +0 -0
  78. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/context.test.ts +0 -0
  79. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/entities.test.ts +0 -0
  80. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/schema/entities.ts +0 -0
  81. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/loadSchema.test.ts +0 -0
  82. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/setup/requestPermissions.ts +0 -0
  83. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/summary/collectSubjectMetadata.ts +0 -0
  84. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/summary/summary.test.ts +0 -0
  85. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/summary/summary.ts +0 -0
  86. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/README.md +0 -0
  87. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/bom-utf16.tsv +0 -0
  88. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/bom-utf8.json +0 -0
  89. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/generate-filenames.ts +0 -0
  90. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/bids_examples.test.ts +0 -0
  91. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/common.ts +0 -0
  92. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/derivatives.test.ts +0 -0
  93. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/empty_files.test.ts +0 -0
  94. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/valid_dataset.test.ts +0 -0
  95. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/valid_filenames.test.ts +0 -0
  96. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/local/valid_headers.test.ts +0 -0
  97. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/nullReadBytes.ts +0 -0
  98. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/tests/schema-expression-language.test.ts +0 -0
  99. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/check.ts +0 -0
  100. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/columns.test.ts +0 -0
  101. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/columns.ts +0 -0
  102. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/issues.ts +0 -0
  103. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/schema.ts +0 -0
  104. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/types/validation-result.ts +0 -0
  105. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/errors.ts +0 -0
  106. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/logger.test.ts +0 -0
  107. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/logger.ts +0 -0
  108. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/memoize.ts +0 -0
  109. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/utils/objectPathHandler.ts +0 -0
  110. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/citation.ts +0 -0
  111. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/filenameValidate.ts +0 -0
  112. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/hed.ts +0 -0
  113. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/internal/emptyFile.ts +0 -0
  114. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/internal/unusedFile.ts +0 -0
  115. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/validators/json.ts +0 -0
  116. {bids_validator_deno-2.1.0 → bids_validator_deno-2.2.0}/src/version.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bids-validator-deno
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Typescript implementation of the BIDS validator
5
5
  Keywords: BIDS,BIDS validator
6
6
  Author: bids-standard developers
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bids/validator",
3
- "version": "2.1.0",
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@8.17.1",
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@4.0.1",
35
- "@ignore": "npm:ignore@7.0.3",
36
- "@libs/xml": "jsr:@libs/xml@6.0.4",
37
- "@mango/nifti": "npm:@bids/nifti-reader-js@0.6.9",
38
- "@std/assert": "jsr:@std/assert@1.0.12",
39
- "@std/fmt": "jsr:@std/fmt@1.0.6",
40
- "@std/fs": "jsr:@std/fs@1.0.15",
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.0.8",
44
- "@std/streams": "jsr:@std/streams@1.0.9",
45
- "@std/yaml": "jsr:@std/yaml@^1.0.5"
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
  }
@@ -18,7 +18,7 @@ keywords = [
18
18
  "BIDS validator",
19
19
  ]
20
20
  dynamic = []
21
- version = "2.1.0"
21
+ version = "2.2.0"
22
22
 
23
23
  [project.license]
24
24
  text = "MIT"
@@ -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 type { FileIgnoreRules } from './ignore.ts'
3
- import type { BIDSFile, FileTree } from '../types/filetree.ts'
4
- import { filesToTree, pathsToTree } from './filetree.ts'
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 { type BIDSFile, FileTree } from '../types/filetree.ts'
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 file.readBytes(maxBytes)
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 file.readBytes(file.size)
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
- export async function loadJSON(file: BIDSFile): Promise<Record<string, unknown>> {
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 { loadHeader, axisCodes } from './nifti.ts'
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, add(scale(cosX, dot(cosX, cosZ)), scale(orthY, dot(orthY, 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]