stack-3.9.1: src/System/Process/Pager.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-|
Module : System.Process.Pager
Description : Run external pagers (@$PAGER@, @less@, @more@).
License : BSD-3-Clause
Run external pagers (@$PAGER@, @less@, @more@).
-}
module System.Process.Pager
( pageWriter
, pageText
, PagerException (..)
) where
import Control.Monad.Trans.Maybe ( MaybeT (runMaybeT, MaybeT) )
import qualified Data.Text.IO as T
import Stack.Prelude
import System.Directory ( findExecutable )
import System.Environment ( lookupEnv )
import System.Process
( createProcess, cmdspec, shell, proc, waitForProcess
, CmdSpec (ShellCommand, RawCommand)
, StdStream (CreatePipe)
, CreateProcess (std_in, close_fds, delegate_ctlc)
)
-- | Type representing exceptions thrown by functions exported by the
-- "System.Process.Pager" module.
data PagerException
= PagerExitFailure CmdSpec Int
deriving Show
instance Exception PagerException where
displayException (PagerExitFailure cmd n) =
let getStr (ShellCommand c) = c
getStr (RawCommand exePath _) = exePath
in concat
[ "Error: [S-9392]\n"
, "Pager (`"
, getStr cmd
, "') exited with non-zero status: "
, show n
]
-- | Run pager, providing a function that writes to the pager's input.
pageWriter :: (Handle -> IO ()) -> IO ()
pageWriter writer = do
let alternatives =
cmdspecFromEnvVar
<|> cmdspecFromExeName "less"
<|> cmdspecFromExeName "more"
runMaybeT alternatives >>= \case
Just pager -> do
(Just h,_,_,procHandle) <- createProcess pager
{ std_in = CreatePipe
, close_fds = True
, delegate_ctlc = True
}
(_ :: Either IOException ()) <- try (do writer h; hClose h)
waitForProcess procHandle >>= \case
ExitSuccess -> pure ()
ExitFailure n -> throwIO (PagerExitFailure (cmdspec pager) n)
Nothing -> writer stdout
where
cmdspecFromEnvVar = shell <$> MaybeT (lookupEnv "PAGER")
cmdspecFromExeName =
fmap (\command -> proc command []) . MaybeT . findExecutable
-- | Run pager to display a 'Text'
pageText :: Text -> IO ()
pageText = pageWriter . flip T.hPutStr