packages feed

gitrev-typed-0.1: src/Development/GitRev/Internal/Environment.hs

-- | Provides utilities for querying environment variables.
--
-- @since 0.1
module Development.GitRev.Internal.Environment
  ( EnvError (..),
    envValQ,
    runInEnvDirQ,
    withEnvValQ,
  )
where

import Control.Exception (Exception (displayException))
import Control.Monad (join)
import Development.GitRev.Internal.Git.Common qualified as GitC
import Language.Haskell.TH (Q, runIO)
import Language.Haskell.TH.Syntax (Lift)
import System.Directory.OsPath qualified as Dir
import System.Environment qualified as Env
import System.OsPath qualified as OsPath

-- $setup
-- >>> import Development.GitRev.Typed (qToCode)
-- >>> import Language.Haskell.TH (Q, runIO, runQ)
-- >>> import System.Environment (setEnv)

-- | Performs an environment variable lookup in 'Q'.
--
-- ==== __Examples__
--
-- >>> setEnv "SOME_VAR" "val"
-- >>> $$(qToCode $ envValQ "SOME_VAR")
-- Right "val"
--
-- @since 0.1
envValQ ::
  -- | The environment variable @k@.
  String ->
  -- | The result @v@ or an error.
  Q (Either EnvError String)
envValQ var = withEnvValQ var pure

-- | Runs the given 'Q'-action under the directory @d@ pointed to by the
-- given environment variable.
--
-- ==== __Examples__
--
-- >>> import System.Directory (listDirectory)
-- >>> setEnv "SOME_DIR" "./src"
-- >>> $$(qToCode $ runInEnvDirQ "SOME_DIR" $ runIO (listDirectory "./"))
-- Right ["Development"]
--
-- @since 0.1
runInEnvDirQ ::
  forall a.
  -- | The environment variable @k@ that should point to some directory
  -- @d@.
  String ->
  -- | The 'Q' action @q@.
  Q a ->
  -- | The result of running @q@ in directory @d@.
  Q (Either EnvError a)
runInEnvDirQ var m = fmap join $ withEnvValQ var $ \repoDirFp -> do
  repoDirOs <- OsPath.encodeUtf repoDirFp

  -- Try to change directory
  eCurrDir <- runIO $ GitC.trySync $ do
    currDir <- Dir.getCurrentDirectory
    Dir.setCurrentDirectory repoDirOs
    pure currDir

  let mkErr = Left . MkEnvError var (Just repoDirFp)

  case eCurrDir of
    Left ex -> do
      let rsn = "Could not set directory: " ++ displayException ex
      pure $ mkErr rsn
    Right currDir -> do
      r <- m
      eResult <- runIO $ GitC.trySync $ Dir.setCurrentDirectory currDir
      case eResult of
        Left ex -> do
          let rsn = "Could not restore directory: " ++ displayException ex
          pure $ mkErr rsn
        Right _ -> pure $ Right r

-- | Environment variable lookup failure.
--
-- @since 0.1
data EnvError = MkEnvError
  { -- | The environment variable.
    --
    -- @since 0.1
    var :: String,
    -- | The value of the environment variable, if it exists.
    --
    -- @since 0.1
    value :: Maybe String,
    -- | Text reason for the failure.
    --
    -- @since 0.1
    reason :: String
  }
  deriving stock
    ( -- | @since 0.1
      Eq,
      -- | @since 0.1
      Lift,
      -- | @since 0.1
      Show
    )

-- | @since 0.1
instance Exception EnvError where
  displayException err =
    mconcat
      [ "Environment error with env variable '",
        var err,
        "', value ",
        valStr,
        ": ",
        reason err
      ]
    where
      valStr = case value err of
        Nothing -> "<none>"
        Just value -> "'" ++ value ++ "'"

-- | Runs a 'Q'-action on the result of an environment variable, if it exists.
--
-- ==== __Examples__
--
-- >>> import System.Directory (listDirectory)
-- >>> setEnv "SOME_DIR" "./src"
-- >>> $$(qToCode $ withEnvValQ "SOME_DIR" (runIO . listDirectory))
-- Right ["Development"]
--
-- @since 0.1
withEnvValQ ::
  forall a.
  -- | The environment variable @k@ to lookup.
  String ->
  -- | Function to run on @k@'s /value/ if @k@ exists.
  (String -> Q a) ->
  Q (Either EnvError a)
withEnvValQ var onEnv = do
  lookupEnvQ var >>= \case
    Nothing -> pure $ Left $ MkEnvError var Nothing "No such var found."
    Just result -> Right <$> onEnv result

lookupEnvQ :: String -> Q (Maybe String)
lookupEnvQ = runIO . Env.lookupEnv