-- | A showcase project, as read from a project YAML file
module MathiasSM.Yaml.Project (
    Status (..),
    statusName,
    Project (..),
    parseProject,
    parseProjectYaml,
) where

import Control.Monad ((>=>))
import Data.Aeson (FromJSON (parseJSON), Object)
import Data.Maybe (fromMaybe)
import Data.Time (Day)
import MathiasSM.Metadata qualified as K
import MathiasSM.Yaml (Check (runCheck), decodeYaml, enumFromJSON, optional, required)

-- | How far along a project is
data Status = Alpha | Finished | Ongoing | Unmaintained
    deriving (Status -> Status -> Bool
(Status -> Status -> Bool)
-> (Status -> Status -> Bool) -> Eq Status
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Status -> Status -> Bool
== :: Status -> Status -> Bool
$c/= :: Status -> Status -> Bool
/= :: Status -> Status -> Bool
Eq, Int -> Status -> ShowS
[Status] -> ShowS
Status -> String
(Int -> Status -> ShowS)
-> (Status -> String) -> ([Status] -> ShowS) -> Show Status
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Status -> ShowS
showsPrec :: Int -> Status -> ShowS
$cshow :: Status -> String
show :: Status -> String
$cshowList :: [Status] -> ShowS
showList :: [Status] -> ShowS
Show, Int -> Status
Status -> Int
Status -> [Status]
Status -> Status
Status -> Status -> [Status]
Status -> Status -> Status -> [Status]
(Status -> Status)
-> (Status -> Status)
-> (Int -> Status)
-> (Status -> Int)
-> (Status -> [Status])
-> (Status -> Status -> [Status])
-> (Status -> Status -> [Status])
-> (Status -> Status -> Status -> [Status])
-> Enum Status
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: Status -> Status
succ :: Status -> Status
$cpred :: Status -> Status
pred :: Status -> Status
$ctoEnum :: Int -> Status
toEnum :: Int -> Status
$cfromEnum :: Status -> Int
fromEnum :: Status -> Int
$cenumFrom :: Status -> [Status]
enumFrom :: Status -> [Status]
$cenumFromThen :: Status -> Status -> [Status]
enumFromThen :: Status -> Status -> [Status]
$cenumFromTo :: Status -> Status -> [Status]
enumFromTo :: Status -> Status -> [Status]
$cenumFromThenTo :: Status -> Status -> Status -> [Status]
enumFromThenTo :: Status -> Status -> Status -> [Status]
Enum, Status
Status -> Status -> Bounded Status
forall a. a -> a -> Bounded a
$cminBound :: Status
minBound :: Status
$cmaxBound :: Status
maxBound :: Status
Bounded)

-- | How a project status is spelled
statusName :: Status -> String
statusName :: Status -> String
statusName Status
Alpha = String
"alpha"
statusName Status
Finished = String
"finished"
statusName Status
Ongoing = String
"ongoing"
statusName Status
Unmaintained = String
"unmaintained"

instance FromJSON Status where
    parseJSON :: Value -> Parser Status
parseJSON = String -> (Status -> String) -> Value -> Parser Status
forall a.
(Enum a, Bounded a) =>
String -> (a -> String) -> Value -> Parser a
enumFromJSON String
"status" Status -> String
statusName

-- | What a showcase project needs to be listed
data Project = Project
    { Project -> String
projectTitle :: String
    , Project -> String
projectHref :: String
    , Project -> Status
projectStatus :: Status
    , Project -> Day
projectStart :: Day
    , Project -> Maybe Day
projectEnd :: Maybe Day
    , Project -> String
projectShortDescription :: String
    , Project -> String
projectLongDescription :: String
    , Project -> [String]
projectTeam :: [String]
    , Project -> Maybe Int
projectPriority :: Maybe Int
    }
    deriving (Project -> Project -> Bool
(Project -> Project -> Bool)
-> (Project -> Project -> Bool) -> Eq Project
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Project -> Project -> Bool
== :: Project -> Project -> Bool
$c/= :: Project -> Project -> Bool
/= :: Project -> Project -> Bool
Eq, Int -> Project -> ShowS
[Project] -> ShowS
Project -> String
(Int -> Project -> ShowS)
-> (Project -> String) -> ([Project] -> ShowS) -> Show Project
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Project -> ShowS
showsPrec :: Int -> Project -> ShowS
$cshow :: Project -> String
show :: Project -> String
$cshowList :: [Project] -> ShowS
showList :: [Project] -> ShowS
Show)

-- | Parses a project, reporting every problem (not just the first)
parseProject :: Object -> Either [String] Project
parseProject :: Object -> Either [String] Project
parseProject Object
o =
    Check Project -> Either [String] Project
forall a. Check a -> Either [String] a
runCheck
        ( String
-> String
-> Status
-> Day
-> Maybe Day
-> String
-> String
-> [String]
-> Maybe Int
-> Project
Project
            (String
 -> String
 -> Status
 -> Day
 -> Maybe Day
 -> String
 -> String
 -> [String]
 -> Maybe Int
 -> Project)
-> Check String
-> Check
     (String
      -> Status
      -> Day
      -> Maybe Day
      -> String
      -> String
      -> [String]
      -> Maybe Int
      -> Project)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object -> Key -> Check String
forall a. FromJSON a => Object -> Key -> Check a
required Object
o Key
K.Title
            Check
  (String
   -> Status
   -> Day
   -> Maybe Day
   -> String
   -> String
   -> [String]
   -> Maybe Int
   -> Project)
-> Check String
-> Check
     (Status
      -> Day
      -> Maybe Day
      -> String
      -> String
      -> [String]
      -> Maybe Int
      -> Project)
forall a b. Check (a -> b) -> Check a -> Check b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Key -> Check String
forall a. FromJSON a => Object -> Key -> Check a
required Object
o Key
K.Href
            Check
  (Status
   -> Day
   -> Maybe Day
   -> String
   -> String
   -> [String]
   -> Maybe Int
   -> Project)
-> Check Status
-> Check
     (Day
      -> Maybe Day
      -> String
      -> String
      -> [String]
      -> Maybe Int
      -> Project)
forall a b. Check (a -> b) -> Check a -> Check b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Key -> Check Status
forall a. FromJSON a => Object -> Key -> Check a
required Object
o Key
K.Status
            Check
  (Day
   -> Maybe Day
   -> String
   -> String
   -> [String]
   -> Maybe Int
   -> Project)
-> Check Day
-> Check
     (Maybe Day -> String -> String -> [String] -> Maybe Int -> Project)
forall a b. Check (a -> b) -> Check a -> Check b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Key -> Check Day
forall a. FromJSON a => Object -> Key -> Check a
required Object
o Key
K.StartDate
            Check
  (Maybe Day -> String -> String -> [String] -> Maybe Int -> Project)
-> Check (Maybe Day)
-> Check (String -> String -> [String] -> Maybe Int -> Project)
forall a b. Check (a -> b) -> Check a -> Check b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Key -> Check (Maybe Day)
forall a. FromJSON a => Object -> Key -> Check (Maybe a)
optional Object
o Key
K.EndDate
            Check (String -> String -> [String] -> Maybe Int -> Project)
-> Check String
-> Check (String -> [String] -> Maybe Int -> Project)
forall a b. Check (a -> b) -> Check a -> Check b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Key -> Check String
forall a. FromJSON a => Object -> Key -> Check a
required Object
o Key
K.ShortDescription
            Check (String -> [String] -> Maybe Int -> Project)
-> Check String -> Check ([String] -> Maybe Int -> Project)
forall a b. Check (a -> b) -> Check a -> Check b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Key -> Check String
forall a. FromJSON a => Object -> Key -> Check a
required Object
o Key
K.LongDescription
            Check ([String] -> Maybe Int -> Project)
-> Check [String] -> Check (Maybe Int -> Project)
forall a b. Check (a -> b) -> Check a -> Check b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> ([String] -> Maybe [String] -> [String]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [String] -> [String])
-> Check (Maybe [String]) -> Check [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object -> Key -> Check (Maybe [String])
forall a. FromJSON a => Object -> Key -> Check (Maybe a)
optional Object
o Key
K.Team)
            Check (Maybe Int -> Project) -> Check (Maybe Int) -> Check Project
forall a b. Check (a -> b) -> Check a -> Check b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Key -> Check (Maybe Int)
forall a. FromJSON a => Object -> Key -> Check (Maybe a)
optional Object
o Key
K.Priority
        )
        Either [String] Project
-> (Project -> Either [String] Project) -> Either [String] Project
forall a b.
Either [String] a -> (a -> Either [String] b) -> Either [String] b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Project -> Either [String] Project
endsAfterStart
  where
    endsAfterStart :: Project -> Either [String] Project
endsAfterStart Project
project = case Project -> Maybe Day
projectEnd Project
project of
        Just Day
end | Day
end Day -> Day -> Bool
forall a. Ord a => a -> a -> Bool
< Project -> Day
projectStart Project
project -> [String] -> Either [String] Project
forall a b. a -> Either a b
Left [String
"endDate: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Day -> String
forall a. Show a => a -> String
show Day
end String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" is before startDate " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Day -> String
forall a. Show a => a -> String
show (Project -> Day
projectStart Project
project)]
        Maybe Day
_ -> Project -> Either [String] Project
forall a b. b -> Either a b
Right Project
project

-- | 'parseProject' on the contents of a project file
parseProjectYaml :: String -> Either [String] Project
parseProjectYaml :: String -> Either [String] Project
parseProjectYaml = String -> Either [String] Object
forall a. FromJSON a => String -> Either [String] a
decodeYaml (String -> Either [String] Object)
-> (Object -> Either [String] Project)
-> String
-> Either [String] Project
forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> Object -> Either [String] Project
parseProject