{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE ScopedTypeVariables, RankNTypes, DeriveDataTypeable #-} -- | Run external pagers (@$PAGER@, @less@, @more@) and editors (@$VISUAL@, -- @$EDITOR@, @nano@, @pico@, @vi@). module System.Process.PagerEditor (-- * Pager pageWriter ,pageByteString ,pageBuilder ,pageFile ,pageString ,PagerException(..) -- * Editor ,editFile ,editReaderWriter ,editByteString ,editString ,EditorException(..)) where import Stack.Prelude hiding (ByteString) import Data.ByteString.Lazy (ByteString,hPut,readFile) import Data.ByteString.Builder (Builder,stringUtf8,hPutBuilder) import System.Directory (findExecutable) import System.Environment (lookupEnv) import System.Exit (ExitCode(..)) import System.FilePath (()) import System.Process (createProcess,shell,proc,waitForProcess,StdStream (CreatePipe) ,CreateProcess(std_in, close_fds, delegate_ctlc)) import System.IO (hPutStr,readFile,stdout) -- | Run pager, providing a function that writes to the pager's input. pageWriter :: (Handle -> IO ()) -> IO () pageWriter writer = do mpager <- lookupEnv "PAGER" `orElse` findExecutable "less" `orElse` findExecutable "more" case mpager of Just pager -> do (Just h,_,_,procHandle) <- createProcess (shell pager) {std_in = CreatePipe ,close_fds = True ,delegate_ctlc = True} (_::Either IOException ()) <- try (do writer h hClose h) exit <- waitForProcess procHandle case exit of ExitSuccess -> return () ExitFailure n -> throwIO (PagerExitFailure pager n) return () Nothing -> writer stdout -- | Run pager to display a lazy ByteString. pageByteString :: ByteString -> IO () pageByteString = pageWriter . flip hPut -- | Run pager to display a ByteString-Builder. pageBuilder :: Builder -> IO () pageBuilder = pageWriter . flip hPutBuilder -- | Run pager to display contents of a file. pageFile :: FilePath -> IO () pageFile p = pageByteString =<< Data.ByteString.Lazy.readFile p -- | Run pager to display a string. pageString :: String -> IO () pageString = pageBuilder . stringUtf8 -- | Run editor to edit a file. editFile :: FilePath -> IO () editFile path = do meditor <- lookupEnv "VISUAL" `orElse` lookupEnv "EDITOR" `orElse` findExecutable "nano" `orElse` findExecutable "pico" `orElse` findExecutable "vi" case meditor of Just editor -> do (_,_,_,procHandle) <- createProcess (proc "sh" ["-c", editor ++ " \"$1\"", "sh", path]) {close_fds = True,delegate_ctlc = True} exitCode <- waitForProcess procHandle case exitCode of ExitSuccess -> return () ExitFailure n -> throwIO (EditorExitFailure editor n) Nothing -> throwIO EditorNotFound -- | Run editor, providing functions to write and read the file contents. editReaderWriter :: forall a. String -> (Handle -> IO ()) -> (FilePath -> IO a) -> IO a editReaderWriter filename writer reader = withSystemTempDirectory "" (\p -> do let p' = p filename withFile p' WriteMode writer editFile p' reader p') -- | Run editor on a ByteString. editByteString :: String -> ByteString -> IO ByteString editByteString f s = editReaderWriter f (`hPut` s) Data.ByteString.Lazy.readFile -- | Run editor on a String. editString :: String -> String -> IO String editString f s = editReaderWriter f (`hPutStr` s) System.IO.readFile -- | Short-circuit first Just. orElse :: (Monad m) => m (Maybe a) -> m (Maybe a) -> m (Maybe a) orElse a b = do m <- a case m of Just _ -> return m Nothing -> b -- | Exception running pager. data PagerException = PagerNotFound | PagerExitFailure FilePath Int deriving Typeable instance Show PagerException where show PagerNotFound = "No pager found (tried $PAGER, `less`, and `more`.)" show (PagerExitFailure p n) = "Pager (`" ++ p ++ "') exited with non-zero status: " ++ show n instance Exception PagerException -- | Exception running editor. data EditorException = EditorNotFound | EditorExitFailure FilePath Int deriving Typeable instance Show EditorException where show EditorNotFound = "No editor found (tried $VISUAL, $PAGER, `nano`, `pico`, and `vi`.)" show (EditorExitFailure p n) = "Editor (`" ++ p ++ "') exited with non-zero status: " ++ show n instance Exception EditorException