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.
Files changed (54) hide show
  1. package/dist/gff/annotationTextToGFF.d.ts +5 -0
  2. package/dist/gff/annotationTextToGFF.js +15 -0
  3. package/dist/gff/annotationTextToGFF.js.map +1 -0
  4. package/dist/gff/annotationsToGFF.js +1 -1
  5. package/dist/gff/annotationsToGFF.js.map +1 -1
  6. package/dist/gff/gffToAnnotations.js +4 -4
  7. package/dist/gff/gffToAnnotations.js.map +1 -1
  8. package/dist/gff/parseGFF.js +14 -20
  9. package/dist/gff/parseGFF.js.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/msa/A3mMSA.d.ts +3 -9
  14. package/dist/msa/A3mMSA.js +31 -32
  15. package/dist/msa/A3mMSA.js.map +1 -1
  16. package/dist/msa/BaseMSA.d.ts +9 -3
  17. package/dist/msa/BaseMSA.js +20 -0
  18. package/dist/msa/BaseMSA.js.map +1 -1
  19. package/dist/msa/ClustalMSA.d.ts +0 -14
  20. package/dist/msa/ClustalMSA.js +3 -23
  21. package/dist/msa/ClustalMSA.js.map +1 -1
  22. package/dist/msa/EmfMSA.d.ts +0 -15
  23. package/dist/msa/EmfMSA.js +1 -24
  24. package/dist/msa/EmfMSA.js.map +1 -1
  25. package/dist/msa/FastaMSA.d.ts +3 -8
  26. package/dist/msa/FastaMSA.js +10 -23
  27. package/dist/msa/FastaMSA.js.map +1 -1
  28. package/dist/msa/StockholmMSA.d.ts +0 -2
  29. package/dist/msa/StockholmMSA.js +2 -10
  30. package/dist/msa/StockholmMSA.js.map +1 -1
  31. package/dist/msa/fastaRecords.d.ts +6 -4
  32. package/dist/msa/fastaRecords.js +12 -6
  33. package/dist/msa/fastaRecords.js.map +1 -1
  34. package/dist/msa/index.js +17 -11
  35. package/dist/msa/index.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/gff/annotationTextToGFF.ts +17 -0
  38. package/src/gff/annotationsToGFF.ts +1 -1
  39. package/src/gff/gffToAnnotations.test.ts +2 -0
  40. package/src/gff/gffToAnnotations.ts +4 -4
  41. package/src/gff/interProToGFF.test.ts +7 -0
  42. package/src/gff/parseGFF.test.ts +14 -0
  43. package/src/gff/parseGFF.ts +18 -26
  44. package/src/index.ts +1 -0
  45. package/src/msa/A3mMSA.test.ts +16 -12
  46. package/src/msa/A3mMSA.ts +32 -42
  47. package/src/msa/BaseMSA.ts +23 -4
  48. package/src/msa/ClustalMSA.ts +3 -27
  49. package/src/msa/EmfMSA.ts +1 -29
  50. package/src/msa/FastaMSA.ts +12 -31
  51. package/src/msa/StockholmMSA.ts +2 -12
  52. package/src/msa/fastaRecords.ts +13 -6
  53. package/src/msa/index.test.ts +87 -5
  54. package/src/msa/index.ts +19 -11
@@ -1 +1 @@
1
- {"version":3,"file":"StockholmMSA.js","sourceRoot":"","sources":["../../src/msa/StockholmMSA.ts"],"names":[],"mappings":"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"}
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 defline up to
12
- * the first whitespace, tab included. A repeated id keeps the first record: ids
13
- * index the row data, so emitting the id twice would render a phantom duplicate
14
- * row showing the last record's residues.
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[];
@@ -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 defline up to
11
- * the first whitespace, tab included. A repeated id keeps the first record: ids
12
- * index the row data, so emitting the id twice would render a phantom duplicate
13
- * row showing the last record's residues.
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
- const id = /^>(\S*)/.exec(line)[1];
32
- current = id ? { id, lines: [] } : undefined;
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;;;;;;;;;GASG;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,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,MAAM,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAE,CAAC,CAAC,CAAE,CAAA;YACpC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;QAC9C,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"}
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
- // when `format` is given the heuristic sniffing is bypassed -- callers that
11
- // already know the format (e.g. impg emitting fasta-aln) can force it rather
12
- // than rely on auto-detection, which is necessarily ambiguous between formats
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.replace(/^\uFEFF/, '').trimStart();
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
- return new FastaMSA(text);
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
- return new ClustalMSA(text);
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
@@ -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;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAWzD,MAAM,QAAQ,GAAG,gCAAgC,CAAA;AAEjD,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAC9E,0CAA0C;AAC1C,MAAM,UAAU,QAAQ,CACtB,KAAa,EACb,gBAAgB,GAAG,CAAC,EACpB,MAAkB;IAElB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,CAAA;IACrD,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,uFAAuF,CACxF,CAAA;IACH,CAAC;IACD,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;QAC3B,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAA;IACjD,CAAC;IACD,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QACrB,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IACD,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QACrB,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAA;IACjD,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAA;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,OAAO,UAAU,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;AAC3C,CAAC;AAED,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,cAAc,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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msa-parsers",
3
- "version": "8.2.0",
3
+ "version": "8.3.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -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 that read as a directional "gene" and get an arrowhead in the
4
- // overlay. Exon/CDS/UTR/domain features deliberately stay rectangular blocks —
5
- // turning every exon into an arrow is misleading, since exons are segments of a
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: {
@@ -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)
@@ -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 | undefined> {
19
+ function parseAttributes(col9?: string): Record<string, string> {
20
20
  if (!col9) {
21
21
  return {}
22
22
  }
23
- return Object.fromEntries(
24
- col9
25
- .split(';')
26
- .map(f => f.trim())
27
- .filter(f => !!f)
28
- .map(f => {
29
- const eq = f.indexOf('=')
30
- const key = (eq === -1 ? f : f.slice(0, eq)).trim()
31
- const val = eq === -1 ? undefined : f.slice(eq + 1)
32
- // split on comma (the GFF3 multi-value separator) BEFORE decoding, so a
33
- // literal comma encoded as %2C survives the split and isn't mistaken
34
- // for a separator
35
- return [
36
- key,
37
- val
38
- ? val
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
@@ -34,3 +34,4 @@ export {
34
34
  interProToGFF,
35
35
  parseGFF,
36
36
  } from './gff/index.ts'
37
+ export { annotationTextToGFF } from './gff/annotationTextToGFF.ts'
@@ -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, splitFastaRecords } from './fastaRecords.ts'
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
- private MSA: { seqdata: Record<string, string> }
71
- private orderedNames: string[]
72
-
73
- constructor(text: string) {
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(text: string): boolean {
86
- if (!fastaSniff(text)) {
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 = splitFastaRecords(text)
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
- const rows = seqs.map(parseRow)
108
- const hasLowercase = rows.some(r => r.inserts.some(i => !!i))
109
- const sameMatchLength = new Set(rows.map(r => r.matches.length)).size === 1
110
-
111
- return hasLowercase && sameMatchLength
112
- }
113
-
114
- getMSA() {
115
- return this.MSA
116
- }
117
-
118
- getNames() {
119
- return this.orderedNames
120
- }
121
-
122
- getRow(name: string) {
123
- return this.MSA.seqdata[name] ?? ''
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[], names: string[]): Record<string, 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
- const expanded: Record<string, string> = Object.create(null)
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
- expanded[names[seqIdx]!] = result.join('')
163
- }
164
-
165
- return expanded
154
+ return result.join('')
155
+ })
166
156
  }
@@ -1,12 +1,31 @@
1
1
  import type { MSATrack, NodeWithIds } from '../types.ts'
2
2
 
3
3
  export default abstract class BaseMSA {
4
- abstract getMSA(): unknown
5
- abstract getRow(name: string): string
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
@@ -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
- super()
14
- this.MSA = parse(text)
15
- this.byName = new Map()
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
  }