{-# LANGUAGE TypeFamilies, CPP #-}
-- | Simple interface for shell scripting-like tasks.
module Control.Shell
  ( -- * Running Shell programs
    Shell, ExitReason (..)
  , shell, shell_, exitString

    -- * Error handling and control flow
  , (|>), capture, captureStdErr, capture2, stream, lift
  , try, orElse, exit
  , Guard (..), guard, when, unless

    -- * Environment handling
  , withEnv, withoutEnv, lookupEnv, getEnv, cmdline

    -- * Running external commands
  , MonadIO (..), Env (..)
  , run, sudo
  , unsafeLiftIO
  , absPath, shellEnv, getShellEnv, joinResult, runSh

    -- * Working with directories
  , cpdir, pwd, ls, mkdir, rmdir, inDirectory, isDirectory
  , withHomeDirectory, inHomeDirectory, withAppDirectory, inAppDirectory
  , forEachFile, forEachFile_, forEachDirectory, forEachDirectory_

    -- * Working with files
  , isFile, rm, mv, cp, input, output
  , withFile, withBinaryFile, openFile, openBinaryFile

    -- * Working with temporary files and directories
  , FileMode (..)
  , withTempFile, withCustomTempFile
  , withTempDirectory, withCustomTempDirectory
  , inTempDirectory, inCustomTempDirectory

    -- * Working with handles
  , Handle, IOMode (..), BufferMode (..)
  , hFlush, hClose, hReady
  , hGetBuffering, hSetBuffering
  , getStdIn, getStdOut, getStdErr

    -- * Text I/O
  , hPutStr, hPutStrLn, echo, echo_, ask, stdin
  , hGetLine, hGetContents

    -- * Terminal text formatting
  , module Control.Shell.Color

    -- * ByteString I/O
  , hGetBytes, hPutBytes, hGetByteLine, hGetByteContents

    -- * Convenient re-exports
  , module System.FilePath
  , module Control.Monad
  ) where
import qualified System.Environment as Env
import System.IO.Unsafe
import Control.Shell.Base hiding (getEnv, takeEnvLock, releaseEnvLock, setShellEnv)
import qualified Control.Shell.Base as CSB
import Control.Shell.Handle
import Control.Shell.File
import Control.Shell.Directory
import Control.Shell.Temp
import Control.Shell.Control
import Control.Shell.Color
import Control.Monad hiding (guard, when, unless)
import System.FilePath

-- | Convert an 'ExitReason' into a 'String'. Successful termination yields
--   the empty string, while abnormal termination yields the termination
--   error message. If the program terminaged abnormally but without an error
--   message - i.e. the error message is empty string - the error message will
--   be shown as @"abnormal termination"@.
exitString :: ExitReason -> String
exitString Success      = ""
exitString (Failure "") = "abnormal termination"
exitString (Failure s)  = s

-- | Get the complete environment for the current computation.
getShellEnv :: Shell Env
getShellEnv = CSB.getEnv

insert :: Eq k => k -> v -> [(k, v)] -> [(k, v)]
insert k' v' (kv@(k, _) : kvs)
  | k == k'   = (k', v') : kvs
  | otherwise = kv : insert k' v' kvs
insert k v _  = [(k, v)]

delete :: Eq k => k -> [(k, v)] -> [(k, v)]
delete k' (kv@(k, _) : kvs)
  | k == k'   = kvs
  | otherwise = kv : delete k' kvs
delete _ _    = []

-- | The executable's command line arguments.
cmdline :: [String]
cmdline = unsafePerformIO Env.getArgs

-- | Run a computation with the given environment variable set.
withEnv :: String -> String -> Shell a -> Shell a
withEnv k v m = do
  e <- CSB.getEnv
  inEnv (e {envEnvVars = insert k v (envEnvVars e)}) m

-- | Run a computation with the given environment variable unset.
withoutEnv :: String -> Shell a -> Shell a
withoutEnv k m = do
  e <- CSB.getEnv
  inEnv (e {envEnvVars = delete k (envEnvVars e)}) m

-- | Get the value of an environment variable. Returns Nothing if the variable 
--   doesn't exist.
lookupEnv :: String -> Shell (Maybe String)
lookupEnv k = lookup k . envEnvVars <$> CSB.getEnv

-- | Get the value of an environment variable. Returns the empty string if
--   the variable doesn't exist.
getEnv :: String -> Shell String
getEnv key = maybe "" id `fmap` lookupEnv key

-- | Run a command with elevated privileges.
sudo :: FilePath -> [String] -> Shell ()
sudo cmd as = run "sudo" (cmd:"--":as)

-- | Performs a command inside a temporary directory. The directory will be
--   cleaned up after the command finishes.
inTempDirectory :: Shell a -> Shell a
inTempDirectory = withTempDirectory . flip inDirectory

-- | Performs a command inside a temporary directory. The directory will be
--   cleaned up after the command finishes.
inCustomTempDirectory :: FilePath -> Shell a -> Shell a
inCustomTempDirectory dir m = withCustomTempDirectory dir $ flip inDirectory m

-- | Get the standard input, output and error handle respectively.
getStdIn, getStdOut, getStdErr :: Shell Handle
getStdIn  = envStdIn  <$> CSB.getEnv
getStdOut = envStdOut <$> CSB.getEnv
getStdErr = envStdErr <$> CSB.getEnv
