coformat-0.3.0.0: src/Language/Coformat/Pipeline.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DataKinds, GADTs #-}
{-# LANGUAGE TypeApplications, OverloadedStrings, RecordWildCards, QuasiQuotes #-}
module Language.Coformat.Pipeline
( runOptPipeline
, PipelineOpts(..)
) where
import qualified Data.ByteString.Char8 as BS
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import Control.Concurrent.Async.Pool
import Control.Monad.Except
import Control.Monad.Logger
import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.Foldable
import Data.List.NonEmpty(NonEmpty)
import Data.Maybe
import Data.String.Interpolate.IsString
import Data.Traversable
import Numeric.Natural
import Language.Coformat.Descr
import Language.Coformat.Descr.Operations
import Language.Coformat.Formatter
import Language.Coformat.Formatter.Failure
import Language.Coformat.Optimization
import Language.Coformat.Score
import Language.Coformat.Util
import Language.Coformat.Variables
data InitializeOptionsResult = InitializeOptionsResult
{ baseStyle :: T.Text
, baseScore :: Score
, baseOptions :: [ConfigItemT 'Value]
, filledOptions :: [ConfigItemT 'Value]
, userForcedOpts :: [ConfigItemT 'Value]
}
initializeOptions :: (MonadError String m, MonadLoggerIO m)
=> Formatter -> [PreparedFile] -> Maybe FilePath -> [String] -> m InitializeOptionsResult
initializeOptions Formatter { formatterInfo = formatterInfo@FormatterInfo { .. }, .. } preparedFiles maybeResumePath forceStrs = do
OptsDescription { .. } <- parseOpts execName formatterOpts
let varyingOptions = filter (not . (`elem` hardcodedOptsNames) . name) knownOpts
userForcedOpts <- parseUserOpts forceStrs knownOpts
let allFixedOpts = hardcodedOpts <> userForcedOpts
maybeResumeObj <- for maybeResumePath $ liftIO . BS.readFile >=> liftEither . parseResumeObject
maybeResumeOptions <- for maybeResumeObj $ liftEither . parseResumeOptions varyingOptions
(baseStyle, baseScore) <-
case maybeResumeOptions of
Nothing -> chooseBaseStyle formatterInfo baseStyles allFixedOpts preparedFiles
Just (baseStyle, constantOpts) -> do
let fmtAct = runFormatFiles allFixedOpts [i|Calculating the score of the resumed-from style|]
score <- convert (show @Failure) $ runReaderT fmtAct FmtEnv { .. }
pure (baseStyle, score)
logInfoN [i|Using initial style: #{baseStyle} with score of #{baseScore}|]
baseOptions <- parseOpts execName $ defaultStyleOpts baseStyle varyingOptions allFixedOpts
let filledOptions | Just (_, resumeOptions) <- maybeResumeOptions = baseOptions `replaceItemsWith` resumeOptions
| otherwise = baseOptions
pure InitializeOptionsResult { .. }
where
hardcodedOptsNames = name <$> hardcodedOpts
parseUserOpts :: (MonadError String m, ParseableConfigState f) => [String] -> [ConfigItemT f] -> m [ConfigItemT 'Value]
parseUserOpts opts baseOpts = forM opts $ splitStr >=> findBaseOpt >=> uncurry parseConfigValue
where
splitStr str | (name, _:valStr) <- break (== ':') str = pure (T.splitOn "." $ T.pack name, valStr)
| otherwise = throwError [i|Unable to parse `#{str}`: it should have the form of `name:value`|]
findBaseOpt (name, valStr) | Just item <- HM.lookup name baseOptsMap = pure (item, valStr)
| otherwise = throwError [i|Unable to find option `#{name}`|]
baseOptsMap = HM.fromList [ (name item, item) | item <- baseOpts]
data PipelineOpts = PipelineOpts
{ maxSubsetSize :: Maybe Natural
, resumePath :: Maybe FilePath
, input :: NonEmpty FilePath
, taskGroup :: TaskGroup
, forceStrs :: [String]
, formatter :: Formatter
}
runOptPipeline :: (MonadError String m, MonadLoggerIO m)
=> PipelineOpts -> m BS.ByteString
runOptPipeline PipelineOpts { formatter = formatter@Formatter { .. }, .. } = do
preparedFiles <- mapM prepareFile $ toList input
InitializeOptionsResult { .. } <- initializeOptions formatter preparedFiles resumePath forceStrs
let categoricalVariables = [ IxedVariable dv idx
| (Just dv, idx) <- zip (typToDV . value <$> filledOptions) [0..]
]
let integralVariables = [ IxedVariable dv idx
| (Just dv, idx) <- zip (typToIV . value <$> filledOptions) [0..]
]
let constantOpts = hardcodedOpts formatterInfo <> userForcedOpts
let fmtEnv = FmtEnv { .. }
let optEnv = OptEnv { maxSubsetSize = fromMaybe 1 maxSubsetSize, .. }
let optState = initOptState filledOptions baseScore
finalOptState <- convert (show @UnexpectedFailure) $ flip runReaderT (fmtEnv, optEnv, taskGroup) $ execStateT (fixGD Nothing 1) optState
pure $ serializeOptions formatterInfo baseStyle $ constantOpts <> currentOpts finalOptState `subtractMatching` baseOptions