packages feed

biostockholm (empty) → 0.1

raw patch · 4 files changed

+638/−0 lines, 4 filesdep +HUnitdep +basedep +biocoresetup-changed

Dependencies added: HUnit, base, biocore, bytestring, containers, deepseq, explicit-exception, hspec

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Felipe Lessa++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Felipe Lessa nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ biostockholm.cabal view
@@ -0,0 +1,56 @@+Name:                biostockholm+Version:             0.1+Synopsis:            Reading and writing Stockholm files (multiple sequence alignment, used by Rfam and Infernal).+License:             BSD3+License-file:        LICENSE+Author:              Felipe Lessa+Maintainer:          felipe.lessa@gmail.com+Category:            Bioinformatics+Build-type:          Simple+Cabal-version:       >=1.8+Description:+  Parsing and pretty printing of files in Stockholm 1.0 format.  See:+  .+  * <http://sonnhammer.sbc.su.se/Stockholm.html>+  .+  * <ftp://ftp.sanger.ac.uk/pub/databases/Pfam/current_release/relnotes.txt>+  .+  * <http://en.wikipedia.org/wiki/Stockholm_format>++Source-repository head+  Type:     darcs+  Location: http://patch-tag.com/r/felipe/biostockholm++Library+  Hs-Source-Dirs: src+  Exposed-modules:+    Bio.Sequence.Stockholm+  Ghc-Options: -Wall+  Build-depends:+        base               >= 3     && < 5+      , containers         >= 0.2   && < 0.5+      , bytestring         == 0.9.*+      , deepseq            == 1.1.*+      , explicit-exception == 0.1.*++      , biocore            >= 0.1   && < 0.3++Test-suite runtests+  Type: exitcode-stdio-1.0+  Hs-Source-Dirs: src+  Main-is: runtests.hs+  Other-modules:+    Bio.Sequence.Stockholm+  Ghc-Options: -Wall+  Cpp-Options: -DTEST+  Build-depends:+        base               >= 3     && < 5+      , containers         >= 0.2   && < 0.5+      , bytestring         == 0.9.*+      , deepseq            == 1.1.*+      , explicit-exception == 0.1.*++      , biocore            >= 0.1   && < 0.3++      , hspec              == 0.9.*+      , HUnit              == 1.2.*
+ src/Bio/Sequence/Stockholm.hs view
@@ -0,0 +1,550 @@+{-# LANGUAGE CPP, EmptyDataDecls, DeriveDataTypeable, OverloadedStrings #-}++-- | Parsing and pretty printing of files in Stockholm 1.0+-- format.  See:+--+--    - <http://sonnhammer.sbc.su.se/Stockholm.html>+--+--    - <ftp://ftp.sanger.ac.uk/pub/databases/Pfam/current_release/relnotes.txt>+--+--    - <http://en.wikipedia.org/wiki/Stockholm_format>++module Bio.Sequence.Stockholm+    (-- * Data types+     Stockholm(..)+    ,StockholmSeq(..)+    ,Ann(..)+    ,FileAnnotation(..)+    ,SequenceAnnotation(..)+    ,ColumnAnnotation(..)+    ,InFile+    ,InSeq+    ,findAnn++     -- * Parsing+    ,parseStockholm+    ,StockholmExc(..)++     -- * Printing+    ,prettyPrintStockholm++#ifdef TEST+     -- * Test cases+    ,test_Stockholm+#endif+    )+    where++-- from base+import Control.Applicative ((<$>))+import Control.Arrow (second)+import Control.DeepSeq (NFData(..))+import Control.Monad (mplus)+import Data.Char (isSpace)+import Data.List (foldl', find)+import Data.Maybe (fromMaybe)+import Data.Typeable (Typeable)+import Text.Show (showParen, showString)++-- from containers+import qualified Data.Map as M++-- from bytestring+import qualified Data.ByteString.Lazy.Char8 as B+import Data.ByteString.Lazy.Char8 (ByteString)++-- from biocore+import Bio.Core.Sequence++-- from explicit-exception+import Control.Monad.Exception.Synchronous (Exceptional, throw)+++#ifdef TEST+import Test.Hspec.Monadic+import Test.Hspec.HUnit ()+import Test.HUnit+#endif+++-- | An Stockholm 1.0 formatted file represented in memory.+data Stockholm = Stockholm [Ann FileAnnotation]+                           [Ann (ColumnAnnotation InFile)]+                           [StockholmSeq]+                 deriving (Show, Eq, Typeable)++instance NFData Stockholm where+    rnf (Stockholm file clmn seqs) = rnf file `seq` rnf clmn `seq` rnf seqs++-- | A sequence in Stockholm 1.0 format.+data StockholmSeq = StSeq !SeqLabel+                          !SeqData+                          [Ann SequenceAnnotation]+                          [Ann (ColumnAnnotation InSeq)]+                    deriving (Eq, Typeable)++-- We don't derive Show to be able support biocore-0.1, which+-- doesn't have Show instances for SeqLabel and SeqData.+instance Show StockholmSeq where+    showsPrec prec (StSeq (SeqLabel l) (SeqData d) sa ca) =+        showParen (prec > 10) $+          showString "StSeq (SeqLabel " .+          showsPrec 11 l .+          showString ") (SeqData " .+          showsPrec 11 d .+          (')':) . (' ':) .+          showsPrec 11 sa .+          (' ':) .+          showsPrec 11 ca+++instance NFData StockholmSeq where+    rnf (StSeq _ _ sa ca) = rnf sa `seq` rnf ca++instance BioSeq StockholmSeq where+    seqlabel  (StSeq sl _ _ _) = sl+    seqdata   (StSeq _ sd _ _) = sd+    seqlength (StSeq _ sd _ _) = Offset $ B.length (unSD sd)++-- | A generic annotation.+data Ann d = Ann { feature :: !d+                 , text    :: !ByteString+                 }+             deriving (Show, Eq, Ord, Typeable)++instance NFData (Ann d) where+    -- already strict, default instance++-- | Possible file annotations.+data FileAnnotation =+    AC -- ^ Accession number:    Accession number in form PFxxxxx.version or PBxxxxxx.+  | ID -- ^ Identification:      One word name for family.+  | DE -- ^ Definition:          Short description of family.+  | AU -- ^ Author:              Authors of the entry.+  | SE -- ^ Source of seed:      The source suggesting the seed members belong to one family.+  | GA -- ^ Gathering method:    Search threshold to build the full alignment.+  | TC -- ^ Trusted Cutoff:      Lowest sequence score and domain score of match in the full alignment.+  | NC -- ^ Noise Cutoff:        Highest sequence score and domain score of match not in full alignment.+  | TP -- ^ Type:                Type of family (presently Family, Domain, Motif or Repeat).+  | SQ -- ^ Sequence:            Number of sequences in alignment.+  | AM -- ^ Alignment Method:    The order ls and fs hits are aligned to the model to build the full align.++  | DC -- ^ Database Comment:    Comment about database reference.+  | DR -- ^ Database Reference:  Reference to external database.+  | RC -- ^ Reference Comment:   Comment about literature reference.+  | RN -- ^ Reference Number:    Reference Number.+  | RM -- ^ Reference Medline:   Eight digit medline UI number.+  | RT -- ^ Reference Title:     Reference Title.+  | RA -- ^ Reference Author:    Reference Author+  | RL -- ^ Reference Location:  Journal location.+  | PI -- ^ Previous identifier: Record of all previous ID lines.+  | KW -- ^ Keywords:            Keywords.+  | CC -- ^ Comment:             Comments.+  | NE -- ^ Pfam accession:      Indicates a nested domain.+  | NL -- ^ Location:            Location of nested domains - sequence ID, start and end of insert.++  | F_Other !ByteString -- ^ Other file annotation.+    deriving (Show, Eq, Ord, Typeable)++-- | Possible column annotations.  Phantom type can be 'InFile'+-- or 'InSeq'.+data ColumnAnnotation a =+    SS -- ^ Secondary structure.+  | SA -- ^ Surface accessibility.+  | TM -- ^ TransMembrane.+  | PP -- ^ Posterior probability.+  | LI -- ^ LIgand binding.+  | AS -- ^ Active site.+  | PAS -- ^ AS - Pfam predicted.+  | SAS -- ^ AS - from SwissProt.+  | IN -- ^ INtron (in or after).++  | C_Other !ByteString -- ^ Other column annotation.+    deriving (Show, Eq, Ord, Typeable)++-- | Phantom type for 'ColumnAnnotation's of the whole file.+data InFile+-- | Phantom type for 'ColumnAnnotation's of a single sequence.+data InSeq++-- | Possible sequence annotations.+data SequenceAnnotation =+    S_AC -- ^ Accession number+  | S_DE -- ^ Description+  | S_DR -- ^ Database reference+  | OS -- ^ Organism (species)+  | OC -- ^ Organism classification (clade, etc.)+  | LO -- ^ Look (Color, etc.)++  | S_Other !ByteString -- ^ Other sequence annotation.+    deriving (Show, Eq, Ord, Typeable)+++-- | Class used internally below just to simplify the code.+class IsAnnotation a where+    parseAnn :: ByteString -> a+    showAnn  :: a -> ByteString++mkParseAnn :: (ByteString -> a -> ByteString) -> [(ByteString, a)]+           -> (ByteString -> a) -> ByteString -> a+mkParseAnn modify anns mkOther =+    let annots = M.fromList anns+    in \feat -> let featMod = modify feat (error "mkParseAnn: never here")+                in fromMaybe (mkOther feat) $ M.lookup featMod annots++mkShowAnn :: Ord a => (ByteString -> a -> ByteString) -> [(ByteString, a)]+          -> (a -> Maybe ByteString) -> a -> ByteString+mkShowAnn modify anns fromOther =+    let annots = M.fromList [(a,b) | (b,a) <- anns]+    in \ann -> fromMaybe (error "mkShowAnn: never here 2") $+               fromOther ann `mplus` (mod' <$> M.lookup ann annots)+             where mod' = flip modify (error "mkShowAnn: never here 1")++instance IsAnnotation SequenceAnnotation where+    parseAnn = mkParseAnn const seqAnns S_Other+    showAnn  = mkShowAnn  const seqAnns f+        where f (S_Other o) = Just o+              f _           = Nothing++seqAnns :: [(ByteString, SequenceAnnotation)]+seqAnns = [("LO",LO), ("OC",OC), ("OS",OS),+           ("AC",S_AC), ("DE",S_DE), ("DR",S_DR)]+++instance IsAnnotation FileAnnotation where+    parseAnn = mkParseAnn const fileAnns F_Other+    showAnn  = mkShowAnn  const fileAnns f+        where f (F_Other o) = Just o+              f _           = Nothing++fileAnns :: [(ByteString, FileAnnotation)]+fileAnns = [("AC",AC), ("AM",AM), ("AU",AU), ("CC",CC),+            ("DC",DC), ("DE",DE), ("DR",DR), ("GA",GA),+            ("ID",ID), ("KW",KW), ("NC",NC), ("NE",NE),+            ("NL",NL), ("PI",PI), ("RA",RA), ("RC",RC),+            ("RL",RL), ("RM",RM), ("RN",RN), ("RT",RT),+            ("SE",SE), ("SQ",SQ), ("TC",TC), ("TP",TP)]+++instance ClmnAnnLoc a => IsAnnotation (ColumnAnnotation a) where+    parseAnn = mkParseAnn removeSuffix clmnAnns C_Other+        where+          removeSuffix feat phantom =+              let suffix = clmnAnnSuffix phantom+                  (f, s) = B.splitAt (B.length feat - B.length suffix) feat+              in if suffix == s then f else ""+    showAnn = mkShowAnn addSuffix clmnAnns f+        where+          f (C_Other o) = Just o+          f _           = Nothing+          addSuffix feat phantom = feat `B.append` clmnAnnSuffix phantom++clmnAnns :: [(ByteString, ColumnAnnotation a)]+clmnAnns = [("AS",AS), ("IN",IN), ("LI",LI), ("PAS",PAS), ("PP",PP),+            ("SA",SA), ("SAS",SAS), ("SS",SS), ("TM",TM)]++class ClmnAnnLoc a where+    clmnAnnSuffix :: b a -> ByteString+instance ClmnAnnLoc InSeq where+    clmnAnnSuffix _ = ""+instance ClmnAnnLoc InFile where+    clmnAnnSuffix _ = "_cons"++parseAnn' :: IsAnnotation a => ByteString -> ByteString -> Ann a+parseAnn' = Ann . parseAnn+++++#ifdef TEST+test_parseAnnots :: Specs+test_parseAnnots =+    describe "parse*" $ do+      it "1"  $ parseAnn "AC" @?= AC+      it "2"  $ parseAnn "SQ" @?= SQ+      it "3a" $ parseAnn "SS" @?= (SS :: ColumnAnnotation InSeq)+      it "3b" $ parseAnn "SS" @?= (C_Other "SS" :: ColumnAnnotation InFile)+      it "4a" $ parseAnn "SS_cons" @?= (SS :: ColumnAnnotation InFile)+      it "4b" $ parseAnn "SS_cons" @?= (C_Other "SS_cons" :: ColumnAnnotation InSeq)+      it "5a" $ parseAnn "SS_CONS" @?= (C_Other "SS_CONS" :: ColumnAnnotation InSeq)+      it "5b" $ parseAnn "SS_CONS" @?= (C_Other "SS_CONS" :: ColumnAnnotation InFile)+      it "6"  $ parseAnn "LO" @?= LO+#endif+++-- | Find an annotation.  For example, you may use @'findAnn' 'SS'@+-- to find the secondary of an Stockholm file.+findAnn :: Eq d => d -> [Ann d] -> Maybe ByteString+findAnn x = fmap text . find ((== x) . feature)+++-- | Exceptions that may happen while parsing a Stockholm file.+class StockholmExc e where+    -- | File is empty.+    emptyFileExc :: e++    -- | Header is missing.+    headerExc :: e++    -- | Malformed annotation.  The line is passed as argument.+    malformedAnnExc :: ByteString -> e++    -- | Unknown annotation type.+    unknownAnnTypeExc :: Char -> e++    -- | Malformed sequence line. The line is passed as argument.+    malformedSeqDataExc :: ByteString -> e++instance StockholmExc () where+    emptyFileExc          = ()+    headerExc             = ()+    malformedAnnExc     _ = ()+    unknownAnnTypeExc   _ = ()+    malformedSeqDataExc _ = ()++instance StockholmExc ByteString where+    emptyFileExc = "parseStockholm: empty file."+    headerExc = "parseStockholm: header is missing."+    malformedAnnExc line =+        B.concat ["parseStockholm: malformed annotation '", line, "'."]+    unknownAnnTypeExc typ =+        B.concat ["parseStockholm: unknown annotation type '", B.pack [typ], "'."]+    malformedSeqDataExc line =+        B.concat ["parseStockholm: malformed sequence data line '", line, "'."]+++++++++++++++++-- | Type (F, C, S or R), corresponding sequence, name and data.+type ParseAnnRet = ((Char, Maybe SeqLabel, ByteString), ByteString)++-- | @parseAnnotation line@ tries to parse a line as an annotation.+parseAnnotation :: (StockholmExc e) => ByteString -> Exceptional e ParseAnnRet+parseAnnotation line+    | not (B.isPrefixOf "#=G" line) || B.length line < 5 =+        throw (malformedAnnExc line)+parseAnnotation line =+  let Just (typ, rest) = B.uncons $ B.drop 3 line+      (word1, text1) = second dropSpace . B.break isSpace $ dropSpace rest+      (word2, text2) = second dropSpace . B.break isSpace $ text1+      global = ((typ, Nothing,               word1), text1)+      seqspe = ((typ, Just (SeqLabel word1), word2), text2)+  in case typ of+       'F' -> return global+       'C' -> return global+       'S' -> return seqspe+       'R' -> return seqspe+       _ -> throw (unknownAnnTypeExc typ)++dropSpace :: ByteString -> ByteString+dropSpace = B.dropWhile isSpace+++-- | @parseSeqData line@ tries to parse a line as some data of a sequence.+parseSeqData :: (StockholmExc e) => ByteString+             -> Exceptional e (SeqLabel, SeqData)+parseSeqData str = case B.words str of+                     [ident, sq] -> return (SeqLabel ident, SeqData sq)+                     _ -> throw (malformedSeqDataExc str)+++-- | @parseStockholm@ parses a file in Stockholm 1.0 format.+--+--   Each file must be completely read before it is used because+--   the Stockholm format allows information to be given in any+--   part of the file.  However, there may be multiple+--   \"Stockholm files\" concatenated in a single \"filesystem+--   file\".  These multiple files are read independently, which+--   is why we return a list of 'Exceptional'@s@.+--+--   If you prefer to read the whole file in one go, use+--   @'sequence' (parseStockholm input)@, which will fail if any+--   family fails.+parseStockholm :: (StockholmExc e) => ByteString+               -> [Exceptional e Stockholm]+parseStockholm = map parseStockholm' . split .+                 filter (not . B.all isSpace) . B.lines+    where+      split [] = []+      split xs = case break (B.isPrefixOf "//") xs of+                   (y, ys) -> y : split (tail ys)++parseStockholm' :: (StockholmExc e) => [ByteString]+                -> Exceptional e Stockholm+parseStockholm' = header . filter (not . B.null)+    where+      -- Find+      header (h:hs)+          | h == stockholm = do (annots,seqs) <- go initial hs+                                return (makeStockholm annots seqs)+          | otherwise      = throw headerExc+          where stockholm = "# STOCKHOLM 1.0"+                initial   = (M.empty, M.empty)+      header [] = throw emptyFileExc++      -- End of file+      go acc [] = return acc++      -- Annotation+      go (annots,seqs) (line:ls) | B.take 2 line == "#=" = do+        annot <- parseAnnotation line+        go (insertDM annot annots, seqs) ls++      -- Comment+      go (annots,seqs) (l:ls) | B.head l == '#' =+        go (annots,seqs) ls++      -- Otherwise a sequence+      go (annots,seqs) (line:ls) = do+        seqData <- parseSeqData line+        go (annots, insertDM seqData seqs) ls+++type DiffMap a b = M.Map a ([b] -> [b])++insertDM :: Ord a => (a,b) -> DiffMap a b -> DiffMap a b+insertDM (key,val) = M.insertWith (flip (.)) key (val:)++finishDM :: (b -> ByteString) -> DiffMap a b -> M.Map a ByteString+finishDM f = fmap (B.concat . map f . ($ []))++-- | Glue everything in place, as the Stockholm format lets+--   everything be everywhere and split in any number of parts.+makeStockholm :: DiffMap (Char, Maybe SeqLabel, ByteString) ByteString+              -> DiffMap SeqLabel SeqData -> Stockholm+makeStockholm annotsDM seqsDM =+    let annots = finishDM id   annotsDM+        seqs   = finishDM unSD seqsDM++        -- Separate the global from the sequence annotations+        go ('F', Nothing, feat) txt (f,c,r) = (parseAnn' feat txt:f,c,r)+        go ('C', Nothing, feat) txt (f,c,r) = (f,parseAnn' feat txt:c,r)+        go (typ, Just sq, feat) txt (f,c,r) = (f,c,(typ,sq,feat,txt):r)+        go _ _ _ = error "makeStockholm: not here, ever"++        -- Separate the sequence annotations to each annotation+        add ('S',sq,feat,txt) = flip M.adjust sq $ \(StSeq sl sd sa ca) ->+                                  StSeq sl sd (parseAnn' feat txt : sa) ca+        add ('R',sq,feat,txt) = flip M.adjust sq $ \(StSeq sl sd sa ca) ->+                                  StSeq sl sd sa (parseAnn' feat txt : ca)+        add _ = error "makeStockholm: not here either"++        -- Glue everything+        (file, clmn, rest) = M.foldWithKey go ([],[],[]) annots+        plainseqs = M.mapWithKey (\k s -> StSeq k (SeqData s) [] []) seqs+    in Stockholm file clmn (M.elems $ foldl' (flip add) plainseqs rest)+++++-- | Pretty-prints an Stockholm file.  We follow Rfam preferences+-- and do not wrap lines.+prettyPrintStockholm :: Stockholm -> B.ByteString+prettyPrintStockholm (Stockholm file clmn seqs) =+    let showAnnF :: IsAnnotation a => Char -> Ann a -> (ByteString, ByteString)+        showAnnF t ann = (B.concat [B.pack ("#=G" ++ t : " "),+                                    showAnn (feature ann)], text ann)+        showAnnS :: IsAnnotation a => ByteString -> Char -> Ann a -> (ByteString, ByteString)+        showAnnS s t ann = (B.unwords [B.pack ("#=G" ++ [t]), s,+                                       showAnn (feature ann)], text ann)++        fileLines = map (showAnnF 'F') file+        clmnLines = map (showAnnF 'C') clmn+        sequences = do+          StSeq (SeqLabel name) (SeqData seqd) sa ca <- seqs+          (name, seqd) : map (showAnnS name 'R') ca+                      ++ map (showAnnS name 'S') sa++        allLines    = fileLines ++ sequences ++ clmnLines+        firstColLen = maximum $ map (B.length . fst) allLines+        mkLine (col1, col2) = B.concat [col1, B.replicate n ' ', col2]+            where n = 1 + firstColLen - B.length col1+    in B.unlines ("# STOCKHOLM 1.0" : map mkLine allLines ++ ["//"])++++#ifdef TEST+stockFile :: B.ByteString+stockFile = B.unlines [+  "# STOCKHOLM 1.0",+  "#=GF AU Infernal 1.0",+  "",+  "#=GS Purine1 DE Number 1 :)",+  "Purine1      AAAAUUGAAUAUCGUUUUACUUGUUUAUGUC-GUGAAU-UGGCAC-GACG",+  "Purine2      AAAAUUUAAUAA-GAAGCACUCAUAUAAUCCCGAGAAUAUGGCUCGGGAG",+  "Purine3      UGGCAGUAACUAGCGUCACUUCGUAUAACCCCAGUGAUAUGGAUUGGGGG",+  "#=GC SS_cons :::::::::::::::::((((((((,,,<<<-<<<_______>>>->>>,",+  "",+  "# We may have comments =)",+  "",+  "Purine1      UUUCUACAAGGUG-CCGGAA--CACCUAACAAUAAGUAAGUCAGCAGUGA",+  "Purine2      UCUCUACCGAACAACCGUAAAUUGUUCGACUAUGAGUGAAAGUGUACCUA",+  "Purine3      UCUCUACCAGGAACCAAUAA--AUCCUGAUUACGAAGAGUUUAGUGCUUU",+  "#=GC SS_cons ,,,,,,,<<<<<<_________>>>>>>,,))))))))::::::::::::",+  "",+  "Purine1      GAU",+  "Purine2      GGG",+  "Purine3      AGU",+  "#=GC SS_cons :::",+  "// "]++purine1, purine2, purine3 :: SeqData+ss_cons :: ByteString+purine1 = SeqData "AAAAUUGAAUAUCGUUUUACUUGUUUAUGUC-GUGAAU-UGGCAC-GACGUUUCUACAAGGUG-CCGGAA--CACCUAACAAUAAGUAAGUCAGCAGUGAGAU"+purine2 = SeqData "AAAAUUUAAUAA-GAAGCACUCAUAUAAUCCCGAGAAUAUGGCUCGGGAGUCUCUACCGAACAACCGUAAAUUGUUCGACUAUGAGUGAAAGUGUACCUAGGG"+purine3 = SeqData "UGGCAGUAACUAGCGUCACUUCGUAUAACCCCAGUGAUAUGGAUUGGGGGUCUCUACCAGGAACCAAUAA--AUCCUGAUUACGAAGAGUUUAGUGCUUUAGU"+ss_cons =         ":::::::::::::::::((((((((,,,<<<-<<<_______>>>->>>,,,,,,,,<<<<<<_________>>>>>>,,)))))))):::::::::::::::"++result :: [Stockholm]+result = [Stockholm file clmn seqs]+    where+      file = [Ann AU "Infernal 1.0"]+      clmn = [Ann SS ss_cons]+      seqs = [mkStock "Purine1" purine1 [Ann S_DE "Number 1 :)"],+              mkStock "Purine2" purine2 [],+              mkStock "Purine3" purine3 []]+      mkStock name data_ sa = StSeq name data_ sa []++stockFile2 :: B.ByteString+stockFile2 = B.unlines [stockFile, stockFile]++result2 :: [Stockholm]+result2 = result ++ result++returnExc :: a -> Exceptional B.ByteString a+returnExc = return++test_parseStockholm :: Specs+test_parseStockholm =+    describe "parseStockholm" $ do+      it "correctly parses test file 1" $ parseStockholm stockFile  @?= returnExc result+      it "correctly parses test file 2" $ parseStockholm stockFile2 @?= returnExc result2++test_prettyPrintStockholm :: Specs+test_prettyPrintStockholm =+    describe "parseStockholm/prettyPrintStockholm" $ do+      it "parses printed test file 1" $ parseStockholm (func result)  @?= returnExc result+      it "parses printed test file 2" $ parseStockholm (func result2) @?= returnExc result2+    where func = B.unlines . map prettyPrintStockholm+#endif+++#ifdef TEST+test_Stockholm :: Specs+test_Stockholm = describe "Bio.Sequence.Stockholm" $ do+                   test_parseAnnots+                   test_parseStockholm+                   test_prettyPrintStockholm+#endif