packages feed

BiobaseInfernal-0.8.1.0: Biobase/SElab/Taxonomy/Import.hs

-- | Import an Rfam @taxonomy.txt@ file. Provides a simple "fromFile"
-- function that produces both maps in one pass. @fromFile@ will check the
-- file suffix and on @.gz@ suffixes decompress the input on-the-fly.

module Biobase.SElab.Taxonomy.Import where

import           Codec.Compression.GZip (decompress)
import           Control.Applicative ((<|>))
import           Control.Arrow (second)
import           Control.Monad
import           Data.Attoparsec.Text.Lazy as AT
import           Data.Char (isDigit)
import           Data.HashMap.Strict (HashMap)
import           Data.List (foldl')
import           Data.Text.Lazy.Encoding (decodeUtf8)
import           Data.Text.Lazy.IO as TL
import           Data.Text (Text)
import           Data.Vector (fromList)
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import           System.FilePath (takeExtension)
import           Data.String.Conversions.Monomorphic

import Biobase.Types.Accession (Accession(..),Species)
import Biobase.Types.Names
import Biobase.Types.Taxonomy



-- | Parse a single Taxon line.
--
-- TODO there are unknown words at the end of each line. make those known

parseTaxon :: Parser Taxon
parseTaxon = do
  accession <- Accession <$> takeWhile1 isDigit <?> "accession"
  skipSpace <?> "1st space"
  species <- speciesName <$> takeWhile1 (/='\t') <?> "species"
  skipSpace <?> "2nd space"
  classification <- (fromList . map (,Unknown) . map fromST) <$> takeWhile1 (\z -> z/=';' && z/='.') `sepBy` "; " <?> "classification"
  unknowns <- manyTill anyChar (endOfInput <|> endOfLine)
  return $ Taxon {..}

-- | Taxonomy according to @Infernal@ stored in two hashmaps. The first
-- from @Accession@ to @Taxon@, the second from species name to @Taxon@.

type Taxonomy = ( HashMap (Accession Species) Taxon   -- ^ find @Taxon@ via accession number
                , HashMap SpeciesName         Taxon   -- ^ find @Taxon@ via species name
                )

-- | Parses the taxonomy.txt file.

parseTaxonomy :: Parser Taxonomy
parseTaxonomy = foldl' go (HM.empty , HM.empty) <$> manyTill parseTaxon endOfInput
  where go (!a, !s) x = (HM.insert (accession x) x a, HM.insert (species x) x s)

-- | Read @taxonomy.txt.gz@ / @taxonomy.txt@ file into structure.

fromFile :: FilePath -> IO Taxonomy
fromFile f = case takeExtension f of
  ".gz" -> (go . decodeUtf8 . decompress) <$> BL.readFile f
  _     -> go <$> TL.readFile f
  where go txt = case AT.parse parseTaxonomy txt of
          Done ""   r       -> r
          Done ncon r       -> error $ "unconsumed input " ++ f ++ ": " ++ (TL.unpack $ TL.take 1000 ncon)
          Fail ncon ctx err -> error $ "error parsing " ++ f ++ ": " ++ show (ctx,err)