bids-validator-deno 2.1.1__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 (115) hide show
  1. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/PKG-INFO +1 -1
  2. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/deno.json +5 -3
  3. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/pyproject.toml +1 -1
  4. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/access.ts +5 -5
  5. bids_validator_deno-2.2.0/src/files/browser.ts +34 -0
  6. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/deno.test.ts +1 -1
  7. bids_validator_deno-2.2.0/src/files/deno.ts +99 -0
  8. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/filetree.test.ts +21 -3
  9. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/filetree.ts +1 -24
  10. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/inheritance.test.ts +1 -1
  11. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/json.test.ts +1 -1
  12. bids_validator_deno-2.2.0/src/files/openers.ts +128 -0
  13. bids_validator_deno-2.2.0/src/files/repo.ts +141 -0
  14. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/tsv.test.ts +37 -35
  15. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/tsv.ts +4 -2
  16. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/issues/datasetIssues.test.ts +1 -1
  17. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/issues/list.ts +0 -1
  18. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/main.ts +8 -3
  19. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/applyRules.ts +1 -5
  20. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/datatypes.test.ts +1 -1
  21. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/expressionLanguage.test.ts +27 -13
  22. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/fixtures.test.ts +1 -1
  23. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/tables.test.ts +1 -5
  24. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/walk.test.ts +1 -1
  25. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/walk.ts +21 -14
  26. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/setup/options.test.ts +1 -0
  27. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/setup/options.ts +16 -2
  28. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/local/hed-integration.test.ts +4 -4
  29. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/regression.test.ts +4 -4
  30. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/simple-dataset.ts +1 -1
  31. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/utils.ts +6 -0
  32. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/types/filetree.ts +67 -19
  33. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/utils/output.ts +3 -2
  34. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/bids.test.ts +1 -1
  35. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/citation.test.ts +1 -1
  36. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/filenameCase.test.ts +3 -3
  37. bids_validator_deno-2.2.0/src/validators/filenameCase.ts +16 -0
  38. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/filenameValidate.test.ts +1 -1
  39. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/validateFiles.test.ts +1 -1
  40. bids_validator_deno-2.1.1/src/files/browser.ts +0 -65
  41. bids_validator_deno-2.1.1/src/files/deno.ts +0 -158
  42. bids_validator_deno-2.1.1/src/validators/filenameCase.ts +0 -16
  43. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/LICENSE +0 -0
  44. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/README.md +0 -0
  45. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/pdm_build.py +0 -0
  46. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/.git-meta.json +0 -0
  47. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/bids-validator.ts +0 -0
  48. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/access.test.ts +0 -0
  49. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/browser.test.ts +0 -0
  50. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/dwi.test.ts +0 -0
  51. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/dwi.ts +0 -0
  52. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/gzip.test.ts +0 -0
  53. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/gzip.ts +0 -0
  54. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/ignore.test.ts +0 -0
  55. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/ignore.ts +0 -0
  56. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/inheritance.ts +0 -0
  57. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/json.ts +0 -0
  58. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/nifti.test.ts +0 -0
  59. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/nifti.ts +0 -0
  60. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/streams.test.ts +0 -0
  61. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/streams.ts +0 -0
  62. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/tiff.test.ts +0 -0
  63. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/files/tiff.ts +0 -0
  64. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/issues/datasetIssues.ts +0 -0
  65. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/issues/list.test.ts +0 -0
  66. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/applyRules.test.ts +0 -0
  67. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/associations.ts +0 -0
  68. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/context.test.ts +0 -0
  69. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/context.ts +0 -0
  70. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/datatypes.ts +0 -0
  71. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/entities.test.ts +0 -0
  72. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/entities.ts +0 -0
  73. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/expressionLanguage.ts +0 -0
  74. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/schema/tables.ts +0 -0
  75. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/setup/loadSchema.test.ts +0 -0
  76. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/setup/loadSchema.ts +0 -0
  77. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/setup/requestPermissions.ts +0 -0
  78. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/summary/collectSubjectMetadata.ts +0 -0
  79. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/summary/summary.test.ts +0 -0
  80. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/summary/summary.ts +0 -0
  81. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/README.md +0 -0
  82. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/bom-utf16.tsv +0 -0
  83. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/bom-utf8.json +0 -0
  84. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/generate-filenames.ts +0 -0
  85. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/local/bids_examples.test.ts +0 -0
  86. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/local/common.ts +0 -0
  87. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/local/derivatives.test.ts +0 -0
  88. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/local/empty_files.test.ts +0 -0
  89. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/local/nifti_rules.test.ts +0 -0
  90. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/local/valid_dataset.test.ts +0 -0
  91. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/local/valid_filenames.test.ts +0 -0
  92. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/local/valid_headers.test.ts +0 -0
  93. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/nullReadBytes.ts +0 -0
  94. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/tests/schema-expression-language.test.ts +0 -0
  95. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/types/check.ts +0 -0
  96. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/types/columns.test.ts +0 -0
  97. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/types/columns.ts +0 -0
  98. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/types/issues.ts +0 -0
  99. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/types/schema.ts +0 -0
  100. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/types/validation-result.ts +0 -0
  101. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/utils/errors.ts +0 -0
  102. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/utils/logger.test.ts +0 -0
  103. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/utils/logger.ts +0 -0
  104. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/utils/memoize.ts +0 -0
  105. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/utils/objectPathHandler.ts +0 -0
  106. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/bids.ts +0 -0
  107. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/citation.ts +0 -0
  108. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/filenameIdentify.test.ts +0 -0
  109. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/filenameIdentify.ts +0 -0
  110. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/filenameValidate.ts +0 -0
  111. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/hed.ts +0 -0
  112. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/internal/emptyFile.ts +0 -0
  113. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/internal/unusedFile.ts +0 -0
  114. {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.0}/src/validators/json.ts +0 -0
  115. {bids_validator_deno-2.1.1 → 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.1
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.1",
3
+ "version": "2.2.0",
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.0"
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,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,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) => {
@@ -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,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
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Utilities for reading git-annex metadata
3
+ */
4
+ import { dirname, join, parse, SEPARATOR_PATTERN } from '@std/path'
5
+ import { default as git } from 'isomorphic-git'
6
+ import { createMD5 } from 'hash-wasm'
7
+
8
+ const textDecoder = new TextDecoder('utf-8')
9
+ export const annexKeyRegex =
10
+ /^(?<hashname>[A-Z0-9]+)-s(?<size>\d+)--(?<digest>[0-9a-fA-F]+)(?<ext>\.[\w\-. ]*)?/
11
+ export const rmetLineRegex =
12
+ /^(?<timestamp>\d+(\.\d+)?)s (?<uuid>[0-9a-fA-F-]+):V \+(?<version>[^#]+)#(?<path>.+)/
13
+
14
+ type Rmet = {
15
+ timestamp: number
16
+ uuid: string
17
+ version: string
18
+ path: string
19
+ }
20
+
21
+ export async function readAnnexPath(
22
+ filepath: string,
23
+ options: any,
24
+ ): Promise<string> {
25
+ const oid = await git.resolveRef({ ref: 'git-annex', ...options })
26
+ const { blob } = await git.readBlob({ oid, filepath, ...options })
27
+ return textDecoder.decode(blob)
28
+ }
29
+
30
+ /**
31
+ * git-annex hashDirLower implementation based on https://git-annex.branchable.com/internals/hashing/
32
+ * Compute the directory path from a git-annex filename
33
+ */
34
+ export async function hashDirLower(annexKey: string): Promise<[string, string]> {
35
+ const computeMD5 = await createMD5()
36
+ computeMD5.init()
37
+ computeMD5.update(annexKey)
38
+ const digest = computeMD5.digest('hex')
39
+ return [digest.slice(0, 3), digest.slice(3, 6)]
40
+ }
41
+
42
+ /**
43
+ * Read remote metadata entries for a given annex key
44
+ *
45
+ * *.log.rmet
46
+ * Path: {md5(key)[0:3]}/{md5(key)[3:6]}/{key}.log.rmet
47
+ * Contents:
48
+ * <timestamp> <uuid>:V +<version>#<path>
49
+ *
50
+ * The general form is <uuid>:<key> [+-]<value> and is an append-only log
51
+ * We may at some point care about doing this correctly.
52
+ */
53
+ async function readRmet(key: string, options: any): Promise<Record<string, Rmet>> {
54
+ const hashDirs = await hashDirLower(key)
55
+ const rmet = await readAnnexPath(join(...hashDirs, `${key}.log.rmet`), options)
56
+ const ret: Record<string, Rmet> = {}
57
+ for (const line of rmet.split('\n')) {
58
+ const match = line.match(rmetLineRegex)
59
+ if (match) {
60
+ ret[match!.groups!.uuid] = match!.groups as unknown as Rmet
61
+ }
62
+ }
63
+ return ret
64
+ }
65
+
66
+ /**
67
+ * Read special remote configuration from remote.log
68
+ *
69
+ * remote.log
70
+ * <uuid> [<key>=<value>]...
71
+ * keys of interest:
72
+ * name
73
+ * type (S3)
74
+ * publicurl
75
+ * timestamp
76
+ */
77
+ export async function readRemotes(options: any): Promise<Record<string, Record<string, string>>> {
78
+ const remotesText = await readAnnexPath('remote.log', options)
79
+ const byUUID: Record<string, Record<string, string>> = {}
80
+ for (const line of remotesText.split('\n')) {
81
+ const [uuid, ...keyvals] = line.split(' ')
82
+ byUUID[uuid] = Object.fromEntries(keyvals.map((kv) => kv.split('=')))
83
+ }
84
+ return byUUID
85
+ }
86
+
87
+ /**
88
+ * Resolve an annexed file location to an HTTP URL, if a public S3 remote is available
89
+ */
90
+ export async function resolveAnnexedFile(
91
+ path: string,
92
+ remote?: string,
93
+ options?: any,
94
+ ): Promise<{ url: string; size: number }> {
95
+ // path is known to be a symlink
96
+ const target = await Deno.readLink(path)
97
+ const { dir, base } = parse(target)
98
+
99
+ if (!options?.gitdir) {
100
+ const dirs = dir.split(SEPARATOR_PATTERN)
101
+ const gitdir = join(dirname(path), ...dirs.slice(0, dirs.indexOf('.git') + 1))
102
+ options = { ...options, gitdir }
103
+ }
104
+
105
+ const size = +base.match(annexKeyRegex)?.groups?.size!
106
+
107
+ const rmet = await readRmet(base, options)
108
+ const remotes = await readRemotes(options)
109
+ let uuid: string
110
+ if (remote) {
111
+ let matching: string | undefined
112
+ for (const [u, r] of Object.entries(remotes)) {
113
+ // Only consider public S3 remotes.
114
+ // This will need to be expanded for other types of remotes in future
115
+ if (!r?.publicurl) {
116
+ continue
117
+ }
118
+ if (r.name === remote) {
119
+ matching = u
120
+ break
121
+ }
122
+ }
123
+ if (!matching) {
124
+ throw new Error(`No remote named ${remote}`)
125
+ }
126
+ uuid = matching
127
+ } else {
128
+ // Take the newest remote (reverse sort timestamps, take first)
129
+ uuid = Object.entries(rmet).toSorted((a, b) => +b[1].timestamp - +a[1].timestamp)[0][0]
130
+ }
131
+ const { publicurl } = remotes[uuid]
132
+
133
+ if (!publicurl) {
134
+ throw new Error(`No publicurl found for remote ${uuid}`)
135
+ }
136
+
137
+ const metadata = rmet[uuid]
138
+ const url = `${publicurl}/${metadata.path}?versionId=${metadata.version}`
139
+
140
+ return { url, size }
141
+ }