{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoImplicitPrelude #-} module OpenSuse.Types.ChangeLog ( ChangeLog(..), Entry(..) , parseEntry, parseDashedLine, parseDateAddressLine, parseDescription ) where import OpenSuse.Prelude import OpenSuse.Prelude.Parser as Parse import qualified OpenSuse.Prelude.PrettyPrinting as Pretty ( ) import OpenSuse.Types.EMailAddress import Data.Time.Format newtype ChangeLog = ChangeLog [Entry] deriving (Show, Eq, Ord, Generic, NFData, Semigroup, Monoid) instance HasParser ChangeLog where parser = ChangeLog <$> (parseDashedLine *> many parser) data Entry = Entry { changedAt :: UTCTime , changedBy :: EMailAddress , changeDescription :: Text } deriving (Show, Eq, Ord, Generic) instance NFData Entry instance HasParser Entry where parser = parseEntry -- * Useful parsers for ChangeLog elements {-# ANN parseEntry "HLint: ignore Use <$>" #-} parseEntry :: CharParser st input m Entry parseEntry = do (ts,author) <- parseDateAddressLine parseEmptyLine txt <- parseDescription return (Entry ts author (packText txt)) parseDashedLine :: CharParser st input m () parseDashedLine = do _ <- string "------------------------------------------------------------" skipMany1 (char '-') _ <- endOfLine return () -- | Note that the input must be terminated by a newline. -- -- >>> parseTest parseDateAddressLine "Wed Jun 27 09:25:07 UTC 2018 - foo@example.org\n" -- (2018-06-27 09:25:07 UTC,EMailAddress "foo@example.org") parseDateAddressLine :: CharParser st input m (UTCTime, EMailAddress) parseDateAddressLine = do ts <- many1 (alphaNum <|> oneOf ": ") _ <- char '-' addr <- parser _ <- endOfLine utct <- parseTimeM True defaultTimeLocale changeLogDateFormat ts return (utct,addr) -- | Consume an empty line, i.e. a line that contains only whitespace. parseEmptyLine :: CharParser st input m () parseEmptyLine = skipMany (oneOf " \t") <* endOfLine -- | Consume all text until the end of the file or a dashed line is found. In -- the latter case, the dashed line is consumed as well. This is unfortunate, -- but it's how the 'notFollowedBy' combinator works, unfortunately, parseDescription :: CharParser st input m String parseDescription = manyTill anyChar (try parseDashedLine <|> eof) -- | Appropriate format parameter for 'formatTime' and 'parseTimeM'. changeLogDateFormat :: String changeLogDateFormat = "%a %b %e %H:%M:%S %Z %Y"