packages feed

cobot-io 0.1.2.3 → 0.1.2.4

raw patch · 7 files changed

+691/−7 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Bio.PDB.Type: MTRIXn :: FieldType
- Bio.PDB.Type: ORIGXn :: FieldType
- Bio.PDB.Type: SCALEn :: FieldType
- Bio.PDB.Type: SPLT :: FieldType
+ Bio.PDB.Parser: instance GHC.Show.Show Bio.PDB.Parser.CoordLike
+ Bio.PDB.Parser: instance GHC.Show.Show Bio.PDB.Parser.PdbData
+ Bio.PDB.Parser: pdbP :: Parser PDB
+ Bio.PDB.Reader: fromFilePDB :: MonadIO m => FilePath -> m (Either Text ([PDBWarnings], Either Text PDB))
+ Bio.PDB.Reader: fromTextPDB :: Text -> Either Text ([PDBWarnings], Either Text PDB)
+ Bio.PDB.Reader: instance GHC.Classes.Eq Bio.PDB.Reader.PDBWarnings
+ Bio.PDB.Reader: instance GHC.Show.Show Bio.PDB.Reader.PDBWarnings
+ Bio.PDB.Type: MTRIX1 :: FieldType
+ Bio.PDB.Type: MTRIX2 :: FieldType
+ Bio.PDB.Type: MTRIX3 :: FieldType
+ Bio.PDB.Type: ORIGX1 :: FieldType
+ Bio.PDB.Type: ORIGX2 :: FieldType
+ Bio.PDB.Type: ORIGX3 :: FieldType
+ Bio.PDB.Type: SCALE1 :: FieldType
+ Bio.PDB.Type: SCALE2 :: FieldType
+ Bio.PDB.Type: SCALE3 :: FieldType
+ Bio.PDB.Type: SPLIT :: FieldType
+ Bio.PDB.Type: instance GHC.Classes.Ord Bio.PDB.Type.FieldType

Files

ChangeLog.md view
@@ -2,6 +2,11 @@  ## [Unreleased] +## [0.1.2.4] - 2019-12-23+### Added+- Preprocessing for pdb-files.+- Pdb parser.+ ## [0.1.2.3] - 2019-12-12 ### Fixed - Fixes for .mae pasrser.
cobot-io.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 77c33361ec08da24af8711ddff5d4e8185bfe4b1fd097bb10c985f15857655f4+-- hash: 1ecc108258e91fab1cf40b3e7a3dbb217465d2d0d4b32b76560469f2cf1ca543  name:           cobot-io-version:        0.1.2.3+version:        0.1.2.4 synopsis:       Biological data file formats and IO description:    Please see the README on GitHub at <https://github.com/less-wrong/cobot-io#readme> category:       Bio@@ -50,6 +50,8 @@       Bio.MMTF.MessagePack       Bio.MMTF.Type       Bio.PDB+      Bio.PDB.Parser+      Bio.PDB.Reader       Bio.PDB.Type       Bio.Sequence       Bio.Sequence.Basecalled@@ -100,6 +102,7 @@       MAEParserSpec       MAESpec       MMTFSpec+      PDBSpec       SequenceSpec       UniprotSpec       Paths_cobot_io
+ src/Bio/PDB/Parser.hs view
@@ -0,0 +1,157 @@+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Bio.PDB.Parser+  ( pdbP )+where+++import           Bio.PDB.Type         (Atom (..), Chain, FieldType, Model,+                                       PDB (..), RemarkCode)+import           Control.Applicative  (many, some, (<|>))+import           Control.DeepSeq      ()+import           Data.Attoparsec.Text (Parser, choice, count, endOfInput,+                                       endOfLine, isEndOfLine, satisfy,+                                       skipWhile, space, string, takeWhile)+import qualified Data.List            as L (groupBy)+import           Data.Map.Strict      (Map, fromListWithKey)+import           Data.Maybe           (catMaybes)+import           Data.Monoid          ((<>))+import           Data.Text            as T (Text, concat, pack, stripEnd)+import qualified Data.Vector          as V (Vector, concat, fromList, singleton)+import           GHC.Generics         ()+import           Text.Read            (readMaybe)++pdbP :: Parser PDB+pdbP = do+    pdbData <- many (choice [titleP, remarkStringP, manyModelsP, otherFieldP]) -- parser order is important+    let+        models         = V.concat $ catMaybes (getModels <$> pdbData)+        otherFieldsMap = fromRevListWith (<>) $ catMaybes (getOtherField <$> pdbData)+        title          = T.concat $ catMaybes (getTitle <$> pdbData)+        remarks        = fromRevListWith (<>) $ catMaybes (getRemarks <$> pdbData)++    return $ PDB title models remarks otherFieldsMap+  where+    getModels :: PdbData -> Maybe (V.Vector Model)+    getModels item = case item of+      ModelData x -> Just x+      _           -> Nothing+    getOtherField :: PdbData -> Maybe (FieldType, V.Vector Text)+    getOtherField item = case item of+      OtherFieldData (Just x, y) -> Just (x, V.singleton y)+      _                          -> Nothing+    getTitle :: PdbData -> Maybe Text+    getTitle item = case item of+      TitleData x -> Just x+      _           -> Nothing+    getRemarks :: PdbData -> Maybe (RemarkCode, V.Vector Text)+    getRemarks item = case item of+      RemarkData (x, y) -> Just (x, V.singleton y)+      _                 -> Nothing++data PdbData = ModelData (V.Vector Model)+             | OtherFieldData (Maybe FieldType, Text)+             | RemarkData (RemarkCode, Text)+             | TitleData Text+  deriving (Show)++notEndLineChar :: Parser Char+notEndLineChar = satisfy $ not . isEndOfLine++takeText :: Parser Text+takeText = Data.Attoparsec.Text.takeWhile $ not . isEndOfLine++atomP :: Parser CoordLike+atomP = let atom = Atom <$>+                    (string "ATOM " *>                                       -- (1 -  6) # we increased atomSerial field for one symbol+                    (read <$> count 6 notEndLineChar) <* space)              -- (7 - 11)  atomSerial+                    <*> (T.pack <$> count 4 notEndLineChar)                  -- (13 - 16) atomName+                    <*> notEndLineChar                                       -- (17)      atomAltLoc+                    <*> (T.pack <$> count 3 notEndLineChar) <* space         -- (18 - 20) atomResName+                    <*> notEndLineChar                                       -- (22)      atomChainID+                    <*> (read <$> count 4 notEndLineChar)                    -- (23 - 26) atomResSeq+                    <*> notEndLineChar <* count 3 space                      -- (27)      atomICode+                    <*> (read <$> count 8 notEndLineChar)                    -- (31 - 38) atomX+                    <*> (read <$> count 8 notEndLineChar)                    -- (39 - 46) atomY+                    <*> (read <$> count 8 notEndLineChar)                    -- (47 - 54) atomZ+                    <*> (read <$> count 6 notEndLineChar)                    -- (55 - 60) atomOccupancy+                    <*> (read <$> count 6 notEndLineChar) <* count 10 space  -- (61 - 66) atomTempFactor+                    <*> (T.pack <$> count 2 notEndLineChar)                  -- (77 - 78) atomElement+                    <*> (T.pack <$> count 2 notEndLineChar)                  -- (79 - 80) atomCharge+                    <* (endOfLine <|> endOfInput)+        in AtomLine <$> atom++coordNotAtomP :: Parser CoordLike+coordNotAtomP = do+    _ <- string "HETATM" <|> string "TER " <|> string "ANISOU" <|> string "CONECT"+    skipWhile $ not . isEndOfLine+    endOfLine+    return CoordNotAtomLine++data CoordLike = AtomLine Atom | CoordNotAtomLine+  deriving (Show)++coordLikeP :: Parser [CoordLike]+coordLikeP = some (coordNotAtomP <|> atomP)++chainsP :: Parser (V.Vector Chain)+chainsP = do+    coordLikeLines <- coordLikeP+    let atoms  = catMaybes (getAtom <$> coordLikeLines)+        chains = V.fromList (map V.fromList $ groupByChains atoms)+    pure chains+  where+    getAtom :: CoordLike -> Maybe Atom+    getAtom line = case line of+      AtomLine x -> Just x+      _          -> Nothing+    groupByChains :: [Atom]-> [[Atom]]+    groupByChains = L.groupBy (\x y ->  atomChainID x == atomChainID y)++modelP :: Parser Model+modelP = do+    _ <- string "MODEL"+    skipWhile $ not . isEndOfLine+    endOfLine+    chains <- chainsP+    string "ENDMDL" >> skipWhile (not . isEndOfLine)+    endOfLine <|> endOfInput+    pure chains++manyModelsP :: Parser PdbData+manyModelsP = do+    models <- (:[]) <$> chainsP <|> some modelP+    return $ ModelData (V.fromList models)++titleStringP :: Parser Text+titleStringP = do+    _ <- string "TITLE "+    titleText <- takeText+    endOfLine+    pure $ T.stripEnd titleText++titleP :: Parser PdbData+titleP =  do+    titleText <- T.concat <$> some titleStringP+    return $ TitleData titleText++remarkStringP :: Parser PdbData+remarkStringP = do+    _ <- string "REMARK"+    _ <- space+    (remarkCode :: RemarkCode) <- readMaybe <$> count 3 notEndLineChar+    _ <- notEndLineChar+    remarkText <- takeText+    endOfLine+    pure $ RemarkData (remarkCode, T.stripEnd remarkText)++fromRevListWith :: Ord k => (a -> a -> a) -> [(k,a)] -> Map k a+fromRevListWith f xs = fromListWithKey (\_ x y -> f y x) xs++otherFieldP :: Parser PdbData+otherFieldP = do+    (fieldType :: Maybe FieldType) <- readMaybe <$> count 6 notEndLineChar+    fieldTypeText <- takeText+    endOfLine <|> endOfInput+    return $ OtherFieldData (fieldType,  T.stripEnd fieldTypeText)
+ src/Bio/PDB/Reader.hs view
@@ -0,0 +1,73 @@+module Bio.PDB.Reader+  ( fromTextPDB+  , fromFilePDB+  ) where++import           Bio.PDB.Parser         (pdbP)+import           Bio.PDB.Type           (PDB (..))+import           Control.Monad.IO.Class (MonadIO, liftIO)+import           Data.Attoparsec.Text   (parseOnly)+import           Data.Bifunctor         (first)+import           Data.List              as L (findIndices, length)+import           Data.Maybe             (catMaybes)+import           Data.Text              as T (Text, length, lines, pack,+                                              replicate, take, unlines)+import qualified Data.Text.IO           as TIO (readFile)+++type LineNumber = Int++data PDBWarnings = LineTooLong LineNumber+                 | LineTooShort LineNumber+  deriving (Show, Eq)++standardizeText :: Text -> ([PDBWarnings], Text)+standardizeText text = (textWarnings, T.unlines standardizedLines)+  where+    textLines = T.lines text+    desiredLength = 80  -- cause it is max length in standart pdb file++    warnings'n'text = map standardizeLine $ zip [0..] textLines+    textWarnings = catMaybes (fst <$> warnings'n'text)+    standardizedLines = snd <$> warnings'n'text++    standardizeLine :: (Int, Text) -> (Maybe PDBWarnings, Text)+    standardizeLine (lineNumber,line) | lineLength < desiredLength = (Just (LineTooShort lineNumber), line <> T.replicate spacesCount " ")+                                      | lineLength > desiredLength = (Just (LineTooLong lineNumber), T.take desiredLength line)+                                      | otherwise = (Nothing, line)+      where+        lineLength = T.length line+        spacesCount = desiredLength - lineLength+++isMdlLine :: Text -> Bool+isMdlLine line = elem (T.take 6 line) modelStrings || elem (T.take 5 line) modelStrings+  where+    modelStrings = ["MODEL ", "ENDMDL", "ATOM ", "TER   ", "HETATM", "ANISOU", "CONECT"]++checkRow :: [Int] -> Bool+checkRow [] = True+checkRow xs = last xs - head xs + 1 == L.length xs++checkMdlLines :: ([PDBWarnings], Text) -> Bool+checkMdlLines warnings'n'text = checkRow mdlLineNumbers+  where+    mdlLineNumbers = findIndices isMdlLine $ T.lines (snd warnings'n'text)++preprocess :: Text -> Either Text ([PDBWarnings], Text)+preprocess text = do+  let standardizedText = standardizeText text+  if checkMdlLines standardizedText+  then Right standardizedText+  else Left "There are trash strings between model strings"++fromFilePDB :: MonadIO m => FilePath -> m (Either Text ([PDBWarnings], Either Text PDB))+fromFilePDB f = do+  content <- liftIO (TIO.readFile f)+  let preprocessed = preprocess content+  pure $ fmap (first pack . parseOnly pdbP) <$> preprocessed++fromTextPDB :: Text -> Either Text ([PDBWarnings], Either Text PDB)+fromTextPDB text = fmap (first pack . parseOnly pdbP) <$> preprocessed+  where+    preprocessed = preprocess text
src/Bio/PDB/Type.hs view
@@ -24,7 +24,7 @@    -- Title Section (except TITLE and REMARKS)      HEADER    | OBSLTE-   | SPLT+   | SPLIT    | CAVEAT    | COMPND    | SOURCE@@ -59,12 +59,18 @@    | SITE    -- Crystallographic and Coordinate Transformation Section    | CRYST1-   | MTRIXn-   | ORIGXn-   | SCALEn+   | MTRIX1+   | MTRIX2+   | MTRIX3+   | ORIGX1+   | ORIGX2+   | ORIGX3+   | SCALE1+   | SCALE2+   | SCALE3    -- Bookkeeping Section    | MASTER-  deriving (Show, Eq, Read, Generic, NFData)+  deriving (Show, Eq, Read, Generic, NFData, Ord)  type Model = Vector Chain 
+ test/PDBSpec.hs view
@@ -0,0 +1,428 @@+{-# LANGUAGE OverloadedStrings #-}++module PDBSpec where++import           Bio.PDB.Reader  (fromTextPDB)+import           Bio.PDB.Type    (Atom (..), FieldType (..), PDB (..))+import qualified Data.Map.Strict (empty, fromList, singleton)+import           Data.Text       as T (Text, intercalate, length, lines, pack,+                                       replicate, take)+import qualified Data.Vector     as V (empty, fromList, singleton)+import           Test.Hspec+++oneModelSpecP :: Spec+oneModelSpecP = describe "One model." $+        it "correctly parses pdb with only one model without strings \"MODEL\" & \"ENDMDL\"" $ do+        let mt  = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" +++                                         "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" +++                                         "COMPND compnd\n" +++                                         "SOURCE source\n" +++                                         "KEYWDS keywds\n" +++                                         "AUTHOR  Masha\n" +++                                         "REVDAT revdat\n" +++                                         "REMARK   1 REFERENCE 1\n" +++                                         "SEQRES seqres\n" +++                                         "CRYST1 cryst1\n" +++                                         "ORIGX1 origx1 n=1\n" +++                                         "SCALE2 sclaen n=2\n" +++                                         "ATOM   2032  OXT CYS A 214      -4.546 -29.673  26.796  1.0 143.51           O  \n" +++                                         "ATOM   2033  H   CYS A 214      -6.124 -27.225  26.558  1.00 15.00           H  \n" +++                                         "TER    2034      CYS A 214                                                      \n" +++                                         "ATOM   2035  N   GLU B   1      18.637 -61.583  66.852  1.0 118.48           N  \n" +++                                         "TER   12534      ARG D 474                                                      \n" +++                                         "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" +++                                         "HETATM12538  C4  NAG B 475       6.943 -19.507  -9.597  1.00 25.87           C  \n" +++                                         "ATOM   2036  CA  GLU B   1      19.722 -62.606  66.868  1.00 19.77           C  \n" +++                                         "CONECT conect\n" +++                                         "CONECT conect conect\n" +++                                         "MASTER 1 2 3 4 5 6 7 8\n" +++                                         "END"+                                       )+        mt `shouldBe` Right ([], Right oneModelPDB)++oneModelPDB :: PDB+oneModelPDB =  PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"+                   , remarks = Data.Map.Strict.singleton (Just 1) (V.singleton "REFERENCE 1")+                   , models = V.singleton $ V.fromList [V.fromList [Atom {atomSerial = 2032, atomName = " OXT", atomAltLoc = ' ', atomResName = "CYS", atomChainID = 'A', atomResSeq = 214, atomICode = ' ',+                                                                           atomX = -4.546, atomY = -29.673, atomZ = 26.796, atomOccupancy = 1.0, atomTempFactor = 143.51, atomElement = " O",+                                                                           atomCharge = "  "},+                                                                           Atom {atomSerial = 2033, atomName = " H  ", atomAltLoc = ' ', atomResName = "CYS", atomChainID = 'A',+                                                                           atomResSeq = 214, atomICode = ' ', atomX = -6.124, atomY = -27.225, atomZ = 26.558, atomOccupancy = 1.0, atomTempFactor = 15.0,+                                                                           atomElement = " H", atomCharge = "  "}],+                                                                           V.fromList [Atom {atomSerial = 2035, atomName = " N  ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = ' ',+                                                                           atomX = 18.637, atomY = -61.583, atomZ = 66.852, atomOccupancy = 1.0, atomTempFactor = 118.48, atomElement = " N", atomCharge = "  "},+                                                                           Atom {atomSerial = 2036, atomName = " CA ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = ' ',+                                                                           atomX = 19.722, atomY = -62.606, atomZ = 66.868, atomOccupancy = 1.0, atomTempFactor = 19.77, atomElement = " C", atomCharge = "  "}]+                                                                        ]+                  , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source"]),(KEYWDS,V.fromList[" keywds"]),(AUTHOR,V.fromList["  Masha"]),(REVDAT,V.fromList[" revdat"]),(SEQRES,V.fromList [" seqres"]),+                                                            (CRYST1,V.fromList [" cryst1"]),(ORIGX1,V.fromList[" origx1 n=1"]),(SCALE2,V.fromList [" sclaen n=2"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]+                   }++manyModelsSpecP :: Spec+manyModelsSpecP = describe "Some models." $+        it "correctly parses pdb with many models - they have strings \"MODEL\" & \"ENDMDL\" and text has disordered strings" $ do+        let mt = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" +++                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" +++                                        "COMPND compnd\n" +++                                        "SOURCE source\n" +++                                        "REMARK 1   REFERENCE 1\n" +++                                        "REMARK 2   Reference 2_1/1\n" +++                                        "CRYST1 cryst1\n" +++                                        "ORIGX1 origx1 n=1\n" +++                                        "SCALE2 sclaen n=2\n" +++                                        "MODEL  4  \n" +++                                        "ATOM   2033  H   CYS A 214      -6.124 -27.225  26.558  1.00 15.00           H  \n" +++                                        "TER    2034      CYS A 214                                                      \n" +++                                        "ANISOU anisou\n" +++                                        "ENDMDL \n" +++                                        "MODEL  5 \n" +++                                        "ATOM   2035  N   GLU B   1      18.637-691.583  66.852  1.0 118.48           N  \n" +++                                        "ATOM  12531 HH12 ARG D 474      45.558 -39.551 -49.936  1.00 15.00           H  \n" +++                                        "ENDMDL \n" +++                                        "MODEL  6 \n" +++                                        "ATOM  12532 HH21 ARG D 474      47.457 -38.007 -47.445  1.00 15.00           H  \n" +++                                        "TER   12534      ARG D 474                                                      \n" +++                                        "ATOM  12533 HH22 ARG D 474      47.405 -39.268 -48.629  1.00 15.00           H  \n" +++                                        "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" +++                                        "HETATM12538  C4  NAG B 475       6.943 -19.507  -9.597  1.00 25.87           C  \n" +++                                        "CONECT conect conect\n" +++                                        "CONECT conect\n" +++                                        "ENDMDL\n" +++                                        "SEQRES seqres\n" +++                                        "KEYWDS keywds\n" +++                                        "EXPDTA expdta\n" +++                                        "AUTHOR  Masha\n" +++                                        "REVDAT revdat\n" +++                                        "MASTER 1 2 3 4 5 6 7 8\n"+                                      )+        mt `shouldBe` Right ([], Right manyModelsPDB)++manyModelsPDB :: PDB+manyModelsPDB = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"+                    , remarks = Data.Map.Strict.fromList [(Just 1, V.singleton "REFERENCE 1"), (Just 2, V.singleton "Reference 2_1/1")]+                    , models = V.fromList [V.fromList [V.fromList [Atom {atomSerial = 2033, atomName = " H  ", atomAltLoc = ' ', atomResName = "CYS",+                                                                                                  atomChainID = 'A', atomResSeq = 214, atomICode = ' ', atomX = -6.124, atomY = -27.225,+                                                                                                  atomZ = 26.558, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]+                                                                          ],+                                                    V.fromList [V.fromList [Atom {atomSerial = 2035, atomName = " N  ", atomAltLoc = ' ', atomResName = "GLU",+                                                                                                 atomChainID = 'B', atomResSeq = 1, atomICode = ' ', atomX = 18.637, atomY = -691.583,+                                                                                                 atomZ = 66.852, atomOccupancy = 1.0, atomTempFactor = 118.48, atomElement = " N", atomCharge = "  "}],+                                                                          V.fromList [Atom {atomSerial = 12531, atomName = "HH12", atomAltLoc = ' ', atomResName = "ARG",+                                                                                                 atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 45.558, atomY = -39.551, atomZ = -49.936,+                                                                                                 atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]+                                                                         ],+                                                    V.fromList [V.fromList [Atom {atomSerial = 12532, atomName = "HH21", atomAltLoc = ' ', atomResName = "ARG",+                                                                                                 atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 47.457, atomY = -38.007, atomZ = -47.445,+                                                                                                 atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "},+                                                                                                Atom {atomSerial = 12533, atomName = "HH22", atomAltLoc = ' ', atomResName = "ARG",+                                                                                                 atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 47.405, atomY = -39.268, atomZ = -48.629,+                                                                                                 atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]+                                                                          ]+                                                      ]+                    , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source"]),(KEYWDS,V.fromList[" keywds"]),(EXPDTA,V.fromList[" expdta"]),(AUTHOR,V.fromList["  Masha"]),(REVDAT,V.fromList[" revdat"]),(SEQRES,V.fromList [" seqres"]),+                                                              (CRYST1,V.fromList [" cryst1"]),(ORIGX1,V.fromList[" origx1 n=1"]),(SCALE2,V.fromList [" sclaen n=2"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]+                    }++noModelsSpecP :: Spec+noModelsSpecP = describe "No models." $+        it "correctly parses pdb without models (no ATOM, TER, HETATM strings)" $ do+        let mt = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" +++                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" +++                                        "COMPND compnd\n" +++                                        "SOURCE source\n" +++                                        "KEYWDS keywds\n" +++                                        "EXPDTA expdta\n" +++                                        "AUTHOR  Masha\n" +++                                        "REVDAT revdat\n" +++                                        "REMARK 1   REFERENCE 1\n" +++                                        "SEQRES seqres\n" +++                                        "CRYST1 cryst1\n" +++                                        "ORIGX1 origx1 n=1\n" +++                                        "SCALE2 sclaen n=2\n" +++                                        "MASTER 1 2 3 4 5 6 7 8\n"+                                      )+        mt `shouldBe` Right ([], Right noModelsPDB)++noModelsPDB :: PDB+noModelsPDB = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"+                  , remarks = Data.Map.Strict.singleton (Just 1) (V.singleton "REFERENCE 1")+                  , models = V.empty+                  , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source"]),(KEYWDS,V.fromList[" keywds"]),(EXPDTA,V.fromList[" expdta"]),(AUTHOR,V.fromList["  Masha"]),(REVDAT,V.fromList[" revdat"]),(SEQRES,V.fromList [" seqres"]),+                                                            (CRYST1,V.fromList [" cryst1"]),(ORIGX1,V.fromList[" origx1 n=1"]),(SCALE2,V.fromList [" sclaen n=2"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]+                  }++allFieldsModelSpecP :: Spec+allFieldsModelSpecP = describe "PDB with all strings." $+        it "correctly parses pdb with all types of string" $ do+        let mt = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" +++                                        "OBSLTE obslte\n" +++                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" +++                                        "SPLIT split\n" +++                                        "CAVEAT caveat\n" +++                                        "COMPND compnd\n" +++                                        "SOURCE source\n" +++                                        "KEYWDS keywds\n" +++                                        "EXPDTA expdta1\n" +++                                        "EXPDTA expdta2\n" +++                                        "NUMMDL nummdl\n" +++                                        "MDLTYP mdltyp mdltyp\n" +++                                        "AUTHOR  Masha\n" +++                                        "REVDAT revdat\n" +++                                        "SPRSDE sprsde\n" +++                                        "JRNL   jrnl\n" +++                                        "REMARK 1   REFERENCE 1\n" +++                                        "DBREF dbref\n" +++                                        "DBREF1 dbref1\n" +++                                        "DBREF2 dbref2_1\n" +++                                        "DBREF2 dbref2_2\n" +++                                        "SEQADV seqadv\n" +++                                        "SEQRES seqres\n" +++                                        "MODRES modres\n" +++                                        "HET    het\n" +++                                        "HETNAM hetnam\n" +++                                        "HETSYN hetsyn\n" +++                                        "FORMUL of love\n" +++                                        "HELIX helix\n" +++                                        "SHEET sheet\n" +++                                        "SSBOND ssbond\n" +++                                        "LINK   link\n" +++                                        "CISPEP cispep\n" +++                                        "SITE   site\n" +++                                        "CRYST1 cryst1\n" +++                                        "ORIGX1 origx1 n=1\n" +++                                        "SCALE2 sclaen n=2\n" +++                                        "MTRIX3 matrixn n=3\n" +++                                        "MODEL 1\n" +++                                        "ATOM  12532 HH21 ARG D 474      47.457 -38.007 -47.445  1.00 15.00           H  \n" +++                                        "TER   12534      ARG D 474                                                      \n" +++                                        "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" +++                                        "CONECT conect\n" +++                                        "CONECT conect conect\n" +++                                        "ENDMDL\n" +++                                        "MASTER 1 2 3 4 5 6 7 8\n" +++                                        "END"+                                      )+        mt `shouldBe` Right ([], Right pdbWithAllFields)++pdbWithAllFields :: PDB+pdbWithAllFields = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"+                       , models = V.fromList [V.fromList [V.singleton Atom {atomSerial = 12532, atomName = "HH21", atomAltLoc = ' ', atomResName = "ARG",+                                     atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 47.457, atomY = -38.007, atomZ = -47.445,+                                     atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]]+                       , remarks = Data.Map.Strict.singleton (Just 1) (V.singleton "REFERENCE 1")+                       , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(OBSLTE, V.fromList [" obslte"]), (SPLIT, V.fromList ["split"]), (CAVEAT, V.fromList [" caveat"]),+                                                                 (COMPND, V.fromList [" compnd"]),(SOURCE, V.fromList [" source"]),(KEYWDS, V.fromList [" keywds"]),+                                                                 (EXPDTA, V.fromList [" expdta1"," expdta2"]),(NUMMDL, V.fromList [" nummdl"]),(MDLTYP, V.fromList [" mdltyp mdltyp"]),+                                                                 (AUTHOR, V.fromList ["  Masha"]),(REVDAT, V.fromList [" revdat"]),(SPRSDE, V.fromList [" sprsde"]),+                                                                 (JRNL, V.fromList [" jrnl"]),(DBREF, V.fromList ["dbref"]), (DBREF1, V.fromList [" dbref1"]), (DBREF2, V.fromList [" dbref2_1"," dbref2_2"]),+                                                                 (SEQADV, V.fromList [" seqadv"]),(SEQRES, V.fromList [" seqres"]),(MODRES, V.fromList [" modres"]),+                                                                 (HET, V.fromList [" het"]), (HETNAM, V.fromList [" hetnam"]), (HETSYN, V.fromList [" hetsyn"]),(FORMUL, V.fromList [" of love"]),(HELIX, V.fromList ["helix"]),+                                                                 (SHEET, V.fromList ["sheet"]),(SSBOND, V.fromList [" ssbond"]),(LINK, V.fromList [" link"]),+                                                                 (CISPEP, V.fromList [" cispep"]),(SITE,V.fromList [" site"]),(CRYST1, V.fromList [" cryst1"]),(MTRIX3, V.fromList [" matrixn n=3"]),+                                                                 (ORIGX1, V.fromList [" origx1 n=1"]),(SCALE2, V.fromList [" sclaen n=2"]),(MASTER, V.fromList [" 1 2 3 4 5 6 7 8"])]+                       }+++emptySpecP :: Spec+emptySpecP = describe "empty PDB." $+        it "correctly parses empty pdb" $ do+        let mt = fromTextPDB ""+        mt `shouldBe` Right ([], Right emptyPdb)++emptyPdb :: PDB+emptyPdb = PDB { title = ""+               , models = V.empty+               , remarks = Data.Map.Strict.empty+               , otherFields = Data.Map.Strict.empty+               }++trashBetweenModelsSpecP :: Spec+trashBetweenModelsSpecP = describe "PDB has trash." $+        it "correctly parses pdb with trash string between models and other field strings" $ do+        let mt = fromTextPDB  . lenghtenLines $ T.pack ( "trash strings 1\n" +++                                        "HEADER header\n" +++                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" +++                                        "REMARK 1   REFERENCE 1\n" +++                                        "trash strings 2\n" +++                                        "REMARK 2   Reference 2_1/1\n" +++                                        "MODEL  4  \n" +++                                        "ATOM   2033  H   CYS A 214      -6.124 -27.225  26.558  1.00 15.00           H  \n" +++                                        "TER    2034      CYS A 214                                                      \n" +++                                        "ANISOU anisou\n" +++                                        "ENDMDL \n" +++                                        "CRYST1 cryst1\n" +++                                        "trash strings 3\n" +++                                        "SEQRES seqres\n" +++                                        "MODEL  5 \n" +++                                        "ATOM   2035  N   GLU B   1      18.637-691.583  66.852  1.0 118.48           N  \n" +++                                        "ATOM  12531 HH12 ARG D 474      45.558 -39.551 -49.936  1.00 15.00           H  \n" +++                                        "ENDMDL \n" +++                                        "trash strings 4\n" +++                                        "MODEL  6 \n" +++                                        "ATOM  12532 HH21 ARG D 474      47.457 -38.007 -47.445  1.00 15.00           H  \n" +++                                        "TER   12534      ARG D 474                                                      \n" +++                                        "ATOM  12533 HH22 ARG D 474      47.405 -39.268 -48.629  1.00 15.00           H  \n" +++                                        "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" +++                                        "HETATM12538  C4  NAG B 475       6.943 -19.507  -9.597  1.00 25.87           C  \n" +++                                        "CONECT conect conect\n" +++                                        "ENDMDL\n" +++                                        "MASTER 1 2 3 4 5 6 7 8\n" +++                                        "trash strings 5\n"+                                       )+        mt `shouldBe` Left "There are trash strings between model strings"++pdbWithoutTrash :: PDB+pdbWithoutTrash = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"+                      , remarks = Data.Map.Strict.fromList [(Just 1, V.singleton "REFERENCE 1"), (Just 2, V.singleton "Reference 2_1/1")]+                      , models = V.fromList [V.fromList [V.fromList [Atom {atomSerial = 2033, atomName = " H  ", atomAltLoc = ' ', atomResName = "CYS",+                                                                                                    atomChainID = 'A', atomResSeq = 214, atomICode = ' ', atomX = -6.124, atomY = -27.225,+                                                                                                    atomZ = 26.558, atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]+                                                                            ],+                                                      V.fromList [V.fromList [Atom {atomSerial = 2035, atomName = " N  ", atomAltLoc = ' ', atomResName = "GLU",+                                                                                                   atomChainID = 'B', atomResSeq = 1, atomICode = ' ', atomX = 18.637, atomY = -691.583,+                                                                                                   atomZ = 66.852, atomOccupancy = 1.0, atomTempFactor = 118.48, atomElement = " N", atomCharge = "  "}],+                                                                            V.fromList [Atom {atomSerial = 12531, atomName = "HH12", atomAltLoc = ' ', atomResName = "ARG",+                                                                                                   atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 45.558, atomY = -39.551, atomZ = -49.936,+                                                                                                   atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]+                                                                           ],+                                                      V.fromList [V.fromList [Atom {atomSerial = 12532, atomName = "HH21", atomAltLoc = ' ', atomResName = "ARG",+                                                                                                   atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 47.457, atomY = -38.007, atomZ = -47.445,+                                                                                                   atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "},+                                                                                                  Atom {atomSerial = 12533, atomName = "HH22", atomAltLoc = ' ', atomResName = "ARG",+                                                                                                   atomChainID = 'D', atomResSeq = 474, atomICode = ' ', atomX = 47.405, atomY = -39.268, atomZ = -48.629,+                                                                                                   atomOccupancy = 1.0, atomTempFactor = 15.0, atomElement = " H", atomCharge = "  "}]+                                                                            ]+                                                        ]+                      , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(SEQRES,V.fromList [" seqres"]),+                                                                (CRYST1,V.fromList [" cryst1"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]+                      }++onlyOneModelSpecP :: Spec+onlyOneModelSpecP = describe "Only One model." $+        it "correctly parses pdb with only one model without other field/title/trash/remarks strings" $ do+        let mt = fromTextPDB . lenghtenLines $ T.pack ( "ATOM   2032  OXT CYS A 214      -4.546 -29.673  26.796  1.0 143.51           O  \n" +++                                         "ATOM   2033  H   CYS A 214      -6.124 -27.225  26.558  1.00 15.00           H  \n" +++                                         "TER    2034      CYS A 214                                                      \n" +++                                         "ATOM   2035  N   GLU B   1      18.637 -61.583  66.852  1.0 118.48           N  \n" +++                                         "TER   12534      ARG D 474                                                      \n" +++                                         "HETATM12535  C1  NAG B 475       5.791 -20.194  -7.051  1.00 34.66           C  \n" +++                                         "HETATM12538  C4  NAG B 475       6.943 -19.507  -9.597  1.00 25.87           C  \n" +++                                         "ATOM   2036  CA  GLU B   1      19.722 -62.606  66.868  1.00 19.77           C  \n"+                                        )+        mt `shouldBe` Right ([], Right onlyOneModelPDB)++onlyOneModelPDB :: PDB+onlyOneModelPDB = PDB { title = ""+                      , remarks = Data.Map.Strict.empty+                      , models = V.singleton $ V.fromList [V.fromList [Atom {atomSerial = 2032, atomName = " OXT", atomAltLoc = ' ', atomResName = "CYS", atomChainID = 'A', atomResSeq = 214, atomICode = ' ',+                                                                              atomX = -4.546, atomY = -29.673, atomZ = 26.796, atomOccupancy = 1.0, atomTempFactor = 143.51, atomElement = " O",+                                                                              atomCharge = "  "},+                                                                              Atom {atomSerial = 2033, atomName = " H  ", atomAltLoc = ' ', atomResName = "CYS", atomChainID = 'A',+                                                                              atomResSeq = 214, atomICode = ' ', atomX = -6.124, atomY = -27.225, atomZ = 26.558, atomOccupancy = 1.0, atomTempFactor = 15.0,+                                                                              atomElement = " H", atomCharge = "  "}],+                                                                              V.fromList [Atom {atomSerial = 2035, atomName = " N  ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = ' ',+                                                                              atomX = 18.637, atomY = -61.583, atomZ = 66.852, atomOccupancy = 1.0, atomTempFactor = 118.48, atomElement = " N", atomCharge = "  "},+                                                                              Atom {atomSerial = 2036, atomName = " CA ", atomAltLoc = ' ', atomResName = "GLU", atomChainID = 'B', atomResSeq = 1, atomICode = ' ',+                                                                              atomX = 19.722, atomY = -62.606, atomZ = 66.868, atomOccupancy = 1.0, atomTempFactor = 19.77, atomElement = " C", atomCharge = "  "}]+                                                                              ]+                      , otherFields = Data.Map.Strict.empty+                      }++repeatedStringsSpecP :: Spec+repeatedStringsSpecP = describe "PDB with repeated other field strings." $+        it "correctly parses pdb with repeated other field strings (SOURCE)" $ do+        let mt = fromTextPDB . lenghtenLines $ T.pack ( "HEADER header\n" +++                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" +++                                        "COMPND compnd\n" +++                                        "EXPDTA expdta\n" +++                                        "AUTHOR  Masha\n" +++                                        "SOURCE source1\n" +++                                        "KEYWDS keywds\n" +++                                        "REVDAT revdat\n" +++                                        "REMARK 1   REFERENCE 1\n" +++                                        "SEQRES seqres\n" +++                                        "CRYST1 cryst1\n" +++                                        "ORIGX1 origx1 n=1\n" +++                                        "SOURCE source2\n" +++                                        "SCALE2 sclaen n=2\n" +++                                        "MASTER 1 2 3 4 5 6 7 8\n" +++                                        "SOURCE source3\n" +++                                        "END"+                                      )+        mt `shouldBe` Right ([], Right repeatedStringsPDB)++repeatedStringsPDB :: PDB+repeatedStringsPDB = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"+                         , remarks = Data.Map.Strict.singleton (Just 1) (V.singleton "REFERENCE 1")+                         , models = V.empty+                         , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source1", " source2", " source3"]),(KEYWDS,V.fromList[" keywds"]),(EXPDTA,V.fromList[" expdta"]),(AUTHOR,V.fromList["  Masha"]),(REVDAT,V.fromList[" revdat"]),(SEQRES,V.fromList [" seqres"]),+                                                                   (CRYST1,V.fromList [" cryst1"]),(ORIGX1,V.fromList[" origx1 n=1"]),(SCALE2,V.fromList [" sclaen n=2"]),(MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]+                         }++emptyRemarkSpecP :: Spec+emptyRemarkSpecP = describe "PDB with repeated remark strings without code." $+        it "correctly parses pdb with repeated remark strings without code" $ do+        let mt = fromTextPDB . lenghtenLines $ T.pack ( "REMARK 111 remark111_1/2\n" +++                                        "COMPND compnd\n" +++                                        "SOURCE source1\n" +++                                        "KEYWDS keywds\n" +++                                        "REMARK 111 remark111_2/2\n" +++                                        "REVDAT revdat\n" +++                                        "REMARK 2   remark2_1/1\n" +++                                        "SOURCE source2\n" +++                                        "REMARK     empty remark 1/2\n" +++                                        "END    \n" +++                                        "REMARK     empty remark 2/2\n" +++                                        "SCALE2 sclaen n=2\n"+                                      )+        mt `shouldBe` Right ([], Right pdbWithEmptyRemarks)++pdbWithEmptyRemarks :: PDB+pdbWithEmptyRemarks = PDB { title = ""+                         , remarks = Data.Map.Strict.fromList [(Just 2, V.fromList ["remark2_1/1"]), (Just 111, V.fromList ["remark111_1/2", "remark111_2/2"]), (Nothing, V.fromList ["empty remark 1/2", "empty remark 2/2"])]+                         , models = V.empty+                         , otherFields = Data.Map.Strict.fromList [(COMPND,V.fromList[" compnd"]),(SOURCE,V.fromList[" source1", " source2"]),+                                                                    (KEYWDS,V.fromList[" keywds"]), (REVDAT,V.fromList[" revdat"]), (SCALE2,V.fromList [" sclaen n=2"])]+                         }++emptyModelSpecP :: Spec+emptyModelSpecP = describe "PDB with one empty model." $+        it "correctly parses pdb with one model without strings inside" $ do+        let mt = fromTextPDB . lenghtenLines $ T.pack ( "trash strings 1\n" +++                                        "HEADER header\n" +++                                        "TITLE STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME\n" +++                                        "REMARK 1   REFERENCE 1\n" +++                                        "trash strings 2\n" +++                                        "REMARK 2   Reference 2_1/1\n" +++                                        "MODEL  4  \n" +++                                        "ENDMDL\n" +++                                        "MASTER 1 2 3 4 5 6 7 8\n" +++                                        "trash strings 5\n"+                                      )+        mt `shouldBe` Right ([], Right pdbEmptyModel)+++pdbEmptyModel :: PDB+pdbEmptyModel = PDB { title = "STRUCTURE OF THE TRANSFORMED MONOCLINIC  LYSOZYME"+                    , remarks = Data.Map.Strict.fromList [(Just 1, V.singleton "REFERENCE 1"), (Just 2, V.singleton "Reference 2_1/1")]+                    , models = V.empty+                    , otherFields = Data.Map.Strict.fromList [(HEADER, V.fromList [" header"]),+                                                              (MASTER,V.fromList[" 1 2 3 4 5 6 7 8"])]+                    }++lenghtenLines :: Text -> Text+lenghtenLines text = longLinedText+   where+       textLines = T.lines text+       longTextLines = changeLine <$> textLines+       desiredLength = 80  -- cause it is max length in standart pdb file+       longLinedText = T.intercalate "\n" longTextLines++       changeLine :: Text -> Text+       changeLine line | T.length line > desiredLength = T.take desiredLength line+                       | T.length line < desiredLength = line <> T.replicate spacesCount " "+                       | otherwise = line+            where+                    spacesCount = desiredLength - T.length line+
test/Spec.hs view
@@ -11,6 +11,7 @@ import           System.IO import           Test.Hspec import           UniprotSpec+import           PDBSpec  main :: IO () main = do@@ -40,3 +41,14 @@          -- Mae          maeParserSpec          maeSpec+         -- PDB+         oneModelSpecP+         manyModelsSpecP+         noModelsSpecP+         allFieldsModelSpecP+         emptySpecP+         trashBetweenModelsSpecP+         onlyOneModelSpecP+         repeatedStringsSpecP+         emptyRemarkSpecP+         emptyModelSpecP