module OrgStat.Config
( ConfigException (..)
, ConfDate(..)
, ConfRange(..)
, ConfReportType(..)
, ConfScope (..)
, ConfReport (..)
, OrgStatConfig (..)
) where
import Data.Aeson (FromJSON (..), Value (Object, String), (.!=),
(.:), (.:?))
import Data.Aeson.Types (typeMismatch)
import Data.Default (def)
import Data.List.NonEmpty (NonEmpty)
import qualified Data.Text as T
import Data.Time (LocalTime)
import Data.Time.Format (defaultTimeLocale, parseTimeM)
import Universum
import OrgStat.Report.Timeline (TimelineParams, tpBackground, tpColumnHeight,
tpColumnWidth, tpLegend, tpTopDay)
import OrgStat.Scope (AstPath (..), ScopeModifier (..))
import OrgStat.Util (parseColour, (??~))
data ConfigException
= ConfigParseException Text
| ConfigLogicException Text
deriving (Show, Typeable)
instance Exception ConfigException
data ConfDate
= ConfNow
| ConfLocal !LocalTime
deriving (Show)
data ConfRange
= ConfFromTo !ConfDate !ConfDate
| ConfBlockWeek !Integer
| ConfBlockDay !Integer
| ConfBlockMonth !Integer
deriving (Show)
data ConfReportType = Timeline
{ timelineRange :: !ConfRange
, timelineScope :: !Text
, timelineParams :: !TimelineParams
} deriving (Show)
data ConfScope = ConfScope
{ csName :: !Text
, csPaths :: !(NonEmpty FilePath)
} deriving (Show)
data ConfReport = ConfReport
{ crType :: !ConfReportType
, crName :: !Text
, crModifiers :: ![ScopeModifier]
} deriving (Show)
data OrgStatConfig = OrgStatConfig
{ confScopes :: ![ConfScope]
, confReports :: ![ConfReport]
, confBaseTimelineParams :: !TimelineParams
, confTodoKeywords :: ![Text]
, confOutputDir :: !FilePath
, confColorSalt :: !Int
} deriving (Show)
instance FromJSON AstPath where
parseJSON (String s) = pure $ AstPath $ filter (not . T.null) $ T.splitOn "/" s
parseJSON invalid = typeMismatch "AstPath" invalid
instance FromJSON ScopeModifier where
parseJSON (Object v) = do
v .: "type" >>= \case
(String "prune") -> ModPruneSubtree <$> v .: "path" <*> v .:? "depth" .!= 0
(String "select") -> ModSelectSubtree <$> v .: "path"
other -> fail $ "Unsupported scope modifier type: " ++ show other
parseJSON invalid = typeMismatch "ScopeModifier" invalid
instance FromJSON ConfDate where
parseJSON (String "now") = pure $ ConfNow
parseJSON (String s) =
case parseTimeM True defaultTimeLocale "%Y-%m-%d %H:%M" (T.unpack s) of
Nothing -> fail $
"Couldn't read date " <> show s <>
". Correct format is 2016-01-01 23:59"
Just ut -> pure $ ConfLocal ut
parseJSON invalid = typeMismatch "ConfDate" invalid
instance FromJSON ConfRange where
parseJSON (String s) | any (`T.isPrefixOf` s) ["day", "week", "month"] = do
let splitted = T.splitOn "-" $ if "-" `T.isInfixOf` s then s else s <> "-"
[range, number] = splitted
constructor :: (Monad m, MonadFail m) => Integer -> m ConfRange
constructor i = case range of
"day" -> pure $ ConfBlockDay i
"week" -> pure $ ConfBlockWeek i
"month" -> pure $ ConfBlockMonth i
t -> fail $ "ConfRange@parseJSON can't parse " <> T.unpack t <>
" should be [day|week|month]"
numberParsed
| number == "" = pure 0
| otherwise = case readMaybe (T.unpack number) of
Nothing -> fail $ "Couldn't parse number modifier of " <> T.unpack s
Just x -> pure x
when (length splitted /= 2) $
fail $ "Couldn't parse range " <> T.unpack s <>
", splitted is " <> show splitted
constructor =<< numberParsed
parseJSON (Object v) = ConfFromTo <$> v .: "from" <*> v .: "to"
parseJSON invalid = typeMismatch "ConfRange" invalid
instance FromJSON TimelineParams where
parseJSON (Object v) = do
legend <- v .:? "legend"
topDay <- v .:? "topDay"
colWidth <- v .:? "colWidth"
colHeight <- v .:? "colHeight"
bgColorRaw <- v .:? "background"
pure $ def & tpLegend ??~ legend
& tpTopDay ??~ topDay
& tpColumnWidth ??~ colWidth
& tpColumnHeight ??~ colHeight
& tpBackground ??~ (T.strip <$> bgColorRaw >>= parseColour @Text)
parseJSON invalid = typeMismatch "TimelineParams" invalid
instance FromJSON ConfReportType where
parseJSON o@(Object v) = do
v .: "type" >>= \case
(String "timeline") ->
Timeline <$> v .: "range"
<*> v .:? "scope" .!= "default"
<*> parseJSON o
other -> fail $ "Unsupported scope modifier type: " ++ show other
parseJSON invalid = typeMismatch "ConfReportType" invalid
instance FromJSON ConfScope where
parseJSON (Object v) = do
ConfScope <$> v .:? "name" .!= "default" <*> v .: "paths"
parseJSON invalid = typeMismatch "ConfScope" invalid
instance FromJSON ConfReport where
parseJSON (Object v) = do
ConfReport <$> v .: "type"
<*> v .:? "name" .!= "default"
<*> v .:? "modifiers" .!= []
parseJSON invalid = typeMismatch "ConfReport" invalid
instance FromJSON OrgStatConfig where
parseJSON (Object v) = do
OrgStatConfig <$> v .: "scopes"
<*> v .: "reports"
<*> v .:? "timelineDefault" .!= def
<*> v .:? "todoKeywords" .!= []
<*> v .:? "output" .!= "./orgstat"
<*> v .:? "colorSalt" .!= 0
parseJSON invalid = typeMismatch "ConfReport" invalid