bids-validator-deno 2.1.1__tar.gz → 2.2.1__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 (118) hide show
  1. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/PKG-INFO +1 -1
  2. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/deno.json +5 -3
  3. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/pyproject.toml +1 -1
  4. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/access.ts +5 -5
  5. bids_validator_deno-2.2.1/src/files/browser.ts +34 -0
  6. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/deno.test.ts +1 -1
  7. bids_validator_deno-2.2.1/src/files/deno.ts +99 -0
  8. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/filetree.test.ts +14 -3
  9. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/filetree.ts +1 -24
  10. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/inheritance.test.ts +1 -1
  11. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/inheritance.ts +18 -0
  12. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/json.test.ts +1 -1
  13. bids_validator_deno-2.2.1/src/files/openers.test.ts +154 -0
  14. bids_validator_deno-2.2.1/src/files/openers.ts +128 -0
  15. bids_validator_deno-2.2.1/src/files/repo.ts +141 -0
  16. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/tsv.test.ts +22 -37
  17. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/tsv.ts +4 -2
  18. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/issues/datasetIssues.test.ts +1 -1
  19. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/issues/list.ts +0 -1
  20. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/main.ts +8 -3
  21. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/applyRules.ts +1 -5
  22. bids_validator_deno-2.2.1/src/schema/associations.test.ts +32 -0
  23. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/associations.ts +68 -31
  24. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/context.test.ts +16 -4
  25. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/context.ts +11 -20
  26. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/datatypes.test.ts +1 -1
  27. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/expressionLanguage.test.ts +30 -15
  28. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/fixtures.test.ts +28 -3
  29. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/tables.test.ts +1 -5
  30. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/walk.test.ts +1 -1
  31. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/walk.ts +40 -18
  32. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/options.test.ts +1 -0
  33. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/options.ts +16 -2
  34. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/hed-integration.test.ts +8 -9
  35. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/regression.test.ts +4 -4
  36. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/simple-dataset.ts +1 -1
  37. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/filetree.ts +67 -19
  38. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/output.ts +3 -2
  39. bids_validator_deno-2.2.1/src/utils/queue.ts +43 -0
  40. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/bids.test.ts +1 -1
  41. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/bids.ts +1 -6
  42. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/citation.test.ts +1 -1
  43. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameCase.test.ts +3 -3
  44. bids_validator_deno-2.2.1/src/validators/filenameCase.ts +16 -0
  45. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameIdentify.test.ts +9 -9
  46. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameValidate.test.ts +4 -4
  47. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/validateFiles.test.ts +9 -5
  48. bids_validator_deno-2.1.1/src/files/browser.ts +0 -65
  49. bids_validator_deno-2.1.1/src/files/deno.ts +0 -158
  50. bids_validator_deno-2.1.1/src/validators/filenameCase.ts +0 -16
  51. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/LICENSE +0 -0
  52. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/README.md +0 -0
  53. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/pdm_build.py +0 -0
  54. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/.git-meta.json +0 -0
  55. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/bids-validator.ts +0 -0
  56. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/access.test.ts +0 -0
  57. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/browser.test.ts +0 -0
  58. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/dwi.test.ts +0 -0
  59. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/dwi.ts +0 -0
  60. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/gzip.test.ts +0 -0
  61. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/gzip.ts +0 -0
  62. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/ignore.test.ts +0 -0
  63. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/ignore.ts +0 -0
  64. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/json.ts +0 -0
  65. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/nifti.test.ts +0 -0
  66. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/nifti.ts +0 -0
  67. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/streams.test.ts +0 -0
  68. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/streams.ts +0 -0
  69. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/tiff.test.ts +0 -0
  70. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/tiff.ts +0 -0
  71. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/issues/datasetIssues.ts +0 -0
  72. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/issues/list.test.ts +0 -0
  73. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/applyRules.test.ts +0 -0
  74. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/datatypes.ts +0 -0
  75. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/entities.test.ts +0 -0
  76. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/entities.ts +0 -0
  77. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/expressionLanguage.ts +0 -0
  78. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/tables.ts +0 -0
  79. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/loadSchema.test.ts +0 -0
  80. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/loadSchema.ts +0 -0
  81. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/requestPermissions.ts +0 -0
  82. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/summary/collectSubjectMetadata.ts +0 -0
  83. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/summary/summary.test.ts +0 -0
  84. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/summary/summary.ts +0 -0
  85. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/README.md +0 -0
  86. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/bom-utf16.tsv +0 -0
  87. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/bom-utf8.json +0 -0
  88. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/generate-filenames.ts +0 -0
  89. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/bids_examples.test.ts +0 -0
  90. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/common.ts +0 -0
  91. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/derivatives.test.ts +0 -0
  92. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/empty_files.test.ts +0 -0
  93. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/nifti_rules.test.ts +0 -0
  94. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/valid_dataset.test.ts +0 -0
  95. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/valid_filenames.test.ts +0 -0
  96. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/valid_headers.test.ts +0 -0
  97. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/nullReadBytes.ts +0 -0
  98. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/schema-expression-language.test.ts +0 -0
  99. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/utils.ts +0 -0
  100. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/check.ts +0 -0
  101. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/columns.test.ts +0 -0
  102. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/columns.ts +0 -0
  103. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/issues.ts +0 -0
  104. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/schema.ts +0 -0
  105. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/validation-result.ts +0 -0
  106. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/errors.ts +0 -0
  107. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/logger.test.ts +0 -0
  108. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/logger.ts +0 -0
  109. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/memoize.ts +0 -0
  110. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/objectPathHandler.ts +0 -0
  111. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/citation.ts +0 -0
  112. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameIdentify.ts +0 -0
  113. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameValidate.ts +0 -0
  114. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/hed.ts +0 -0
  115. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/internal/emptyFile.ts +0 -0
  116. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/internal/unusedFile.ts +0 -0
  117. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/json.ts +0 -0
  118. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/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.1
3
+ Version: 2.2.1
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.1",
3
+ "version": "2.2.1",
4
4
  "exports": {
5
5
  ".": "./src/bids-validator.ts",
6
6
  "./main": "./src/main.ts",
@@ -31,7 +31,7 @@
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",
34
+ "@hed/validator": "npm:hed-validator@~4.1.4",
35
35
  "@ignore": "npm:ignore@^7.0.5",
36
36
  "@libs/xml": "jsr:@libs/xml@^6.0.8",
37
37
  "@mango/nifti": "npm:@bids/nifti-reader-js@^0.6.9",
@@ -42,7 +42,9 @@
42
42
  "@std/log": "jsr:@std/log@^0.224.14",
43
43
  "@std/path": "jsr:@std/path@^1.1.2",
44
44
  "@std/streams": "jsr:@std/streams@^1.0.12",
45
- "@std/yaml": "jsr:@std/yaml@^1.0.9"
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/"
@@ -18,7 +18,7 @@ keywords = [
18
18
  "BIDS validator",
19
19
  ]
20
20
  dynamic = []
21
- version = "2.1.1"
21
+ version = "2.2.1"
22
22
 
23
23
  [project.license]
24
24
  text = "MIT"
@@ -10,12 +10,12 @@ function IOErrorToIssue(err: { code: string; name: string }): Issue {
10
10
  return { code: 'FILE_READ', subCode: err.name, issueMessage }
11
11
  }
12
12
 
13
- export function openStream(file: BIDSFile): ReadableStream<Uint8Array<ArrayBuffer>> {
14
- try {
15
- return file.stream
16
- } catch (err: any) {
13
+ export async function openStream(
14
+ file: BIDSFile,
15
+ ): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
16
+ return file.stream().catch((err: any) => {
17
17
  throw { location: file.path, ...IOErrorToIssue(err) }
18
- }
18
+ })
19
19
  }
20
20
 
21
21
  export async function readBytes(
@@ -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,18 @@
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 { StringOpener } from './openers.test.ts'
6
+
7
+ export function pathToFile(path: string, ignored: boolean = false): BIDSFile {
8
+ const name = path.split('/').pop() as string
9
+ return new BIDSFile(path, new StringOpener(''), ignored)
10
+ }
11
+
12
+ export function pathsToTree(paths: string[], ignore?: string[]): FileTree {
13
+ const ignoreRules = new FileIgnoreRules(ignore ?? [])
14
+ return filesToTree(paths.map((path) => pathToFile(path, ignoreRules.test(path))))
15
+ }
5
16
 
6
17
  Deno.test('FileTree generation', async (t) => {
7
18
  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,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) => {
@@ -1,5 +1,6 @@
1
1
  import type { BIDSFile, FileTree } from '../types/filetree.ts'
2
2
  import { readEntities } from '../schema/entities.ts'
3
+ import { loadJSON } from './json.ts'
3
4
 
4
5
  type Ret<T> = T extends [string, ...string[]] ? (BIDSFile | BIDSFile[]) : BIDSFile
5
6
 
@@ -77,3 +78,20 @@ export function* walkBack<T extends string[]>(
77
78
  fileTree = fileTree.parent
78
79
  }
79
80
  }
81
+
82
+ export async function readSidecars(
83
+ source: BIDSFile,
84
+ ): Promise<Map<string, Record<string, unknown>>> {
85
+ const ret: Map<string, Record<string, unknown>> = new Map()
86
+ for (const file of walkBack(source)) {
87
+ try {
88
+ ret.set(file.path, await loadJSON(file))
89
+ } catch (e: any) {
90
+ // Expect JSON parsing errors to be handled when the file is loaded directly
91
+ if (!e?.code) {
92
+ throw e
93
+ }
94
+ }
95
+ }
96
+ return ret
97
+ }
@@ -3,7 +3,7 @@ import type { BIDSFile } from '../types/filetree.ts'
3
3
  import type { FileIgnoreRules } from './ignore.ts'
4
4
  import { testAsyncFileAccess } from './access.test.ts'
5
5
 
6
- import { pathsToTree } from '../files/filetree.ts'
6
+ import { pathsToTree } from '../files/filetree.test.ts'
7
7
  import { loadJSON } from './json.ts'
8
8
 
9
9
  function encodeUTF16(text: string) {
@@ -0,0 +1,154 @@
1
+ /**
2
+ * File openers for use in testing
3
+ */
4
+ import { assertEquals } from '@std/assert'
5
+ import { type FileOpener } from '../types/filetree.ts'
6
+ import { streamFromString } from '../tests/utils.ts'
7
+ import { createUTF8Stream } from './streams.ts'
8
+
9
+ const textEncoder = new TextEncoder()
10
+ const textDecoder = new TextDecoder()
11
+
12
+ export class BytesOpener implements FileOpener {
13
+ contents: Uint8Array<ArrayBuffer>
14
+ size: number
15
+
16
+ constructor(contents: Uint8Array<ArrayBuffer>) {
17
+ this.contents = contents
18
+ this.size = contents.length
19
+ }
20
+
21
+ async text(): Promise<string> {
22
+ return textDecoder.decode(this.contents)
23
+ }
24
+
25
+ async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
26
+ return this.contents.slice(offset, offset + size)
27
+ }
28
+
29
+ async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
30
+ const contents = this.contents
31
+ return new ReadableStream({
32
+ start(controller) {
33
+ controller.enqueue(contents)
34
+ controller.close()
35
+ },
36
+ })
37
+ }
38
+ }
39
+
40
+ export class StringOpener extends BytesOpener {
41
+ constructor(contents: string) {
42
+ super(textEncoder.encode(contents))
43
+ }
44
+ }
45
+
46
+ export class StreamOpener implements FileOpener {
47
+ #stream: ReadableStream<Uint8Array<ArrayBuffer>>
48
+ size: number
49
+
50
+ constructor(stream: ReadableStream<Uint8Array<ArrayBuffer>>, size: number) {
51
+ this.#stream = stream
52
+ this.size = size
53
+ }
54
+
55
+ async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
56
+ const tee = this.#stream.tee()
57
+ this.#stream = tee[1]
58
+ return tee[0]
59
+ }
60
+
61
+ async text(): Promise<string> {
62
+ const stream = await this.stream()
63
+ const reader = stream.pipeThrough(createUTF8Stream()).getReader()
64
+ const chunks: string[] = []
65
+ try {
66
+ while (true) {
67
+ const { done, value } = await reader.read()
68
+ if (done) break
69
+ chunks.push(value)
70
+ }
71
+ return chunks.join('')
72
+ } finally {
73
+ reader.releaseLock()
74
+ }
75
+ }
76
+
77
+ // Not a thoroughly tested implementation
78
+ // Do not move this out of test files without adding more substantial tests
79
+ async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
80
+ const stream = await this.stream()
81
+ const reader = stream.getReader()
82
+ const result = new Uint8Array(size)
83
+ let pos = 0
84
+ try {
85
+ while (pos < offset + size) {
86
+ const { done, value } = await reader.read()
87
+ if (done) break
88
+ if (pos + value.length > offset) {
89
+ result.set(
90
+ value.subarray(Math.max(0, offset - pos), Math.min(value.length, offset + size - pos)),
91
+ Math.max(0, pos - offset),
92
+ )
93
+ }
94
+ pos += value.length
95
+ }
96
+ } finally {
97
+ reader.releaseLock()
98
+ }
99
+ return result.subarray(0, Math.min(size, pos - offset))
100
+ }
101
+ }
102
+
103
+ export class CompressedStringOpener extends StreamOpener {
104
+ constructor(contents: string) {
105
+ // Use uncompressed length as size, for simplicity
106
+ super(streamFromString(contents).pipeThrough(new CompressionStream('gzip')), contents.length)
107
+ }
108
+ }
109
+
110
+ async function testOpener(t: Deno.TestContext, opener: FileOpener) {
111
+ await t.step('size', async () => {
112
+ assertEquals(opener.size, 13)
113
+ })
114
+ await t.step('text()', async () => {
115
+ assertEquals(await opener.text(), 'Hello, world!')
116
+ })
117
+ await t.step('readBytes()', async () => {
118
+ assertEquals(textDecoder.decode(await opener.readBytes(5)), 'Hello')
119
+ assertEquals(textDecoder.decode(await opener.readBytes(7, 5)), ', world')
120
+ })
121
+ await t.step('stream()', async () => {
122
+ const stream = await opener.stream()
123
+ const chunks: string[] = []
124
+ for await (const chunk of stream) {
125
+ chunks.push(textDecoder.decode(chunk))
126
+ }
127
+ assertEquals(chunks.join(''), 'Hello, world!')
128
+ })
129
+ }
130
+
131
+ Deno.test('Validate BytesOpener', async (t) => {
132
+ await testOpener(t, new BytesOpener(textEncoder.encode('Hello, world!')))
133
+ })
134
+
135
+ Deno.test('Validate StringOpener', async (t) => {
136
+ await testOpener(t, new StringOpener('Hello, world!'))
137
+ })
138
+
139
+ Deno.test('Validate StreamOpener', async (t) => {
140
+ await testOpener(t, new StreamOpener(streamFromString('Hello, world!'), 13))
141
+ })
142
+
143
+ Deno.test('Validate CompressedStringOpener', async (t) => {
144
+ const opener = new CompressedStringOpener('Hello, world!')
145
+ await t.step('Decompress', async () => {
146
+ const stream = await opener.stream()
147
+ const decompressedStream = stream.pipeThrough(new DecompressionStream('gzip'))
148
+ const chunks: string[] = []
149
+ for await (const chunk of decompressedStream) {
150
+ chunks.push(textDecoder.decode(chunk))
151
+ }
152
+ assertEquals(chunks.join(''), 'Hello, world!')
153
+ })
154
+ })
@@ -0,0 +1,128 @@
1
+ /**
2
+ * BIDS file openers
3
+ *
4
+ * These classes implement stream, text and random bytes access to BIDS resources.
5
+ */
6
+ import { join } from '@std/path'
7
+ import { type FileOpener } from '../types/filetree.ts'
8
+ import { createUTF8Stream } from './streams.ts'
9
+
10
+ export class FsFileOpener implements FileOpener {
11
+ path: string
12
+ fileInfo!: Deno.FileInfo
13
+
14
+ constructor(datasetPath: string, path: string, fileInfo?: Deno.FileInfo) {
15
+ this.path = join(datasetPath, path)
16
+ if (fileInfo) {
17
+ this.fileInfo = fileInfo
18
+ } else {
19
+ try {
20
+ this.fileInfo = Deno.statSync(this.path)
21
+ } catch (error) {
22
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
23
+ this.fileInfo = Deno.lstatSync(this.path)
24
+ }
25
+ }
26
+ }
27
+ }
28
+
29
+ get size(): number {
30
+ return this.fileInfo.size
31
+ }
32
+
33
+ async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
34
+ const handle = await this.open()
35
+ return handle.readable
36
+ }
37
+
38
+ /**
39
+ * Read the entire file and decode as utf-8 text
40
+ */
41
+ async text(): Promise<string> {
42
+ const stream = await this.stream()
43
+ const reader = stream.pipeThrough(createUTF8Stream()).getReader()
44
+ const chunks: string[] = []
45
+ try {
46
+ while (true) {
47
+ const { done, value } = await reader.read()
48
+ if (done) break
49
+ chunks.push(value)
50
+ }
51
+ return chunks.join('')
52
+ } finally {
53
+ reader.releaseLock()
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Read bytes in a range efficiently from a given file
59
+ *
60
+ * Reads up to size bytes, starting at offset.
61
+ * If EOF is encountered, the resulting array may be smaller.
62
+ */
63
+ async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
64
+ const handle = await this.open()
65
+ const buf = new Uint8Array(size)
66
+ await handle.seek(offset, Deno.SeekMode.Start)
67
+ const nbytes = await handle.read(buf) ?? 0
68
+ await handle.close()
69
+ return buf.subarray(0, nbytes)
70
+ }
71
+
72
+ async open(): Promise<Deno.FsFile> {
73
+ return Deno.open(this.path, { read: true, write: false })
74
+ }
75
+ }
76
+
77
+ export class BrowserFileOpener implements FileOpener {
78
+ file: File
79
+ constructor(file: File) {
80
+ this.file = file
81
+ }
82
+
83
+ get size(): number {
84
+ return this.file.size
85
+ }
86
+
87
+ async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
88
+ return Promise.resolve(this.file.stream() as ReadableStream<Uint8Array<ArrayBuffer>>)
89
+ }
90
+
91
+ async text(): Promise<string> {
92
+ return this.file.text()
93
+ }
94
+
95
+ async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
96
+ return new Uint8Array(await this.file.slice(offset, size).arrayBuffer())
97
+ }
98
+ }
99
+
100
+ export class HTTPOpener implements FileOpener {
101
+ url: string
102
+ size: number
103
+
104
+ constructor(url: string, size: number = -1) {
105
+ this.url = url
106
+ this.size = size
107
+ }
108
+
109
+ async stream(): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
110
+ const response = await fetch(this.url)
111
+ if (!response.ok || !response.body) {
112
+ throw new Error(`Failed to fetch ${this.url}: ${response.status} ${response.statusText}`)
113
+ }
114
+ return response.body
115
+ }
116
+
117
+ async text(): Promise<string> {
118
+ const response = await fetch(this.url)
119
+ return response.text()
120
+ }
121
+
122
+ async readBytes(size: number, offset = 0): Promise<Uint8Array<ArrayBuffer>> {
123
+ const headers = new Headers()
124
+ headers.append('Range', `bytes=${offset}-${offset + size - 1}`)
125
+ const response = await fetch(this.url, { headers })
126
+ return new Uint8Array(await response.arrayBuffer())
127
+ }
128
+ }