msa-parsers 8.2.0 → 8.3.0
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/dist/gff/annotationTextToGFF.d.ts +5 -0
- package/dist/gff/annotationTextToGFF.js +15 -0
- package/dist/gff/annotationTextToGFF.js.map +1 -0
- package/dist/gff/annotationsToGFF.js +1 -1
- package/dist/gff/annotationsToGFF.js.map +1 -1
- package/dist/gff/gffToAnnotations.js +4 -4
- package/dist/gff/gffToAnnotations.js.map +1 -1
- package/dist/gff/parseGFF.js +14 -20
- package/dist/gff/parseGFF.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/msa/A3mMSA.d.ts +3 -9
- package/dist/msa/A3mMSA.js +31 -32
- package/dist/msa/A3mMSA.js.map +1 -1
- package/dist/msa/BaseMSA.d.ts +9 -3
- package/dist/msa/BaseMSA.js +20 -0
- package/dist/msa/BaseMSA.js.map +1 -1
- package/dist/msa/ClustalMSA.d.ts +0 -14
- package/dist/msa/ClustalMSA.js +3 -23
- package/dist/msa/ClustalMSA.js.map +1 -1
- package/dist/msa/EmfMSA.d.ts +0 -15
- package/dist/msa/EmfMSA.js +1 -24
- package/dist/msa/EmfMSA.js.map +1 -1
- package/dist/msa/FastaMSA.d.ts +3 -8
- package/dist/msa/FastaMSA.js +10 -23
- package/dist/msa/FastaMSA.js.map +1 -1
- package/dist/msa/StockholmMSA.d.ts +0 -2
- package/dist/msa/StockholmMSA.js +2 -10
- package/dist/msa/StockholmMSA.js.map +1 -1
- package/dist/msa/fastaRecords.d.ts +6 -4
- package/dist/msa/fastaRecords.js +12 -6
- package/dist/msa/fastaRecords.js.map +1 -1
- package/dist/msa/index.js +17 -11
- package/dist/msa/index.js.map +1 -1
- package/package.json +1 -1
- package/src/gff/annotationTextToGFF.ts +17 -0
- package/src/gff/annotationsToGFF.ts +1 -1
- package/src/gff/gffToAnnotations.test.ts +2 -0
- package/src/gff/gffToAnnotations.ts +4 -4
- package/src/gff/interProToGFF.test.ts +7 -0
- package/src/gff/parseGFF.test.ts +14 -0
- package/src/gff/parseGFF.ts +18 -26
- package/src/index.ts +1 -0
- package/src/msa/A3mMSA.test.ts +16 -12
- package/src/msa/A3mMSA.ts +32 -42
- package/src/msa/BaseMSA.ts +23 -4
- package/src/msa/ClustalMSA.ts +3 -27
- package/src/msa/EmfMSA.ts +1 -29
- package/src/msa/FastaMSA.ts +12 -31
- package/src/msa/StockholmMSA.ts +2 -12
- package/src/msa/fastaRecords.ts +13 -6
- package/src/msa/index.test.ts +87 -5
- package/src/msa/index.ts +19 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockholmMSA.js","sourceRoot":"","sources":["../../src/msa/StockholmMSA.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,OAAO,MAAM,cAAc,CAAA;AAClC,OAAO,WAAW,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAK/C,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,OAAO;IACvC,IAAI,CAAiB;IACrB,GAAG,CAAe;IAE1B,YAAY,IAAY,EAAE,gBAAwB;QAChD,
|
|
1
|
+
{"version":3,"file":"StockholmMSA.js","sourceRoot":"","sources":["../../src/msa/StockholmMSA.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,9 +8,11 @@ export declare function fastaSniff(text: string): boolean;
|
|
|
8
8
|
*
|
|
9
9
|
* A record starts at a `>` that begins a line, so a `>` inside a defline
|
|
10
10
|
* (`x->y`, HGVS `c.1799T>A`) stays part of it, and anything before the first
|
|
11
|
-
* record (ColabFold's `#8\t1` header) is skipped. The id is the
|
|
12
|
-
* the
|
|
13
|
-
*
|
|
14
|
-
* row
|
|
11
|
+
* record (ColabFold's `#8\t1` header) is skipped. The id is the first word of
|
|
12
|
+
* the defline, so `> name` reads as `name`, and a defline with no word names
|
|
13
|
+
* its record `unnamed_N` after its position in the file. A repeated id keeps
|
|
14
|
+
* the first record: ids index the row data, so emitting the id twice would
|
|
15
|
+
* render a phantom duplicate row showing the last record's residues.
|
|
15
16
|
*/
|
|
16
17
|
export declare function splitFastaRecords(text: string): FastaRecord[];
|
|
18
|
+
export declare function toRecords(input: string | FastaRecord[]): FastaRecord[];
|
package/dist/msa/fastaRecords.js
CHANGED
|
@@ -7,10 +7,11 @@ export function fastaSniff(text) {
|
|
|
7
7
|
*
|
|
8
8
|
* A record starts at a `>` that begins a line, so a `>` inside a defline
|
|
9
9
|
* (`x->y`, HGVS `c.1799T>A`) stays part of it, and anything before the first
|
|
10
|
-
* record (ColabFold's `#8\t1` header) is skipped. The id is the
|
|
11
|
-
* the
|
|
12
|
-
*
|
|
13
|
-
* row
|
|
10
|
+
* record (ColabFold's `#8\t1` header) is skipped. The id is the first word of
|
|
11
|
+
* the defline, so `> name` reads as `name`, and a defline with no word names
|
|
12
|
+
* its record `unnamed_N` after its position in the file. A repeated id keeps
|
|
13
|
+
* the first record: ids index the row data, so emitting the id twice would
|
|
14
|
+
* render a phantom duplicate row showing the last record's residues.
|
|
14
15
|
*/
|
|
15
16
|
export function splitFastaRecords(text) {
|
|
16
17
|
const records = [];
|
|
@@ -25,11 +26,13 @@ export function splitFastaRecords(text) {
|
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
};
|
|
29
|
+
let count = 0;
|
|
28
30
|
for (const line of text.split('\n')) {
|
|
29
31
|
if (line.startsWith('>')) {
|
|
30
32
|
flush();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
count++;
|
|
34
|
+
const id = /^>\s*(\S*)/.exec(line)[1] || `unnamed_${count}`;
|
|
35
|
+
current = { id, lines: [] };
|
|
33
36
|
}
|
|
34
37
|
else {
|
|
35
38
|
current?.lines.push(line);
|
|
@@ -38,4 +41,7 @@ export function splitFastaRecords(text) {
|
|
|
38
41
|
flush();
|
|
39
42
|
return records;
|
|
40
43
|
}
|
|
44
|
+
export function toRecords(input) {
|
|
45
|
+
return typeof input === 'string' ? splitFastaRecords(input) : input;
|
|
46
|
+
}
|
|
41
47
|
//# sourceMappingURL=fastaRecords.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastaRecords.js","sourceRoot":"","sources":["../../src/msa/fastaRecords.ts"],"names":[],"mappings":"AAKA,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAChC,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"fastaRecords.js","sourceRoot":"","sources":["../../src/msa/fastaRecords.ts"],"names":[],"mappings":"AAKA,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAChC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,OAAO,GAAkB,EAAE,CAAA;IACjC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,IAAI,OAAoD,CAAA;IAExD,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;YACpB,OAAO,CAAC,IAAI,CAAC;gBACX,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC;aAClD,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,CAAA;IAED,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,KAAK,EAAE,CAAA;YACP,KAAK,EAAE,CAAA;YACP,MAAM,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAE,CAAC,CAAC,CAAC,IAAI,WAAW,KAAK,EAAE,CAAA;YAC5D,OAAO,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;QAC7B,CAAC;aAAM,CAAC;YACN,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3B,CAAC;IACH,CAAC;IACD,KAAK,EAAE,CAAA;IAEP,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAA6B;IACrD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;AACrE,CAAC"}
|
package/dist/msa/index.js
CHANGED
|
@@ -3,16 +3,18 @@ import ClustalMSA from './ClustalMSA.js';
|
|
|
3
3
|
import EmfMSA from './EmfMSA.js';
|
|
4
4
|
import FastaMSA from './FastaMSA.js';
|
|
5
5
|
import StockholmMSA, { stockholmSniff } from './StockholmMSA.js';
|
|
6
|
-
import { fastaSniff } from './fastaRecords.js';
|
|
6
|
+
import { fastaSniff, splitFastaRecords } from './fastaRecords.js';
|
|
7
7
|
export { parseEmfTree } from 'emf-js';
|
|
8
8
|
export { default as parseNewick } from './parseNewick.js';
|
|
9
9
|
const htmlPage = /^<(!doctype\s+html|html[\s>])/i;
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// that share a leading '>' (fasta vs a3m)
|
|
10
|
+
const clustalHeader = /^(CLUSTAL|MUSCLE|PROBCONS|MSAPROBS|Kalign)/i;
|
|
11
|
+
// A caller that knows the format, such as impg emitting fasta-aln, passes
|
|
12
|
+
// `format` to skip sniffing, which cannot always tell FASTA from A3M
|
|
14
13
|
export function parseMSA(input, currentAlignment = 0, format) {
|
|
15
|
-
const text = input
|
|
14
|
+
const text = input
|
|
15
|
+
.replace(/^\uFEFF/, '')
|
|
16
|
+
.replaceAll(/\r\n?/g, '\n')
|
|
17
|
+
.trimStart();
|
|
16
18
|
if (htmlPage.test(text)) {
|
|
17
19
|
throw new Error('Received an HTML page, not an alignment. Check that the URL points at the file itself');
|
|
18
20
|
}
|
|
@@ -34,16 +36,20 @@ export function parseMSA(input, currentAlignment = 0, format) {
|
|
|
34
36
|
if (stockholmSniff(text)) {
|
|
35
37
|
return new StockholmMSA(text, currentAlignment);
|
|
36
38
|
}
|
|
37
|
-
if (A3mMSA.sniff(text)) {
|
|
38
|
-
return new A3mMSA(text);
|
|
39
|
-
}
|
|
40
39
|
if (fastaSniff(text)) {
|
|
41
|
-
|
|
40
|
+
const records = splitFastaRecords(text);
|
|
41
|
+
return A3mMSA.sniff(records) ? new A3mMSA(records) : new FastaMSA(records);
|
|
42
42
|
}
|
|
43
43
|
if (text.startsWith('SEQ')) {
|
|
44
44
|
return new EmfMSA(text);
|
|
45
45
|
}
|
|
46
|
-
|
|
46
|
+
if (clustalHeader.test(text)) {
|
|
47
|
+
return new ClustalMSA(text);
|
|
48
|
+
}
|
|
49
|
+
const firstLine = text.split('\n', 1)[0].slice(0, 80);
|
|
50
|
+
throw new Error(firstLine
|
|
51
|
+
? `Unrecognized alignment format: expected FASTA, A3M, Stockholm, Clustal or EMF, but the first line reads "${firstLine}"`
|
|
52
|
+
: 'The alignment file is empty');
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Get ungapped sequence from an aligned row
|
package/dist/msa/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/msa/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAA;AAChC,OAAO,UAAU,MAAM,iBAAiB,CAAA;AACxC,OAAO,MAAM,MAAM,aAAa,CAAA;AAChC,OAAO,QAAQ,MAAM,eAAe,CAAA;AACpC,OAAO,YAAY,EAAE,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/msa/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { interProScanResponseToAnnotations } from '../interProScanToAnnotations.ts'
|
|
2
|
+
import { annotationsToGFF } from './annotationsToGFF.ts'
|
|
3
|
+
|
|
4
|
+
import type { InterProScanResponse } from '../types.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* An annotation file's text as GFF3: GFF passes through, and the JSON an
|
|
8
|
+
* InterProScan run returns converts, taking each result's row from its xref.
|
|
9
|
+
*/
|
|
10
|
+
export function annotationTextToGFF(text: string): string {
|
|
11
|
+
const trimmed = text.trimStart()
|
|
12
|
+
if (!trimmed.startsWith('{')) {
|
|
13
|
+
return text
|
|
14
|
+
}
|
|
15
|
+
const response: InterProScanResponse = JSON.parse(trimmed)
|
|
16
|
+
return annotationsToGFF(interProScanResponseToAnnotations(response))
|
|
17
|
+
}
|
|
@@ -20,7 +20,7 @@ export function annotationsToGFF(
|
|
|
20
20
|
const { accession, name, description, featureType } = annotation
|
|
21
21
|
const { id, start, end, strand } = annotation
|
|
22
22
|
return [
|
|
23
|
-
id,
|
|
23
|
+
id.replaceAll(/[%\t\n\r]/g, encodeURIComponent),
|
|
24
24
|
'InterProScan',
|
|
25
25
|
// GFF-sourced annotations carry their original type; writing every
|
|
26
26
|
// feature back out as protein_match would turn an exon overlay into
|
|
@@ -68,6 +68,7 @@ test('gives gene-level features a direction but leaves exons/domains as blocks',
|
|
|
68
68
|
const annotations = gffToAnnotations([
|
|
69
69
|
record({ type: 'gene', strand: '+', Name: 'GENEA' }),
|
|
70
70
|
record({ type: 'mRNA', strand: '-', Name: 'GENEB' }),
|
|
71
|
+
record({ type: 'CDS', strand: '-', Name: 'orfA' }),
|
|
71
72
|
record({ type: 'exon', strand: '+', Name: 'exon-1' }),
|
|
72
73
|
record({ type: 'protein_match', strand: '+', Name: 'PF00001' }),
|
|
73
74
|
])
|
|
@@ -76,6 +77,7 @@ test('gives gene-level features a direction but leaves exons/domains as blocks',
|
|
|
76
77
|
).toEqual({
|
|
77
78
|
GENEA: 1, // + gene -> arrow right
|
|
78
79
|
GENEB: -1, // - gene -> arrow left
|
|
80
|
+
orfA: -1, // a prokaryotic gene is a CDS row
|
|
79
81
|
'exon-1': undefined, // exon stays a block even though it is stranded
|
|
80
82
|
PF00001: undefined, // protein domain stays a block
|
|
81
83
|
})
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Annotation, GFFRecord } from '../types.ts'
|
|
2
2
|
|
|
3
|
-
// Feature types
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// single transcript, not independently-oriented genes.
|
|
3
|
+
// Feature types drawn with an arrowhead for their strand. A prokaryotic GFF
|
|
4
|
+
// writes each gene as a CDS row. Exon, UTR and domain features stay blocks,
|
|
5
|
+
// since an exon is a segment of one transcript.
|
|
7
6
|
const GENE_LEVEL_TYPES = new Set([
|
|
8
7
|
'gene',
|
|
8
|
+
'CDS',
|
|
9
9
|
'pseudogene',
|
|
10
10
|
'mRNA',
|
|
11
11
|
'transcript',
|
|
@@ -189,6 +189,13 @@ describe('interProToGFF', () => {
|
|
|
189
189
|
])
|
|
190
190
|
})
|
|
191
191
|
|
|
192
|
+
test('round-trips a row name holding a percent sign', () => {
|
|
193
|
+
const gff = annotationsToGFF(
|
|
194
|
+
gffToAnnotations(parseGFF('a%25b/1-9\tsrc\tgene\t1\t5\t.\t+\t.\tName=x')),
|
|
195
|
+
)
|
|
196
|
+
expect(parseGFF(gff)[0]?.seq_id).toBe('a%b/1-9')
|
|
197
|
+
})
|
|
198
|
+
|
|
192
199
|
test('URL-encodes special characters in attributes', () => {
|
|
193
200
|
const results: Record<string, InterProScanResults> = {
|
|
194
201
|
seq1: {
|
package/src/gff/parseGFF.test.ts
CHANGED
|
@@ -111,6 +111,20 @@ MPIGSKERPTFFEIFKTRCNKADLGPISLN
|
|
|
111
111
|
expect(result[0]?.Note).toBe('100%done')
|
|
112
112
|
})
|
|
113
113
|
|
|
114
|
+
test('decodes a percent-encoded seq_id', () => {
|
|
115
|
+
const result = parseGFF(
|
|
116
|
+
'HBA%2FHUMAN/27-137\tsrc\tgene\t1\t5\t.\t+\t.\tName=x',
|
|
117
|
+
)
|
|
118
|
+
expect(result[0]?.seq_id).toBe('HBA/HUMAN/27-137')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('drops an attribute with no value', () => {
|
|
122
|
+
const result = parseGFF('a\tsrc\tgene\t1\t5\t.\t+\t.\tName=x;flag;Note=')
|
|
123
|
+
expect(Object.keys(result[0]!)).not.toContain('flag')
|
|
124
|
+
expect(Object.keys(result[0]!)).not.toContain('Note')
|
|
125
|
+
expect(result[0]?.Name).toBe('x')
|
|
126
|
+
})
|
|
127
|
+
|
|
114
128
|
test('keeps text after a second equals sign', () => {
|
|
115
129
|
const gff = 'seq1\tSource\ttype\t1\t10\t.\t.\t.\tNote=a=b'
|
|
116
130
|
const result = parseGFF(gff)
|
package/src/gff/parseGFF.ts
CHANGED
|
@@ -16,34 +16,26 @@ function unquote(val: string) {
|
|
|
16
16
|
return val.replace(/^"(.*)"$/, '$1')
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function parseAttributes(col9?: string): Record<string, string
|
|
19
|
+
function parseAttributes(col9?: string): Record<string, string> {
|
|
20
20
|
if (!col9) {
|
|
21
21
|
return {}
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.split(',')
|
|
40
|
-
.map(v => unquote(safeDecode(v).trim()))
|
|
41
|
-
.join(' ')
|
|
42
|
-
: undefined,
|
|
43
|
-
]
|
|
44
|
-
})
|
|
45
|
-
.filter(([key]) => key !== ''),
|
|
46
|
-
)
|
|
23
|
+
const attributes: Record<string, string> = {}
|
|
24
|
+
for (const field of col9.split(';')) {
|
|
25
|
+
const eq = field.indexOf('=')
|
|
26
|
+
const key = field.slice(0, eq).trim()
|
|
27
|
+
const val = field.slice(eq + 1)
|
|
28
|
+
// split on comma (the GFF3 multi-value separator) BEFORE decoding, so a
|
|
29
|
+
// literal comma encoded as %2C survives the split and isn't mistaken
|
|
30
|
+
// for a separator
|
|
31
|
+
if (eq !== -1 && key && val) {
|
|
32
|
+
attributes[key] = val
|
|
33
|
+
.split(',')
|
|
34
|
+
.map(v => unquote(safeDecode(v).trim()))
|
|
35
|
+
.join(' ')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return attributes
|
|
47
39
|
}
|
|
48
40
|
|
|
49
41
|
export function parseGFF(str?: string): GFFRecord[] {
|
|
@@ -73,7 +65,7 @@ export function parseGFF(str?: string): GFFRecord[] {
|
|
|
73
65
|
// "start=") can't turn a number field into a string
|
|
74
66
|
return {
|
|
75
67
|
...parseAttributes(col9),
|
|
76
|
-
seq_id: seq_id ?? '',
|
|
68
|
+
seq_id: safeDecode(seq_id ?? ''),
|
|
77
69
|
source: source ?? '',
|
|
78
70
|
type: type ?? '',
|
|
79
71
|
start: Number(start) || 0,
|
package/src/index.ts
CHANGED
package/src/msa/A3mMSA.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest'
|
|
2
2
|
|
|
3
3
|
import A3mMSA from './A3mMSA.ts'
|
|
4
|
+
import { splitFastaRecords } from './fastaRecords.ts'
|
|
4
5
|
|
|
5
6
|
// A3M format rules (see https://yanglab.qd.sdu.edu.cn/trRosetta/msa_format.html):
|
|
6
7
|
//
|
|
@@ -68,6 +69,21 @@ ACDEF
|
|
|
68
69
|
ACDEF---`
|
|
69
70
|
expect(A3mMSA.sniff(notA3m)).toBe(false)
|
|
70
71
|
})
|
|
72
|
+
|
|
73
|
+
test('returns false when rows with inserts differ in match columns', () => {
|
|
74
|
+
expect(A3mMSA.sniff('>a\nACDEFgh\n>b\nACDEFG\n')).toBe(false)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('reads records split once by the caller', () => {
|
|
78
|
+
const a3m = '>seq1\nACDEFghiKLMNPQ\n>seq2\nACDEFKLMNPQ\n'
|
|
79
|
+
const records = splitFastaRecords(a3m)
|
|
80
|
+
expect(A3mMSA.sniff(records)).toBe(true)
|
|
81
|
+
const fromRecords = new A3mMSA(records)
|
|
82
|
+
const fromText = new A3mMSA(a3m)
|
|
83
|
+
expect(fromRecords.getNames()).toEqual(fromText.getNames())
|
|
84
|
+
expect(fromRecords.getRow('seq1')).toBe(fromText.getRow('seq1'))
|
|
85
|
+
expect(fromRecords.getRow('seq2')).toBe('ACDEF...KLMNPQ')
|
|
86
|
+
})
|
|
71
87
|
})
|
|
72
88
|
|
|
73
89
|
describe('parsing', () => {
|
|
@@ -294,18 +310,6 @@ ACDEF`
|
|
|
294
310
|
expect(msa.getWidth()).toBe(5)
|
|
295
311
|
})
|
|
296
312
|
|
|
297
|
-
test('getMSA returns seqdata', () => {
|
|
298
|
-
const a3m = `>seq1
|
|
299
|
-
ACDEF
|
|
300
|
-
>seq2
|
|
301
|
-
GHIKL`
|
|
302
|
-
const msa = new A3mMSA(a3m)
|
|
303
|
-
const data = msa.getMSA()
|
|
304
|
-
|
|
305
|
-
expect(data.seqdata).toHaveProperty('seq1')
|
|
306
|
-
expect(data.seqdata).toHaveProperty('seq2')
|
|
307
|
-
})
|
|
308
|
-
|
|
309
313
|
test('getTree returns noTree structure', () => {
|
|
310
314
|
const a3m = `>seq1
|
|
311
315
|
ACDEF
|
package/src/msa/A3mMSA.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import BaseMSA from './BaseMSA.ts'
|
|
2
|
-
import { fastaSniff,
|
|
2
|
+
import { fastaSniff, toRecords } from './fastaRecords.ts'
|
|
3
|
+
|
|
4
|
+
import type { FastaRecord } from './fastaRecords.ts'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* A3M Format Parser
|
|
@@ -67,27 +69,18 @@ function parseRow(seq: string): ParsedRow {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
export default class A3mMSA extends BaseMSA {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
super()
|
|
75
|
-
const records = splitFastaRecords(text)
|
|
76
|
-
this.orderedNames = records.map(r => r.id)
|
|
77
|
-
this.MSA = {
|
|
78
|
-
seqdata: expandA3M(
|
|
79
|
-
records.map(r => r.seq),
|
|
80
|
-
this.orderedNames,
|
|
81
|
-
),
|
|
82
|
-
}
|
|
72
|
+
constructor(input: string | FastaRecord[]) {
|
|
73
|
+
const records = toRecords(input)
|
|
74
|
+
const expanded = expandA3M(records.map(r => r.seq))
|
|
75
|
+
super(records.map((r, i) => [r.id, expanded[i]!]))
|
|
83
76
|
}
|
|
84
77
|
|
|
85
|
-
static sniff(
|
|
86
|
-
if (!fastaSniff(
|
|
78
|
+
static sniff(input: string | FastaRecord[]): boolean {
|
|
79
|
+
if (typeof input === 'string' && !fastaSniff(input)) {
|
|
87
80
|
return false
|
|
88
81
|
}
|
|
89
82
|
|
|
90
|
-
const seqs =
|
|
83
|
+
const seqs = toRecords(input)
|
|
91
84
|
.map(r => r.seq)
|
|
92
85
|
.filter(s => !!s)
|
|
93
86
|
|
|
@@ -104,23 +97,24 @@ export default class A3mMSA extends BaseMSA {
|
|
|
104
97
|
return false
|
|
105
98
|
}
|
|
106
99
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
100
|
+
let hasLowercase = false
|
|
101
|
+
let matchCount: number | undefined
|
|
102
|
+
for (const seq of seqs) {
|
|
103
|
+
let matches = 0
|
|
104
|
+
for (let i = 0; i < seq.length; i++) {
|
|
105
|
+
const code = seq.charCodeAt(i)
|
|
106
|
+
if (isMatch(code)) {
|
|
107
|
+
matches++
|
|
108
|
+
} else if (isLower(code)) {
|
|
109
|
+
hasLowercase = true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (matchCount !== undefined && matches !== matchCount) {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
matchCount = matches
|
|
116
|
+
}
|
|
117
|
+
return hasLowercase
|
|
124
118
|
}
|
|
125
119
|
}
|
|
126
120
|
|
|
@@ -129,7 +123,7 @@ export default class A3mMSA extends BaseMSA {
|
|
|
129
123
|
* the longest insert any row places there, and rows without one are padded with
|
|
130
124
|
* '.' so all rows stay in register.
|
|
131
125
|
*/
|
|
132
|
-
function expandA3M(rawSeqs: string[]
|
|
126
|
+
function expandA3M(rawSeqs: string[]): string[] {
|
|
133
127
|
const rows = rawSeqs.map(parseRow)
|
|
134
128
|
// a loop, not Math.max(...): an a3m from an hhblits search routinely carries
|
|
135
129
|
// six figures of hits, and spreading one argument per row throws
|
|
@@ -147,9 +141,7 @@ function expandA3M(rawSeqs: string[], names: string[]): Record<string, string> {
|
|
|
147
141
|
}
|
|
148
142
|
}
|
|
149
143
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
for (const [seqIdx, { matches, inserts }] of rows.entries()) {
|
|
144
|
+
return rows.map(({ matches, inserts }) => {
|
|
153
145
|
const result: string[] = []
|
|
154
146
|
for (let pos = 0; pos <= numPositions; pos++) {
|
|
155
147
|
const ins = inserts[pos] ?? ''
|
|
@@ -159,8 +151,6 @@ function expandA3M(rawSeqs: string[], names: string[]): Record<string, string> {
|
|
|
159
151
|
result.push(matches[pos] ?? '-')
|
|
160
152
|
}
|
|
161
153
|
}
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return expanded
|
|
154
|
+
return result.join('')
|
|
155
|
+
})
|
|
166
156
|
}
|
package/src/msa/BaseMSA.ts
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
import type { MSATrack, NodeWithIds } from '../types.ts'
|
|
2
2
|
|
|
3
3
|
export default abstract class BaseMSA {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
abstract getNames(): string[]
|
|
7
|
-
|
|
4
|
+
protected rows = new Map<string, string>()
|
|
5
|
+
private names: string[]
|
|
8
6
|
private width?: number
|
|
9
7
|
|
|
8
|
+
/**
|
|
9
|
+
* A repeated name keeps its first row: names index the rows, so listing the
|
|
10
|
+
* name twice would render a phantom duplicate row.
|
|
11
|
+
*/
|
|
12
|
+
constructor(rows: Iterable<readonly [string, string]>) {
|
|
13
|
+
for (const [name, seq] of rows) {
|
|
14
|
+
if (!this.rows.has(name)) {
|
|
15
|
+
this.rows.set(name, seq)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
this.names = [...this.rows.keys()]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getRow(name: string): string {
|
|
22
|
+
return this.rows.get(name) ?? ''
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getNames(): string[] {
|
|
26
|
+
return this.names
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
/**
|
|
11
30
|
* Column count of the alignment: the length of its widest row, since a
|
|
12
31
|
* hand-edited fasta or some aligners leave rows short. The viewer's gap
|
package/src/msa/ClustalMSA.ts
CHANGED
|
@@ -5,40 +5,16 @@ import BaseMSA from './BaseMSA.ts'
|
|
|
5
5
|
export default class ClustalMSA extends BaseMSA {
|
|
6
6
|
private MSA: ReturnType<typeof parse>
|
|
7
7
|
|
|
8
|
-
// getRow is called once per leaf on every render pass, so a linear scan of
|
|
9
|
-
// alns makes a full pass quadratic in the row count
|
|
10
|
-
private byName: Map<string, string>
|
|
11
|
-
|
|
12
8
|
constructor(text: string) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
for (const aln of this.MSA.alns) {
|
|
17
|
-
// first wins, as the find() this replaced did
|
|
18
|
-
if (!this.byName.has(aln.id)) {
|
|
19
|
-
this.byName.set(aln.id, aln.seq)
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
getMSA() {
|
|
25
|
-
return this.MSA
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getRow(name: string): string {
|
|
29
|
-
return this.byName.get(name) ?? ''
|
|
9
|
+
const msa = parse(text)
|
|
10
|
+
super(msa.alns.map(aln => [aln.id, aln.seq]))
|
|
11
|
+
this.MSA = msa
|
|
30
12
|
}
|
|
31
13
|
|
|
32
14
|
getHeader() {
|
|
33
15
|
return this.MSA.header
|
|
34
16
|
}
|
|
35
17
|
|
|
36
|
-
// deduped, matching the first-wins byName map: emitting a repeated id twice
|
|
37
|
-
// renders a phantom duplicate row showing the first record's residues
|
|
38
|
-
getNames() {
|
|
39
|
-
return [...this.byName.keys()]
|
|
40
|
-
}
|
|
41
|
-
|
|
42
18
|
get seqConsensus() {
|
|
43
19
|
return this.MSA.consensus
|
|
44
20
|
}
|
package/src/msa/EmfMSA.ts
CHANGED
|
@@ -3,35 +3,7 @@ import { parseEmfAln } from 'emf-js'
|
|
|
3
3
|
import BaseMSA from './BaseMSA.ts'
|
|
4
4
|
|
|
5
5
|
export default class EmfMSA extends BaseMSA {
|
|
6
|
-
private MSA: ReturnType<typeof parseEmfAln>
|
|
7
|
-
|
|
8
|
-
// getRow is called once per leaf on every render pass, so a linear scan of
|
|
9
|
-
// the alignment makes a full pass quadratic in the row count
|
|
10
|
-
private byName: Map<string, string>
|
|
11
|
-
|
|
12
6
|
constructor(text: string) {
|
|
13
|
-
super()
|
|
14
|
-
this.MSA = parseEmfAln(text)
|
|
15
|
-
this.byName = new Map()
|
|
16
|
-
for (const aln of this.MSA) {
|
|
17
|
-
// first wins, as the find() this replaced did
|
|
18
|
-
if (!this.byName.has(aln.protein)) {
|
|
19
|
-
this.byName.set(aln.protein, aln.seq)
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
getMSA() {
|
|
25
|
-
return this.MSA
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getRow(name: string): string {
|
|
29
|
-
return this.byName.get(name) ?? ''
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// deduped, matching the first-wins byName map: emitting a repeated id twice
|
|
33
|
-
// renders a phantom duplicate row showing the first record's residues
|
|
34
|
-
getNames() {
|
|
35
|
-
return [...this.byName.keys()]
|
|
7
|
+
super(parseEmfAln(text).map(aln => [aln.protein, aln.seq]))
|
|
36
8
|
}
|
|
37
9
|
}
|