{-# 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 (Int -> ChangeLog -> ShowS
[ChangeLog] -> ShowS
ChangeLog -> String
(Int -> ChangeLog -> ShowS)
-> (ChangeLog -> String)
-> ([ChangeLog] -> ShowS)
-> Show ChangeLog
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ChangeLog] -> ShowS
$cshowList :: [ChangeLog] -> ShowS
show :: ChangeLog -> String
$cshow :: ChangeLog -> String
showsPrec :: Int -> ChangeLog -> ShowS
$cshowsPrec :: Int -> ChangeLog -> ShowS
Show, ChangeLog -> ChangeLog -> Bool
(ChangeLog -> ChangeLog -> Bool)
-> (ChangeLog -> ChangeLog -> Bool) -> Eq ChangeLog
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ChangeLog -> ChangeLog -> Bool
$c/= :: ChangeLog -> ChangeLog -> Bool
== :: ChangeLog -> ChangeLog -> Bool
$c== :: ChangeLog -> ChangeLog -> Bool
Eq, Eq ChangeLog
Eq ChangeLog
-> (ChangeLog -> ChangeLog -> Ordering)
-> (ChangeLog -> ChangeLog -> Bool)
-> (ChangeLog -> ChangeLog -> Bool)
-> (ChangeLog -> ChangeLog -> Bool)
-> (ChangeLog -> ChangeLog -> Bool)
-> (ChangeLog -> ChangeLog -> ChangeLog)
-> (ChangeLog -> ChangeLog -> ChangeLog)
-> Ord ChangeLog
ChangeLog -> ChangeLog -> Bool
ChangeLog -> ChangeLog -> Ordering
ChangeLog -> ChangeLog -> ChangeLog
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: ChangeLog -> ChangeLog -> ChangeLog
$cmin :: ChangeLog -> ChangeLog -> ChangeLog
max :: ChangeLog -> ChangeLog -> ChangeLog
$cmax :: ChangeLog -> ChangeLog -> ChangeLog
>= :: ChangeLog -> ChangeLog -> Bool
$c>= :: ChangeLog -> ChangeLog -> Bool
> :: ChangeLog -> ChangeLog -> Bool
$c> :: ChangeLog -> ChangeLog -> Bool
<= :: ChangeLog -> ChangeLog -> Bool
$c<= :: ChangeLog -> ChangeLog -> Bool
< :: ChangeLog -> ChangeLog -> Bool
$c< :: ChangeLog -> ChangeLog -> Bool
compare :: ChangeLog -> ChangeLog -> Ordering
$ccompare :: ChangeLog -> ChangeLog -> Ordering
$cp1Ord :: Eq ChangeLog
Ord, (forall x. ChangeLog -> Rep ChangeLog x)
-> (forall x. Rep ChangeLog x -> ChangeLog) -> Generic ChangeLog
forall x. Rep ChangeLog x -> ChangeLog
forall x. ChangeLog -> Rep ChangeLog x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep ChangeLog x -> ChangeLog
$cfrom :: forall x. ChangeLog -> Rep ChangeLog x
Generic, ChangeLog -> ()
(ChangeLog -> ()) -> NFData ChangeLog
forall a. (a -> ()) -> NFData a
rnf :: ChangeLog -> ()
$crnf :: ChangeLog -> ()
NFData, b -> ChangeLog -> ChangeLog
NonEmpty ChangeLog -> ChangeLog
ChangeLog -> ChangeLog -> ChangeLog
(ChangeLog -> ChangeLog -> ChangeLog)
-> (NonEmpty ChangeLog -> ChangeLog)
-> (forall b. Integral b => b -> ChangeLog -> ChangeLog)
-> Semigroup ChangeLog
forall b. Integral b => b -> ChangeLog -> ChangeLog
forall a.
(a -> a -> a)
-> (NonEmpty a -> a)
-> (forall b. Integral b => b -> a -> a)
-> Semigroup a
stimes :: b -> ChangeLog -> ChangeLog
$cstimes :: forall b. Integral b => b -> ChangeLog -> ChangeLog
sconcat :: NonEmpty ChangeLog -> ChangeLog
$csconcat :: NonEmpty ChangeLog -> ChangeLog
<> :: ChangeLog -> ChangeLog -> ChangeLog
$c<> :: ChangeLog -> ChangeLog -> ChangeLog
Semigroup, Semigroup ChangeLog
ChangeLog
Semigroup ChangeLog
-> ChangeLog
-> (ChangeLog -> ChangeLog -> ChangeLog)
-> ([ChangeLog] -> ChangeLog)
-> Monoid ChangeLog
[ChangeLog] -> ChangeLog
ChangeLog -> ChangeLog -> ChangeLog
forall a.
Semigroup a -> a -> (a -> a -> a) -> ([a] -> a) -> Monoid a
mconcat :: [ChangeLog] -> ChangeLog
$cmconcat :: [ChangeLog] -> ChangeLog
mappend :: ChangeLog -> ChangeLog -> ChangeLog
$cmappend :: ChangeLog -> ChangeLog -> ChangeLog
mempty :: ChangeLog
$cmempty :: ChangeLog
$cp1Monoid :: Semigroup ChangeLog
Monoid)

instance HasParser ChangeLog where
  parser :: ParsecT st input m ChangeLog
parser = [Entry] -> ChangeLog
ChangeLog ([Entry] -> ChangeLog)
-> ParsecT st input m [Entry] -> ParsecT st input m ChangeLog
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (ParsecT st input m ()
forall st input (m :: * -> *). CharParser st input m ()
parseDashedLine ParsecT st input m ()
-> ParsecT st input m [Entry] -> ParsecT st input m [Entry]
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> ParsecT st input m Entry -> ParsecT st input m [Entry]
forall s u (m :: * -> *) a. ParsecT s u m a -> ParsecT s u m [a]
many ParsecT st input m Entry
forall a st input (m :: * -> *).
HasParser a =>
CharParser st input m a
parser)

data Entry = Entry
  { Entry -> UTCTime
changedAt :: UTCTime
  , Entry -> EMailAddress
changedBy :: EMailAddress
  , Entry -> Text
changeDescription :: Text
  }
  deriving (Int -> Entry -> ShowS
[Entry] -> ShowS
Entry -> String
(Int -> Entry -> ShowS)
-> (Entry -> String) -> ([Entry] -> ShowS) -> Show Entry
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Entry] -> ShowS
$cshowList :: [Entry] -> ShowS
show :: Entry -> String
$cshow :: Entry -> String
showsPrec :: Int -> Entry -> ShowS
$cshowsPrec :: Int -> Entry -> ShowS
Show, Entry -> Entry -> Bool
(Entry -> Entry -> Bool) -> (Entry -> Entry -> Bool) -> Eq Entry
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Entry -> Entry -> Bool
$c/= :: Entry -> Entry -> Bool
== :: Entry -> Entry -> Bool
$c== :: Entry -> Entry -> Bool
Eq, Eq Entry
Eq Entry
-> (Entry -> Entry -> Ordering)
-> (Entry -> Entry -> Bool)
-> (Entry -> Entry -> Bool)
-> (Entry -> Entry -> Bool)
-> (Entry -> Entry -> Bool)
-> (Entry -> Entry -> Entry)
-> (Entry -> Entry -> Entry)
-> Ord Entry
Entry -> Entry -> Bool
Entry -> Entry -> Ordering
Entry -> Entry -> Entry
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: Entry -> Entry -> Entry
$cmin :: Entry -> Entry -> Entry
max :: Entry -> Entry -> Entry
$cmax :: Entry -> Entry -> Entry
>= :: Entry -> Entry -> Bool
$c>= :: Entry -> Entry -> Bool
> :: Entry -> Entry -> Bool
$c> :: Entry -> Entry -> Bool
<= :: Entry -> Entry -> Bool
$c<= :: Entry -> Entry -> Bool
< :: Entry -> Entry -> Bool
$c< :: Entry -> Entry -> Bool
compare :: Entry -> Entry -> Ordering
$ccompare :: Entry -> Entry -> Ordering
$cp1Ord :: Eq Entry
Ord, (forall x. Entry -> Rep Entry x)
-> (forall x. Rep Entry x -> Entry) -> Generic Entry
forall x. Rep Entry x -> Entry
forall x. Entry -> Rep Entry x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Entry x -> Entry
$cfrom :: forall x. Entry -> Rep Entry x
Generic)

instance NFData Entry

instance HasParser Entry where
  parser :: ParsecT st input m Entry
parser = ParsecT st input m Entry
forall st input (m :: * -> *). CharParser st input m Entry
parseEntry

-- * Useful parsers for ChangeLog elements

{-# ANN parseEntry "HLint: ignore Use <$>" #-}
parseEntry :: CharParser st input m Entry
parseEntry :: ParsecT st input m Entry
parseEntry = do
  (UTCTime
ts,EMailAddress
author) <- ParsecT st input m (UTCTime, EMailAddress)
forall st input (m :: * -> *).
CharParser st input m (UTCTime, EMailAddress)
parseDateAddressLine
  ParsecT st input m ()
forall st input (m :: * -> *). CharParser st input m ()
parseEmptyLine
  String
txt <- ParsecT st input m String
forall st input (m :: * -> *). CharParser st input m String
parseDescription
  Entry -> ParsecT st input m Entry
forall (m :: * -> *) a. Monad m => a -> m a
return (UTCTime -> EMailAddress -> Text -> Entry
Entry UTCTime
ts EMailAddress
author (String -> Text
packText String
txt))

parseDashedLine :: CharParser st input m ()
parseDashedLine :: ParsecT st input m ()
parseDashedLine = do
  String
_ <- String -> ParsecT st input m String
forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m String
string String
"------------------------------------------------------------"
  ParsecT st input m Char -> ParsecT st input m ()
forall s (m :: * -> *) t u a.
Stream s m t =>
ParsecT s u m a -> ParsecT s u m ()
skipMany1 (Char -> ParsecT st input m Char
forall s (m :: * -> *) u.
Stream s m Char =>
Char -> ParsecT s u m Char
char Char
'-')
  Char
_ <- ParsecT st input m Char
forall s (m :: * -> *) u. Stream s m Char => ParsecT s u m Char
endOfLine
  () -> ParsecT st input m ()
forall (m :: * -> *) a. Monad m => a -> m a
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 :: ParsecT st input m (UTCTime, EMailAddress)
parseDateAddressLine = do
  String
ts <- ParsecT st input m Char -> ParsecT st input m String
forall s (m :: * -> *) t u a.
Stream s m t =>
ParsecT s u m a -> ParsecT s u m [a]
many1 (ParsecT st input m Char
forall s (m :: * -> *) u. Stream s m Char => ParsecT s u m Char
alphaNum ParsecT st input m Char
-> ParsecT st input m Char -> ParsecT st input m Char
forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> String -> ParsecT st input m Char
forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m Char
oneOf String
": ")
  Char
_ <- Char -> ParsecT st input m Char
forall s (m :: * -> *) u.
Stream s m Char =>
Char -> ParsecT s u m Char
char Char
'-'
  EMailAddress
addr <- ParsecT st input m EMailAddress
forall a st input (m :: * -> *).
HasParser a =>
CharParser st input m a
parser
  Char
_ <- ParsecT st input m Char
forall s (m :: * -> *) u. Stream s m Char => ParsecT s u m Char
endOfLine
  UTCTime
utct <- Bool
-> TimeLocale -> String -> String -> ParsecT st input m UTCTime
forall (m :: * -> *) t.
(MonadFail m, ParseTime t) =>
Bool -> TimeLocale -> String -> String -> m t
parseTimeM Bool
True TimeLocale
defaultTimeLocale String
changeLogDateFormat String
ts
  (UTCTime, EMailAddress)
-> ParsecT st input m (UTCTime, EMailAddress)
forall (m :: * -> *) a. Monad m => a -> m a
return (UTCTime
utct,EMailAddress
addr)

-- | Consume an empty line, i.e. a line that contains only whitespace.
parseEmptyLine :: CharParser st input m ()
parseEmptyLine :: ParsecT st input m ()
parseEmptyLine = ParsecT st input m Char -> ParsecT st input m ()
forall s u (m :: * -> *) a. ParsecT s u m a -> ParsecT s u m ()
skipMany (String -> ParsecT st input m Char
forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m Char
oneOf String
" \t") ParsecT st input m ()
-> ParsecT st input m Char -> ParsecT st input m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* ParsecT st input m Char
forall s (m :: * -> *) u. Stream s m Char => ParsecT s u m Char
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 :: ParsecT st input m String
parseDescription = ParsecT st input m Char
-> ParsecT st input m () -> ParsecT st input m String
forall s (m :: * -> *) t u a end.
Stream s m t =>
ParsecT s u m a -> ParsecT s u m end -> ParsecT s u m [a]
manyTill ParsecT st input m Char
forall s (m :: * -> *) u. Stream s m Char => ParsecT s u m Char
anyChar (ParsecT st input m () -> ParsecT st input m ()
forall s u (m :: * -> *) a. ParsecT s u m a -> ParsecT s u m a
try ParsecT st input m ()
forall st input (m :: * -> *). CharParser st input m ()
parseDashedLine ParsecT st input m ()
-> ParsecT st input m () -> ParsecT st input m ()
forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> ParsecT st input m ()
forall s (m :: * -> *) t u.
(Stream s m t, Show t) =>
ParsecT s u m ()
eof)

-- | Appropriate format parameter for 'formatTime' and 'parseTimeM'.
changeLogDateFormat :: String
changeLogDateFormat :: String
changeLogDateFormat = String
"%a %b %e %H:%M:%S %Z %Y"