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 +5/−0
- cobot-io.cabal +5/−2
- src/Bio/PDB/Parser.hs +157/−0
- src/Bio/PDB/Reader.hs +73/−0
- src/Bio/PDB/Type.hs +11/−5
- test/PDBSpec.hs +428/−0
- test/Spec.hs +12/−0
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