@jbrowse/cli 4.3.0 → 5.0.0-beta.10
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.
- package/README.md +451 -72
- package/dist/base.js +0 -3
- package/dist/bin.js +2 -1
- package/dist/cliFetch.js +3 -0
- package/dist/commands/add-assembly/index.js +46 -19
- package/dist/commands/add-assembly/utils.js +204 -290
- package/dist/commands/add-connection.js +73 -79
- package/dist/commands/add-track-json.js +24 -12
- package/dist/commands/add-track-utils/adapter-utils.js +189 -288
- package/dist/commands/add-track-utils/file-operations.js +27 -11
- package/dist/commands/add-track-utils/multiwig.js +47 -0
- package/dist/commands/add-track-utils/track-config.js +66 -25
- package/dist/commands/add-track-utils/validators.js +36 -20
- package/dist/commands/add-track.js +200 -62
- package/dist/commands/admin-server/index.js +39 -50
- package/dist/commands/admin-server/utils.js +238 -126
- package/dist/commands/create.js +18 -34
- package/dist/commands/make-density/density-generator.js +180 -0
- package/dist/commands/make-density/index.js +139 -0
- package/dist/commands/make-pif/index.js +133 -11
- package/dist/commands/make-pif/pif-generator.js +243 -43
- package/dist/commands/process-utils.js +13 -2
- package/dist/commands/remove-track.js +20 -13
- package/dist/commands/set-default-session.js +35 -31
- package/dist/commands/shared/config-operations.js +3 -10
- package/dist/commands/shared/density.js +12 -0
- package/dist/commands/shared/sort-utils.js +67 -10
- package/dist/commands/shared/validators.js +38 -3
- package/dist/commands/sort-bed.js +2 -33
- package/dist/commands/sort-gff.js +2 -33
- package/dist/commands/text-index/adapter-utils.js +19 -26
- package/dist/commands/text-index/aggregate.js +40 -34
- package/dist/commands/text-index/command.js +37 -7
- package/dist/commands/text-index/config-utils.js +62 -85
- package/dist/commands/text-index/file-list.js +32 -18
- package/dist/commands/text-index/index.js +4 -4
- package/dist/commands/text-index/indexing-utils.js +44 -96
- package/dist/commands/text-index/per-track.js +44 -31
- package/dist/commands/text-index/validators.js +1 -1
- package/dist/commands/upgrade.js +20 -32
- package/dist/commands/validate/configManifest.generated.js +7613 -0
- package/dist/commands/validate/configSchema.generated.js +20400 -0
- package/dist/commands/validate/displayDefaultKeys.js +28 -0
- package/dist/commands/validate/index.js +113 -0
- package/dist/commands/validate/liftConfig.js +34 -0
- package/dist/commands/validate/markRules/aggregateFieldName.js +9 -0
- package/dist/commands/validate/markRules/autoBin.js +27 -0
- package/dist/commands/validate/markRules/colorScale.js +244 -0
- package/dist/commands/validate/markRules/cssColorsLevel4.js +162 -0
- package/dist/commands/validate/markRules/jexlStrings.js +23 -0
- package/dist/commands/validate/markRules/markProblems.js +532 -0
- package/dist/commands/validate/markRules/markRuleFacts.js +6 -0
- package/dist/commands/validate/markRules/markSpecs.js +96 -0
- package/dist/commands/validate/markRules/markVocabulary.js +57 -0
- package/dist/commands/validate/markRules/shapeNameList.js +7 -0
- package/dist/commands/validate/markRules/stepChannels.js +55 -0
- package/dist/commands/validate/schemaValidate.js +425 -0
- package/dist/commands/validate/suggest.js +35 -0
- package/dist/commands/validate/types.js +6 -0
- package/dist/commands/validate/validateConfig.js +502 -0
- package/dist/index.js +104 -50
- package/dist/types/common.js +3 -27
- package/dist/utils.js +309 -128
- package/dist/version.js +1 -1
- package/package.json +8 -14
- package/bin/run +0 -4
- package/bundle/index.js +0 -3686
- package/dist/commands/make-pif/cigar-utils.js +0 -24
- package/dist/commands/make-pif/file-utils.js +0 -30
- package/dist/commands/track-utils.js +0 -74
- package/dist/util.js +0 -35
|
@@ -1,302 +1,203 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { adapterTypesToTrackTypeMap, formats, matchFormat, resolveIndexType, sidecarCandidateNames, trackTypeForAdapter, } from '@jbrowse/add-track-core';
|
|
3
|
+
import { isURL } from '../../types/common.js';
|
|
4
|
+
import { densitySidecarPath } from '../shared/density.js';
|
|
5
|
+
import { configManifest } from '../validate/configManifest.generated.js';
|
|
6
|
+
function hasLocField(spec) {
|
|
7
|
+
return 'locField' in spec;
|
|
8
|
+
}
|
|
9
|
+
const adapterTypeToSpec = {};
|
|
10
|
+
for (const { spec } of formats) {
|
|
11
|
+
if (hasLocField(spec) && !adapterTypeToSpec[spec.adapterType]) {
|
|
12
|
+
adapterTypeToSpec[spec.adapterType] = spec;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The sidecar actually sitting beside `location`, or the conventional name when
|
|
17
|
+
* none of the spellings is there — so a missing index still reports the file
|
|
18
|
+
* that was expected, and `--load` still names it.
|
|
19
|
+
*
|
|
20
|
+
* Probed with `existsSync`, which is false for every candidate of a URL and so
|
|
21
|
+
* leaves a remote track with exactly the conventional guess it had before this
|
|
22
|
+
* existed. A remote `.csi` still wants `--indexFile`.
|
|
23
|
+
*/
|
|
24
|
+
export function siblingSidecar(location, suffix) {
|
|
25
|
+
const candidates = sidecarCandidateNames(location, suffix);
|
|
26
|
+
return candidates.find(c => fs.existsSync(c)) ?? candidates[0];
|
|
27
|
+
}
|
|
28
|
+
// The adapters that declare a `densityAdapter` slot, read off the generated
|
|
29
|
+
// manifest `jbrowse validate` uses. The hand-written list missed `GWASAdapter`,
|
|
30
|
+
// which inherits the slot through `bedTabixConfigSchema` instead of spreading
|
|
31
|
+
// `densityAdapterConfigSchemaFields`, so `--density` on a Pan-UKBB file was
|
|
32
|
+
// refused by a message that listed nine adapters and not the tenth.
|
|
33
|
+
const densityAdapterTypes = new Set(Object.entries(configManifest.adapters)
|
|
34
|
+
.filter(([, entry]) => entry.slots.some(s => s.name === 'densityAdapter'))
|
|
35
|
+
.map(([name]) => name)
|
|
36
|
+
.sort());
|
|
37
|
+
/**
|
|
38
|
+
* The adapter with its density sidecar attached, and the local file to load
|
|
39
|
+
* beside the config when there is one.
|
|
40
|
+
*
|
|
41
|
+
* An explicit `--density` is taken as given, since a URL cannot be probed;
|
|
42
|
+
* otherwise the conventional `<file>.density.bw` counts only when it is
|
|
43
|
+
* actually there, the way `siblingSidecar` probes an index.
|
|
44
|
+
*/
|
|
45
|
+
export function withDensityAdapter({ adapter, location, density, makeLocation, }) {
|
|
46
|
+
const supported = densityAdapterTypes.has(adapter.type);
|
|
47
|
+
if (density !== undefined && !supported) {
|
|
48
|
+
throw new Error(`--density has no slot on ${adapter.type}. The adapters carrying a density sidecar are: ${[...densityAdapterTypes].join(', ')}`);
|
|
49
|
+
}
|
|
50
|
+
const probed = location !== undefined && fs.existsSync(densitySidecarPath(location))
|
|
51
|
+
? densitySidecarPath(location)
|
|
52
|
+
: undefined;
|
|
53
|
+
if (probed !== undefined && !supported) {
|
|
54
|
+
console.warn(`Warning: ${probed} sits beside the track file but ${adapter.type} has no densityAdapter slot, so it is not attached. The adapters carrying one are indexed: bgzip and tabix the file, then add it.`);
|
|
55
|
+
}
|
|
56
|
+
const file = density === undefined ? (supported ? probed : undefined) : density;
|
|
57
|
+
return file === undefined
|
|
58
|
+
? { adapter }
|
|
59
|
+
: {
|
|
60
|
+
adapter: {
|
|
61
|
+
...adapter,
|
|
62
|
+
densityAdapter: {
|
|
63
|
+
type: 'BigWigAdapter',
|
|
64
|
+
bigWigLocation: makeLocation(file),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
file: isURL(file) ? undefined : file,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// builds the adapter object and the list of source files for a spec in one
|
|
71
|
+
// place, so the config it writes and the files add-track copies can never drift
|
|
72
|
+
function buildFromSpec(spec, { location, index, bed1, bed2, makeLocation }) {
|
|
73
|
+
switch (spec.kind) {
|
|
74
|
+
case 'single':
|
|
75
|
+
return {
|
|
76
|
+
adapter: {
|
|
77
|
+
type: spec.adapterType,
|
|
78
|
+
[spec.locField]: makeLocation(location),
|
|
79
|
+
},
|
|
80
|
+
files: [location],
|
|
81
|
+
};
|
|
82
|
+
case 'indexed': {
|
|
83
|
+
const idx = index || siblingSidecar(location, spec.suffix);
|
|
4
84
|
return {
|
|
5
|
-
|
|
6
|
-
|
|
85
|
+
adapter: {
|
|
86
|
+
type: spec.adapterType,
|
|
87
|
+
[spec.locField]: makeLocation(location),
|
|
88
|
+
index: {
|
|
89
|
+
location: makeLocation(idx),
|
|
90
|
+
// the type follows the file that was CHOSEN, not the one the user
|
|
91
|
+
// typed, so a detected `.csi` is opened as one
|
|
92
|
+
indexType: resolveIndexType(idx, spec.indexType),
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
files: [location, idx],
|
|
7
96
|
};
|
|
8
97
|
}
|
|
9
|
-
|
|
98
|
+
case 'sidecar': {
|
|
99
|
+
const sidecars = spec.sidecars.map(s => ({
|
|
100
|
+
field: s.field,
|
|
101
|
+
path: s.fromIndex && index ? index : siblingSidecar(location, s.suffix),
|
|
102
|
+
}));
|
|
10
103
|
return {
|
|
11
|
-
|
|
12
|
-
|
|
104
|
+
adapter: {
|
|
105
|
+
type: spec.adapterType,
|
|
106
|
+
[spec.locField]: makeLocation(location),
|
|
107
|
+
...Object.fromEntries(sidecars.map(s => [s.field, makeLocation(s.path)])),
|
|
108
|
+
},
|
|
109
|
+
files: [location, ...sidecars.map(s => s.path)],
|
|
13
110
|
};
|
|
14
111
|
}
|
|
112
|
+
case 'anchors':
|
|
113
|
+
return {
|
|
114
|
+
adapter: {
|
|
115
|
+
type: spec.adapterType,
|
|
116
|
+
[spec.locField]: makeLocation(location),
|
|
117
|
+
bed1Location: bed1 ? makeLocation(bed1) : undefined,
|
|
118
|
+
bed2Location: bed2 ? makeLocation(bed2) : undefined,
|
|
119
|
+
},
|
|
120
|
+
files: [location, bed1, bed2],
|
|
121
|
+
};
|
|
122
|
+
case 'unsupported':
|
|
123
|
+
return { adapter: { type: 'UNSUPPORTED' }, files: [] };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export function makeLocationProtocol(protocol) {
|
|
127
|
+
return (location) => {
|
|
128
|
+
if (protocol === 'uri') {
|
|
129
|
+
return { uri: location, locationType: 'UriLocation' };
|
|
130
|
+
}
|
|
131
|
+
if (protocol === 'localPath') {
|
|
132
|
+
return { localPath: location, locationType: 'LocalPathLocation' };
|
|
133
|
+
}
|
|
15
134
|
throw new Error(`invalid protocol ${protocol}`);
|
|
16
135
|
};
|
|
17
136
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
else if (/\.bam$/i.test(location)) {
|
|
27
|
-
return {
|
|
28
|
-
file: location,
|
|
29
|
-
index: index || `${location}.bai`,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
else if (/\.cram$/i.test(location)) {
|
|
33
|
-
return {
|
|
34
|
-
file: location,
|
|
35
|
-
index: index || `${location}.crai`,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
else if (/\.gff3?\.b?gz$/i.test(location) ||
|
|
39
|
-
/\.vcf\.b?gz$/i.test(location) ||
|
|
40
|
-
/\.bed\.b?gz$/i.test(location) ||
|
|
41
|
-
/\.bedmethyl\.gz$/i.test(location) ||
|
|
42
|
-
/\.pif\.b?gz$/i.test(location)) {
|
|
43
|
-
return {
|
|
44
|
-
file: location,
|
|
45
|
-
index: index || `${location}.tbi`,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
else if (/\.(fa|fasta|fas|fna|mfa)$/i.test(location)) {
|
|
49
|
-
return {
|
|
50
|
-
file: location,
|
|
51
|
-
index: index || `${location}.fai`,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
else if (/\.(fa|fasta|fas|fna|mfa)\.b?gz$/i.test(location)) {
|
|
55
|
-
return {
|
|
56
|
-
file: location,
|
|
57
|
-
index: `${location}.fai`,
|
|
58
|
-
index2: `${location}.gzi`,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
else if (/\.2bit$/i.test(location) ||
|
|
62
|
-
/\.bedpe(\.gz)?$/i.test(location) ||
|
|
63
|
-
/\/trackData.jsonz?$/i.test(location) ||
|
|
64
|
-
/\/sparql$/i.test(location) ||
|
|
65
|
-
/\.out(\.gz)?$/i.test(location) ||
|
|
66
|
-
/\.paf(\.gz)?$/i.test(location) ||
|
|
67
|
-
/\.delta(\.gz)?$/i.test(location) ||
|
|
68
|
-
/\.bed$/i.test(location) ||
|
|
69
|
-
/\.(bw|bigwig)$/i.test(location) ||
|
|
70
|
-
/\.(bb|bigbed)$/i.test(location) ||
|
|
71
|
-
/\.vcf$/i.test(location) ||
|
|
72
|
-
/\.gtf$/i.test(location) ||
|
|
73
|
-
/\.gff3?$/i.test(location) ||
|
|
74
|
-
/\.chain(\.gz)?$/i.test(location) ||
|
|
75
|
-
/\.hic$/i.test(location)) {
|
|
76
|
-
return {
|
|
77
|
-
file: location,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
return {};
|
|
137
|
+
/**
|
|
138
|
+
* The bare filename the format table matches against — the same thing
|
|
139
|
+
* `@jbrowse/core`'s `getFileName` hands the guesser chain, so a path and a URL
|
|
140
|
+
* with a presigned query string both reduce to what the regexes expect.
|
|
141
|
+
*/
|
|
142
|
+
export function fileNameOf(location) {
|
|
143
|
+
return (location.replaceAll('\\', '/').split('/').at(-1)?.split(/[?#]/)[0] ?? '');
|
|
81
144
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
type:
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else if (/\.gff3?\.b?gz$/i.test(location)) {
|
|
108
|
-
return {
|
|
109
|
-
type: 'Gff3TabixAdapter',
|
|
110
|
-
gffGzLocation: makeLocation(location),
|
|
111
|
-
index: {
|
|
112
|
-
location: makeLocation(index || `${location}.tbi`),
|
|
113
|
-
indexType: index?.toUpperCase().endsWith('CSI') ? 'CSI' : 'TBI',
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
else if (/\.gtf$/i.test(location)) {
|
|
118
|
-
return {
|
|
119
|
-
type: 'GtfAdapter',
|
|
120
|
-
gtfLocation: makeLocation(location),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
else if (/\.vcf$/i.test(location)) {
|
|
124
|
-
return {
|
|
125
|
-
type: 'VcfAdapter',
|
|
126
|
-
vcfLocation: makeLocation(location),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
else if (/\.vcf\.b?gz$/i.test(location)) {
|
|
130
|
-
return {
|
|
131
|
-
type: 'VcfTabixAdapter',
|
|
132
|
-
vcfGzLocation: makeLocation(location),
|
|
133
|
-
index: {
|
|
134
|
-
location: makeLocation(index || `${location}.tbi`),
|
|
135
|
-
indexType: index?.toUpperCase().endsWith('CSI') ? 'CSI' : 'TBI',
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
else if (/\.vcf\.idx$/i.test(location)) {
|
|
140
|
-
return {
|
|
141
|
-
type: 'UNSUPPORTED',
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
else if (/\.bedpe(.gz)?$/i.test(location)) {
|
|
145
|
-
return {
|
|
146
|
-
type: 'BedpeAdapter',
|
|
147
|
-
bedpeLocation: makeLocation(location),
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
else if (/\.bed$/i.test(location)) {
|
|
151
|
-
return {
|
|
152
|
-
type: 'BedAdapter',
|
|
153
|
-
bedLocation: makeLocation(location),
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
else if (/\.pif\.b?gz$/i.test(location)) {
|
|
157
|
-
return {
|
|
158
|
-
type: 'PairwiseIndexedPAFAdapter',
|
|
159
|
-
pifGzLocation: makeLocation(location),
|
|
160
|
-
index: {
|
|
161
|
-
location: makeLocation(index || `${location}.tbi`),
|
|
162
|
-
indexType: index?.toUpperCase().endsWith('CSI') ? 'CSI' : 'TBI',
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
else if (/\.bedmethyl\.gz$/i.test(location) ||
|
|
167
|
-
/\.bed\.b?gz$/i.test(location)) {
|
|
168
|
-
return {
|
|
169
|
-
type: 'BedTabixAdapter',
|
|
170
|
-
bedGzLocation: makeLocation(location),
|
|
171
|
-
index: {
|
|
172
|
-
location: makeLocation(index || `${location}.tbi`),
|
|
173
|
-
indexType: index?.toUpperCase().endsWith('CSI') ? 'CSI' : 'TBI',
|
|
174
|
-
},
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
else if (/\.(bb|bigbed)$/i.test(location)) {
|
|
178
|
-
return {
|
|
179
|
-
type: 'BigBedAdapter',
|
|
180
|
-
bigBedLocation: makeLocation(location),
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
else if (/\.(bw|bigwig)$/i.test(location)) {
|
|
184
|
-
return {
|
|
185
|
-
type: 'BigWigAdapter',
|
|
186
|
-
bigWigLocation: makeLocation(location),
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
else if (/\.(fa|fasta|fna|mfa)$/i.test(location)) {
|
|
190
|
-
return {
|
|
191
|
-
type: 'IndexedFastaAdapter',
|
|
192
|
-
fastaLocation: makeLocation(location),
|
|
193
|
-
faiLocation: makeLocation(index || `${location}.fai`),
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
else if (/\.(fa|fasta|fna|mfa)\.b?gz$/i.test(location)) {
|
|
197
|
-
return {
|
|
198
|
-
type: 'BgzipFastaAdapter',
|
|
199
|
-
fastaLocation: makeLocation(location),
|
|
200
|
-
faiLocation: makeLocation(`${location}.fai`),
|
|
201
|
-
gziLocation: makeLocation(`${location}.gzi`),
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
else if (/\.2bit$/i.test(location)) {
|
|
205
|
-
return {
|
|
206
|
-
type: 'TwoBitAdapter',
|
|
207
|
-
twoBitLocation: makeLocation(location),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
else if (/\.sizes$/i.test(location)) {
|
|
211
|
-
return {
|
|
212
|
-
type: 'UNSUPPORTED',
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
else if (/\/trackData.jsonz?$/i.test(location)) {
|
|
216
|
-
return {
|
|
217
|
-
type: 'NCListAdapter',
|
|
218
|
-
rootUrlTemplate: makeLocation(location),
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
else if (/\/sparql$/i.test(location)) {
|
|
222
|
-
return {
|
|
223
|
-
type: 'SPARQLAdapter',
|
|
224
|
-
endpoint: location,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
else if (/\.hic$/i.test(location)) {
|
|
228
|
-
return {
|
|
229
|
-
type: 'HicAdapter',
|
|
230
|
-
hicLocation: makeLocation(location),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
else if (/\.paf(.gz)?$/i.test(location)) {
|
|
234
|
-
return {
|
|
235
|
-
type: 'PAFAdapter',
|
|
236
|
-
pafLocation: makeLocation(location),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
else if (/\.out(.gz)?$/i.test(location)) {
|
|
240
|
-
return {
|
|
241
|
-
type: 'MashMapAdapter',
|
|
242
|
-
outLocation: makeLocation(location),
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
else if (/\.chain(.gz)?$/i.test(location)) {
|
|
246
|
-
return {
|
|
247
|
-
type: 'ChainAdapter',
|
|
248
|
-
chainLocation: makeLocation(location),
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
else if (/\.delta(.gz)?$/i.test(location)) {
|
|
252
|
-
return {
|
|
253
|
-
type: 'DeltaAdapter',
|
|
254
|
-
deltaLocation: makeLocation(location),
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
else if (/\.anchors(\.gz)?$/i.test(location)) {
|
|
258
|
-
return {
|
|
259
|
-
type: 'MCScanAnchorsAdapter',
|
|
260
|
-
mcscanAnchorsLocation: makeLocation(location),
|
|
261
|
-
bed1Location: bed1 ? makeLocation(bed1) : undefined,
|
|
262
|
-
bed2Location: bed2 ? makeLocation(bed2) : undefined,
|
|
263
|
-
};
|
|
145
|
+
// resolves the file-layout spec that both the adapter config and the copied
|
|
146
|
+
// file set derive from, honoring an explicit --adapterType. `typeOverride` is
|
|
147
|
+
// the label to stamp on the adapter when the extension's layout is reused under
|
|
148
|
+
// a different adapter type.
|
|
149
|
+
function resolveSpec(location, adapterType) {
|
|
150
|
+
const spec = matchFormat(fileNameOf(location))?.spec;
|
|
151
|
+
if (adapterType) {
|
|
152
|
+
const known = adapterTypeToSpec[adapterType];
|
|
153
|
+
if (known) {
|
|
154
|
+
// explicit --adapterType resolves back to its known file layout
|
|
155
|
+
return { spec: known };
|
|
156
|
+
}
|
|
157
|
+
else if (spec) {
|
|
158
|
+
// unknown adapter type on a recognized extension: reuse that extension's
|
|
159
|
+
// file layout (location field + any sidecar/index files) under the new
|
|
160
|
+
// type name, so e.g. --adapterType MyPAFAdapter on a .paf keeps its
|
|
161
|
+
// pafLocation instead of dropping the file. Override with --config if the
|
|
162
|
+
// custom adapter uses a different field.
|
|
163
|
+
return { spec, typeOverride: adapterType };
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// custom adapter type on an unrecognized extension: no layout to reuse
|
|
167
|
+
return { typeOverride: adapterType };
|
|
168
|
+
}
|
|
264
169
|
}
|
|
265
|
-
else
|
|
266
|
-
return {
|
|
267
|
-
type: 'MCScanSimpleAnchorsAdapter',
|
|
268
|
-
mcscanSimpleAnchorsLocation: makeLocation(location),
|
|
269
|
-
bed1Location: bed1 ? makeLocation(bed1) : undefined,
|
|
270
|
-
bed2Location: bed2 ? makeLocation(bed2) : undefined,
|
|
271
|
-
};
|
|
170
|
+
else {
|
|
171
|
+
return { spec };
|
|
272
172
|
}
|
|
273
|
-
return {
|
|
274
|
-
type: 'UNKNOWN',
|
|
275
|
-
};
|
|
276
173
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
return adapterTypesToTrackTypeMap[adapterType] || 'FeatureTrack';
|
|
174
|
+
// derives the track adapter and the raw source files together from one spec, so
|
|
175
|
+
// the config written and the files add-track copies can never drift. mapLocation
|
|
176
|
+
// turns a raw source path into the location the adapter stores (relative path +
|
|
177
|
+
// protocol wrapper); the returned files stay raw for the copy step.
|
|
178
|
+
export function guessTrack({ location, index, bed1, bed2, adapterType, mapLocation, }) {
|
|
179
|
+
const { spec, typeOverride } = resolveSpec(location, adapterType);
|
|
180
|
+
if (spec) {
|
|
181
|
+
const { adapter, files } = buildFromSpec(spec, {
|
|
182
|
+
location,
|
|
183
|
+
index,
|
|
184
|
+
bed1,
|
|
185
|
+
bed2,
|
|
186
|
+
makeLocation: mapLocation,
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
adapter: typeOverride ? { ...adapter, type: typeOverride } : adapter,
|
|
190
|
+
files,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return { adapter: { type: typeOverride ?? 'UNKNOWN' }, files: [] };
|
|
194
|
+
}
|
|
195
|
+
export function guessTrackType(adapterType, location) {
|
|
196
|
+
return (trackTypeForAdapter(adapterType, location && fileNameOf(location)) ||
|
|
197
|
+
'FeatureTrack');
|
|
302
198
|
}
|
|
199
|
+
// the synteny adapters are exactly those mapping to a SyntenyTrack, so derive
|
|
200
|
+
// the set instead of maintaining a second hand-written list that can drift
|
|
201
|
+
export const syntenyAdapterTypes = new Set(Object.entries(adapterTypesToTrackTypeMap)
|
|
202
|
+
.filter(([, trackType]) => trackType === 'SyntenyTrack')
|
|
203
|
+
.map(([adapterType]) => adapterType));
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ignoreNotFound } from '../../utils.js';
|
|
3
4
|
const { copyFile, rename, symlink, unlink } = fs.promises;
|
|
4
5
|
const { COPYFILE_EXCL } = fs.constants;
|
|
5
|
-
|
|
6
|
+
async function loadFile({ src, destDir, mode, subDir = '', force = false, }) {
|
|
6
7
|
if (mode === 'inPlace') {
|
|
7
8
|
return;
|
|
8
9
|
}
|
|
9
10
|
const dest = path.join(destDir, subDir, path.basename(src));
|
|
11
|
+
// the file already sits where the config wants it (add-track run from inside
|
|
12
|
+
// the install dir), so there is nothing to copy/move/link. Bailing before the
|
|
13
|
+
// force-unlink matters: dest and src are the same file, so unlinking it
|
|
14
|
+
// deleted the user's data and left the copy with nothing to read. A source
|
|
15
|
+
// that isn't there still has to fail, so fall through to get its ENOENT.
|
|
16
|
+
if (path.resolve(src) === path.resolve(dest) && fs.existsSync(src)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
10
19
|
if (force) {
|
|
11
|
-
|
|
12
|
-
await unlink(dest);
|
|
13
|
-
}
|
|
14
|
-
catch (e) {
|
|
15
|
-
if (e && typeof e === 'object' && 'code' in e && e.code !== 'ENOENT') {
|
|
16
|
-
throw e;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
20
|
+
await ignoreNotFound(unlink(dest));
|
|
19
21
|
}
|
|
20
22
|
if (mode === 'copy') {
|
|
21
23
|
return copyFile(src, dest, force ? 0 : COPYFILE_EXCL);
|
|
@@ -28,3 +30,17 @@ export async function loadFile({ src, destDir, mode, subDir = '', force = false,
|
|
|
28
30
|
}
|
|
29
31
|
return undefined;
|
|
30
32
|
}
|
|
33
|
+
// load a set of source files into the config directory, skipping the operation
|
|
34
|
+
// entirely when no load mode is given (e.g. a URL track). undefined entries
|
|
35
|
+
// (optional index/bed files) are ignored
|
|
36
|
+
export async function loadFiles({ files, destDir, mode, subDir, force, }) {
|
|
37
|
+
// inPlace references files where they sit, so it needs no destination dir
|
|
38
|
+
if (mode && mode !== 'inPlace') {
|
|
39
|
+
if (subDir) {
|
|
40
|
+
fs.mkdirSync(path.join(destDir, subDir), { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
await Promise.all(files
|
|
43
|
+
.filter((f) => !!f)
|
|
44
|
+
.map(src => loadFile({ src, destDir, mode, subDir, force })));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { isURL } from '../../types/common.js';
|
|
3
|
+
// Parse the --multiwig argument into a subadapter list, mirroring @jbrowse/img's
|
|
4
|
+
// --multiwig: either a comma-separated list of BigWig files/URLs, or a `.json`
|
|
5
|
+
// file holding an array of BigWig locations or full subadapter objects (each a
|
|
6
|
+
// BigWigAdapter config carrying its own name/color/group).
|
|
7
|
+
//
|
|
8
|
+
// A string entry becomes an explicit `{ type: 'BigWigAdapter', bigWigLocation }`
|
|
9
|
+
// subadapter — NOT an entry in the adapter's `bigWigs` string-array shorthand.
|
|
10
|
+
// The shorthand can only carry bare URIs and does not resolve a relative path
|
|
11
|
+
// against the config's baseUri, so a relative local file 404s there. Building the
|
|
12
|
+
// location object ourselves runs it through the same mapLocation as every other
|
|
13
|
+
// track file, so `--load copy --subDir bw` yields `{ uri: 'bw/a.bw' }` that
|
|
14
|
+
// resolves relative to config.json exactly like a single-file track.
|
|
15
|
+
//
|
|
16
|
+
// Object entries are full subadapter configs, so they pass through unchanged
|
|
17
|
+
// (keeping their per-row metadata) and own their own locations — use URLs or
|
|
18
|
+
// config-relative uris there, since --load only copies the string entries.
|
|
19
|
+
export function buildMultiWiggle({ sources, mapLocation, }) {
|
|
20
|
+
const files = [];
|
|
21
|
+
const subadapters = parseSources(sources).map(entry => {
|
|
22
|
+
if (typeof entry === 'string') {
|
|
23
|
+
if (!isURL(entry)) {
|
|
24
|
+
files.push(entry);
|
|
25
|
+
}
|
|
26
|
+
return { type: 'BigWigAdapter', bigWigLocation: mapLocation(entry) };
|
|
27
|
+
}
|
|
28
|
+
return entry;
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
adapter: { type: 'MultiWiggleAdapter', subadapters },
|
|
32
|
+
files,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function parseSources(sources) {
|
|
36
|
+
if (sources.toLowerCase().endsWith('.json')) {
|
|
37
|
+
const data = JSON.parse(fs.readFileSync(sources, 'utf8'));
|
|
38
|
+
if (!Array.isArray(data)) {
|
|
39
|
+
throw new Error(`${sources}: expected a JSON array of BigWig URLs or subadapter objects`);
|
|
40
|
+
}
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
return sources
|
|
44
|
+
.split(',')
|
|
45
|
+
.map(s => s.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
}
|