msa-parsers 1.0.3

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 (85) hide show
  1. package/dist/gff/gffToInterPro.d.ts +20 -0
  2. package/dist/gff/gffToInterPro.js +83 -0
  3. package/dist/gff/gffToInterPro.js.map +1 -0
  4. package/dist/gff/gffToInterPro.test.d.ts +1 -0
  5. package/dist/gff/gffToInterPro.test.js +181 -0
  6. package/dist/gff/gffToInterPro.test.js.map +1 -0
  7. package/dist/gff/index.d.ts +3 -0
  8. package/dist/gff/index.js +4 -0
  9. package/dist/gff/index.js.map +1 -0
  10. package/dist/gff/interProToGFF.d.ts +9 -0
  11. package/dist/gff/interProToGFF.js +48 -0
  12. package/dist/gff/interProToGFF.js.map +1 -0
  13. package/dist/gff/interProToGFF.test.d.ts +1 -0
  14. package/dist/gff/interProToGFF.test.js +189 -0
  15. package/dist/gff/interProToGFF.test.js.map +1 -0
  16. package/dist/gff/parseGFF.d.ts +2 -0
  17. package/dist/gff/parseGFF.js +41 -0
  18. package/dist/gff/parseGFF.js.map +1 -0
  19. package/dist/gff/parseGFF.test.d.ts +1 -0
  20. package/dist/gff/parseGFF.test.js +92 -0
  21. package/dist/gff/parseGFF.test.js.map +1 -0
  22. package/dist/index.d.ts +5 -0
  23. package/dist/index.js +9 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/msa/A3mMSA.d.ts +33 -0
  26. package/dist/msa/A3mMSA.js +280 -0
  27. package/dist/msa/A3mMSA.js.map +1 -0
  28. package/dist/msa/A3mMSA.test.d.ts +1 -0
  29. package/dist/msa/A3mMSA.test.js +155 -0
  30. package/dist/msa/A3mMSA.test.js.map +1 -0
  31. package/dist/msa/ClustalMSA.d.ts +30 -0
  32. package/dist/msa/ClustalMSA.js +53 -0
  33. package/dist/msa/ClustalMSA.js.map +1 -0
  34. package/dist/msa/EmfMSA.d.ts +27 -0
  35. package/dist/msa/EmfMSA.js +53 -0
  36. package/dist/msa/EmfMSA.js.map +1 -0
  37. package/dist/msa/FastaMSA.d.ts +19 -0
  38. package/dist/msa/FastaMSA.js +69 -0
  39. package/dist/msa/FastaMSA.js.map +1 -0
  40. package/dist/msa/StockholmMSA.d.ts +54 -0
  41. package/dist/msa/StockholmMSA.js +113 -0
  42. package/dist/msa/StockholmMSA.js.map +1 -0
  43. package/dist/msa/index.d.ts +18 -0
  44. package/dist/msa/index.js +34 -0
  45. package/dist/msa/index.js.map +1 -0
  46. package/dist/msa/index.test.d.ts +1 -0
  47. package/dist/msa/index.test.js +60 -0
  48. package/dist/msa/index.test.js.map +1 -0
  49. package/dist/msa/parseNewick.d.ts +60 -0
  50. package/dist/msa/parseNewick.js +95 -0
  51. package/dist/msa/parseNewick.js.map +1 -0
  52. package/dist/msa/stockholmParser.d.ts +22 -0
  53. package/dist/msa/stockholmParser.js +141 -0
  54. package/dist/msa/stockholmParser.js.map +1 -0
  55. package/dist/msa/stockholmParser.test.d.ts +1 -0
  56. package/dist/msa/stockholmParser.test.js +111 -0
  57. package/dist/msa/stockholmParser.test.js.map +1 -0
  58. package/dist/types.d.ts +66 -0
  59. package/dist/types.js +2 -0
  60. package/dist/types.js.map +1 -0
  61. package/dist/util.d.ts +2 -0
  62. package/dist/util.js +10 -0
  63. package/dist/util.js.map +1 -0
  64. package/package.json +25 -0
  65. package/src/gff/gffToInterPro.test.ts +202 -0
  66. package/src/gff/gffToInterPro.ts +113 -0
  67. package/src/gff/index.ts +3 -0
  68. package/src/gff/interProToGFF.test.ts +206 -0
  69. package/src/gff/interProToGFF.ts +59 -0
  70. package/src/gff/parseGFF.test.ts +106 -0
  71. package/src/gff/parseGFF.ts +46 -0
  72. package/src/index.ts +29 -0
  73. package/src/msa/A3mMSA.test.ts +192 -0
  74. package/src/msa/A3mMSA.ts +320 -0
  75. package/src/msa/ClustalMSA.ts +67 -0
  76. package/src/msa/EmfMSA.ts +67 -0
  77. package/src/msa/FastaMSA.ts +82 -0
  78. package/src/msa/StockholmMSA.ts +141 -0
  79. package/src/msa/index.test.ts +74 -0
  80. package/src/msa/index.ts +44 -0
  81. package/src/msa/parseNewick.ts +94 -0
  82. package/src/msa/stockholmParser.test.ts +123 -0
  83. package/src/msa/stockholmParser.ts +157 -0
  84. package/src/types.ts +68 -0
  85. package/src/util.ts +19 -0
@@ -0,0 +1,46 @@
1
+ import type { GFFRecord } from '../types'
2
+
3
+ function parseAttributes(col9?: string): Record<string, string | undefined> {
4
+ if (!col9) {
5
+ return {}
6
+ }
7
+ return Object.fromEntries(
8
+ col9
9
+ .split(';')
10
+ .map(f => f.trim())
11
+ .filter(f => !!f)
12
+ .map(f => f.split('='))
13
+ .map(([key, val]) => [
14
+ key?.trim() ?? '',
15
+ val ? decodeURIComponent(val).trim().split(',').join(' ') : undefined,
16
+ ])
17
+ .filter(([key]) => key !== ''),
18
+ )
19
+ }
20
+
21
+ export function parseGFF(str?: string): GFFRecord[] {
22
+ if (!str) {
23
+ return []
24
+ }
25
+ return str
26
+ .split('\n')
27
+ .map(f => f.trim())
28
+ .filter(f => !!f && !f.startsWith('#'))
29
+ .map(f => {
30
+ const parts = f.split('\t')
31
+ const [seq_id, source, type, start, end, score, strand, phase] = parts
32
+ const col9 = parts[8]
33
+
34
+ return {
35
+ seq_id: seq_id ?? '',
36
+ source: source ?? '',
37
+ type: type ?? '',
38
+ start: Number(start) || 0,
39
+ end: Number(end) || 0,
40
+ score: Number(score) || 0,
41
+ strand: strand ?? '.',
42
+ phase: phase ?? '.',
43
+ ...parseAttributes(col9),
44
+ }
45
+ })
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ // Types
2
+ export * from './types'
3
+
4
+ // Utilities
5
+ export { generateNodeIds } from './util'
6
+
7
+ // MSA parsers
8
+ export {
9
+ A3mMSA,
10
+ ClustalMSA,
11
+ EmfMSA,
12
+ FastaMSA,
13
+ StockholmMSA,
14
+ getUngappedSequence,
15
+ parseEmfTree,
16
+ parseMSA,
17
+ parseNewick,
18
+ stockholmSniff,
19
+ } from './msa'
20
+ export type { MSAParserType } from './msa'
21
+
22
+ // GFF parsing
23
+ export {
24
+ gffToInterProResponse,
25
+ gffToInterProResults,
26
+ interProResponseToGFF,
27
+ interProToGFF,
28
+ parseGFF,
29
+ } from './gff'
@@ -0,0 +1,192 @@
1
+ import { describe, expect, test } from 'vitest'
2
+
3
+ import A3mMSA from './A3mMSA'
4
+
5
+ describe('A3mMSA', () => {
6
+ describe('sniff', () => {
7
+ test('returns false for non-FASTA text', () => {
8
+ expect(A3mMSA.sniff('not fasta')).toBe(false)
9
+ expect(A3mMSA.sniff('CLUSTAL W')).toBe(false)
10
+ })
11
+
12
+ test('returns false for regular FASTA', () => {
13
+ const fasta = `>seq1
14
+ ACDEFGHIKLMNPQRSTVWY
15
+ >seq2
16
+ ACDEFGHIKLMNPQRSTVWY`
17
+ expect(A3mMSA.sniff(fasta)).toBe(false)
18
+ })
19
+
20
+ test('returns true for A3M format', () => {
21
+ const a3m = `>seq1
22
+ ACDEFghiKLMNPQ
23
+ >seq2
24
+ ACDEF---KLMNPQ`
25
+ expect(A3mMSA.sniff(a3m)).toBe(true)
26
+ })
27
+
28
+ test('returns false for single sequence', () => {
29
+ const a3m = `>seq1
30
+ ACDEFghiKLMNPQ`
31
+ expect(A3mMSA.sniff(a3m)).toBe(false)
32
+ })
33
+ })
34
+
35
+ describe('parsing', () => {
36
+ test('parses simple A3M', () => {
37
+ const a3m = `>seq1
38
+ ACDEFghiKLMNPQ
39
+ >seq2
40
+ ACDEF---KLMNPQ`
41
+ const msa = new A3mMSA(a3m)
42
+
43
+ expect(msa.getNames()).toEqual(['seq1', 'seq2'])
44
+ const seq1 = msa.getRow('seq1')
45
+ const seq2 = msa.getRow('seq2')
46
+
47
+ expect(seq1.length).toBe(seq2.length)
48
+ expect(seq1).toContain('GHI')
49
+ })
50
+
51
+ test('expands lowercase insertions', () => {
52
+ const a3m = `>seq1
53
+ ACabc
54
+ >seq2
55
+ AC---`
56
+ const msa = new A3mMSA(a3m)
57
+
58
+ const seq1 = msa.getRow('seq1')
59
+ const seq2 = msa.getRow('seq2')
60
+
61
+ expect(seq1).toBe('ACABC')
62
+ expect(seq2).toBe('AC...')
63
+ })
64
+
65
+ test('handles multiple insertions', () => {
66
+ const a3m = `>seq1
67
+ AabcDdefG
68
+ >seq2
69
+ A---D---G`
70
+ const msa = new A3mMSA(a3m)
71
+
72
+ const seq1 = msa.getRow('seq1')
73
+ const seq2 = msa.getRow('seq2')
74
+
75
+ expect(seq1.length).toBe(seq2.length)
76
+ })
77
+
78
+ test('getWidth returns correct width', () => {
79
+ const a3m = `>seq1
80
+ ACDEF
81
+ >seq2
82
+ ACDEF`
83
+ const msa = new A3mMSA(a3m)
84
+
85
+ expect(msa.getWidth()).toBe(5)
86
+ })
87
+
88
+ test('getMSA returns seqdata', () => {
89
+ const a3m = `>seq1
90
+ ACDEF
91
+ >seq2
92
+ GHIKL`
93
+ const msa = new A3mMSA(a3m)
94
+ const data = msa.getMSA()
95
+
96
+ expect(data.seqdata).toHaveProperty('seq1')
97
+ expect(data.seqdata).toHaveProperty('seq2')
98
+ })
99
+
100
+ test('getTree returns noTree structure', () => {
101
+ const a3m = `>seq1
102
+ ACDEF
103
+ >seq2
104
+ GHIKL`
105
+ const msa = new A3mMSA(a3m)
106
+ const tree = msa.getTree()
107
+
108
+ expect(tree.noTree).toBe(true)
109
+ expect(tree.children).toHaveLength(2)
110
+ })
111
+
112
+ test('handles empty sequences', () => {
113
+ const a3m = `>seq1
114
+ ACDEF`
115
+ const msa = new A3mMSA(a3m)
116
+
117
+ expect(msa.getNames()).toEqual(['seq1'])
118
+ })
119
+
120
+ test('handles sequences with only ID on defline', () => {
121
+ const a3m = `>seq1 description here
122
+ ACDEF
123
+ >seq2 another description
124
+ GHIKL`
125
+ const msa = new A3mMSA(a3m)
126
+
127
+ expect(msa.getNames()).toEqual(['seq1', 'seq2'])
128
+ })
129
+
130
+ test('preserves sequence order', () => {
131
+ const a3m = `>z_seq
132
+ AAAAA
133
+ >a_seq
134
+ CCCCC
135
+ >m_seq
136
+ DDDDD`
137
+ const msa = new A3mMSA(a3m)
138
+
139
+ expect(msa.getNames()).toEqual(['z_seq', 'a_seq', 'm_seq'])
140
+ })
141
+ })
142
+
143
+ describe('properties', () => {
144
+ test('alignmentNames is empty array', () => {
145
+ const a3m = `>seq1
146
+ ACDEF`
147
+ const msa = new A3mMSA(a3m)
148
+
149
+ expect(msa.alignmentNames).toEqual([])
150
+ })
151
+
152
+ test('seqConsensus is undefined', () => {
153
+ const a3m = `>seq1
154
+ ACDEF`
155
+ const msa = new A3mMSA(a3m)
156
+
157
+ expect(msa.seqConsensus).toBeUndefined()
158
+ })
159
+
160
+ test('secondaryStructureConsensus is undefined', () => {
161
+ const a3m = `>seq1
162
+ ACDEF`
163
+ const msa = new A3mMSA(a3m)
164
+
165
+ expect(msa.secondaryStructureConsensus).toBeUndefined()
166
+ })
167
+
168
+ test('tracks is empty array', () => {
169
+ const a3m = `>seq1
170
+ ACDEF`
171
+ const msa = new A3mMSA(a3m)
172
+
173
+ expect(msa.tracks).toEqual([])
174
+ })
175
+
176
+ test('getStructures returns empty object', () => {
177
+ const a3m = `>seq1
178
+ ACDEF`
179
+ const msa = new A3mMSA(a3m)
180
+
181
+ expect(msa.getStructures()).toEqual({})
182
+ })
183
+
184
+ test('getHeader returns empty object', () => {
185
+ const a3m = `>seq1
186
+ ACDEF`
187
+ const msa = new A3mMSA(a3m)
188
+
189
+ expect(msa.getHeader()).toEqual({})
190
+ })
191
+ })
192
+ })
@@ -0,0 +1,320 @@
1
+ import type { NodeWithIds } from '../types'
2
+
3
+ /**
4
+ * A3M Format Parser
5
+ *
6
+ * The A3M format consists of aligned FASTA, in which alignments are shown with:
7
+ * - Inserts as lowercase characters
8
+ * - Matches as uppercase characters
9
+ * - Deletions as '-'
10
+ * - Gaps aligned to inserts as '.'
11
+ *
12
+ * Note that gaps aligned to inserts can be omitted in the A3M format.
13
+ *
14
+ * Example:
15
+ * >query
16
+ * ETESMKTVRIREKIKKFLGDRPRNTAEILEHINSTMRHGTTSQQLGNVLSKDKDIVKVGYIKRSGILSGGYDICEWATRNWVAEHCPEWTE
17
+ * >seq1
18
+ * ----MRTTRLRQKIKKFLNERGeANTTEILEHVNSTMRHGTTPQQLGNVLSKDKDILKVATTKRGGALSGRYEICVWTLRP-----------
19
+ *
20
+ * In the above, 'e' after 'G' in seq1 is a lowercase insert.
21
+ *
22
+ * @see https://yanglab.qd.sdu.edu.cn/trRosetta/msa_format.html
23
+ */
24
+
25
+ // Char code helpers for fast character classification
26
+ const CODE_A = 65 // 'A'
27
+ const CODE_Z = 90 // 'Z'
28
+ const CODE_a = 97 // 'a'
29
+ const CODE_z = 122 // 'z'
30
+ const CODE_DASH = 45 // '-'
31
+ const CODE_DOT = 46 // '.'
32
+
33
+ function isLower(code: number): boolean {
34
+ return code >= CODE_a && code <= CODE_z
35
+ }
36
+
37
+ export default class A3mMSA {
38
+ private MSA: { seqdata: Record<string, string> }
39
+ private orderedNames: string[]
40
+
41
+ constructor(text: string) {
42
+ const rawSeqs: string[] = []
43
+ const names: string[] = []
44
+
45
+ // First pass: parse sequences (like FASTA), preserving order
46
+ for (const entry of text.split('>')) {
47
+ if (!/\S/.test(entry)) {
48
+ continue
49
+ }
50
+ const newlineIdx = entry.indexOf('\n')
51
+ if (newlineIdx === -1) {
52
+ continue
53
+ }
54
+ const defLine = entry.slice(0, newlineIdx)
55
+ const spaceIdx = defLine.indexOf(' ')
56
+ const id = spaceIdx === -1 ? defLine : defLine.slice(0, spaceIdx)
57
+ if (id) {
58
+ rawSeqs.push(entry.slice(newlineIdx + 1).replaceAll(/\s/g, ''))
59
+ names.push(id)
60
+ }
61
+ }
62
+
63
+ this.orderedNames = names
64
+ this.MSA = { seqdata: this.expandA3M(rawSeqs, names) }
65
+ }
66
+
67
+ /**
68
+ * Detect if text is likely A3M format
69
+ */
70
+ static sniff(text: string): boolean {
71
+ if (!text.startsWith('>')) {
72
+ return false
73
+ }
74
+
75
+ const seqs: string[] = []
76
+ for (const entry of text.split('>')) {
77
+ if (!/\S/.test(entry)) {
78
+ continue
79
+ }
80
+ const newlineIdx = entry.indexOf('\n')
81
+ if (newlineIdx === -1) {
82
+ continue
83
+ }
84
+ const seq = entry.slice(newlineIdx + 1).replaceAll(/\s/g, '')
85
+ if (seq) {
86
+ seqs.push(seq)
87
+ }
88
+ }
89
+
90
+ if (seqs.length < 2) {
91
+ return false
92
+ }
93
+
94
+ // Check for lowercase and compute lengths in single pass per sequence
95
+ // In A3M, only uppercase letters are match columns (not gaps)
96
+ let hasLowercase = false
97
+ let firstUppercaseLen = -1
98
+ let sameUppercaseLength = true
99
+
100
+ for (const seq of seqs) {
101
+ let uppercaseLen = 0
102
+ for (let i = 0; i < seq.length; i++) {
103
+ const code = seq.charCodeAt(i)
104
+ if (isLower(code)) {
105
+ hasLowercase = true
106
+ } else if (code >= CODE_A && code <= CODE_Z) {
107
+ uppercaseLen++
108
+ }
109
+ }
110
+
111
+ if (firstUppercaseLen === -1) {
112
+ firstUppercaseLen = uppercaseLen
113
+ } else {
114
+ if (uppercaseLen !== firstUppercaseLen) {
115
+ sameUppercaseLength = false
116
+ }
117
+ }
118
+ }
119
+
120
+ return hasLowercase && sameUppercaseLength
121
+ }
122
+
123
+ /**
124
+ * Expand A3M format to standard aligned format.
125
+ *
126
+ * In A3M, lowercase characters are insertions that implicitly introduce
127
+ * gaps in sequences that don't have an insert at that position.
128
+ * Gaps (-) following match columns in sequences without inserts align
129
+ * with lowercase inserts in other sequences.
130
+ */
131
+ private expandA3M(
132
+ rawSeqs: string[],
133
+ names: string[],
134
+ ): Record<string, string> {
135
+ const numSeqs = names.length
136
+ if (numSeqs === 0) {
137
+ return {}
138
+ }
139
+
140
+ // Parse sequences: extract match chars (uppercase only) and insert content
141
+ // For each sequence, track: matchChars, insertContent (after each match)
142
+ const matchChars: string[][] = []
143
+ const insertContent: string[][] = []
144
+
145
+ for (let seqIdx = 0; seqIdx < numSeqs; seqIdx++) {
146
+ const seq = rawSeqs[seqIdx]!
147
+ const matches: string[] = []
148
+ const inserts: string[] = []
149
+ let i = 0
150
+
151
+ while (i < seq.length) {
152
+ const code = seq.charCodeAt(i)
153
+
154
+ if (code >= CODE_A && code <= CODE_Z) {
155
+ // Uppercase letter - match column
156
+ matches.push(seq[i]!)
157
+ // Collect following lowercase/gap characters as insert content
158
+ let ins = ''
159
+ let j = i + 1
160
+ while (j < seq.length) {
161
+ const c = seq.charCodeAt(j)
162
+ if (isLower(c) || c === CODE_DASH || c === CODE_DOT) {
163
+ ins += seq[j]!
164
+ j++
165
+ } else {
166
+ break
167
+ }
168
+ }
169
+ inserts.push(ins)
170
+ i = j
171
+ } else if (code === CODE_DASH || code === CODE_DOT) {
172
+ // Leading gap before first match - skip
173
+ i++
174
+ } else if (isLower(code)) {
175
+ // Leading insert before first match
176
+ let ins = ''
177
+ while (i < seq.length && isLower(seq.charCodeAt(i))) {
178
+ ins += seq[i]!
179
+ i++
180
+ }
181
+ // Add empty match with this insert
182
+ matches.push('')
183
+ inserts.push(ins)
184
+ } else {
185
+ i++
186
+ }
187
+ }
188
+
189
+ matchChars.push(matches)
190
+ insertContent.push(inserts)
191
+ }
192
+
193
+ // Find number of match positions (should be same for all valid A3M)
194
+ const numPositions = Math.max(...matchChars.map(m => m.length), 0)
195
+
196
+ // Find max insert length at each position (only count lowercase, not gaps)
197
+ const maxInserts = new Array<number>(numPositions).fill(0)
198
+ for (let seqIdx = 0; seqIdx < numSeqs; seqIdx++) {
199
+ const inserts = insertContent[seqIdx]!
200
+ for (let pos = 0; pos < inserts.length; pos++) {
201
+ // Count only lowercase characters as actual inserts
202
+ let lcCount = 0
203
+ for (const c of inserts[pos]!) {
204
+ if (isLower(c.charCodeAt(0))) {
205
+ lcCount++
206
+ }
207
+ }
208
+ if (lcCount > maxInserts[pos]!) {
209
+ maxInserts[pos] = lcCount
210
+ }
211
+ }
212
+ }
213
+
214
+ // Build expanded sequences
215
+ const expanded: Record<string, string> = {}
216
+
217
+ for (let seqIdx = 0; seqIdx < numSeqs; seqIdx++) {
218
+ const matches = matchChars[seqIdx]!
219
+ const inserts = insertContent[seqIdx]!
220
+ const result: string[] = []
221
+
222
+ for (let pos = 0; pos < numPositions; pos++) {
223
+ const maxIns = maxInserts[pos]!
224
+
225
+ if (pos < matches.length) {
226
+ const matchChar = matches[pos]!
227
+ const insContent = inserts[pos] || ''
228
+
229
+ // Add match character (or gap if empty)
230
+ result.push(matchChar || '-')
231
+
232
+ // Process insert content
233
+ let lcContent = ''
234
+ for (const c of insContent) {
235
+ if (isLower(c.charCodeAt(0))) {
236
+ lcContent += c.toUpperCase()
237
+ }
238
+ }
239
+
240
+ // Add the insert content (uppercased)
241
+ result.push(lcContent)
242
+
243
+ // Pad with gaps to match max insert length
244
+ const padding = maxIns - lcContent.length
245
+ if (padding > 0) {
246
+ result.push('.'.repeat(padding))
247
+ }
248
+ } else {
249
+ // This sequence is shorter - add gaps
250
+ result.push('-')
251
+ if (maxIns > 0) {
252
+ result.push('.'.repeat(maxIns))
253
+ }
254
+ }
255
+ }
256
+
257
+ expanded[names[seqIdx]!] = result.join('')
258
+ }
259
+
260
+ return expanded
261
+ }
262
+
263
+ getMSA() {
264
+ return this.MSA
265
+ }
266
+
267
+ getRowData() {
268
+ return undefined
269
+ }
270
+
271
+ getNames() {
272
+ return this.orderedNames
273
+ }
274
+
275
+ getRow(name: string) {
276
+ return this.MSA.seqdata[name] || ''
277
+ }
278
+
279
+ getWidth() {
280
+ const name = Object.keys(this.MSA.seqdata)[0]
281
+ return name ? this.getRow(name).length : 0
282
+ }
283
+
284
+ getStructures() {
285
+ return {}
286
+ }
287
+
288
+ get alignmentNames() {
289
+ return []
290
+ }
291
+
292
+ getHeader() {
293
+ return {}
294
+ }
295
+
296
+ getTree(): NodeWithIds {
297
+ return {
298
+ id: 'root',
299
+ name: 'root',
300
+ noTree: true,
301
+ children: this.getNames().map(name => ({
302
+ id: name,
303
+ children: [],
304
+ name,
305
+ })),
306
+ }
307
+ }
308
+
309
+ get seqConsensus() {
310
+ return undefined
311
+ }
312
+
313
+ get secondaryStructureConsensus() {
314
+ return undefined
315
+ }
316
+
317
+ get tracks() {
318
+ return []
319
+ }
320
+ }
@@ -0,0 +1,67 @@
1
+ import { parse } from 'clustal-js'
2
+
3
+ import type { NodeWithIds } from '../types'
4
+
5
+ export default class ClustalMSA {
6
+ private MSA: ReturnType<typeof parse>
7
+
8
+ constructor(text: string) {
9
+ this.MSA = parse(text)
10
+ }
11
+
12
+ getMSA() {
13
+ return this.MSA
14
+ }
15
+
16
+ getRow(name: string): string {
17
+ return this.MSA.alns.find(aln => aln.id === name)?.seq || ''
18
+ }
19
+
20
+ getWidth() {
21
+ return this.MSA.alns[0]!.seq.length
22
+ }
23
+
24
+ getRowData() {
25
+ return undefined
26
+ }
27
+
28
+ getHeader() {
29
+ return this.MSA.header
30
+ }
31
+
32
+ getNames() {
33
+ return this.MSA.alns.map(aln => aln.id)
34
+ }
35
+
36
+ getStructures() {
37
+ return {}
38
+ }
39
+
40
+ get alignmentNames() {
41
+ return []
42
+ }
43
+
44
+ getTree(): NodeWithIds {
45
+ return {
46
+ id: 'root',
47
+ name: 'root',
48
+ noTree: true,
49
+ children: this.getNames().map(name => ({
50
+ id: name,
51
+ name,
52
+ children: [],
53
+ })),
54
+ }
55
+ }
56
+
57
+ get seqConsensus() {
58
+ return this.MSA.consensus
59
+ }
60
+ get secondaryStructureConsensus() {
61
+ return undefined
62
+ }
63
+
64
+ get tracks() {
65
+ return []
66
+ }
67
+ }
@@ -0,0 +1,67 @@
1
+ import { parseEmfAln } from 'emf-js'
2
+
3
+ import type { NodeWithIds } from '../types'
4
+
5
+ export default class EmfMSA {
6
+ private MSA: ReturnType<typeof parseEmfAln>
7
+
8
+ constructor(text: string) {
9
+ this.MSA = parseEmfAln(text)
10
+ }
11
+
12
+ getMSA() {
13
+ return this.MSA
14
+ }
15
+
16
+ getRow(name: string): string {
17
+ return this.MSA.find(aln => aln.protein === name)?.seq || ''
18
+ }
19
+
20
+ getWidth() {
21
+ return this.MSA[0]!.seq.length
22
+ }
23
+
24
+ getRowData() {
25
+ return undefined
26
+ }
27
+
28
+ getHeader() {
29
+ return ''
30
+ }
31
+
32
+ getNames() {
33
+ return this.MSA.map(aln => aln.protein)
34
+ }
35
+
36
+ getStructures() {
37
+ return {}
38
+ }
39
+
40
+ get alignmentNames() {
41
+ return []
42
+ }
43
+
44
+ getTree(): NodeWithIds {
45
+ return {
46
+ id: 'root',
47
+ name: 'root',
48
+ noTree: true,
49
+ children: this.getNames().map(name => ({
50
+ id: name,
51
+ name,
52
+ children: [],
53
+ })),
54
+ }
55
+ }
56
+
57
+ get seqConsensus() {
58
+ return undefined
59
+ }
60
+ get secondaryStructureConsensus() {
61
+ return undefined
62
+ }
63
+
64
+ get tracks() {
65
+ return []
66
+ }
67
+ }