packages feed

ghcup-0.2.1.0: lib-opt/GHCup/OptParse/Set.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}

module GHCup.OptParse.Set where




import GHCup.OptParse.Common

import GHCup.Command.List
import GHCup.Command.Set
import GHCup.Errors
import GHCup.Input.Parsers
    ( SetToolVersion (..)
    , ghcVersionEither'
    , resolveVersion'
    , tagEither
    , toolParser
    , toolVersionEither'
    )
import GHCup.Prelude.Logger
import GHCup.Prelude.String.QQ
import GHCup.Types

#if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail ( MonadFail )
#endif
import Control.Exception.Safe          ( MonadMask )
import Control.Monad.Reader
import Control.Monad.Trans.Resource
import Data.Bifunctor                  ( second )
import Data.Either
import Data.Functor
import Data.Maybe
import Data.Variant.Excepts
import Data.Versions
import GHC.Unicode
import Options.Applicative             hiding ( ParseError, style )
import Options.Applicative.Pretty.Shim ( text )
import Prelude                         hiding ( appendFile )
import System.Exit
import Text.PrettyPrint.HughesPJClass  ( prettyShow )

import qualified Data.Text as T




    ----------------
    --[ Commands ]--
    ----------------


data SetCommand
  = SetGHC SetOptions
  | SetCabal SetOptions
  | SetHLS SetOptions
  | SetStack SetOptions
  | SetOther SetOptionsNew
  deriving (Eq, Show)




    ---------------
    --[ Options ]--
    ---------------


data SetOptions = SetOptions
  { sToolVer :: SetToolVersion
  }
  deriving (Eq, Show)

data SetOptionsNew = SetOptionsNew
  { sTool :: Tool
  , sToolVer :: SetToolVersion
  }
  deriving (Eq, Show)




    ---------------
    --[ Parsers ]--
    ---------------


setParser :: Parser SetCommand
setParser =
  subparser
      (  command
          "ghc"
          (   SetGHC
          <$> info
                (setOpts ghc <**> helper)
                (  progDesc "Set GHC version"
                <> footerDoc (Just $ text setGHCFooter)
                )
          )
      <> command
           "cabal"
           (   SetCabal
           <$> info
                 (setOpts cabal <**> helper)
                 (  progDesc "Set Cabal version"
                 <> footerDoc (Just $ text setCabalFooter)
                 )
           )
      <> command
           "hls"
           (   SetHLS
           <$> info
                 (setOpts hls <**> helper)
                 (  progDesc "Set haskell-language-server version"
                 <> footerDoc (Just $ text setHLSFooter)
                 )
           )
      <> command
           "stack"
           (   SetStack
           <$> info
                 (setOpts stack <**> helper)
                 (  progDesc "Set stack version"
                 <> footerDoc (Just $ text setStackFooter)
                 )
           )
      )
    <|> (   SetOther
           <$> setOptsNew
           )
 where
  setGHCFooter :: String
  setGHCFooter = [s|Discussion:
    Sets the the current GHC version by creating non-versioned
    symlinks for all ghc binaries of the specified version in
    "~/.ghcup/bin/<binary>".|]

  setCabalFooter :: String
  setCabalFooter = [s|Discussion:
    Sets the the current Cabal version.|]

  setStackFooter :: String
  setStackFooter = [s|Discussion:
    Sets the the current Stack version.|]

  setHLSFooter :: String
  setHLSFooter = [s|Discussion:
    Sets the the current haskell-language-server version.|]


setOpts :: Tool -> Parser SetOptions
setOpts tool =
    SetOptions . fromMaybe SetRecommended <$>
      optional (setVersionArgument [ListInstalled True] tool)

setOptsNew :: Parser SetOptionsNew
setOptsNew = SetOptionsNew
  <$> argument (eitherReader toolParser) (metavar "TOOL")
  <*> (fromMaybe SetRecommended <$> optional (argument (eitherReader setEither) (metavar "VERSION|TAG|next")))
 where
  setEither s' =
        parseSet s'
    <|> second SetToolTag (tagEither s')
    <|> second SetGHCVersion (ghcVersionEither' s')
  parseSet s' = case fmap toLower s' of
                  "next" -> Right SetNext
                  other  -> Left $ "Unknown tag/version " <> other

setVersionArgument :: [ListCriteria] -> Tool -> Parser SetToolVersion
setVersionArgument criteria tool =
  argument (eitherReader setEither)
    (metavar "VERSION|TAG|next"
    <> completer (tagCompleter tool ["next"])
    <> (completer . versionCompleter criteria) tool)
 where
  setEither s' =
        parseSet s'
    <|> second SetToolTag (tagEither s')
    <|> se s'
  se s' = case tool of
           Tool "ghc" -> second SetGHCVersion (ghcVersionEither' s')
           _          -> second SetToolVersion (toolVersionEither' s')
  parseSet s' = case fmap toLower s' of
                  "next" -> Right SetNext
                  other  -> Left $ "Unknown tag/version " <> other




    --------------
    --[ Footer ]--
    --------------


setFooter :: String
setFooter = [s|Discussion:
  Sets the currently active GHC or cabal version. When no command is given,
  defaults to setting GHC with the specified version/tag (if no tag
  is given, sets GHC to 'recommended' version).
  It is recommended to always specify a subcommand (ghc/cabal/hls/stack).|]



    ---------------------------
    --[ Effect interpreters ]--
    ---------------------------


type SetEffects = '[ FileDoesNotExistError
                   , NotInstalled
                   , TagNotFound
                   , DayNotFound
                   , NextVerNotFound
                   , NoToolVersionSet
                   , ParseError
                   ]



    -------------------
    --[ Entrypoints ]--
    -------------------


set :: forall m. ( Monad m
       , MonadMask m
       , MonadUnliftIO m
       , MonadFail m
       )
    => SetCommand
    -> Settings
    -> (IO (AppState, IO ()), LeanAppState)
    -> m ExitCode
set setCommand settings (getAppState', leanAppstate) = case setCommand of
  (SetGHC sopts)   -> setOther (toSetOptionsNew ghc sopts)
  (SetCabal sopts) -> setOther (toSetOptionsNew cabal sopts)
  (SetHLS sopts)   -> setOther (toSetOptionsNew hls sopts)
  (SetStack sopts) -> setOther (toSetOptionsNew stack sopts)
  (SetOther sopts) -> setOther sopts

 where
  guessMode = if guessVersion settings then GLaxWithInstalled else GStrict
  runLogger = flip runReaderT leanAppstate
  run action' = do
    (appstate', up) <- liftIO getAppState'
    r <- flip runReaderT appstate'
                  . runResourceT
                  . runE
                    @SetEffects
                  $ action'
    pure (r, up)


  toSetOptionsNew sTool SetOptions{..} = SetOptionsNew{..}

  setOther :: SetOptionsNew
           -> m ExitCode
  setOther SetOptionsNew{ sTool, sToolVer } = run (do
          (TargetVersionReq v _) <- liftE $ resolveVersion' sToolVer guessMode sTool
          liftE $ setToolVersion sTool v
        )
      >>= \case
            (VRight TargetVersion{..}, up) -> do
              runLogger
                $ logInfo $
                    T.pack (prettyShow sTool) <> " " <> prettyVer _tvVersion <> " successfully set as default version" <> maybe "" (" for cross target " <>) _tvTarget
              liftIO up
              pure ExitSuccess
            (VLeft e, _) -> do
              runLogger $ logError $ T.pack $ prettyHFError e
              pure $ ExitFailure 5