packages feed

hackport-0.8.5.2: src/Hackport/Env.hs

{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleInstances #-}

module Hackport.Env
  (
    -- * Env monad
    Env
  , runEnv
  , HasGlobalEnv(..)
  , HasEnv (..)
    -- ** Warning buffer
  , WarningBuffer
  , getWarningBuffer
  , modifyWarningBuffer
  , displayWarnings
    -- * Global env
  , GlobalEnv (..)
    -- * Subcommand env
    -- ** List
  , ListEnv (..)
    -- ** Make Ebuild
  , MakeEbuildEnv (..)
    -- ** Status
  , StatusEnv (..)
    -- ** Merge
  , MergeEnv (..)
    -- * Metadata
  , WritesMetadata (..)
  , UseHackageRemote
    -- * Re-exports
  , module Control.Monad
  , module Control.Monad.Reader
  ) where

import Control.Monad
import Control.Monad.Reader
import Control.Monad.State.Strict (StateT, MonadState, runStateT, get, modify)
import qualified Data.DList as DL
import qualified Data.List as L
import Data.List.NonEmpty (NonEmpty (..))
import Data.Semigroup (Last(..))
import qualified Distribution.Simple.Utils as Cabal
import qualified Distribution.Verbosity as V

import Status.Types (StatusDirection)

-- | @hackport@ is noisy. Hold off on displaying any warnings until the bulk
--   of the output has been printed. We use a 'DL.DList' to hold the pending
--   warnings.
type WarningBuffer = DL.DList String

-- | Monad that carries command line information and 'WarningBuffer' state
type Env env = ReaderT (GlobalEnv, env) (StateT WarningBuffer IO)

class HasGlobalEnv m where
  -- | Convenient way to grab the 'GlobalEnv'
  askGlobalEnv :: m GlobalEnv

instance Monad m => HasGlobalEnv (ReaderT (GlobalEnv,env) m) where
  askGlobalEnv = asks fst

class HasEnv env m where
  -- | Convenient way to grab the custom @env@ data
  askEnv :: m env

instance Monad m => HasEnv env (ReaderT (a,env) m) where
  askEnv = asks snd

-- | Convenient way to get the current 'WarningBuffer' state
getWarningBuffer :: MonadState WarningBuffer m => m WarningBuffer
getWarningBuffer = get

-- | Convenient way to modify the current 'WarningBuffer' state
modifyWarningBuffer :: MonadState WarningBuffer m
  => (WarningBuffer -> WarningBuffer) -> m ()
modifyWarningBuffer = modify

-- | Read the warning buffer and output using 'Cabal.warn'.
displayWarnings :: MonadIO m => V.Verbosity -> WarningBuffer -> m ()
displayWarnings v dl = unless (null dl) $ liftIO . Cabal.warn v
  $ unlines
    -- insert an empty line between each paragraph
    $ L.intercalate [""]
      -- A list of strings forms a paragraph
      $ ["hackport emitted the following warnings:"]
        -- Add indentation to each string in the warning buffer
        -- This forms a list of paragraphs (one for each warning)
        : (indent <$> DL.toList dl)
  where
    indent :: String -> [String]
    indent = map ("  " ++) . lines

-- | Environment info that every @hackport@ subcommand needs
data GlobalEnv = GlobalEnv
  { globalVerbosity :: V.Verbosity
  , globalPathToOverlay :: Maybe FilePath
  , globalPathToPortage :: Maybe FilePath
  }
  deriving (Show, Eq, Ord)
  deriving Semigroup via Last GlobalEnv

instance Monoid GlobalEnv where
  mempty = GlobalEnv V.normal Nothing Nothing

-- | Environment info specific to the @list@ subcommand
newtype ListEnv = ListEnv
  { listPackages :: [String]
  }
  deriving (Show, Eq, Ord)

-- | Class for the @make-ebuild@ and @merge@ subcommands, which write to
--   @metadata.xml@
class WritesMetadata a where
  -- | Should @hackport@ write @hackage@ remote info to @metadata.xml@?
  useHackageRemote :: a -> UseHackageRemote

type UseHackageRemote = Bool

-- | Environment info specific to the @make-ebuild@ subcommand
data MakeEbuildEnv = MakeEbuildEnv
  { makeEbuildCategory :: String
  , makeEbuildCabalFiles :: NonEmpty FilePath
  , makeEbuildCabalFlags :: Maybe String
  , makeEbuildUseHackageRemote :: UseHackageRemote
  }
  deriving (Show, Eq, Ord)

instance WritesMetadata MakeEbuildEnv where
  useHackageRemote = makeEbuildUseHackageRemote

-- | Environment info specific to the @status@ subcommand
data StatusEnv = StatusEnv
  { statusDirection :: StatusDirection
  , statusPackages :: [String]
  }
  deriving (Eq)

-- | Environment info specific to the @merge@ subcommand
data MergeEnv = MergeEnv
  { mergeCabalFlags :: Maybe String
  , mergePackage :: String
  }

instance WritesMetadata MergeEnv where
  useHackageRemote _ = True

-- | Run with environment information. This calls 'displayWarnings' at the
--   end of the execution.
runEnv
  :: Env env a
  -> env
  -> GlobalEnv
  -> IO a
runEnv env e global = do
    (a, dl) <- runStateT (runReaderT env (global, e)) DL.empty
    displayWarnings (V.verboseNoWrap (globalVerbosity global)) dl
    pure a