@jbrowse/plugin-gff3 4.3.0 → 5.0.0-beta.1
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/esm/Gff3Adapter/Gff3Adapter.d.ts +2 -10
- package/esm/Gff3Adapter/Gff3Adapter.js +43 -56
- package/esm/Gff3Adapter/configSchema.d.ts +29 -5
- package/esm/Gff3Adapter/configSchema.js +46 -12
- package/esm/Gff3Adapter/index.js +3 -2
- package/esm/Gff3Feature.d.ts +55 -0
- package/esm/Gff3Feature.js +110 -0
- package/esm/Gff3TabixAdapter/Gff3TabixAdapter.d.ts +4 -13
- package/esm/Gff3TabixAdapter/Gff3TabixAdapter.js +47 -89
- package/esm/Gff3TabixAdapter/configSchema.d.ts +58 -18
- package/esm/Gff3TabixAdapter/configSchema.js +63 -35
- package/esm/Gff3TabixAdapter/index.js +3 -2
- package/esm/index.js +2 -4
- package/package.json +9 -13
- package/esm/Gff3Adapter/gffParser.d.ts +0 -7
- package/esm/Gff3Adapter/gffParser.js +0 -26
- package/esm/GuessGff3/index.d.ts +0 -2
- package/esm/GuessGff3/index.js +0 -30
- package/esm/featureData.d.ts +0 -16
- package/esm/featureData.js +0 -52
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
|
-
import {
|
|
2
|
+
import type { Gff3AdapterConfig } from './configSchema.ts';
|
|
3
3
|
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
4
4
|
import type { Feature } from '@jbrowse/core/util/simpleFeature';
|
|
5
5
|
import type { NoAssemblyRegion } from '@jbrowse/core/util/types';
|
|
6
|
-
|
|
7
|
-
export default class Gff3Adapter extends BaseFeatureDataAdapter {
|
|
8
|
-
calculatedIntervalTreeMap: Record<string, IntervalTree<Feature>>;
|
|
9
|
-
gffFeatures?: Promise<{
|
|
10
|
-
header: string;
|
|
11
|
-
intervalTreeMap: Record<string, (sc?: StatusCallback) => IntervalTree<Feature>>;
|
|
12
|
-
}>;
|
|
13
|
-
private loadDataP;
|
|
6
|
+
export default class Gff3Adapter extends BaseFeatureDataAdapter<Gff3AdapterConfig> {
|
|
14
7
|
private loadData;
|
|
15
8
|
getRefNames(opts?: BaseOptions): Promise<string[]>;
|
|
16
9
|
getHeader(opts?: BaseOptions): Promise<string>;
|
|
17
10
|
getFeatures(query: NoAssemblyRegion, opts?: BaseOptions): import("rxjs").Observable<Feature>;
|
|
18
11
|
}
|
|
19
|
-
export {};
|
|
@@ -1,51 +1,41 @@
|
|
|
1
|
-
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
|
-
import {
|
|
1
|
+
import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
|
+
import { fetchAndMaybeUnzip } from '@jbrowse/core/util';
|
|
3
3
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
4
|
+
import { groupLinesByRef, makeFeatureIntervalTreeMap, } from '@jbrowse/core/util/parseLineByLine';
|
|
4
5
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import { parseGffBuffer } from "./gffParser.js";
|
|
6
|
+
import { parseLinesLazy } from 'gff-nostream';
|
|
7
|
+
import { Gff3Feature } from '../Gff3Feature.js';
|
|
8
8
|
export default class Gff3Adapter extends BaseFeatureDataAdapter {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
9
|
+
// the whole file is resident after one load, so the fetch/parse status comes
|
|
10
|
+
// from inside the load itself rather than a label wrapped around it
|
|
11
|
+
loadData = cachedSetup({
|
|
12
|
+
setup: async (opts) => {
|
|
13
|
+
const buffer = await fetchAndMaybeUnzip(openLocation(this.getConf('gffLocation'), this.pluginManager), opts);
|
|
14
|
+
const { headerLines, linesByRef } = groupLinesByRef(buffer, opts.statusCallback);
|
|
15
|
+
const intervalTreeMap = makeFeatureIntervalTreeMap(linesByRef,
|
|
16
|
+
// lines are already split and comment/FASTA-filtered by
|
|
17
|
+
// groupLinesByRef, so feed them straight to parseLinesLazy rather than
|
|
18
|
+
// re-joining and re-splitting through parseStringSync.
|
|
19
|
+
//
|
|
20
|
+
// Lazy because the whole file stays resident for the session: leaving
|
|
21
|
+
// column 9 as text rather than an object per attribute is what keeps
|
|
22
|
+
// that resident set small (8.5x on GENCODE-shaped input), and the
|
|
23
|
+
// render path reads only a handful of attributes anyway — see
|
|
24
|
+
// Gff3Feature.
|
|
25
|
+
(lines, refName) => {
|
|
26
|
+
const features = parseLinesLazy(lines);
|
|
27
|
+
// stamped in place rather than through `{...feature, uniqueId}`:
|
|
28
|
+
// these are freshly parsed objects nobody else holds, and the spread
|
|
29
|
+
// copied every attribute of every top-level feature in the file to
|
|
30
|
+
// add one key
|
|
31
|
+
for (let i = 0; i < features.length; i++) {
|
|
32
|
+
features[i].uniqueId = `${this.id}-${refName}-${i}`;
|
|
31
33
|
}
|
|
32
|
-
return
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
intervalTreeMap,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
async loadData(opts) {
|
|
41
|
-
if (!this.gffFeatures) {
|
|
42
|
-
this.gffFeatures = this.loadDataP(opts).catch((e) => {
|
|
43
|
-
this.gffFeatures = undefined;
|
|
44
|
-
throw e;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
return this.gffFeatures;
|
|
48
|
-
}
|
|
34
|
+
return features;
|
|
35
|
+
}, 'Parsing GFF data');
|
|
36
|
+
return { header: headerLines.join('\n'), intervalTreeMap };
|
|
37
|
+
},
|
|
38
|
+
});
|
|
49
39
|
async getRefNames(opts = {}) {
|
|
50
40
|
const { intervalTreeMap } = await this.loadData(opts);
|
|
51
41
|
return Object.keys(intervalTreeMap);
|
|
@@ -55,21 +45,18 @@ export default class Gff3Adapter extends BaseFeatureDataAdapter {
|
|
|
55
45
|
return header;
|
|
56
46
|
}
|
|
57
47
|
getFeatures(query, opts = {}) {
|
|
48
|
+
// no try/catch: ObservableCreate forwards a rejected callback to
|
|
49
|
+
// observer.error itself
|
|
58
50
|
return ObservableCreate(async (observer) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
]) || []) {
|
|
66
|
-
observer.next(f);
|
|
51
|
+
const { start, end, refName } = query;
|
|
52
|
+
const { intervalTreeMap } = await this.loadData(opts);
|
|
53
|
+
const tree = intervalTreeMap[refName];
|
|
54
|
+
if (tree) {
|
|
55
|
+
for (const f of tree(opts.statusCallback).search([start, end])) {
|
|
56
|
+
observer.next(new Gff3Feature(f, f.uniqueId));
|
|
67
57
|
}
|
|
68
|
-
observer.complete();
|
|
69
|
-
}
|
|
70
|
-
catch (e) {
|
|
71
|
-
observer.error(e);
|
|
72
58
|
}
|
|
59
|
+
observer.complete();
|
|
73
60
|
}, opts.stopToken);
|
|
74
61
|
}
|
|
75
62
|
}
|
|
@@ -1,10 +1,34 @@
|
|
|
1
|
+
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* #config Gff3Adapter
|
|
5
|
+
* #category adapter
|
|
6
|
+
* #trackType FeatureTrack
|
|
7
|
+
* #fileFormat feature | GFF3 (plain) | Loaded entirely into memory; for small files
|
|
8
|
+
* used to load plain-text GFF3 files. Loads the whole file into memory, so
|
|
9
|
+
* prefer the Gff3TabixAdapter for large files.
|
|
10
|
+
*
|
|
11
|
+
* #example
|
|
12
|
+
* ```js
|
|
13
|
+
* {
|
|
14
|
+
* type: 'Gff3Adapter',
|
|
15
|
+
* uri: 'https://example.com/genes.gff3',
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
1
19
|
declare const Gff3Adapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
20
|
+
/**
|
|
21
|
+
* #slot
|
|
22
|
+
* location of the GFF3 file. May be gzipped; it is decompressed and parsed
|
|
23
|
+
* in full on first use, so the file has to fit in memory.
|
|
24
|
+
*/
|
|
25
|
+
readonly gffLocation: {
|
|
26
|
+
readonly type: "fileLocation";
|
|
27
|
+
readonly defaultValue: {
|
|
28
|
+
readonly uri: "/path/to/my.gff";
|
|
29
|
+
readonly locationType: "UriLocation";
|
|
7
30
|
};
|
|
8
31
|
};
|
|
9
32
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
33
|
+
export type Gff3AdapterConfig = Instance<typeof Gff3Adapter>;
|
|
10
34
|
export default Gff3Adapter;
|
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
function
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return snap.uri
|
|
4
|
+
? {
|
|
5
|
+
...snap,
|
|
6
|
+
gffLocation: {
|
|
7
|
+
uri: snap.uri,
|
|
8
|
+
baseUri: snap.baseUri,
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
: snap;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* #config Gff3Adapter
|
|
15
|
+
* #category adapter
|
|
16
|
+
* #trackType FeatureTrack
|
|
17
|
+
* #fileFormat feature | GFF3 (plain) | Loaded entirely into memory; for small files
|
|
18
|
+
* used to load plain-text GFF3 files. Loads the whole file into memory, so
|
|
19
|
+
* prefer the Gff3TabixAdapter for large files.
|
|
20
|
+
*
|
|
21
|
+
* #example
|
|
22
|
+
* ```js
|
|
23
|
+
* {
|
|
24
|
+
* type: 'Gff3Adapter',
|
|
25
|
+
* uri: 'https://example.com/genes.gff3',
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
3
29
|
const Gff3Adapter = ConfigurationSchema('Gff3Adapter', {
|
|
30
|
+
/**
|
|
31
|
+
* #slot
|
|
32
|
+
* location of the GFF3 file. May be gzipped; it is decompressed and parsed
|
|
33
|
+
* in full on first use, so the file has to fit in memory.
|
|
34
|
+
*/
|
|
4
35
|
gffLocation: {
|
|
5
36
|
type: 'fileLocation',
|
|
6
37
|
defaultValue: {
|
|
@@ -10,16 +41,19 @@ const Gff3Adapter = ConfigurationSchema('Gff3Adapter', {
|
|
|
10
41
|
},
|
|
11
42
|
}, {
|
|
12
43
|
explicitlyTyped: true,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
44
|
+
/**
|
|
45
|
+
* #preProcessSnapshot
|
|
46
|
+
*
|
|
47
|
+
*
|
|
48
|
+
* preprocessor to allow minimal config:
|
|
49
|
+
*
|
|
50
|
+
* ```json
|
|
51
|
+
* {
|
|
52
|
+
* "type": "Gff3Adapter",
|
|
53
|
+
* "uri": "yourfile.gff3",
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
preProcessSnapshot: normalizeSnapshot,
|
|
24
58
|
});
|
|
25
59
|
export default Gff3Adapter;
|
package/esm/Gff3Adapter/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AdapterType } from '@jbrowse/core/pluggableElementTypes';
|
|
2
|
-
import configSchema from
|
|
2
|
+
import configSchema, { normalizeSnapshot } from './configSchema.js';
|
|
3
3
|
export default function Gff3AdapterF(pluginManager) {
|
|
4
4
|
pluginManager.addAdapterType(() => new AdapterType({
|
|
5
5
|
name: 'Gff3Adapter',
|
|
6
6
|
displayName: 'GFF3 adapter',
|
|
7
|
+
normalizeSnapshot,
|
|
7
8
|
configSchema,
|
|
8
|
-
getAdapterClass: () => import(
|
|
9
|
+
getAdapterClass: () => import('./Gff3Adapter.js').then(r => r.default),
|
|
9
10
|
}));
|
|
10
11
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Feature, SimpleFeatureSerialized } from '@jbrowse/core/util/simpleFeature';
|
|
2
|
+
import type { LazyGffFeature } from 'gff-nostream';
|
|
3
|
+
/** A parsed GFF3 feature carrying the id the adapter minted for it. */
|
|
4
|
+
export type IdentifiedGffFeature = LazyGffFeature & {
|
|
5
|
+
uniqueId: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* A {@link Feature} over a GFF3 feature whose attributes are still raw column-9
|
|
9
|
+
* text, resolving each one only when something asks for it.
|
|
10
|
+
*
|
|
11
|
+
* This exists because the two things that read a GFF3 feature want opposite
|
|
12
|
+
* amounts of it. Rendering reads a fixed handful of keys — `start`, `end`,
|
|
13
|
+
* `strand`, `type`, `phase`, `subfeatures`, plus `name`/`id` for labels and
|
|
14
|
+
* `gbkey` for the stock NCBI filter — and then packs the result into typed
|
|
15
|
+
* arrays, so the feature objects are dropped and never cross the RPC boundary.
|
|
16
|
+
* The feature details panel wants everything, and re-fetches the region from
|
|
17
|
+
* scratch to get it (`GetCanvasFeatureDetails`). Parsing every attribute during
|
|
18
|
+
* a render therefore did work that rendering did not use and that the details
|
|
19
|
+
* path did not reuse.
|
|
20
|
+
*
|
|
21
|
+
* `toJSON` still materializes the lot, which is what the details path
|
|
22
|
+
* serializes, so nothing downstream sees a smaller feature than it used to.
|
|
23
|
+
*/
|
|
24
|
+
export declare class Gff3Feature implements Feature {
|
|
25
|
+
private data;
|
|
26
|
+
private uniqueId;
|
|
27
|
+
private parentFeature?;
|
|
28
|
+
private inflated?;
|
|
29
|
+
constructor(data: IdentifiedGffFeature | LazyGffFeature, uniqueId: string, parentFeature?: Feature | undefined);
|
|
30
|
+
/**
|
|
31
|
+
* A switch rather than a lookup table or a `data[name]` fallthrough: the
|
|
32
|
+
* fixed columns are read tens of times per feature by the layout pass, and
|
|
33
|
+
* they must not pay for an attribute scan. Anything not named here is an
|
|
34
|
+
* attribute, resolved on demand.
|
|
35
|
+
*/
|
|
36
|
+
get(name: 'refName'): string;
|
|
37
|
+
get(name: 'name' | 'type' | 'id' | 'source'): string | undefined;
|
|
38
|
+
get(name: 'start' | 'end'): number;
|
|
39
|
+
get(name: 'phase'): 0 | 1 | 2 | undefined;
|
|
40
|
+
get(name: 'strand'): -1 | 0 | 1 | undefined;
|
|
41
|
+
get(name: 'score'): number | undefined;
|
|
42
|
+
get(name: 'subfeatures'): Feature[] | undefined;
|
|
43
|
+
get(name: string): unknown;
|
|
44
|
+
id(): string;
|
|
45
|
+
parent(): Feature | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Wrapped lazily rather than in the constructor: a feature the layout pass
|
|
48
|
+
* never reaches never allocates a wrapper per descendant. (SimpleFeature
|
|
49
|
+
* inflates its whole subtree eagerly on construction.)
|
|
50
|
+
*/
|
|
51
|
+
children(): Feature[];
|
|
52
|
+
tags(): string[];
|
|
53
|
+
private columns;
|
|
54
|
+
toJSON(): SimpleFeatureSerialized;
|
|
55
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getAttribute, getAttributes } from 'gff-nostream';
|
|
2
|
+
/**
|
|
3
|
+
* A {@link Feature} over a GFF3 feature whose attributes are still raw column-9
|
|
4
|
+
* text, resolving each one only when something asks for it.
|
|
5
|
+
*
|
|
6
|
+
* This exists because the two things that read a GFF3 feature want opposite
|
|
7
|
+
* amounts of it. Rendering reads a fixed handful of keys — `start`, `end`,
|
|
8
|
+
* `strand`, `type`, `phase`, `subfeatures`, plus `name`/`id` for labels and
|
|
9
|
+
* `gbkey` for the stock NCBI filter — and then packs the result into typed
|
|
10
|
+
* arrays, so the feature objects are dropped and never cross the RPC boundary.
|
|
11
|
+
* The feature details panel wants everything, and re-fetches the region from
|
|
12
|
+
* scratch to get it (`GetCanvasFeatureDetails`). Parsing every attribute during
|
|
13
|
+
* a render therefore did work that rendering did not use and that the details
|
|
14
|
+
* path did not reuse.
|
|
15
|
+
*
|
|
16
|
+
* `toJSON` still materializes the lot, which is what the details path
|
|
17
|
+
* serializes, so nothing downstream sees a smaller feature than it used to.
|
|
18
|
+
*/
|
|
19
|
+
export class Gff3Feature {
|
|
20
|
+
data;
|
|
21
|
+
uniqueId;
|
|
22
|
+
parentFeature;
|
|
23
|
+
inflated;
|
|
24
|
+
constructor(data, uniqueId, parentFeature) {
|
|
25
|
+
this.data = data;
|
|
26
|
+
this.uniqueId = uniqueId;
|
|
27
|
+
this.parentFeature = parentFeature;
|
|
28
|
+
}
|
|
29
|
+
get(name) {
|
|
30
|
+
switch (name) {
|
|
31
|
+
case 'start': {
|
|
32
|
+
return this.data.start;
|
|
33
|
+
}
|
|
34
|
+
case 'end': {
|
|
35
|
+
return this.data.end;
|
|
36
|
+
}
|
|
37
|
+
case 'type': {
|
|
38
|
+
return this.data.type;
|
|
39
|
+
}
|
|
40
|
+
case 'refName': {
|
|
41
|
+
return this.data.refName;
|
|
42
|
+
}
|
|
43
|
+
case 'source': {
|
|
44
|
+
return this.data.source;
|
|
45
|
+
}
|
|
46
|
+
case 'score': {
|
|
47
|
+
return this.data.score;
|
|
48
|
+
}
|
|
49
|
+
case 'phase': {
|
|
50
|
+
return this.data.phase;
|
|
51
|
+
}
|
|
52
|
+
case 'strand': {
|
|
53
|
+
// a subfeature with no strand column of its own inherits its parent's,
|
|
54
|
+
// which is what SimpleFeature does too — it resolves the same way here,
|
|
55
|
+
// through the parent handle rather than by copying the field down
|
|
56
|
+
return this.data.strand ?? this.parentFeature?.get('strand');
|
|
57
|
+
}
|
|
58
|
+
case 'subfeatures': {
|
|
59
|
+
return this.children();
|
|
60
|
+
}
|
|
61
|
+
case 'parent': {
|
|
62
|
+
return this.parentFeature;
|
|
63
|
+
}
|
|
64
|
+
default: {
|
|
65
|
+
return getAttribute(this.data, name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
id() {
|
|
70
|
+
return this.uniqueId;
|
|
71
|
+
}
|
|
72
|
+
parent() {
|
|
73
|
+
return this.parentFeature;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Wrapped lazily rather than in the constructor: a feature the layout pass
|
|
77
|
+
* never reaches never allocates a wrapper per descendant. (SimpleFeature
|
|
78
|
+
* inflates its whole subtree eagerly on construction.)
|
|
79
|
+
*/
|
|
80
|
+
children() {
|
|
81
|
+
this.inflated ??= this.data.subfeatures.map((f, i) => new Gff3Feature(f, `${this.uniqueId}-${i}`, this));
|
|
82
|
+
return this.inflated;
|
|
83
|
+
}
|
|
84
|
+
tags() {
|
|
85
|
+
return [
|
|
86
|
+
...Object.keys(this.columns()),
|
|
87
|
+
'subfeatures',
|
|
88
|
+
...Object.keys(getAttributes(this.data)),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
// the eight parsed columns, without the raw attribute text or the nested
|
|
92
|
+
// features, both of which the serialized form carries differently
|
|
93
|
+
columns() {
|
|
94
|
+
const { attributeString, subfeatures, ...rest } = this.data;
|
|
95
|
+
return rest;
|
|
96
|
+
}
|
|
97
|
+
toJSON() {
|
|
98
|
+
const d = {
|
|
99
|
+
...this.columns(),
|
|
100
|
+
...getAttributes(this.data),
|
|
101
|
+
strand: this.get('strand'),
|
|
102
|
+
uniqueId: this.uniqueId,
|
|
103
|
+
subfeatures: this.children().map(f => f.toJSON()),
|
|
104
|
+
};
|
|
105
|
+
if (this.parentFeature) {
|
|
106
|
+
d.parentId = this.parentFeature.id();
|
|
107
|
+
}
|
|
108
|
+
return d;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
import { TabixIndexedFile } from '@gmod/tabix';
|
|
2
1
|
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
|
+
import type { Gff3TabixAdapterConfig } from './configSchema.ts';
|
|
3
3
|
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
4
4
|
import type { Feature } from '@jbrowse/core/util/simpleFeature';
|
|
5
5
|
import type { Region } from '@jbrowse/core/util/types';
|
|
6
|
-
export default class Gff3TabixAdapter extends BaseFeatureDataAdapter {
|
|
7
|
-
private
|
|
8
|
-
private configurePre;
|
|
9
|
-
protected configurePre2(): Promise<{
|
|
10
|
-
gff: TabixIndexedFile;
|
|
11
|
-
dontRedispatchSet: Set<string>;
|
|
12
|
-
}>;
|
|
13
|
-
configure(opts?: BaseOptions): Promise<{
|
|
14
|
-
gff: TabixIndexedFile;
|
|
15
|
-
dontRedispatchSet: Set<string>;
|
|
16
|
-
}>;
|
|
6
|
+
export default class Gff3TabixAdapter extends BaseFeatureDataAdapter<Gff3TabixAdapterConfig> {
|
|
7
|
+
private configure;
|
|
17
8
|
getRefNames(opts?: BaseOptions): Promise<string[]>;
|
|
18
9
|
getHeader(opts?: BaseOptions): Promise<string>;
|
|
10
|
+
getRegionByteSize(regions: Region[], opts?: BaseOptions): Promise<number>;
|
|
19
11
|
getFeatures(query: Region, opts?: BaseOptions): import("rxjs").Observable<Feature>;
|
|
20
|
-
private getFeaturesHelper;
|
|
21
12
|
}
|
|
@@ -1,108 +1,66 @@
|
|
|
1
1
|
import { TabixIndexedFile } from '@gmod/tabix';
|
|
2
|
-
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
3
|
+
import { downloadStatus } from '@jbrowse/core/util';
|
|
4
|
+
import { sharedBgzfWorkerPool } from '@jbrowse/core/util/bgzfWorkerPool';
|
|
5
|
+
import { decompressedBytesBudget } from '@jbrowse/core/util/cacheBudgets';
|
|
6
|
+
import { openLocation, openTabixIndexFilehandle } from '@jbrowse/core/util/io';
|
|
5
7
|
import { doesIntersect2 } from '@jbrowse/core/util/range';
|
|
6
8
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs';
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
+
import { readTabixLinesRedispatched } from '@jbrowse/core/util/tabix';
|
|
10
|
+
import { parseRecordsLazy } from 'gff-nostream';
|
|
11
|
+
import { Gff3Feature } from '../Gff3Feature.js';
|
|
9
12
|
export default class Gff3TabixAdapter extends BaseFeatureDataAdapter {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
csiFilehandle: indexType === 'CSI' ? openLocation(loc, this.pluginManager) : undefined,
|
|
19
|
-
tbiFilehandle: indexType !== 'CSI' ? openLocation(loc, this.pluginManager) : undefined,
|
|
20
|
-
chunkCacheSize: 50 * 2 ** 20,
|
|
21
|
-
});
|
|
22
|
-
return {
|
|
23
|
-
gff,
|
|
24
|
-
dontRedispatchSet: new Set(dontRedispatch),
|
|
25
|
-
header: await gff.getHeader(),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
async configurePre2() {
|
|
29
|
-
if (!this.configured) {
|
|
30
|
-
this.configured = this.configurePre().catch((e) => {
|
|
31
|
-
this.configured = undefined;
|
|
32
|
-
throw e;
|
|
13
|
+
configure = cachedSetup({
|
|
14
|
+
label: 'Downloading index',
|
|
15
|
+
setup: async (_opts, onProgress) => {
|
|
16
|
+
const gff = new TabixIndexedFile({
|
|
17
|
+
filehandle: openLocation(this.getConf('gffGzLocation'), this.pluginManager),
|
|
18
|
+
...openTabixIndexFilehandle(this.getConf(['index', 'location']), this.getConf(['index', 'indexType']), this.pluginManager),
|
|
19
|
+
chunkCacheBudget: decompressedBytesBudget,
|
|
20
|
+
bgzfWorkerPool: sharedBgzfWorkerPool(),
|
|
33
21
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
return {
|
|
23
|
+
gff,
|
|
24
|
+
dontRedispatchSet: new Set(this.getConf('dontRedispatch')),
|
|
25
|
+
// the index is a whole-file read, so its byte ticks turn the
|
|
26
|
+
// "Downloading index" label into a determinate bar
|
|
27
|
+
header: await gff.getHeader({ onProgress }),
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
});
|
|
41
31
|
async getRefNames(opts = {}) {
|
|
42
32
|
const { gff } = await this.configure(opts);
|
|
43
|
-
return gff.getReferenceSequenceNames(opts);
|
|
33
|
+
return downloadStatus('Downloading index', opts.statusCallback, onProgress => gff.getReferenceSequenceNames({ ...opts, onProgress }));
|
|
44
34
|
}
|
|
45
35
|
async getHeader(opts = {}) {
|
|
36
|
+
const { header } = await this.configure(opts);
|
|
37
|
+
return header;
|
|
38
|
+
}
|
|
39
|
+
// Index-only compressed-byte estimate (no feature download), used by the
|
|
40
|
+
// feature-fetch RPC to short-circuit an over-budget region before pulling
|
|
41
|
+
// every line — see executeRenderFeatureData.
|
|
42
|
+
async getRegionByteSize(regions, opts = {}) {
|
|
46
43
|
const { gff } = await this.configure(opts);
|
|
47
|
-
return gff.
|
|
44
|
+
return gff.bytesForRegions(regions, opts);
|
|
48
45
|
}
|
|
49
46
|
getFeatures(query, opts = {}) {
|
|
50
47
|
return ObservableCreate(async (observer) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const { dontRedispatchSet, gff } = await this.configure(opts);
|
|
61
|
-
await updateStatus('Downloading features', statusCallback, () => gff.getLines(query.refName, query.start, query.end, (line, fileOffset, start, end) => {
|
|
62
|
-
const t1 = line.indexOf('\t');
|
|
63
|
-
const t2 = line.indexOf('\t', t1 + 1);
|
|
64
|
-
const t3 = line.indexOf('\t', t2 + 1);
|
|
65
|
-
const type = line.slice(t2 + 1, t3);
|
|
66
|
-
lines.push({
|
|
67
|
-
line,
|
|
68
|
-
lineHash: fileOffset,
|
|
69
|
-
start,
|
|
70
|
-
end,
|
|
71
|
-
hasEscapes: line.includes('%'),
|
|
72
|
-
type,
|
|
73
|
-
});
|
|
74
|
-
}));
|
|
75
|
-
if (allowRedispatch && lines.length) {
|
|
76
|
-
let minStart = Number.POSITIVE_INFINITY;
|
|
77
|
-
let maxEnd = Number.NEGATIVE_INFINITY;
|
|
78
|
-
for (const rec of lines) {
|
|
79
|
-
if (!dontRedispatchSet.has(rec.type)) {
|
|
80
|
-
const start = rec.start - 1;
|
|
81
|
-
if (start < minStart) {
|
|
82
|
-
minStart = start;
|
|
83
|
-
}
|
|
84
|
-
if (rec.end > maxEnd) {
|
|
85
|
-
maxEnd = rec.end;
|
|
86
|
-
}
|
|
48
|
+
try {
|
|
49
|
+
const { gff, dontRedispatchSet } = await this.configure(opts);
|
|
50
|
+
const lines = await readTabixLinesRedispatched(gff, query, dontRedispatchSet, opts);
|
|
51
|
+
// emit only top-level features intersecting the original query. the
|
|
52
|
+
// byte offset stays on our own record and is used purely to mint a
|
|
53
|
+
// stable id, so it never pollutes the feature's data
|
|
54
|
+
for (const { feature, record } of parseRecordsLazy(lines)) {
|
|
55
|
+
if (doesIntersect2(feature.start, feature.end, query.start, query.end)) {
|
|
56
|
+
observer.next(new Gff3Feature(feature, `${this.id}-offset-${record.offset}`));
|
|
87
57
|
}
|
|
88
58
|
}
|
|
89
|
-
|
|
90
|
-
await this.getFeaturesHelper({ ...query, start: minStart, end: maxEnd }, opts, metadata, observer, false, query);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
59
|
+
observer.complete();
|
|
93
60
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
observer.next(new SimpleFeature({
|
|
97
|
-
data: feature,
|
|
98
|
-
id: `${this.id}-offset-${feature._lineHash}`,
|
|
99
|
-
}));
|
|
100
|
-
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
observer.error(e);
|
|
101
63
|
}
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
catch (e) {
|
|
105
|
-
observer.error(e);
|
|
106
|
-
}
|
|
64
|
+
}, opts.stopToken);
|
|
107
65
|
}
|
|
108
66
|
}
|
|
@@ -1,28 +1,68 @@
|
|
|
1
|
+
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* #config Gff3TabixAdapter
|
|
5
|
+
* #category adapter
|
|
6
|
+
* #trackType FeatureTrack
|
|
7
|
+
* #fileFormat feature | GFF3 (tabix)
|
|
8
|
+
* used to load bgzip-compressed, tabix-indexed GFF3 files
|
|
9
|
+
*
|
|
10
|
+
* #example
|
|
11
|
+
* The `uri` shorthand auto-resolves the `.tbi` index; add `csi: true` for a
|
|
12
|
+
* `.csi` index instead:
|
|
13
|
+
* ```js
|
|
14
|
+
* {
|
|
15
|
+
* type: 'Gff3TabixAdapter',
|
|
16
|
+
* uri: 'https://example.com/genes.gff3.gz',
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
1
20
|
declare const Gff3TabixAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
21
|
+
/**
|
|
22
|
+
* #slot
|
|
23
|
+
* location of the bgzip-compressed GFF3, sorted by position. Must be bgzip
|
|
24
|
+
* rather than plain gzip, which tabix cannot index.
|
|
25
|
+
*/
|
|
26
|
+
readonly gffGzLocation: {
|
|
27
|
+
readonly type: "fileLocation";
|
|
28
|
+
readonly defaultValue: {
|
|
29
|
+
readonly uri: "/path/to/my.gff.gz";
|
|
30
|
+
readonly locationType: "UriLocation";
|
|
7
31
|
};
|
|
8
32
|
};
|
|
9
|
-
index: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
10
|
-
indexType: {
|
|
11
|
-
model: import("@jbrowse/mobx-state-tree").ISimpleType<"
|
|
12
|
-
type:
|
|
13
|
-
defaultValue:
|
|
33
|
+
readonly index: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
34
|
+
readonly indexType: {
|
|
35
|
+
readonly model: import("@jbrowse/mobx-state-tree").ISimpleType<"CSI" | "TBI">;
|
|
36
|
+
readonly type: 'stringEnum';
|
|
37
|
+
readonly defaultValue: 'TBI';
|
|
38
|
+
readonly description: '`TBI` is the usual `tabix` output. `CSI` is required for a reference longer than 512 Mb, which TBI cannot address.';
|
|
14
39
|
};
|
|
15
|
-
location: {
|
|
16
|
-
type:
|
|
17
|
-
defaultValue: {
|
|
18
|
-
uri:
|
|
19
|
-
locationType:
|
|
40
|
+
readonly location: {
|
|
41
|
+
readonly type: 'fileLocation';
|
|
42
|
+
readonly defaultValue: {
|
|
43
|
+
readonly uri: '/path/to/my.gz.tbi';
|
|
44
|
+
readonly locationType: 'UriLocation';
|
|
20
45
|
};
|
|
46
|
+
readonly description: 'location of the tabix index. Only needed when it is not named `<file>.tbi` (or `.csi`), which is what the `uri` shorthand assumes.';
|
|
21
47
|
};
|
|
22
48
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
49
|
+
/**
|
|
50
|
+
* #slot
|
|
51
|
+
* the Gff3TabixAdapter has to "redispatch" if it fetches a region and
|
|
52
|
+
* features it finds inside that region extend outside the region we requested.
|
|
53
|
+
* you can disable this for certain feature types to avoid fetching e.g. the
|
|
54
|
+
* entire chromosome
|
|
55
|
+
*
|
|
56
|
+
* the defaults are the whole-sequence records the common GFF3 sources emit:
|
|
57
|
+
* `region` (NCBI), `supercontig`/`scaffold` (Ensembl, for non-chromosomal
|
|
58
|
+
* sequences), plus `chromosome` and `contig`. They span an entire reference
|
|
59
|
+
* and have no children, so letting one expand the fetch would pull a whole
|
|
60
|
+
* chromosome to gain nothing
|
|
61
|
+
*/
|
|
62
|
+
readonly dontRedispatch: {
|
|
63
|
+
readonly type: "stringArray";
|
|
64
|
+
readonly defaultValue: readonly ["chromosome", "region", "contig", "supercontig", "scaffold"];
|
|
26
65
|
};
|
|
27
66
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
67
|
+
export type Gff3TabixAdapterConfig = Instance<typeof Gff3TabixAdapter>;
|
|
28
68
|
export default Gff3TabixAdapter;
|
|
@@ -1,7 +1,30 @@
|
|
|
1
|
-
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { ConfigurationSchema, expandTabixShorthand, tabixIndexFields, } from '@jbrowse/core/configuration';
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return expandTabixShorthand(snap, 'gffGzLocation');
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* #config Gff3TabixAdapter
|
|
7
|
+
* #category adapter
|
|
8
|
+
* #trackType FeatureTrack
|
|
9
|
+
* #fileFormat feature | GFF3 (tabix)
|
|
10
|
+
* used to load bgzip-compressed, tabix-indexed GFF3 files
|
|
11
|
+
*
|
|
12
|
+
* #example
|
|
13
|
+
* The `uri` shorthand auto-resolves the `.tbi` index; add `csi: true` for a
|
|
14
|
+
* `.csi` index instead:
|
|
15
|
+
* ```js
|
|
16
|
+
* {
|
|
17
|
+
* type: 'Gff3TabixAdapter',
|
|
18
|
+
* uri: 'https://example.com/genes.gff3.gz',
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
4
22
|
const Gff3TabixAdapter = ConfigurationSchema('Gff3TabixAdapter', {
|
|
23
|
+
/**
|
|
24
|
+
* #slot
|
|
25
|
+
* location of the bgzip-compressed GFF3, sorted by position. Must be bgzip
|
|
26
|
+
* rather than plain gzip, which tabix cannot index.
|
|
27
|
+
*/
|
|
5
28
|
gffGzLocation: {
|
|
6
29
|
type: 'fileLocation',
|
|
7
30
|
defaultValue: {
|
|
@@ -9,42 +32,47 @@ const Gff3TabixAdapter = ConfigurationSchema('Gff3TabixAdapter', {
|
|
|
9
32
|
locationType: 'UriLocation',
|
|
10
33
|
},
|
|
11
34
|
},
|
|
12
|
-
index: ConfigurationSchema('
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
index: ConfigurationSchema('TabixIndex', { ...tabixIndexFields }),
|
|
36
|
+
/**
|
|
37
|
+
* #slot
|
|
38
|
+
* the Gff3TabixAdapter has to "redispatch" if it fetches a region and
|
|
39
|
+
* features it finds inside that region extend outside the region we requested.
|
|
40
|
+
* you can disable this for certain feature types to avoid fetching e.g. the
|
|
41
|
+
* entire chromosome
|
|
42
|
+
*
|
|
43
|
+
* the defaults are the whole-sequence records the common GFF3 sources emit:
|
|
44
|
+
* `region` (NCBI), `supercontig`/`scaffold` (Ensembl, for non-chromosomal
|
|
45
|
+
* sequences), plus `chromosome` and `contig`. They span an entire reference
|
|
46
|
+
* and have no children, so letting one expand the fetch would pull a whole
|
|
47
|
+
* chromosome to gain nothing
|
|
48
|
+
*/
|
|
26
49
|
dontRedispatch: {
|
|
27
50
|
type: 'stringArray',
|
|
28
|
-
defaultValue: [
|
|
51
|
+
defaultValue: [
|
|
52
|
+
'chromosome',
|
|
53
|
+
'region',
|
|
54
|
+
'contig',
|
|
55
|
+
'supercontig',
|
|
56
|
+
'scaffold',
|
|
57
|
+
],
|
|
29
58
|
},
|
|
30
59
|
}, {
|
|
31
60
|
explicitlyTyped: true,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
61
|
+
/**
|
|
62
|
+
* #preProcessSnapshot
|
|
63
|
+
*
|
|
64
|
+
*
|
|
65
|
+
* preprocessor to allow minimal config, assumes tbi index at
|
|
66
|
+
* yourfile.gff3.gz.tbi (or .csi if csi:true):
|
|
67
|
+
*
|
|
68
|
+
* ```json
|
|
69
|
+
* {
|
|
70
|
+
* "type": "Gff3TabixAdapter",
|
|
71
|
+
* "uri": "yourfile.gff3.gz",
|
|
72
|
+
* "csi": true
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
preProcessSnapshot: normalizeSnapshot,
|
|
49
77
|
});
|
|
50
78
|
export default Gff3TabixAdapter;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AdapterType } from '@jbrowse/core/pluggableElementTypes';
|
|
2
|
-
import configSchema from
|
|
2
|
+
import configSchema, { normalizeSnapshot } from './configSchema.js';
|
|
3
3
|
export default function Gff3TabixAdapterF(pluginManager) {
|
|
4
4
|
pluginManager.addAdapterType(() => new AdapterType({
|
|
5
5
|
name: 'Gff3TabixAdapter',
|
|
6
6
|
displayName: 'GFF3 tabix adapter',
|
|
7
|
+
normalizeSnapshot,
|
|
7
8
|
configSchema,
|
|
8
|
-
getAdapterClass: () => import(
|
|
9
|
+
getAdapterClass: () => import('./Gff3TabixAdapter.js').then(r => r.default),
|
|
9
10
|
}));
|
|
10
11
|
}
|
package/esm/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import Plugin from '@jbrowse/core/Plugin';
|
|
2
|
-
import Gff3AdapterF from
|
|
3
|
-
import Gff3TabixAdapterF from
|
|
4
|
-
import GuessGff3F from "./GuessGff3/index.js";
|
|
2
|
+
import Gff3AdapterF from './Gff3Adapter/index.js';
|
|
3
|
+
import Gff3TabixAdapterF from './Gff3TabixAdapter/index.js';
|
|
5
4
|
export default class GFF3Plugin extends Plugin {
|
|
6
5
|
name = 'GFF3Plugin';
|
|
7
6
|
install(pluginManager) {
|
|
8
7
|
Gff3TabixAdapterF(pluginManager);
|
|
9
8
|
Gff3AdapterF(pluginManager);
|
|
10
|
-
GuessGff3F(pluginManager);
|
|
11
9
|
}
|
|
12
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-gff3",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 gff3.",
|
|
6
6
|
"keywords": [
|
|
@@ -21,13 +21,12 @@
|
|
|
21
21
|
"esm"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"mobx": "^6.15.3",
|
|
24
|
+
"@gmod/tabix": "^3.8.2",
|
|
25
|
+
"@jbrowse/mobx-state-tree": "^6.5.1",
|
|
26
|
+
"gff-nostream": "^5.3.0",
|
|
27
|
+
"mobx": "^7.0.3",
|
|
29
28
|
"rxjs": "^7.8.2",
|
|
30
|
-
"@jbrowse/core": "^
|
|
29
|
+
"@jbrowse/core": "^5.0.0-beta.1"
|
|
31
30
|
},
|
|
32
31
|
"publishConfig": {
|
|
33
32
|
"access": "public"
|
|
@@ -35,16 +34,13 @@
|
|
|
35
34
|
"sideEffects": false,
|
|
36
35
|
"scripts": {
|
|
37
36
|
"build": "pnpm run /^build:/",
|
|
38
|
-
"test": "cd ../..; jest --passWithNoTests plugins/gff3
|
|
37
|
+
"test": "cd ../..; jest --passWithNoTests plugins/gff3",
|
|
39
38
|
"prebuild": "pnpm clean",
|
|
40
|
-
"build:esm": "tsc
|
|
39
|
+
"build:esm": "node ../../node_modules/typescript7/bin/tsc --build tsconfig.build.esm.json",
|
|
41
40
|
"clean": "rimraf esm *.tsbuildinfo"
|
|
42
41
|
},
|
|
43
42
|
"types": "esm/index.d.ts",
|
|
44
43
|
"exports": {
|
|
45
|
-
".":
|
|
46
|
-
"types": "./esm/index.d.ts",
|
|
47
|
-
"import": "./esm/index.js"
|
|
48
|
-
}
|
|
44
|
+
".": "./esm/index.js"
|
|
49
45
|
}
|
|
50
46
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { StatusCallback } from '@jbrowse/core/util/parseLineByLine';
|
|
2
|
-
interface GffParseResult {
|
|
3
|
-
header: string;
|
|
4
|
-
featureMap: Record<string, string>;
|
|
5
|
-
}
|
|
6
|
-
export declare function parseGffBuffer(buffer: Uint8Array, statusCallback?: StatusCallback): GffParseResult;
|
|
7
|
-
export {};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { parseLineByLine } from '@jbrowse/core/util/parseLineByLine';
|
|
2
|
-
export function parseGffBuffer(buffer, statusCallback = () => { }) {
|
|
3
|
-
const headerLines = [];
|
|
4
|
-
const featureMap = {};
|
|
5
|
-
parseLineByLine(buffer, line => {
|
|
6
|
-
if (line.startsWith('#')) {
|
|
7
|
-
headerLines.push(line);
|
|
8
|
-
}
|
|
9
|
-
else if (line.startsWith('>')) {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
else {
|
|
13
|
-
const ret = line.indexOf('\t');
|
|
14
|
-
const refName = line.slice(0, ret);
|
|
15
|
-
if (!featureMap[refName]) {
|
|
16
|
-
featureMap[refName] = '';
|
|
17
|
-
}
|
|
18
|
-
featureMap[refName] += `${line}\n`;
|
|
19
|
-
}
|
|
20
|
-
return true;
|
|
21
|
-
}, statusCallback);
|
|
22
|
-
return {
|
|
23
|
-
header: headerLines.join('\n'),
|
|
24
|
-
featureMap,
|
|
25
|
-
};
|
|
26
|
-
}
|
package/esm/GuessGff3/index.d.ts
DELETED
package/esm/GuessGff3/index.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { testAdapter } from '@jbrowse/core/util';
|
|
2
|
-
import { getFileName, makeIndex, makeIndexType, } from '@jbrowse/core/util/tracks';
|
|
3
|
-
export default function GuessGff3F(pluginManager) {
|
|
4
|
-
pluginManager.addToExtensionPoint('Core-guessAdapterForLocation', (adapterGuesser) => {
|
|
5
|
-
return (file, index, adapterHint) => {
|
|
6
|
-
const fileName = getFileName(file);
|
|
7
|
-
const indexName = index && getFileName(index);
|
|
8
|
-
if (testAdapter(fileName, /\.gff3?\.b?gz$/i, adapterHint, 'Gff3TabixAdapter')) {
|
|
9
|
-
return {
|
|
10
|
-
type: 'Gff3TabixAdapter',
|
|
11
|
-
bamLocation: file,
|
|
12
|
-
gffGzLocation: file,
|
|
13
|
-
index: {
|
|
14
|
-
location: index || makeIndex(file, '.tbi'),
|
|
15
|
-
indexType: makeIndexType(indexName, 'CSI', 'TBI'),
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
else if (testAdapter(fileName, /\.gff3?$/i, adapterHint, 'Gff3Adapter')) {
|
|
20
|
-
return {
|
|
21
|
-
type: 'Gff3Adapter',
|
|
22
|
-
gffLocation: file,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
return adapterGuesser(file, index, adapterHint);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
});
|
|
30
|
-
}
|
package/esm/featureData.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { GFF3FeatureLineWithRefs } from 'gff-nostream';
|
|
2
|
-
interface GFF3Feature {
|
|
3
|
-
start: number;
|
|
4
|
-
end: number;
|
|
5
|
-
strand?: number;
|
|
6
|
-
type: string | null;
|
|
7
|
-
source: string | null;
|
|
8
|
-
refName: string;
|
|
9
|
-
derived_features: unknown[] | null;
|
|
10
|
-
phase?: number;
|
|
11
|
-
score?: number;
|
|
12
|
-
subfeatures: GFF3Feature[] | undefined;
|
|
13
|
-
[key: string]: unknown;
|
|
14
|
-
}
|
|
15
|
-
export declare function featureData(data: GFF3FeatureLineWithRefs): GFF3Feature;
|
|
16
|
-
export {};
|
package/esm/featureData.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
const DEFAULT_FIELDS = new Set([
|
|
2
|
-
'start',
|
|
3
|
-
'end',
|
|
4
|
-
'seq_id',
|
|
5
|
-
'score',
|
|
6
|
-
'type',
|
|
7
|
-
'source',
|
|
8
|
-
'phase',
|
|
9
|
-
'strand',
|
|
10
|
-
]);
|
|
11
|
-
export function featureData(data) {
|
|
12
|
-
const { end, start, child_features, derived_features, attributes, type, source, phase, seq_id, score, strand, } = data;
|
|
13
|
-
let strand2;
|
|
14
|
-
if (strand === '+') {
|
|
15
|
-
strand2 = 1;
|
|
16
|
-
}
|
|
17
|
-
else if (strand === '-') {
|
|
18
|
-
strand2 = -1;
|
|
19
|
-
}
|
|
20
|
-
else if (strand === '.') {
|
|
21
|
-
strand2 = 0;
|
|
22
|
-
}
|
|
23
|
-
const dataAttributes = attributes || {};
|
|
24
|
-
const resultAttributes = {};
|
|
25
|
-
for (const a of Object.keys(dataAttributes)) {
|
|
26
|
-
let b = a.toLowerCase();
|
|
27
|
-
if (DEFAULT_FIELDS.has(b)) {
|
|
28
|
-
b += '2';
|
|
29
|
-
}
|
|
30
|
-
if (dataAttributes[a] && a !== '_lineHash') {
|
|
31
|
-
let attr = dataAttributes[a];
|
|
32
|
-
if (Array.isArray(attr) && attr.length === 1) {
|
|
33
|
-
;
|
|
34
|
-
[attr] = attr;
|
|
35
|
-
}
|
|
36
|
-
resultAttributes[b] = attr;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
...resultAttributes,
|
|
41
|
-
start: start - 1,
|
|
42
|
-
end: end,
|
|
43
|
-
strand: strand2,
|
|
44
|
-
type,
|
|
45
|
-
source,
|
|
46
|
-
refName: seq_id,
|
|
47
|
-
derived_features,
|
|
48
|
-
phase: phase === null ? undefined : Number(phase),
|
|
49
|
-
score: score === null ? undefined : score,
|
|
50
|
-
subfeatures: child_features.flatMap(childLocs => childLocs.map(childLoc => featureData(childLoc))),
|
|
51
|
-
};
|
|
52
|
-
}
|