packages feed

cobot-io-0.1.4.2: src/Bio/GB/Writer.hs

module Bio.GB.Writer
  ( genBankToText
  ) where

import           Bio.GB.Type     (Feature (..), GenBankSequence (..), Locus (..), Meta (..),
                                  Reference (..), Source (..), Version (..))
import           Bio.Sequence    (Border (..), Range (..), RangeBorder (..), markings, shiftRange,
                                  toList)
import           Control.Lens    ((^.))
import qualified Data.List.Split as S (chunksOf)
import           Data.Maybe      (fromMaybe)
import           Data.Text       (Text)
import qualified Data.Text       as T (append, chunksOf, intercalate, length, lines, null, pack,
                                       toLower, unwords)

genBankToText :: GenBankSequence -> Text
genBankToText GenBankSequence{..} = interNewLine parts <> "\n"
  where
    parts = [ metaToText meta
            , featuresToText $ gbSeq ^. markings
            , originToText $ T.pack $ toList gbSeq
            , "//"
            ]

--------------------------------------------------------------------------------
-- Block with meta-information.
--------------------------------------------------------------------------------

metaToText :: Meta -> Text
metaToText Meta{..} = interNewLine parts
  where
    parts = [ locusToText locus
            , textFromMaybe $ fmap (processMany metaIndent "DEFINITION") definition
            , textFromMaybe $ fmap (processMany metaIndent "ACCESSION") accession
            , textFromMaybe $ fmap versionToText version
            , textFromMaybe $ fmap (processMany metaIndent "KEYWORDS") keywords
            , textFromMaybe $ fmap sourceToText source
            ]
            <> fmap referenceToText references
            <> fmap (processMany metaIndent "COMMENT") comments

locusToText :: Locus -> Text
locusToText Locus{..} = toIndent metaIndent "LOCUS" <> locusText
  where
    locusList = [ name
                , showText len <> " bp"
                , molType
                , textFromMaybe $ fmap (T.toLower . showText) form
                , textFromMaybe gbDivision
                , modificationDate
                ]

    locusText = T.intercalate (spaces 5) $ filter (not . T.null) locusList

sourceToText :: Source -> Text
sourceToText Source{..} = interNewLine $ mainPart : pure (textFromMaybe organismPart)
  where
    mainPart     = processMany metaIndent "SOURCE" sourceT
    organismPart = fmap (processMany metaIndent (prependIndent metaPreIndent "ORGANISM")) organism

versionToText :: Version -> Text
versionToText Version{..} = toIndent metaIndent "VERSION" <> version
   where
     version = versionT <> spaces 5 <> maybe mempty ("GI:" <>) gbId

referenceToText :: Reference -> Text
referenceToText Reference{..} = interNewLine $ mainPart : parts
  where
    mainPart = processMany metaIndent "REFERENCE" referenceT

    sectionNames = fmap (prependIndent metaPreIndent) ["AUTHORS", "TITLE", "JOURNAL", "PUBMED"]
    sections     = [authors, title, journal, pubmed]

    parts = zipWith (\a -> textFromMaybe . fmap (processMany metaIndent a)) sectionNames sections

-- | Indentation of data in section with metainformation.
--
metaIndent :: Int
metaIndent = 12

-- | Indentation in subsections of section with meta-information.
--
metaPreIndent :: Int
metaPreIndent = 2

featuresToText :: [(Feature, Range)] -> Text
featuresToText l = interNewLine $ mainPart : sections
  where
    mainPart = processMany featuresIndent "FEATURES" featuresText
    sections = fmap featureToText l

    featuresText :: Text
    featuresText = "Location/Qualifiers"

--------------------------------------------------------------------------------
-- Block with FEATURES section.
--------------------------------------------------------------------------------

featureToText :: (Feature, Range) -> Text
featureToText (Feature{..}, range) = interNewLine $ mainPart : sections
  where
    mainPart = processMany featuresIndent (prependIndent featuresPreIndent fName) (featureRangeToText $ shiftRange 1 range)
    sections = fmap featurePropToText fProps

featurePropToText :: (Text, Text) -> Text
featurePropToText (nameF, textF) = mainPart
  where
    mainPart = processMany featuresIndent mempty ("/" <> nameF <> "=\"" <> textF <> "\"")

featureRangeToText :: Range -> Text
featureRangeToText (Point pos) = showText pos 
featureRangeToText (Span (RangeBorder rbLo lo) (RangeBorder rbHi hi)) = borderToText True rbLo <> showText lo <> ".." <> borderToText False rbHi <> showText hi 
  where
    borderToText :: Bool -> Border -> Text
    borderToText _ Precise      = ""
    borderToText True Exceeded  = "<"
    borderToText False Exceeded = ">"
featureRangeToText (Between lo hi) = showText lo <> "^" <> showText hi
featureRangeToText (Join ranges) = "join(" <> T.intercalate "," (featureRangeToText <$> ranges) <> ")"
featureRangeToText (Complement range) = "complement(" <> featureRangeToText range <> ")"

-- | Indentation of feature's properties in FEATURES section.
--
featuresIndent :: Int
featuresIndent = 21

-- | Indentation in subsections of FEATURES section.
--
featuresPreIndent :: Int
featuresPreIndent = 5

--------------------------------------------------------------------------------
-- Block with ORIGIN table.
--------------------------------------------------------------------------------

originToText :: Text -> Text
originToText text = interNewLine $ mainPart : parts
  where
    mainPart = "ORIGIN"

    manyLines = S.chunksOf lengthOfLineChunk $ T.chunksOf lengthOfChunk text
    parts     = zipWith processLine [1, 1 + lengthOfChunk * lengthOfLineChunk..] manyLines

    processLine :: Int -> [Text] -> Text
    processLine startInd = T.unwords . (prependIndent (originIndent - T.length indText) indText :)
      where
        indText = showText startInd

    -- | Number of nucleotides in one chunk.
    --
    lengthOfChunk :: Int
    lengthOfChunk = 10

    -- | Number of chunks per line of sequence in ORIGIN section.
    --
    lengthOfLineChunk :: Int
    lengthOfLineChunk = 6

-- | Indentation of new line of sequence in ORIGIN section.
--
originIndent :: Int
originIndent = 9

--------------------------------------------------------------------------------
-- Utility functions.
--------------------------------------------------------------------------------

processMany :: Int -> Text -> Text -> Text
processMany indent name ""   = toIndent indent name
processMany indent name text = interNewLine resLines
  where
    (x : xs) = T.lines text

    resLines = toIndent indent name <> x
             : fmap (prependIndent indent) xs

interNewLine :: [Text] -> Text
interNewLine = T.intercalate "\n" . filter (not . T.null)

textFromMaybe :: Maybe Text -> Text
textFromMaybe = fromMaybe mempty

toIndent :: Int -> Text -> Text
toIndent indent name = name <> (spaces $ indent - (T.length name))

prependIndent :: Int -> Text -> Text
prependIndent = T.append . spaces

showText :: Show a => a -> Text
showText = T.pack . show

spaces :: Int -> Text
spaces = T.pack . flip replicate ' '