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.
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/PKG-INFO +1 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/deno.json +5 -3
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/pyproject.toml +1 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/access.ts +5 -5
- bids_validator_deno-2.2.1/src/files/browser.ts +34 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/deno.test.ts +1 -1
- bids_validator_deno-2.2.1/src/files/deno.ts +99 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/filetree.test.ts +14 -3
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/filetree.ts +1 -24
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/inheritance.test.ts +1 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/inheritance.ts +18 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/json.test.ts +1 -1
- bids_validator_deno-2.2.1/src/files/openers.test.ts +154 -0
- bids_validator_deno-2.2.1/src/files/openers.ts +128 -0
- bids_validator_deno-2.2.1/src/files/repo.ts +141 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/tsv.test.ts +22 -37
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/tsv.ts +4 -2
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/issues/datasetIssues.test.ts +1 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/issues/list.ts +0 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/main.ts +8 -3
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/applyRules.ts +1 -5
- bids_validator_deno-2.2.1/src/schema/associations.test.ts +32 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/associations.ts +68 -31
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/context.test.ts +16 -4
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/context.ts +11 -20
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/datatypes.test.ts +1 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/expressionLanguage.test.ts +30 -15
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/fixtures.test.ts +28 -3
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/tables.test.ts +1 -5
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/walk.test.ts +1 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/walk.ts +40 -18
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/options.test.ts +1 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/options.ts +16 -2
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/hed-integration.test.ts +8 -9
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/regression.test.ts +4 -4
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/simple-dataset.ts +1 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/filetree.ts +67 -19
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/output.ts +3 -2
- bids_validator_deno-2.2.1/src/utils/queue.ts +43 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/bids.test.ts +1 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/bids.ts +1 -6
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/citation.test.ts +1 -1
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameCase.test.ts +3 -3
- bids_validator_deno-2.2.1/src/validators/filenameCase.ts +16 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameIdentify.test.ts +9 -9
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameValidate.test.ts +4 -4
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/validateFiles.test.ts +9 -5
- bids_validator_deno-2.1.1/src/files/browser.ts +0 -65
- bids_validator_deno-2.1.1/src/files/deno.ts +0 -158
- bids_validator_deno-2.1.1/src/validators/filenameCase.ts +0 -16
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/LICENSE +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/README.md +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/pdm_build.py +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/.git-meta.json +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/bids-validator.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/access.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/browser.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/dwi.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/dwi.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/gzip.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/gzip.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/ignore.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/ignore.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/json.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/nifti.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/nifti.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/streams.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/streams.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/tiff.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/files/tiff.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/issues/datasetIssues.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/issues/list.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/applyRules.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/datatypes.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/entities.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/entities.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/expressionLanguage.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/schema/tables.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/loadSchema.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/loadSchema.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/setup/requestPermissions.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/summary/collectSubjectMetadata.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/summary/summary.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/summary/summary.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/README.md +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/bom-utf16.tsv +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/bom-utf8.json +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/generate-filenames.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/bids_examples.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/common.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/derivatives.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/empty_files.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/nifti_rules.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/valid_dataset.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/valid_filenames.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/local/valid_headers.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/nullReadBytes.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/schema-expression-language.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/tests/utils.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/check.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/columns.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/columns.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/issues.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/schema.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/types/validation-result.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/errors.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/logger.test.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/logger.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/memoize.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/utils/objectPathHandler.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/citation.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameIdentify.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/filenameValidate.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/hed.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/internal/emptyFile.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/internal/unusedFile.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/validators/json.ts +0 -0
- {bids_validator_deno-2.1.1 → bids_validator_deno-2.2.1}/src/version.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bids/validator",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
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/"
|
|
@@ -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(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
3
|
-
import
|
|
4
|
-
import { filesToTree
|
|
2
|
+
import { FileIgnoreRules } from './ignore.ts'
|
|
3
|
+
import { BIDSFile, type FileOpener, type FileTree } from '../types/filetree.ts'
|
|
4
|
+
import { filesToTree } from './filetree.ts'
|
|
5
|
+
import { 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 {
|
|
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
|
+
}
|