packages feed

opt-env-conf-0.4.0.3: src/OptEnvConf/Nix.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}

module OptEnvConf.Nix where

import Autodocodec
import Autodocodec.Nix
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NE
import Data.Map (Map)
import qualified Data.Map as M
import Data.Text (Text)
import qualified Data.Text as T
import OptEnvConf.Parser
import OptEnvConf.Setting

renderSettingsNixOptions :: forall a. (HasParser a) => Text
renderSettingsNixOptions = renderParserNixOptions (settingsParser :: Parser a)

renderParserNixOptions :: Parser a -> Text
renderParserNixOptions = renderExpr . parserNixOptionExpr

parserNixOptionExpr :: Parser a -> Expr
parserNixOptionExpr = withNixArgs . optionsExpr . parserNixOptions

parserNixOptions :: Parser a -> Map Text Option
parserNixOptions = go
  where
    go :: Parser a -> Map Text Option
    go = \case
      ParserPure _ -> M.empty
      ParserAp p1 p2 -> M.unionWith combineOption (go p1) (go p2)
      ParserSelect p1 p2 -> M.unionWith combineOption (go p1) (go p2)
      ParserEmpty _ -> M.empty
      ParserAlt p1 p2 -> M.unionWith combineOption (go p1) (go p2) -- TODO is this right?
      ParserMany p -> go p
      ParserSome p -> go p
      ParserAllOrNothing _ p -> go p
      ParserCheck _ _ _ p -> go p
      ParserCommands _ cs -> M.unionsWith combineOption $ map goCommand cs
      ParserWithConfig _ p1 p2 ->
        -- I'm not sure if we need the first as well because you wouldn't use a
        -- config to load a config but it's technically possible so let's
        -- support it.
        M.unionWith combineOption (go p1) (go p2)
      ParserSetting _ s ->
        let codecTups = maybe [] NE.toList (settingConfigVals s)
         in M.unionsWith combineOption $ flip map codecTups $ \ConfigValSetting {..} ->
              let go' :: NonEmpty Text -> Map Text Option
                  go' (p :| ps) = case NE.nonEmpty ps of
                    Nothing ->
                      let oc =
                            maybe
                              (optionalFieldWith' p configValSettingCodec)
                              (optionalFieldWith p configValSettingCodec)
                              (T.pack <$> settingHelp s)
                       in objectCodecNixOptions oc
                    Just rest ->
                      let m = go' rest
                       in M.singleton p $ emptyOption {optionType = Just (OptionTypeSubmodule m)}
               in go' $ NE.map T.pack configValSettingPath
    combineOption :: Option -> Option -> Option
    combineOption o1 o2 = case (optionType o1, optionType o2) of
      (Nothing, _) -> o2
      (Just ot1, Nothing) -> o2 {optionType = Just ot1}
      (Just ot1, Just ot2) -> o2 {optionType = Just $ combineOptionType ot1 ot2}

    combineOptionType :: OptionType -> OptionType -> OptionType
    combineOptionType ot1 ot2 = simplifyOptionType $ case (ot1, ot2) of
      (OptionTypeSubmodule m1, OptionTypeSubmodule m2) -> OptionTypeSubmodule $ M.unionWith combineOption m1 m2
      _ -> OptionTypeOneOf [ot1, ot2]
    goCommand :: Command a -> Map Text Option
    goCommand = go . commandParser