schedule-planner-1.0.1.1: src/SchedulePlanner/Serialize.hs
{-# LANGUAGE OverloadedStrings #-}
{-|
Module : $Header$
Description : (de)serializing in- and output
Copyright : (c) Justus Adam, 2015
License : LGPL-3
Maintainer : development@justusadam.com
Stability : experimental
Portability : POSIX
Hold the capeablilities to get and export in- and output data as well as (de)serialize it
-}
module SchedulePlanner.Serialize
( mapToJSON
, formatSchedule
, shortSubject
, DataFile(DataFile)
, eitherDecode
, scheduleToJson
) where
import Control.Arrow as Arrow (first, second, (***))
import Control.Monad (mzero)
import Data.Aeson (FromJSON, Object, ToJSON,
Value (Object), eitherDecode,
object, parseJSON, toJSON, (.:),
(.=))
import Data.Aeson.Types (Parser)
import qualified Data.Composition as Comp ((.:))
import Data.List as List (intercalate)
import qualified Data.Map as Map (Map, elems, lookup, toList)
import Data.Text as T (Text, pack, unpack)
import SchedulePlanner.Calculator (MappedSchedule (..), totalWeight)
import SchedulePlanner.Types
import Text.Printf (printf)
-- | Key for the rules data in the json input
ruleKey :: Text
ruleKey = "rules"
-- | Key for the lesson data in the json input
lessonKey :: Text
lessonKey = "lessons"
-- | Key for the scope property in Rule objects in the json input
scopeKey :: Text
scopeKey = "scope"
-- | Key for the severity property in Rule objects in the json input
severityKey :: Text
severityKey = "severity"
-- | Key for the day property in Rule objects in the json input
ruleDayKey :: Text
ruleDayKey = "day"
-- | Key for the slot property in Rule objects in the json input
ruleSlotKey :: Text
ruleSlotKey = "slot"
-- | Key for the subject property in Lesson objects in the json input
subjectKey :: Text
subjectKey = "subject"
-- | Key for the day property in Lesson objects in the json input
lessonDayKey :: Text
lessonDayKey = "day"
-- | Key for the slot property in Rule objects in the json input
lessonSlotKey :: Text
lessonSlotKey = "slot"
-- | Value used in the "scope" attribute in json to indicate a slot being the target
scopeSlotVal :: Text
scopeSlotVal = "slot"
-- | Value used in the "scope" attribute in json to indicate a day being the target
scopeDayVal :: Text
scopeDayVal = "day"
-- | Value used in the "scope" attribute in json to indicate a cell being the target
scopeCellVal :: Text
scopeCellVal = "cell"
scheduleWeightKey :: Text
scheduleWeightKey = "weight"
-- | How many days a week has
daysPerWeek :: Int
daysPerWeek = 7
-- | The amount of imeslots each day
slotsPerDay :: Int
slotsPerDay = 7
-- | The caracter width of a single slot in output
cellWidth :: Int
cellWidth = 20
-- |Base structure of the input JSON file
data DataFile = DataFile [Rule] [Lesson Text] deriving (Show)
instance FromJSON a => FromJSON (Lesson a) where
parseJSON (Object o) = Lesson
<$> (Slot <$> o .: lessonSlotKey)
<*> (Day <$> o .: lessonDayKey)
<*> pure 0
<*> o .: subjectKey
parseJSON _ = mzero
instance ToJSON a => ToJSON (Lesson a) where
toJSON =
object . sequenceA
[ (.=) lessonSlotKey . unSlot . timeslot
, (.=) lessonDayKey . unDay . day
, (.=) subjectKey . subject
]
instance ToJSON Rule where
toJSON =
object . ((:)
<$> ((.=) severityKey . severity)
<*> uncurry (:) . Arrow.first (scopeKey .=) . getTarget . target)
where
getTarget :: Target -> (Text, [(Text, Value)])
getTarget (TDay d) = (scopeDayVal, [ruleDayKey .= unDay d])
getTarget (TCell c) =
second
( sequenceA
[ (ruleDayKey .=) . unDay . fst
, (ruleSlotKey .=) . unSlot . snd
])
(scopeCellVal, unCell c)
getTarget (TSlot s) = (scopeSlotVal, [ruleSlotKey .= unSlot s])
instance FromJSON Rule where
parseJSON (Object o) = Rule
<$> ((o .: scopeKey) >>= fromScope o)
<*> o .: severityKey
where
fromScope :: Object -> Text -> Parser Target
fromScope obj scope
| scope == scopeDayVal = (TDay . Day) <$> obj .: ruleDayKey
| scope == scopeSlotVal = (TSlot . Slot) <$> obj .: ruleSlotKey
| scope == scopeCellVal = ((TCell . Cell) Comp..: curry (Day *** Slot))
<$> obj .: ruleDayKey
<*> obj .: ruleSlotKey
| otherwise = error $ "unknown scope " ++ unpack scope -- I am so sorry
parseJSON _ = mzero
instance FromJSON DataFile where
parseJSON (Object o) = DataFile
<$> o .: ruleKey
<*> o .: lessonKey
parseJSON _ = mzero
instance ToJSON DataFile where
toJSON (DataFile r l) =
object
[ lessonKey .= l
, ruleKey .= r
]
{-|
Convert a single 'MappedSchedule' to a JSON 'Value'
-}
scheduleToJson :: ToJSON a => MappedSchedule a -> Value
scheduleToJson = object . sequenceA
[ (.=) lessonKey . Map.elems . unMapSchedule
, (.=) scheduleWeightKey . totalWeight
]
-- |Convert a suitable Map to a JSON Value
mapToJSON :: ToJSON a => Map.Map Text a -> Value
mapToJSON = object . map (uncurry (.=)) . Map.toList
-- |Shorten a subject to something printable
shortSubject :: Show s => s -> String
shortSubject = reverse . take cellWidth . reverse . show
{-|
Transform a 'MappedSchedule' into a printable,
and more importantly, readable Text
-}
formatSchedule :: Show s => MappedSchedule s -> Text
formatSchedule (MappedSchedule hours) = pack $ List.intercalate "\n" $ header : map formatDay allHours
where
allHours = [(i, [1..slotsPerDay]) | i <- [1..daysPerWeek]]
formatLesson :: Cell -> String
formatLesson i =
printf ("%" ++ show cellWidth ++ "v") $ maybe [] (shortSubject . subject) (Map.lookup i hours)
formatDay :: (Int, [Int]) -> String
formatDay (i, l) = List.intercalate " | " [formatLesson $ Cell (Day j, Slot i) | j <- l]
header = printf "Total Weight: %10v" (totalWeight (MappedSchedule hours))