From 8027d0d5002cc83a750cb86ef9676995c2c7c1d9 Mon Sep 17 00:00:00 2001 From: Thomas Rich Date: Tue, 29 Jun 2021 14:35:57 -0700 Subject: [PATCH] adding autoAnnotate and convertApELikeRegexToRegex --- src/autoAnnotate.js | 231 +++++++++ src/autoAnnotate.test.js | 992 +++++++++++++++++++++++++++++++++++++++ src/index.js | 3 + 3 files changed, 1226 insertions(+) create mode 100644 src/autoAnnotate.js create mode 100644 src/autoAnnotate.test.js diff --git a/src/autoAnnotate.js b/src/autoAnnotate.js new file mode 100644 index 0000000..5854ca4 --- /dev/null +++ b/src/autoAnnotate.js @@ -0,0 +1,231 @@ +/* Copyright (C) 2018 TeselaGen Biotechnology, Inc. */ +const { forEach, omitBy } = require("lodash"); +const bioData = require("./bioData"); + +const { + normalizePositionByRangeLength, + reversePositionInRange +} = require("ve-range-utils"); +const getReverseComplementSequenceString = require("./getReverseComplementSequenceString"); + +const { ambiguous_dna_values } = bioData; +//seqsToAnnotateById must not be length = 0 +function autoAnnotate({ + seqsToAnnotateById, + annotationsToCheckById, + compareName, + warnIfMoreThan +}) { + const annotationsToAddBySeqId = {}; + + forEach(annotationsToCheckById, ann => { + const reg = new RegExp(ann.sequence, "gi"); + forEach( + omitBy(seqsToAnnotateById, s => !s.sequence.length), + ({ circular, sequence }, id) => { + function getMatches({ seqToMatchAgainst, isReverse, seqLen }) { + let match; + let lastMatch; + // const matches = [] + try { + while ((match = reg.exec(seqToMatchAgainst))) { + const { index: matchStart, 0: matchSeq } = match; + if (matchStart >= seqLen) return; + const matchEnd = matchStart + matchSeq.length; + if (lastMatch) { + if (matchStart > lastMatch.start && matchEnd <= lastMatch.end) { + reg.lastIndex = match.index + 1; + continue; + } + } + lastMatch = { + start: matchStart, + end: matchEnd + }; + const range = { + start: matchStart, + end: normalizePositionByRangeLength(matchEnd - 1, seqLen) + }; + if (!annotationsToAddBySeqId[id]) + annotationsToAddBySeqId[id] = []; + annotationsToAddBySeqId[id].push({ + ...(isReverse + ? { + start: reversePositionInRange(range.end, seqLen), + end: reversePositionInRange(range.start, seqLen) + } + : range), + strand: isReverse ? -1 : 1, + id: ann.id + }); + + reg.lastIndex = match.index + 1; + } + } catch (error) { + console.error(`error:`, error); + } + } + const seqLen = sequence.length; + + const revSeq = getReverseComplementSequenceString(sequence); + getMatches({ + seqLen, + seqToMatchAgainst: circular ? sequence + sequence : sequence + }); + getMatches({ + seqLen, + isReverse: true, + seqToMatchAgainst: circular ? revSeq + revSeq : revSeq + }); + } + ); + }); + + //loop through all patterns and get all matches + + const toReturn = {}; + + forEach(annotationsToAddBySeqId, (anns, id) => { + const origSeq = seqsToAnnotateById[id]; + const alreadyExistingAnnsByStartEnd = {}; + forEach(origSeq.annotations, ann => { + alreadyExistingAnnsByStartEnd[getStartEndStr(ann, { compareName })] = ann; + }); + const warningCounter = {}; + const toAdd = anns + .filter(ann => { + const alreadyExistingAnn = + alreadyExistingAnnsByStartEnd[getStartEndStr(ann, { compareName })]; + if (alreadyExistingAnn) return false; + if (warnIfMoreThan) { + warningCounter[ann.id] = (warningCounter[ann.id] || 0) + 1; + } + return true; + }) + .sort((a, b) => a.start - b.start); + if (toAdd.length) { + toReturn[id] = toAdd; + } + warnIfMoreThan && + forEach(warningCounter, (num, annId) => { + if (num > warnIfMoreThan) { + toReturn.__more_than_warnings = toReturn.__more_than_warnings || {}; + toReturn.__more_than_warnings[id] = + toReturn.__more_than_warnings[id] || []; + toReturn.__more_than_warnings[id].push(annId); + } + }); + }); + return toReturn; +} + +function getStartEndStr( + { start, end, name, strand, forward }, + { compareName } +) { + const isReverse = strand === -1 || forward === false; + return `${start}-${end}-${isReverse ? "rev" : "for"}-${ + compareName ? name : "" + }`; +} + +function convertApELikeRegexToRegex(regString = "") { + let newstr = ""; + let rightOfCaretHolder = ""; + let afterRightCaretHolder = ""; + let beforeRightCaret = ""; + let prevBp; + let hitLeftCaret; + let hitRightCaret; + + // eslint-disable-next-line no-unused-vars + for (const bp of regString.replace("(", "").replace(")", "")) { + /* eslint-disable no-loop-func*/ + /* eslint-disable no-inner-declarations*/ + function maybeHandleRightCaret(justAdded) { + if (hitRightCaret) { + rightOfCaretHolder += justAdded; + afterRightCaretHolder = `${rightOfCaretHolder}${ + afterRightCaretHolder.length ? "|" : "" + }${afterRightCaretHolder}`; + } + } + /* eslint-enable no-loop-func*/ + /* eslint-enable no-inner-declarations*/ + const ambigVal = ambiguous_dna_values[bp.toUpperCase()]; + if (ambigVal && ambigVal.length > 1) { + let valToUse; + if (ambigVal.length === 4) { + valToUse = "."; + } else { + valToUse = `[${ambigVal}]`; + } + newstr += valToUse; + maybeHandleRightCaret(valToUse); + continue; + } + if (bp === "#") { + if (hitRightCaret) throw new Error("Error converting regex"); + const valToUse = prevBp ? `[^${prevBp}]*?` : `.*?`; + newstr += valToUse; + maybeHandleRightCaret(valToUse); + continue; + } + if (bp === "<") { + if (hitRightCaret) throw new Error("Error converting to regex"); + if (hitLeftCaret) throw new Error("Error converting to regex"); + let holder = ""; + let stringToAdd = ""; + let isGroupClosed = true; + let closingBraceHit; + const groups = []; + for (let index = 0; index < newstr.length; index++) { + const char = newstr[index]; + const nextChar = newstr[index + 1]; + if (char === "[") { + isGroupClosed = false; + } else if (char === "]" || closingBraceHit) { + closingBraceHit = true; + if (ambiguous_dna_values[nextChar] || nextChar === "[") { + isGroupClosed = true; + closingBraceHit = false; + } + } + holder += char; + if (isGroupClosed) { + groups.push(holder); + holder = ""; + } + } + let concattedEls = ""; + groups.reverse(); + groups.forEach(g => { + concattedEls = g + concattedEls; + stringToAdd = `${concattedEls}${ + stringToAdd.length ? "|" : "" + }${stringToAdd}`; + }); + newstr = `(${stringToAdd})?`; + hitLeftCaret = true; + continue; + } + if (bp === ">") { + if (hitRightCaret) throw new Error("Error converting regex"); + hitRightCaret = true; + beforeRightCaret = newstr; + continue; + } + newstr += bp; + maybeHandleRightCaret(bp); + prevBp = bp; + } + if (hitRightCaret) { + newstr = `${beforeRightCaret}(${afterRightCaretHolder})?`; + } + return newstr; +} + +module.exports = { + convertApELikeRegexToRegex, + autoAnnotate +}; diff --git a/src/autoAnnotate.test.js b/src/autoAnnotate.test.js new file mode 100644 index 0000000..e4aacbf --- /dev/null +++ b/src/autoAnnotate.test.js @@ -0,0 +1,992 @@ +/* eslint-disable no-unused-expressions */ +const { autoAnnotate, convertApELikeRegexToRegex } = require("./autoAnnotate"); +const { expect } = require("chai"); + +describe("convertApELikeRegexToRegex", function() { + it(`converts as expected`, done => { + expect(convertApELikeRegexToRegex("TnC")).to.eq("T.C"); + expect(convertApELikeRegexToRegex("TNC")).to.eq("T.C"); + expect(convertApELikeRegexToRegex("TBC")).to.eq("T[CGT]C"); + expect(convertApELikeRegexToRegex("T#C")).to.eq("T[^T]*?C"); + expect(convertApELikeRegexToRegex("T#CTTGAG")).to.eq( + "(CA|A)?ATT(TTGAG|TTGA|TTG|TT|T)?" + ); + expect(convertApELikeRegexToRegex("CAA>AT")).to.eq("CAA(AT|A)?"); + expect(convertApELikeRegexToRegex("CAA>ATT")).to.eq("CAA(ATT|AT|A)?"); + expect(convertApELikeRegexToRegex("CAA>TBT")).to.eq( + "CAA(T[CGT]T|T[CGT]|T)?" + ); + expect(convertApELikeRegexToRegex("CA { + try { + convertApELikeRegexToRegex("T#C<"); + } catch (e) { + expect(e).to.exist; + } + done(); + }); + it(`errors as expected`, done => { + try { + convertApELikeRegexToRegex(" { + try { + convertApELikeRegexToRegex(">C>"); + } catch (e) { + expect(e).to.exist; + } + done(); + }); + it(`errors as expected`, done => { + try { + convertApELikeRegexToRegex(">C#"); + } catch (e) { + expect(e).to.exist; + } + done(); + }); +}); +describe("autoAnnotate", function() { + // T#C + // AAAATTTTGGGGGCCCCCAAGT + // T(GGGG)GC + // AAAATTTTGGGGGCCCCCAAGT + // T(#)CC + // AAAATTTTGGGGGCCCCCAAGT + // CATTGAG + // AAAATTTTGGGGGCCCCCAAGT + // CA { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "AAAATTTTGGGGGCCCCCAAGT" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: "TTTT.*CCC", + name: "ann1", + type: "misc_feature" + } + } + }); + expect(results).to.not.be.undefined; + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 21: [ + //21 = sequenceId + //this is the list of new annotations to make + { + start: 4, + end: 17, + strand: 1, + id: 31 + } + ] + }); + done(); + }); + it(`IUPAC # works - T#C should capture part of AAAATTTTGGGGGCCCCCAAGT`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "AAAATTTTGGGGGCCCCCAAGT" + // "ACTTGGGGGCCCCCAAAATTTT" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: convertApELikeRegexToRegex("T#C"), + name: "ann1", + type: "misc_feature" + } + } + }); + expect(results).to.not.be.undefined; + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 21: [ + //21 = sequenceId + //this is the list of new annotations to make + { + start: 7, + end: 13, + strand: 1, + id: 31 + }, + { + start: 12, + end: 18, + strand: -1, + id: 31 + } + ] + }); + done(); + }); + it(`IUPAC # works - T#C { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "AAAATTTTGGGGGCCCCCAAGT" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: convertApELikeRegexToRegex("T#C works - CAA>ATT should capture all of CAAATT`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "CAAATT" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: convertApELikeRegexToRegex("CAA>ATT"), + name: "ann1", + type: "misc_feature" + } + } + }); + expect(results).to.not.be.undefined; + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 21: [ + //21 = sequenceId + //this is the list of new annotations to make + { + start: 0, + end: 5, + strand: 1, + id: 31 + } + ] + }); + done(); + }); + it(`IUPAC > works - CAA>ATT should capture part of CAAATG`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "CAAATT" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: convertApELikeRegexToRegex("CAA>ATT"), + name: "ann1", + type: "misc_feature" + } + } + }); + expect(results).to.not.be.undefined; + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 21: [ + //21 = sequenceId + //this is the list of new annotations to make + { + start: 0, + end: 5, + strand: 1, + id: 31 + } + ] + }); + done(); + }); + it(`complex IUPAC works - correctly auto annotates a single seq with a regex annotation`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "CGCATATTT" + } + }, + annotationsToCheckById: { + 31: { + id: 31, + sequence: convertApELikeRegexToRegex("CCAT { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "ccccccTTT" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: convertApELikeRegexToRegex("ccc { + const convertReg = convertApELikeRegexToRegex( + "gacgtcttatgacaacttgacggctacatcattcactttttcttcacaaccggcacggaactcgctcgggctggccccggtgcattttttaaatacccgcgagaaatagagttgatcgtcaaaaccaacattgcgaccgacggtggcgataggcatccgggtggtgctcaaaagcagcttcgcctggctgatacgttggtc { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: + "gatgttgattctatcatctatctggccagatagatgatagaatcgagcatcgattctatcatctatctgtactatcgattctatcatctatctga", + // tcagatagatgatagaatcgatagtacagatagatgatagaatcgatgctcgattctatcatctatctggccagatagatgatagaatcaacatc + annotations: [ + { + start: 5, + end: 11 + } + ] + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: "gattctatcatctatctg", //cagatagatgatagaatc <-- rev comp + name: "ann1", + type: "misc_feature" + } + } + }); + + expect(results).to.not.be.undefined; + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 21: [ + //21 = sequenceId + //this is the list of new annotations to make + { + start: 6, + end: 23, + strand: 1, + id: 31 + }, + { + start: 26, + end: 43, + strand: -1, + id: 31 + }, + { + start: 51, + end: 68, + strand: 1, + id: 31 + }, + { + start: 76, + end: 93, + strand: 1, + id: 31 + } + ] + }); + done(); + }); + it(`correctly auto annotates a single seq with a full spanning annotation`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "ttttttt" + } + }, + annotationsToCheckById: { + 31: { + id: 31, + sequence: "ttttttt", + name: "ann1", + type: "misc_feature" + } + } + }); + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 21: [{ id: 31, strand: 1, start: 0, end: 6 }] + }); + done(); + }); + it(`a "warning threshold" works as expected`, done => { + const results = autoAnnotate({ + warnIfMoreThan: 5, + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "ttttttt" + } + }, + annotationsToCheckById: { + 31: { + id: 31, + sequence: "t", + name: "ann1", + type: "misc_feature" + } + } + }); + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 21: [ + { + start: 0, + end: 0, + strand: 1, + id: 31 + }, + { + start: 1, + end: 1, + strand: 1, + id: 31 + }, + { + start: 2, + end: 2, + strand: 1, + id: 31 + }, + { + start: 3, + end: 3, + strand: 1, + id: 31 + }, + { + start: 4, + end: 4, + strand: 1, + id: 31 + }, + { + start: 5, + end: 5, + strand: 1, + id: 31 + }, + { + start: 6, + end: 6, + strand: 1, + id: 31 + } + ], + __more_than_warnings: { + 21: ["31"] + } + }); + done(); + }); + it(`correctly does not auto annotate two seqs when an annotation spans them`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "ttttttt" + }, + 22: { + id: 22, + sequence: "aaaaaaa" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: "tttaaa", + name: "ann1", + type: "misc_feature" + } + } + }); + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({}); + done(); + }); + it(`correctly auto annotates two seqs when an annotation spans the origin on the 2nd seq`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + circular: true, + id: 21, + sequence: "ttttttt" + }, + 22: { + circular: true, + id: 22, + sequence: "aaaaaaaattg" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: "attga", + name: "ann1", + type: "misc_feature" + } + } + }); + expect(results).to.deep.eq({ + 22: [ + { + start: 7, + end: 0, + strand: 1, + id: 31 + } + ] + }); + done(); + }); + it(`correctly auto annotates two seq when the casing on the annotation and seq is wonky`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + circular: true, + id: 21, + sequence: "ttttTtt" + }, + 22: { + circular: true, + id: 22, + sequence: "aAaaaaaaTtg" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: "atTgA", + name: "ann1", + type: "misc_feature" + } + } + }); + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 22: [ + { + id: 31, + strand: 1, + start: 7, + end: 0 + } + ] + }); + done(); + }); + + it(`correctly auto annotates a 1 bp seq`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + circular: true, + id: 21, + sequence: "t" + } + }, + annotationsToCheckById: { + 31: { + id: 31, + sequence: "t", + name: "ann1", + type: "misc_feature" + } + } + }); + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 21: [ + { + id: 31, + strand: 1, + start: 0, + end: 0 + } + ] + }); + done(); + }); + it(`correctly does not auto annotate a 1 bp seq when a feature already spans that seq`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + circular: true, + id: 21, + sequence: "t", + annotations: [{ start: 0, end: 0 }] + } + }, + annotationsToCheckById: { + 31: { + id: 31, + sequence: "t", + name: "ann1", + type: "misc_feature" + } + } + }); + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({}); + done(); + }); + it(`correctly auto annotates a 1 bp seq when compareName:true when a feature with a different name already spans that seq`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + circular: true, + id: 21, + sequence: "t", + annotations: [{ start: 0, end: 0, name: "someothername" }] + } + }, + annotationsToCheckById: { + 31: { + id: 31, + sequence: "t", + name: "ann1", + type: "misc_feature" + } + }, + compareName: true + }); + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 21: [ + { + id: 31, + strand: 1, + start: 0, + end: 0 + } + ] + }); + done(); + }); + it(`correctly does not auto annotate several seqs when annotations already span those seqs`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + circular: true, + id: 21, + sequence: "t", + annotations: [{ start: 0, end: 0, strand: 1 }] + }, + 22: { + circular: true, + id: 22, + sequence: "gggggt", + annotations: [ + { start: 3, end: 5 }, + { start: 5, end: 5 } + ] + } + }, + annotationsToCheckById: { + 31: { + id: 31, + sequence: "t", + name: "ann1", + type: "misc_feature" + }, + 32: { + id: 32, + sequence: "ggt", + name: "ann2", + type: "misc_feature" + } + } + }); + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({}); + done(); + }); + it(`correctly auto annotates two seqs when a annotation spans the origin on the 2nd seq on the negative strand`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + circular: true, + id: 21, + sequence: "ttttttt" + }, + 22: { + circular: true, // + id: 22, //01234567890 + //caat t + sequence: "aaaaaaaattg" //caatttttttt + //aaaaaaaattg + //a attg + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: "tcaat", + name: "ann1", + type: "misc_feature" + } + } + }); + //this should return an object keyed by the sequence id with the list of annotations to create + expect(results).to.deep.eq({ + 22: [ + { + id: 31, + strand: -1, + start: 7, + end: 0 + } + ] + }); + done(); + }); + it(`correctly auto annotates a seq when an annotation matches multiple times overlapping`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 22: { + id: 22, + sequence: "aaaaa" + } + }, + annotationsToCheckById: { + 31: { + id: 31, + sequence: "aaaa", + name: "ann1", + type: "misc_feature" + } + } + }); + expect(results).to.deep.eq({ + 22: [ + { + id: 31, + strand: 1, + start: 0, + end: 3 + }, + { + id: 31, + strand: 1, + start: 1, + end: 4 + } + ] + }); + done(); + }); + it(`correctly auto annotates two seqs when an annotation matches multiple times overlapping on the 2nd seq`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 20: { + id: 20, + sequence: "aaaagg" + }, + 22: { + id: 22, + sequence: "aaaaa" + } + }, + + annotationsToCheckById: { + 31: { + id: 31, + sequence: "aaaa", + name: "ann1", + type: "misc_feature" + } + } + }); + expect(results).to.deep.eq({ + 20: [ + { + id: 31, + strand: 1, + start: 0, + end: 3 + } + ], + 22: [ + { + id: 31, + strand: 1, + start: 0, + end: 3 + }, + { + id: 31, + strand: 1, + start: 1, + end: 4 + } + ] + }); + done(); + }); + it(`correctly auto annotates multiple sequences and multiple annotations`, done => { + const results = autoAnnotate({ + seqsToAnnotateById: { + 21: { + id: 21, + sequence: "gatgttgattctatcatctatctggcgagcatctactatca", + annotations: [ + { + strand: -1, //this annotation should not be matched cause it is on the reverse strand! + start: 5, + end: 11 + } + ] + }, + 45: { + id: 45, + circular: true, + sequence: + "gatgttgattctatcgtttttttttttaaaaaaagatgttgattctatcgtttttttttttaaaaaaactggcgagcatctactatca" + } + }, + annotationsToCheckById: { + 31: { + id: 31, + sequence: "ttttttttttaaaaa" //tttttaaaaaaaaaa + }, + 84: { + id: 84, + sequence: "tgattct", //agaatca + name: "zoink1" + }, + 90: { + id: 90, + sequence: "ctatcagatgttg" //crosses the origin on seq45 //caacatctgatag + }, + 91: { + id: 91, + sequence: "ctcgccagatagatga" //tcatctatctggcgag revcomp on 21 + } + } + }); + expect(results).to.not.be.undefined; + expect(results).to.deep.eq({ + 21: [ + { + strand: 1, + start: 5, + end: 11, + id: 84 + }, + { + strand: -1, + start: 13, + end: 28, + id: 91 + } + ], + 45: [ + { + strand: 1, + start: 5, + end: 11, + id: 84 + }, + { + strand: 1, + start: 17, + end: 31, + id: 31 + }, + { + strand: 1, + start: 39, + end: 45, + id: 84 + }, + { + strand: 1, + start: 51, + end: 65, + id: 31 + }, + { + strand: 1, + start: 82, + end: 6, + id: 90 + } + ] + }); + done(); + }); +}); diff --git a/src/index.js b/src/index.js index e637ddc..168c73f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,7 @@ const diffUtils = require("./diffUtils"); +const { autoAnnotate, convertApELikeRegexToRegex } = require("./autoAnnotate"); +module.exports.autoAnnotate = autoAnnotate; +module.exports.convertApELikeRegexToRegex = convertApELikeRegexToRegex; module.exports.diffUtils = diffUtils; module.exports.getDiffFromSeqs = diffUtils.getDiffFromSeqs;