    {-# LANGUAGE FlexibleInstances, TypeSynonymInstances,
                 TypeFamilies, ExistentialQuantification #-}
    -- | This module is a wrapper for the module "Shelly".
    -- The only difference is a main type 'Sh'. In this module
    -- 'Sh' contains a list of results. Actual definition of the type 'Sh' is:
    --
    -- > import qualified Shelly as S
    -- >
    -- > newtype Sh a = Sh { unSh :: S.Sh [a] }
    --
    -- This definition can simplify some filesystem commands.
    -- A monad bind operator becomes a pipe operator and we can write
    --
    -- > findExt ext = findWhen (pure . hasExt ext)
    -- >
    -- > main :: IO ()
    -- > main = shs $ do
    -- >     mkdir "new"
    -- >     findExt "hs"  "." >>= flip cp "new"
    -- >     findExt "cpp" "." >>= rm_f
    -- >     liftIO $ putStrLn "done"
    --
    -- Monad methods "return" and ">>=" behave like methods for
    -- @ListT Shelly.Sh@, but ">>" forgets the number of
    -- the empty effects. So the last line prints @\"done\"@ only once.
    --
    -- Documentation in this module mostly just reference documentation from
    -- the main "Shelly" module.
    --
    -- > {-# LANGUAGE OverloadedStrings #-}
    -- > {-# LANGUAGE ExtendedDefaultRules #-}
    -- > {-# OPTIONS_GHC -fno-warn-type-defaults #-}
    -- > import Shelly
    -- > import Data.Text as T
    -- > default (T.Text)
    module Shelly.Pipe
           (
             -- * Entering Sh.
             Sh, shs, shelly, shellyFailDir, shsFailDir, sub, silently, verbosely, escaping, print_stdout, print_commands, tracing, errExit, log_stdout_with, log_stderr_with
             -- * List functions
             , roll, unroll, liftSh
             -- * Running external commands.
             , FoldCallback
             , run, run_, runFoldLines, cmd
             , (-|-), lastStderr, setStdin, lastExitCode
             , command, command_, command1, command1_
             , sshPairs, sshPairs_

             -- * Modifying and querying environment.
             , setenv, get_env, get_env_text, get_env_def, appendToPath, prependToPath

             -- * Environment directory
             , cd, chdir, pwd

             -- * Printing
             , echo, echo_n, echo_err, echo_n_err, inspect, inspect_err
             , tag, trace, show_command

             -- * Querying filesystem.
             , ls, lsT, test_e, test_f, test_d, test_s, which

             -- * Filename helpers
             , absPath, (</>), (<.>), canonic, canonicalize, relPath, relativeTo
             , hasExt

             -- * Manipulating filesystem.
             , mv, rm, rm_f, rm_rf, cp, cp_r, mkdir, mkdir_p, mkdirTree

             -- * reading/writing Files
             , readfile, readBinary, writefile, appendfile, touchfile, withTmpDir

             -- * exiting the program
             , exit, errorExit, quietExit, terror

             -- * Exceptions
             , catchany, catch_sh, finally_sh
             , ShellyHandler(..), catches_sh
             , catchany_sh

             -- * convert between Text and FilePath
             , toTextIgnore, toTextWarn, fromText

             -- * Utilities.
             , (<$>), whenM, unlessM, time

             -- * Re-exported for your convenience
             , liftIO, when, unless, FilePath

             -- * internal functions for writing extensions
             , get, put

             -- * find functions
             , find, findWhen, findFold
             , findDirFilter, findDirFilterWhen, findFoldDirFilter
             , followSymlink
             ) where

    import Prelude hiding (FilePath)

    import Control.Applicative
    import Control.Monad
    import Control.Monad.Trans
    import Control.Exception hiding (handle)

    import Filesystem.Path(FilePath)

    import qualified Shelly as S

    import Shelly(
          (</>), (<.>), hasExt
        , whenM, unlessM, toTextIgnore
        , fromText, catchany
        , FoldCallback)

    import Data.Maybe(fromMaybe)
    import Shelly.Base(State)
    import Data.ByteString (ByteString)

    import Data.Tree(Tree)

    import Data.Text as T hiding (concat, all, find, cons)

    -- | This type is a simple wrapper for a type @Shelly.Sh@.
    -- 'Sh' contains a list of results.
    newtype Sh a = Sh { unSh :: S.Sh [a] }

    instance Functor Sh where
        fmap f = Sh . fmap (fmap f) . unSh

    instance Monad Sh where
        return  = Sh . return . return
        a >>= f = Sh $ fmap concat $ mapM (unSh . f) =<< unSh a
        a >> b  = Sh $ unSh a >> unSh b

    instance Applicative Sh where
        pure = return
        (<*>) = ap

    instance Alternative Sh where
        empty = mzero
        (<|>) = mplus

    instance MonadPlus Sh where
        mzero = Sh $ return []
        mplus a b = Sh $ liftA2 (++) (unSh a) (unSh b)

    instance MonadIO Sh where
        liftIO = sh1 liftIO

    -------------------------------------------------------
    -- converters

    sh0 :: S.Sh a -> Sh a
    sh0 = Sh . fmap return

    sh1 :: (a -> S.Sh b) -> (a -> Sh b)
    sh1 f = \a -> sh0 (f a)

    sh2 :: (a1 -> a2 -> S.Sh b) -> (a1 -> a2 -> Sh b)
    sh2 f = \a b -> sh0 (f a b)

    sh3 :: (a1 -> a2 -> a3 -> S.Sh b) -> (a1 -> a2 -> a3 -> Sh b)
    sh3 f = \a b c -> sh0 (f a b c)

    sh4 :: (a1 -> a2 -> a3 -> a4 -> S.Sh b) -> (a1 -> a2 -> a3 -> a4 -> Sh b)
    sh4 f = \a b c d -> sh0 (f a b c d)

    sh0s :: S.Sh [a] -> Sh a
    sh0s = Sh

    sh1s :: (a -> S.Sh [b]) -> (a -> Sh b)
    sh1s f = \a -> sh0s (f a)

    {-  Just in case ...
    sh2s :: (a1 -> a2 -> S.Sh [b]) -> (a1 -> a2 -> Sh b)
    sh2s f = \a b -> sh0s (f a b)

    sh3s :: (a1 -> a2 -> a3 -> S.Sh [b]) -> (a1 -> a2 -> a3 -> Sh b)
    sh3s f = \a b c -> sh0s (f a b c)
    -}

    lift1 :: (S.Sh a -> S.Sh b) -> (Sh a -> Sh b)
    lift1 f = Sh . (mapM (f . return) =<< ) . unSh

    lift2 :: (S.Sh a -> S.Sh b -> S.Sh c) -> (Sh a -> Sh b -> Sh c)
    lift2 f a b = Sh $ join $ liftA2 (mapM2 f') (unSh a) (unSh b)
        where f' = \x y -> f (return x) (return y)

    mapM2 :: Monad m => (a -> b -> m c)-> [a] -> [b] -> m [c]
    mapM2 f as bs = sequence $ liftA2 f as bs

    -----------------------------------------------------------

    -- | Unpack list of results.
    unroll :: Sh a -> Sh [a]
    unroll = Sh . fmap return . unSh

    -- | Pack list of results. It performs @concat@ inside 'Sh'.
    roll :: Sh [a] -> Sh a
    roll = Sh . fmap concat . unSh

    -- | Transform result as list. It can be useful for filtering.
    liftSh :: ([a] -> [b]) -> Sh a -> Sh b
    liftSh f = Sh . fmap f . unSh

    ------------------------------------------------------------------
    -- Entering Sh

    -- | see 'S.shelly'
    shelly :: MonadIO m => Sh a -> m [a]
    shelly = S.shelly . unSh

    -- | Performs 'shelly' and then an empty action @return ()@.
    shs :: MonadIO m => Sh () -> m ()
    shs x = shelly x >> return ()

    -- | see 'S.shellyFailDir'
    shellyFailDir :: MonadIO m => Sh a -> m [a]
    shellyFailDir = S.shellyFailDir . unSh

    -- | Performs 'shellyFailDir' and then an empty action @return ()@.
    shsFailDir :: MonadIO m => Sh () -> m ()
    shsFailDir x = shellyFailDir x >> return ()

    -- | see 'S.sub'
    sub :: Sh a -> Sh a
    sub = lift1 S.sub

    -- See 'S.siliently'
    silently :: Sh a -> Sh a
    silently = lift1 S.silently

    -- See 'S.verbosely
    verbosely :: Sh a -> Sh a
    verbosely = lift1 S.verbosely

    -- | see 'S.escaping'
    escaping :: Bool -> Sh a -> Sh a
    escaping b = lift1 (S.escaping b)

    -- | see 'S.log_stdout_with'
    log_stdout_with :: (Text -> IO ()) -> Sh a -> Sh a
    log_stdout_with logger = lift1 (S.log_stdout_with logger)

    -- | see 'S.log_stderr_with'
    log_stderr_with :: (Text -> IO ()) -> Sh a -> Sh a
    log_stderr_with logger = lift1 (S.log_stdout_with logger)

    -- | see 'S.print_stdout'
    print_stdout :: Bool -> Sh a -> Sh a
    print_stdout b = lift1 (S.print_stdout b)

    -- | see 'S.print_commands
    print_commands :: Bool -> Sh a -> Sh a
    print_commands b = lift1 (S.print_commands b)

    -- | see 'S.tracing'
    tracing :: Bool -> Sh a -> Sh a
    tracing b = lift1 (S.tracing b)

    -- | see 'S.errExit'
    errExit :: Bool -> Sh a -> Sh a
    errExit b = lift1 (S.errExit b)

    -- | see 'S.followSymlink'
    followSymlink :: Bool -> Sh a -> Sh a
    followSymlink b = lift1 (S.followSymlink b)

    -- | see 'S.run'
    run :: FilePath -> [Text] -> Sh Text
    run a b = sh0 $ S.run a b

    -- | see 'S.run_'
    run_ :: FilePath -> [Text] -> Sh ()
    run_ a b = sh0 $ S.run_ a b

    -- | see 'S.runFoldLines'
    runFoldLines :: a -> FoldCallback a -> FilePath -> [Text] -> Sh a
    runFoldLines a cb fp ts = sh0 $ S.runFoldLines a cb fp ts

    -- | see 'S.-|-'
    (-|-) :: Sh Text -> Sh b -> Sh b
    (-|-) = lift2 (S.-|-)

    -- | see 'S.lastStderr'
    lastStderr :: Sh Text
    lastStderr = sh0 S.lastStderr

    -- | see 'S.setStdin'
    setStdin :: Text -> Sh ()
    setStdin = sh1 S.setStdin

    -- | see 'S.lastExitCode'
    lastExitCode :: Sh Int
    lastExitCode = sh0 S.lastExitCode

    -- | see 'S.command'
    command :: FilePath -> [Text] -> [Text] -> Sh Text
    command = sh3 S.command

    -- | see 'S.command_'
    command_ :: FilePath -> [Text] -> [Text] -> Sh ()
    command_ = sh3 S.command_

    -- | see 'S.command1'
    command1 :: FilePath -> [Text] -> Text -> [Text] -> Sh Text
    command1 = sh4 S.command1

    -- | see 'S.command1_'
    command1_ :: FilePath -> [Text] -> Text -> [Text] -> Sh ()
    command1_ = sh4 S.command1_

    -- | see 'S.sshPairs'
    sshPairs :: Text -> [(FilePath, [Text])] -> Sh Text
    sshPairs = sh2 S.sshPairs

    -- | see 'S.sshPairs_'
    sshPairs_ :: Text -> [(FilePath, [Text])] -> Sh ()
    sshPairs_ = sh2 S.sshPairs_

    -- | see 'S.setenv'
    setenv :: Text -> Text -> Sh ()
    setenv = sh2 S.setenv

    -- | see 'S.get_env'
    get_env :: Text -> Sh (Maybe Text)
    get_env = sh1 S.get_env

    -- | see 'S.get_env_text'
    get_env_text :: Text -> Sh Text
    get_env_text = sh1 S.get_env_text

    -- | see 'S.get_env_def'
    get_env_def :: Text -> Text -> Sh Text
    get_env_def a d = sh0 $ fmap (fromMaybe d) $ S.get_env a
    {-# DEPRECATED get_env_def "use fromMaybe DEFAULT get_env" #-}

    -- | see 'S.appendToPath'
    appendToPath :: FilePath -> Sh ()
    appendToPath = sh1 S.appendToPath

    -- | see 'S.prependToPath'
    prependToPath :: FilePath -> Sh ()
    prependToPath = sh1 S.prependToPath

    -- | see 'S.cd'
    cd :: FilePath -> Sh ()
    cd = sh1 S.cd

    -- | see 'S.chdir'
    chdir :: FilePath -> Sh a -> Sh a
    chdir p = lift1 (S.chdir p)

    -- | see 'S.pwd'
    pwd :: Sh FilePath
    pwd = sh0 S.pwd

    -----------------------------------------------------------------
    -- Printing

    -- | Echo text to standard (error, when using _err variants) output. The _n
    -- variants do not print a final newline.
    echo, echo_n_err, echo_err, echo_n :: Text -> Sh ()

    echo        = sh1 S.echo
    echo_n_err  = sh1 S.echo_n_err
    echo_err    = sh1 S.echo_err
    echo_n      = sh1 S.echo_n

    -- | see 'S.inspect'
    inspect :: Show s => s -> Sh ()
    inspect = sh1 S.inspect

    -- | see 'S.inspect_err'
    inspect_err :: Show s => s -> Sh ()
    inspect_err = sh1 S.inspect_err

    -- | see 'S.tag'
    tag :: Sh a -> Text -> Sh a
    tag a t = lift1 (flip S.tag t) a

    -- | see 'S.trace'
    trace :: Text -> Sh ()
    trace = sh1 S.trace

    -- | see 'S.show_command'
    show_command :: FilePath -> [Text] -> Text
    show_command = S.show_command

    ------------------------------------------------------------------
    -- Querying filesystem

    -- | see 'S.ls'
    ls :: FilePath -> Sh FilePath
    ls = sh1s S.ls

    -- | see 'S.lsT'
    lsT :: FilePath -> Sh Text
    lsT = sh1s S.lsT

    -- | see 'S.test_e'
    test_e :: FilePath -> Sh Bool
    test_e = sh1 S.test_e

    -- | see 'S.test_f'
    test_f :: FilePath -> Sh Bool
    test_f = sh1 S.test_f

    -- | see 'S.test_d'
    test_d :: FilePath -> Sh Bool
    test_d = sh1 S.test_d

    -- | see 'S.test_s'
    test_s :: FilePath -> Sh Bool
    test_s = sh1 S.test_s

    -- | see 'S.which
    which :: FilePath -> Sh (Maybe FilePath)
    which = sh1 S.which

    ---------------------------------------------------------------------
    -- Filename helpers

    -- | see 'S.absPath'
    absPath :: FilePath -> Sh FilePath
    absPath = sh1 S.absPath

    -- | see 'S.canonic'
    canonic :: FilePath -> Sh FilePath
    canonic = sh1 S.canonic

    -- | see 'S.canonicalize'
    canonicalize :: FilePath -> Sh FilePath
    canonicalize = sh1 S.canonicalize

    -- | see 'S.relPath'
    relPath :: FilePath -> Sh FilePath
    relPath = sh1 S.relPath

    -- | see 'S.relativeTo'
    relativeTo :: FilePath -- ^ anchor path, the prefix
               -> FilePath -- ^ make this relative to anchor path
               -> Sh FilePath
    relativeTo = sh2 S.relativeTo

    -------------------------------------------------------------
    -- Manipulating filesystem

    -- | see 'S.mv'
    mv :: FilePath -> FilePath -> Sh ()
    mv = sh2 S.mv

    -- | see 'S.rm'
    rm :: FilePath -> Sh ()
    rm = sh1 S.rm

    -- | see 'S.rm_f'
    rm_f :: FilePath -> Sh ()
    rm_f = sh1 S.rm_f

    -- | see 'S.rm_rf'
    rm_rf :: FilePath -> Sh ()
    rm_rf = sh1 S.rm_rf

    -- | see 'S.cp'
    cp :: FilePath -> FilePath -> Sh ()
    cp = sh2 S.cp

    -- | see 'S.cp_r'
    cp_r :: FilePath -> FilePath -> Sh ()
    cp_r = sh2 S.cp_r

    -- | see 'S.mkdir'
    mkdir :: FilePath -> Sh ()
    mkdir = sh1 S.mkdir

    -- | see 'S.mkdir_p'
    mkdir_p :: FilePath -> Sh ()
    mkdir_p = sh1 S.mkdir_p

    -- | see 'S.mkdirTree'
    mkdirTree :: Tree FilePath -> Sh ()
    mkdirTree = sh1 S.mkdirTree

    -- | see 'S.readFile'
    readfile :: FilePath -> Sh Text
    readfile = sh1 S.readfile

    -- | see 'S.readBinary'
    readBinary :: FilePath -> Sh ByteString
    readBinary = sh1 S.readBinary

    -- | see 'S.writeFile'
    writefile :: FilePath -> Text -> Sh ()
    writefile = sh2 S.writefile

    -- | see 'S.touchFile'
    touchfile :: FilePath -> Sh ()
    touchfile = sh1 S.touchfile

    -- | see 'S.appendFile'
    appendfile :: FilePath -> Text -> Sh ()
    appendfile = sh2 S.appendfile

    -- | see 'S.withTmpDir'
    withTmpDir :: (FilePath -> Sh a) -> Sh a
    withTmpDir f = Sh $ S.withTmpDir (unSh . f)

    -----------------------------------------------------------------
    -- find

    -- | see 'S.find'
    find :: FilePath -> Sh FilePath
    find = sh1s S.find

    -- | see 'S.findWhen'
    findWhen :: (FilePath -> Sh Bool) -> FilePath -> Sh FilePath
    findWhen p a = Sh $ S.findWhen (fmap and . unSh . p) a

    -- | see 'S.findFold'
    findFold :: (a -> FilePath -> Sh a) -> a -> FilePath -> Sh a
    findFold cons nil a = Sh $ S.findFold cons' nil' a
        where nil'  = return nil
              cons' as dir = unSh $ roll $ mapM (flip cons dir) as

    -- | see 'S.findDirFilter'
    findDirFilter :: (FilePath -> Sh Bool) -> FilePath -> Sh FilePath
    findDirFilter p a = Sh $ S.findDirFilter (fmap and . unSh . p) a

    -- | see 'S.findDirFilterWhen'
    findDirFilterWhen :: (FilePath -> Sh Bool) -- ^ directory filter
                      -> (FilePath -> Sh Bool) -- ^ file filter
                      -> FilePath -- ^ directory
                      -> Sh FilePath
    findDirFilterWhen dirPred filePred a =
        Sh $ S.findDirFilterWhen
                (fmap and . unSh . dirPred)
                (fmap and . unSh . filePred)
                a

    -- | see 'S.findFoldDirFilterWhen'
    findFoldDirFilter :: (a -> FilePath -> Sh a) -> a -> (FilePath -> Sh Bool) -> FilePath -> Sh a
    findFoldDirFilter cons nil p a = Sh $ S.findFoldDirFilter cons' nil' p' a
        where p'    = fmap and . unSh . p
              nil'  = return nil
              cons' as dir = unSh $ roll $ mapM (flip cons dir) as

    -----------------------------------------------------------
    -- exiting the program

    -- | see 'S.exit'
    exit :: Int -> Sh ()
    exit = sh1 S.exit

    -- | see 'S.errorExit'
    errorExit :: Text -> Sh ()
    errorExit = sh1 S.errorExit

    -- | see 'S.quietExit'
    quietExit :: Int -> Sh ()
    quietExit = sh1 S.quietExit

    -- | see 'S.terror'
    terror :: Text -> Sh a
    terror = sh1 S.terror

    ------------------------------------------------------------
    -- Utilities

    -- | see 'S.catch_sh'
    catch_sh :: (Exception e) => Sh a -> (e -> Sh a) -> Sh a
    catch_sh a f = Sh $ S.catch_sh (unSh a) (unSh . f)

    -- | see 'S.catchany_sh'
    catchany_sh :: Sh a -> (SomeException -> Sh a) -> Sh a
    catchany_sh = catch_sh

    -- | see 'S.finally_sh'
    finally_sh :: Sh a -> Sh b -> Sh a
    finally_sh = lift2 S.finally_sh

    -- | see 'S.time'
    time :: Sh a -> Sh (Double, a)
    time = lift1 S.time

    -- | see 'S.ShellyHandler'
    data ShellyHandler a = forall e . Exception e => ShellyHandler (e -> Sh a)

    -- | see 'S.catches_sh'
    catches_sh :: Sh a -> [ShellyHandler a] -> Sh a
    catches_sh a hs = Sh $ S.catches_sh (unSh a) (fmap convert hs)
        where convert :: ShellyHandler a -> S.ShellyHandler [a]
              convert (ShellyHandler f) = S.ShellyHandler (unSh . f)

    ------------------------------------------------------------
    -- convert between Text and FilePath

    -- | see 'S.toTextWarn'
    toTextWarn :: FilePath -> Sh Text
    toTextWarn = sh1 S.toTextWarn

    -------------------------------------------------------------
    -- internal functions for writing extension

    get :: Sh State
    get = sh0 S.get

    put :: State -> Sh ()
    put = sh1 S.put

    --------------------------------------------------------
    -- polyvariadic vodoo

    -- | Converter for the variadic argument version of 'run' called 'cmd'.
    class ShellArg a where toTextArg :: a -> Text
    instance ShellArg Text     where toTextArg = id
    instance ShellArg FilePath where toTextArg = toTextIgnore

    -- Voodoo to create the variadic function 'cmd'
    class ShellCommand t where
        cmdAll :: FilePath -> [Text] -> t

    instance ShellCommand (Sh Text) where
        cmdAll fp args = run fp args

    instance (s ~ Text, Show s) => ShellCommand (Sh s) where
        cmdAll fp args = run fp args

    -- note that Sh () actually doesn't work for its case (_<- cmd) when there is no type signature
    instance ShellCommand (Sh ()) where
        cmdAll fp args = run_ fp args

    instance (ShellArg arg, ShellCommand result) => ShellCommand (arg -> result) where
        cmdAll fp acc = \x -> cmdAll fp (acc ++ [toTextArg x])

    -- | see 'S.cmd'
    cmd :: (ShellCommand result) => FilePath -> result
    cmd fp = cmdAll fp []