    {-# LANGUAGE ScopedTypeVariables #-}
    {- Shell Equivalents
    Copyright (C) 2004-2009 John Goerzen <jgoerzen@complete.org>
    Please see the COPYRIGHT file
    -}

    {- |
       Module     : HSH.ShellEquivs
       Copyright  : Copyright (C) 2009 John Goerzen
       License    : GNU LGPL, version 2.1 or above

       Maintainer : John Goerzen <jgoerzen@complete.org>
       Stability  : provisional
       Portability: portable

    Copyright (c) 2006-2009 John Goerzen, jgoerzen\@complete.org

    This module provides shell-like commands.  Most, but not all, are designed
    to be used directly as part of a HSH pipeline.  All may be used outside
    HSH entirely as well.

    -}

    {-# LANGUAGE ScopedTypeVariables #-}

    #if !(defined(mingw32_HOST_OS) || defined(mingw32_TARGET_OS) || defined(__MINGW32__))
    #define __HSH_POSIX__
    #else
    #define __HSH_WINDOWS__
    #endif

    module HSH.ShellEquivs(
                           abspath,
                           appendTo,
                           basename,
                           bracketCD,
                           catFrom,
                           catBytes,
                           catBytesFrom,
                           catTo,
    #ifdef __HSH_POSIX__
                           catToFIFO,
    #endif
                           cd,
                           cut,
                           cutR,
                           dirname,
                           discard,
                           echo,
                           exit,
                           glob,
                           grep,
                           grepV,
                           egrep,
                           egrepV,
                           joinLines,
                           lower,
                           upper,
                           mkdir,
                           numberLines,
                           pwd,
    #ifdef __HSH_POSIX__
                           readlink,
                           readlinkabs,
    #endif
                           rev,
                           revW,
                           HSH.Command.setenv,
                           space,
                           unspace,
                           tac,
                           tee,
    #ifdef __HSH_POSIX__
                           teeFIFO,
    #endif
                           tr,
                           trd,
                           wcW,
                           wcL,
                           HSH.Command.unsetenv,
                           uniq,
                          ) where

    import Data.List (genericLength, intersperse, isInfixOf, nub)
    import Data.Char (toLower, toUpper)
    import Text.Regex (matchRegex, mkRegex)
    import Text.Printf (printf)
    import Control.Monad (foldM)
    import System.Directory hiding (createDirectory, isSymbolicLink)
    import qualified Control.Exception as E
    -- import System.FilePath (splitPath)

    #ifdef __HSH_POSIX__
    import System.Posix.Files (getFileStatus, isSymbolicLink, readSymbolicLink)
    import System.Posix.User (getEffectiveUserName, getUserEntryForName, homeDirectory)
    import System.Posix.Directory (createDirectory)
    import System.Posix.Types (FileMode())
    import System.Posix.IO
    import System.Posix.Error
    #endif

    import System.Path (absNormPath, bracketCWD)
    import System.Exit
    import System.IO
    import System.Process
    import qualified System.Directory as SD
    import qualified System.Path.Glob as Glob (glob)
    import qualified Data.ByteString.Lazy as BSL
    import qualified Data.ByteString as BS
    import System.IO.Unsafe(unsafeInterleaveIO)
    import HSH.Channel
    import HSH.Command(setenv, unsetenv)

    {- | Return the absolute path of the arg.  Raises an error if the
    computation is impossible. This is a thin wrapper around
    System.Path.absNormPath.  Unix/Linux users note:
    System.Path.absNormPath is known to produce odd results when
    a tilde expansion is requested; you might prefer 'glob' to this
    function if you know your input is free of wildcards. See
    https://github.com/jgoerzen/hsh/issues/7 for details. -}
    abspath :: FilePath -> IO FilePath
    abspath :: FilePath -> IO FilePath
    abspath inp :: FilePath
    inp =
        do FilePath
    p <- IO FilePath
    pwd
           case FilePath -> FilePath -> Maybe FilePath
    absNormPath FilePath
    p FilePath
    inp of
             Nothing -> FilePath -> IO FilePath
    forall (m :: * -> *) a. MonadFail m => FilePath -> m a
    fail (FilePath -> IO FilePath) -> FilePath -> IO FilePath
    forall a b. (a -> b) -> a -> b
    $ "Cannot make " FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath -> FilePath
    forall a. Show a => a -> FilePath
    show FilePath
    inp FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ " absolute within " FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++
                        FilePath -> FilePath
    forall a. Show a => a -> FilePath
    show FilePath
    p
             Just x :: FilePath
    x -> FilePath -> IO FilePath
    forall (m :: * -> *) a. Monad m => a -> m a
    return FilePath
    x

    {- | The filename part of a path -}
    basename :: FilePath -> FilePath
    basename :: FilePath -> FilePath
    basename =  (FilePath, FilePath) -> FilePath
    forall a b. (a, b) -> b
    snd ((FilePath, FilePath) -> FilePath)
    -> (FilePath -> (FilePath, FilePath)) -> FilePath -> FilePath
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> (FilePath, FilePath)
    splitpath

    {- | The directory part of a path -}
    dirname :: FilePath -> FilePath
    dirname :: FilePath -> FilePath
    dirname = (FilePath, FilePath) -> FilePath
    forall a b. (a, b) -> a
    fst ((FilePath, FilePath) -> FilePath)
    -> (FilePath -> (FilePath, FilePath)) -> FilePath -> FilePath
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> (FilePath, FilePath)
    splitpath

    {- | Changes the current working directory to the given path, executes
    the given I\/O action, then changes back to the original directory,
    even if the I\/O action raised an exception.

    This is an alias for the MissingH function System.Path.bracketCWD. -}
    bracketCD :: FilePath -> IO a -> IO a
    bracketCD :: FilePath -> IO a -> IO a
    bracketCD = FilePath -> IO a -> IO a
    forall a. FilePath -> IO a -> IO a
    bracketCWD

    {- | Load the specified files and display them, one at a time.

    The special file @-@ means to display the input.  If it is not given,
    no input is processed at all.

    @-@ may be given a maximum of one time.

    See also 'catBytes' . -}
    catFrom :: [FilePath] -> Channel -> IO Channel
    catFrom :: [FilePath] -> Channel -> IO Channel
    catFrom fplist :: [FilePath]
    fplist ichan :: Channel
    ichan =
        do ByteString
    r <- (ByteString -> FilePath -> IO ByteString)
    -> ByteString -> [FilePath] -> IO ByteString
    forall (t :: * -> *) (m :: * -> *) b a.
    (Foldable t, Monad m) =>
    (b -> a -> m b) -> b -> t a -> m b
    foldM ByteString -> FilePath -> IO ByteString
    foldfunc ByteString
    BSL.empty [FilePath]
    fplist
           Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return (ByteString -> Channel
    forall a. Channelizable a => a -> Channel
    toChannel ByteString
    r)
        where foldfunc :: ByteString -> FilePath -> IO ByteString
    foldfunc accum :: ByteString
    accum fp :: FilePath
    fp =
                      case FilePath
    fp of
                        "-" -> do ByteString
    c <- Channel -> IO ByteString
    chanAsBSL Channel
    ichan
                                  ByteString -> IO ByteString
    forall (m :: * -> *) a. Monad m => a -> m a
    return (ByteString -> ByteString -> ByteString
    BSL.append ByteString
    accum ByteString
    c)
                        fn :: FilePath
    fn -> do ByteString
    c <- FilePath -> IO ByteString
    BSL.readFile FilePath
    fn
                                 ByteString -> IO ByteString
    forall (m :: * -> *) a. Monad m => a -> m a
    return (ByteString -> ByteString -> ByteString
    BSL.append ByteString
    accum ByteString
    c)

    {- | Copy data from input to output, optionally with a fixed
    maximum size, in bytes.  Processes data using ByteStrings internally,
    so be aware of any possible UTF-8 conversions.

    You may wish to use @hSetBuffering h (BlockBuffering Nothing)@ prior to calling
    this function for optimal performance.

    See also 'catFrom', 'catBytesFrom' -}
    catBytes :: (Maybe Integer)    -- ^ Maximum amount of data to transfer
              -> Channel             -- ^ Handle for input
              -> IO Channel
    catBytes :: Maybe Integer -> Channel -> IO Channel
    catBytes count :: Maybe Integer
    count hr :: Channel
    hr = Channel -> Maybe Integer -> Channel -> IO Channel
    catBytesFrom Channel
    hr Maybe Integer
    count Channel
    hr

    {- | Generic version of 'catBytes'; reads data from specified Channel, and
    ignores stdin.
    -}

    catBytesFrom :: Channel          -- ^ Handle to read from
                 -> (Maybe Integer)  -- ^ Maximum amount of data to transfer
                 -> Channel          -- ^ Handle for input (ignored)
                 -> IO Channel
    catBytesFrom :: Channel -> Maybe Integer -> Channel -> IO Channel
    catBytesFrom (ChanHandle hr :: Handle
    hr) count :: Maybe Integer
    count cignore :: Channel
    cignore =
        case Maybe Integer
    count of
             Nothing -> Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Handle -> Channel
    ChanHandle Handle
    hr)
             Just m :: Integer
    m -> do ByteString
    c <- Handle -> Int -> IO ByteString
    BSL.hGet Handle
    hr (Integer -> Int
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral Integer
    m)
                          Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return (ByteString -> Channel
    ChanBSL ByteString
    c)
    catBytesFrom cinput :: Channel
    cinput count :: Maybe Integer
    count cignore :: Channel
    cignore =
        case Maybe Integer
    count of
          Nothing -> Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return Channel
    cinput
          Just m :: Integer
    m -> do ByteString
    r <- Channel -> IO ByteString
    chanAsBSL Channel
    cinput
                       Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return (ByteString -> Channel
    ChanBSL (Int64 -> ByteString -> ByteString
    BSL.take (Integer -> Int64
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral Integer
    m) ByteString
    r))

    {- | Takes input, writes it to the specified file, and does not pass it on.
         The return value is the empty string.  See also 'catToBS',
         'catToFIFO' -}
    catTo :: FilePath -> Channel -> IO Channel
    catTo :: FilePath -> Channel -> IO Channel
    catTo fp :: FilePath
    fp ichan :: Channel
    ichan =
        do Handle
    ofile <- FilePath -> IOMode -> IO Handle
    openFile FilePath
    fp IOMode
    WriteMode
           Bool -> Channel -> Handle -> IO ()
    chanToHandle Bool
    True Channel
    ichan Handle
    ofile
           Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return (FilePath -> Channel
    ChanString "")

    #ifdef __HSH_POSIX__

    {- | Like 'catTo', but opens the destination in ReadWriteMode instead of
    ReadOnlyMode.  Due to an oddity of the Haskell IO system, this is required
    when writing to a named pipe (FIFO) even if you will never read from it.

    This call will BLOCK all threads on open until a reader connects.

    This is provided in addition to 'catTo' because you may want to cat to
    something that you do not have permission to read from.

    This function is only available on POSIX platforms.

    See also 'catTo' -}
    catToFIFO :: FilePath -> Channel -> IO Channel
    catToFIFO :: FilePath -> Channel -> IO Channel
    catToFIFO fp :: FilePath
    fp ichan :: Channel
    ichan =
        do Handle
    h <- FilePath -> IO Handle
    fifoOpen FilePath
    fp
           Bool -> Channel -> Handle -> IO ()
    chanToHandle Bool
    True Channel
    ichan Handle
    h
           Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return (FilePath -> Channel
    ChanString "")

    fifoOpen :: FilePath -> IO Handle
    fifoOpen :: FilePath -> IO Handle
    fifoOpen fp :: FilePath
    fp =
        do Fd
    fd <- (Fd -> Bool) -> FilePath -> FilePath -> IO Fd -> IO Fd
    forall a. (a -> Bool) -> FilePath -> FilePath -> IO a -> IO a
    throwErrnoPathIf (Fd -> Fd -> Bool
    forall a. Ord a => a -> a -> Bool
    < 0) "HSH fifoOpen" FilePath
    fp (IO Fd -> IO Fd) -> IO Fd -> IO Fd
    forall a b. (a -> b) -> a -> b
    $
                 FilePath -> OpenMode -> Maybe FileMode -> OpenFileFlags -> IO Fd
    openFd FilePath
    fp OpenMode
    WriteOnly Maybe FileMode
    forall a. Maybe a
    Nothing OpenFileFlags
    defaultFileFlags
           Fd -> IO Handle
    fdToHandle Fd
    fd

    #endif

    {- | Like 'catTo', but appends to the file. -}
    appendTo :: FilePath -> String -> IO String
    appendTo :: FilePath -> FilePath -> IO FilePath
    appendTo fp :: FilePath
    fp inp :: FilePath
    inp =
        do FilePath -> FilePath -> IO ()
    appendFile FilePath
    fp FilePath
    inp
           FilePath -> IO FilePath
    forall (m :: * -> *) a. Monad m => a -> m a
    return ""

    {- | An alias for System.Directory.setCurrentDirectory.

    Want to change to a user\'s home directory?  Try this:

    > glob "~jgoerzen" >>= cd . head

    See also 'bracketCD'.
    -}
    cd :: FilePath -> IO ()
    cd :: FilePath -> IO ()
    cd = FilePath -> IO ()
    setCurrentDirectory

    {- | Split a list by a given character and select the nth list.

    > cut ' ' 2 "foo bar baz quux" -> "bar"
    -}
    cut :: Integer -> Char -> String -> String
    cut :: Integer -> Char -> FilePath -> FilePath
    cut pos :: Integer
    pos = [Integer] -> Char -> FilePath -> FilePath
    cutR [Integer
    pos]

    {- | Read all input and produce no output.  Discards input completely. -}
    discard :: Channel -> IO Channel
    discard :: Channel -> IO Channel
    discard inh :: Channel
    inh =
        do ByteString
    c <- Channel -> IO ByteString
    chanAsBSL Channel
    inh
           Int64 -> IO Int64
    forall a. a -> IO a
    E.evaluate (ByteString -> Int64
    BSL.length ByteString
    c)
           Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return (FilePath -> Channel
    ChanString "")

    {- | Split a list by a given character and select ranges of the resultant lists.

    > cutR [2..4] ' ' "foo bar baz quux foobar" -> "baz quux foobar"
    > cutR [1..1000] ' ' "foo bar baz quux foobar" -> "bar baz quux foobar"
    > cutR [-1000..1000] ' ' "foo bar baz quux foobar" -> "foo bar baz quux foobar"

       Note that too large and too small indices are essentially ignored.
    -}
    cutR :: [Integer] -> Char -> String -> String
    cutR :: [Integer] -> Char -> FilePath -> FilePath
    cutR nums :: [Integer]
    nums delim :: Char
    delim z :: FilePath
    z = Int -> FilePath -> FilePath
    forall a. Int -> [a] -> [a]
    drop 1 (FilePath -> FilePath) -> FilePath -> FilePath
    forall a b. (a -> b) -> a -> b
    $ [FilePath] -> FilePath
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [Char
    delimChar -> FilePath -> FilePath
    forall a. a -> [a] -> [a]
    :FilePath
    x | (x :: FilePath
    x, y :: Integer
    y) <- [FilePath] -> [Integer] -> [(FilePath, Integer)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [FilePath]
    string [0..], Integer -> [Integer] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    elem Integer
    y [Integer]
    nums]
         where string :: [FilePath]
    string = Char -> FilePath -> [FilePath]
    split Char
    delim FilePath
    z

    {- | Takes a string and sends it on as standard output.

    The input to this function is never read.

    You can pass this thing a String, a ByteString, or even a Handle.

    See also 'echoBS'. -}
    echo :: Channelizable a => a -> Channel -> IO Channel
    echo :: a -> Channel -> IO Channel
    echo inp :: a
    inp _ = Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Channel -> IO Channel) -> (a -> Channel) -> a -> IO Channel
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . a -> Channel
    forall a. Channelizable a => a -> Channel
    toChannel (a -> IO Channel) -> a -> IO Channel
    forall a b. (a -> b) -> a -> b
    $ a
    inp

    {- | Search for the regexp in the lines.  Return those that match. -}
    egrep :: String -> [String] -> [String]
    egrep :: FilePath -> [FilePath] -> [FilePath]
    egrep pat :: FilePath
    pat = (FilePath -> Bool) -> [FilePath] -> [FilePath]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Regex -> FilePath -> Bool
    ismatch Regex
    regex)
        where regex :: Regex
    regex = FilePath -> Regex
    mkRegex FilePath
    pat
              ismatch :: Regex -> FilePath -> Bool
    ismatch r :: Regex
    r inp :: FilePath
    inp = case Regex -> FilePath -> Maybe [FilePath]
    matchRegex Regex
    r FilePath
    inp of
                                Nothing -> Bool
    False
                                Just _ -> Bool
    True

    {- | Search for the regexp in the lines.  Return those that do NOT match. -}
    egrepV :: String -> [String] -> [String]
    egrepV :: FilePath -> [FilePath] -> [FilePath]
    egrepV pat :: FilePath
    pat = (FilePath -> Bool) -> [FilePath] -> [FilePath]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Bool -> Bool
    not (Bool -> Bool) -> (FilePath -> Bool) -> FilePath -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Regex -> FilePath -> Bool
    ismatch Regex
    regex)
        where regex :: Regex
    regex = FilePath -> Regex
    mkRegex FilePath
    pat
              ismatch :: Regex -> FilePath -> Bool
    ismatch r :: Regex
    r inp :: FilePath
    inp = case Regex -> FilePath -> Maybe [FilePath]
    matchRegex Regex
    r FilePath
    inp of
                                Nothing -> Bool
    False
                                Just _ -> Bool
    True

    {- | Exits with the specified error code. 0 indicates no error. -}
    exit :: Int -> IO a
    exit :: Int -> IO a
    exit code :: Int
    code
        | Int
    code Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == 0 = ExitCode -> IO a
    forall a. ExitCode -> IO a
    exitWith ExitCode
    ExitSuccess
        | Bool
    otherwise = ExitCode -> IO a
    forall a. ExitCode -> IO a
    exitWith (Int -> ExitCode
    ExitFailure Int
    code)

    {- | Takes a pattern.  Returns a list of names that match that pattern.
    Handles:

    >~username at beginning of file to expand to user's home dir
    >? matches exactly one character
    >* matches zero or more characters
    >[list] matches any character in list
    >[!list] matches any character not in list

    The result of a tilde expansion on a nonexistant username is to do no
    tilde expansion.

    The tilde with no username equates to the current user.

    Non-tilde expansion is done by the MissingH module System.Path.Glob. -}
    glob :: FilePath -> IO [FilePath]
    glob :: FilePath -> IO [FilePath]
    glob inp :: FilePath
    inp@('~':remainder :: FilePath
    remainder) =
        IO [FilePath] -> (SomeException -> IO [FilePath]) -> IO [FilePath]
    forall e a. Exception e => IO a -> (e -> IO a) -> IO a
    E.catch IO [FilePath]
    expanduser (\(SomeException
    e::E.SomeException) -> FilePath -> IO [FilePath]
    Glob.glob FilePath
    rest)
        where (username :: FilePath
    username, rest :: FilePath
    rest) = (Char -> Bool) -> FilePath -> (FilePath, FilePath)
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    span (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    /= '/') FilePath
    remainder
    #ifdef __HSH_POSIX__
              expanduser :: IO [FilePath]
    expanduser =
                  do FilePath
    lookupuser <-
                         if FilePath
    username FilePath -> FilePath -> Bool
    forall a. Eq a => a -> a -> Bool
    /= ""
                            then FilePath -> IO FilePath
    forall (m :: * -> *) a. Monad m => a -> m a
    return FilePath
    username
                            else IO FilePath
    getEffectiveUserName
                     UserEntry
    ue <- FilePath -> IO UserEntry
    getUserEntryForName FilePath
    lookupuser
                     FilePath -> IO [FilePath]
    Glob.glob (UserEntry -> FilePath
    homeDirectory UserEntry
    ue FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath
    rest)
    #else
              expanduser = fail "non-posix; will be caught above"
    #endif
    glob x :: FilePath
    x = FilePath -> IO [FilePath]
    Glob.glob FilePath
    x

    {- | Search for the string in the lines.  Return those that match.
    Same as:

    > grep needle = filter (isInfixOf needle)
    -}
    grep :: String -> [String] -> [String]
    grep :: FilePath -> [FilePath] -> [FilePath]
    grep = (FilePath -> Bool) -> [FilePath] -> [FilePath]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((FilePath -> Bool) -> [FilePath] -> [FilePath])
    -> (FilePath -> FilePath -> Bool)
    -> FilePath
    -> [FilePath]
    -> [FilePath]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> FilePath -> Bool
    forall a. Eq a => [a] -> [a] -> Bool
    isInfixOf

    {- | Search for the string in the lines.  Return those that do NOT match. -}
    grepV :: String -> [String] -> [String]
    grepV :: FilePath -> [FilePath] -> [FilePath]
    grepV needle :: FilePath
    needle = (FilePath -> Bool) -> [FilePath] -> [FilePath]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Bool -> Bool
    not (Bool -> Bool) -> (FilePath -> Bool) -> FilePath -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> FilePath -> Bool
    forall a. Eq a => [a] -> [a] -> Bool
    isInfixOf FilePath
    needle)

    -- | Join lines of a file
    joinLines :: [String] -> [String]
    joinLines :: [FilePath] -> [FilePath]
    joinLines = FilePath -> [FilePath]
    forall (m :: * -> *) a. Monad m => a -> m a
    return (FilePath -> [FilePath])
    -> ([FilePath] -> FilePath) -> [FilePath] -> [FilePath]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [FilePath] -> FilePath
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat

    #ifdef __HSH_POSIX__
    {- | Creates the given directory.  A value of 0o755 for mode would be typical.

    An alias for System.Posix.Directory.createDirectory.

    The second argument will be ignored on non-POSIX systems. -}
    mkdir :: FilePath -> FileMode -> IO ()
    mkdir :: FilePath -> FileMode -> IO ()
    mkdir = FilePath -> FileMode -> IO ()
    createDirectory
    #else
    mkdir :: FilePath -> a -> IO ()
    mkdir fp _ = SD.createDirectory fp
    #endif

    {- | Number each line of a file -}
    numberLines :: [String] -> [String]
    numberLines :: [FilePath] -> [FilePath]
    numberLines = (Int -> FilePath -> FilePath) -> [Int] -> [FilePath] -> [FilePath]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith (FilePath -> Int -> FilePath -> FilePath
    forall r. PrintfType r => FilePath -> r
    printf "%3d %s") [(1::Int)..]

    {- | An alias for System.Directory.getCurrentDirectory. -}
    pwd :: IO FilePath
    pwd :: IO FilePath
    pwd = IO FilePath
    getCurrentDirectory

    #ifdef __HSH_POSIX__
    {- | Return the destination that the given symlink points to.

    An alias for System.Posix.Files.readSymbolicLink

    This function is only available on POSIX platforms. -}
    readlink :: FilePath -> IO FilePath
    readlink :: FilePath -> IO FilePath
    readlink fp :: FilePath
    fp =
        do Bool
    issym <- (FilePath -> IO FileStatus
    getFileStatus FilePath
    fp IO FileStatus -> (FileStatus -> IO Bool) -> IO Bool
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= Bool -> IO Bool
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Bool -> IO Bool) -> (FileStatus -> Bool) -> FileStatus -> IO Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FileStatus -> Bool
    isSymbolicLink)
           if Bool
    issym
               then FilePath -> IO FilePath
    readSymbolicLink FilePath
    fp
               else FilePath -> IO FilePath
    forall (m :: * -> *) a. Monad m => a -> m a
    return FilePath
    fp

    {- | As 'readlink', but turns the result into an absolute path.

    This function is only available on POSIX platforms. -}
    readlinkabs :: FilePath -> IO FilePath
    readlinkabs :: FilePath -> IO FilePath
    readlinkabs inp :: FilePath
    inp =
           do Bool
    issym <- (FilePath -> IO FileStatus
    getFileStatus FilePath
    inp IO FileStatus -> (FileStatus -> IO Bool) -> IO Bool
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= Bool -> IO Bool
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Bool -> IO Bool) -> (FileStatus -> Bool) -> FileStatus -> IO Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FileStatus -> Bool
    isSymbolicLink)
              if Bool
    issym
                 then do FilePath
    rl <- FilePath -> IO FilePath
    readlink FilePath
    inp
                         case FilePath -> FilePath -> Maybe FilePath
    absNormPath (FilePath -> FilePath
    dirname FilePath
    inp) FilePath
    rl of
                           Nothing -> FilePath -> IO FilePath
    forall (m :: * -> *) a. MonadFail m => FilePath -> m a
    fail (FilePath -> IO FilePath) -> FilePath -> IO FilePath
    forall a b. (a -> b) -> a -> b
    $ "Cannot make " FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ FilePath -> FilePath
    forall a. Show a => a -> FilePath
    show FilePath
    rl FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++ " absolute within " FilePath -> FilePath -> FilePath
    forall a. [a] -> [a] -> [a]
    ++
                                      FilePath -> FilePath
    forall a. Show a => a -> FilePath
    show (FilePath -> FilePath
    dirname FilePath
    inp)
                           Just x :: FilePath
    x -> FilePath -> IO FilePath
    forall (m :: * -> *) a. Monad m => a -> m a
    return FilePath
    x
                 else FilePath -> IO FilePath
    abspath FilePath
    inp
    #endif

    {- | Reverse characters on each line (rev) -}
    rev, revW :: [String] -> [String]
    rev :: [FilePath] -> [FilePath]
    rev = (FilePath -> FilePath) -> [FilePath] -> [FilePath]
    forall a b. (a -> b) -> [a] -> [b]
    map FilePath -> FilePath
    forall a. [a] -> [a]
    reverse

    {- | Reverse words on each line -}
    revW :: [FilePath] -> [FilePath]
    revW = (FilePath -> FilePath) -> [FilePath] -> [FilePath]
    forall a b. (a -> b) -> [a] -> [b]
    map ([FilePath] -> FilePath
    unwords ([FilePath] -> FilePath)
    -> (FilePath -> [FilePath]) -> FilePath -> FilePath
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [FilePath] -> [FilePath]
    forall a. [a] -> [a]
    reverse ([FilePath] -> [FilePath])
    -> (FilePath -> [FilePath]) -> FilePath -> [FilePath]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> [FilePath]
    words)

    {- | Reverse lines in a String (like Unix tac).

    Implemented as:

    > tac = reverse

    See 'uniq'. -}
    tac :: [String] -> [String]
    tac :: [FilePath] -> [FilePath]
    tac = [FilePath] -> [FilePath]
    forall a. [a] -> [a]
    reverse

    {- | Takes input, writes it to all the specified files, and passes it on.
    This function does /NOT/ buffer input.

    See also 'catFrom'. -}
    tee :: [FilePath] -> Channel -> IO Channel
    tee :: [FilePath] -> Channel -> IO Channel
    tee fplist :: [FilePath]
    fplist inp :: Channel
    inp = (FilePath -> IO Handle) -> [FilePath] -> Channel -> IO Channel
    teeBSGeneric (\fp :: FilePath
    fp -> FilePath -> IOMode -> IO Handle
    openFile FilePath
    fp IOMode
    WriteMode) [FilePath]
    fplist Channel
    inp

    #ifdef __HSH_POSIX__
    {- | FIFO-safe version of 'tee'.

    This call will BLOCK all threads on open until a reader connects.

    This function is only available on POSIX platforms. -}
    teeFIFO :: [FilePath] -> Channel -> IO Channel
    teeFIFO :: [FilePath] -> Channel -> IO Channel
    teeFIFO fplist :: [FilePath]
    fplist inp :: Channel
    inp = (FilePath -> IO Handle) -> [FilePath] -> Channel -> IO Channel
    teeBSGeneric FilePath -> IO Handle
    fifoOpen [FilePath]
    fplist Channel
    inp
    #endif

    teeBSGeneric :: (FilePath -> IO Handle)
                 -> [FilePath]
                 -> Channel -> IO Channel
    teeBSGeneric :: (FilePath -> IO Handle) -> [FilePath] -> Channel -> IO Channel
    teeBSGeneric openfunc :: FilePath -> IO Handle
    openfunc fplist :: [FilePath]
    fplist ichan :: Channel
    ichan =
        do [Handle]
    handles <- (FilePath -> IO Handle) -> [FilePath] -> IO [Handle]
    forall (t :: * -> *) (m :: * -> *) a b.
    (Traversable t, Monad m) =>
    (a -> m b) -> t a -> m (t b)
    mapM FilePath -> IO Handle
    openfunc [FilePath]
    fplist
           ByteString
    inp <- Channel -> IO ByteString
    chanAsBSL Channel
    ichan
           [ByteString]
    resultChunks <- [Handle] -> [ByteString] -> IO [ByteString]
    hProcChunks [Handle]
    handles (ByteString -> [ByteString]
    BSL.toChunks ByteString
    inp)
           Channel -> IO Channel
    forall (m :: * -> *) a. Monad m => a -> m a
    return (ByteString -> Channel
    ChanBSL (ByteString -> Channel) -> ByteString -> Channel
    forall a b. (a -> b) -> a -> b
    $ [ByteString] -> ByteString
    BSL.fromChunks [ByteString]
    resultChunks)
        where hProcChunks :: [Handle] -> [BS.ByteString] -> IO [BS.ByteString]
              hProcChunks :: [Handle] -> [ByteString] -> IO [ByteString]
    hProcChunks handles :: [Handle]
    handles chunks :: [ByteString]
    chunks = IO [ByteString] -> IO [ByteString]
    forall a. IO a -> IO a
    unsafeInterleaveIO (IO [ByteString] -> IO [ByteString])
    -> IO [ByteString] -> IO [ByteString]
    forall a b. (a -> b) -> a -> b
    $
                  case [ByteString]
    chunks of
                    [] -> do (Handle -> IO ()) -> [Handle] -> IO ()
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ Handle -> IO ()
    hClose [Handle]
    handles
                             [ByteString] -> IO [ByteString]
    forall (m :: * -> *) a. Monad m => a -> m a
    return [ByteString
    BS.empty]
                    (x :: ByteString
    x:xs :: [ByteString]
    xs) -> do (Handle -> IO ()) -> [Handle] -> IO ()
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (\h :: Handle
    h -> Handle -> ByteString -> IO ()
    BS.hPutStr Handle
    h ByteString
    x) [Handle]
    handles
                                 [ByteString]
    remainder <- [Handle] -> [ByteString] -> IO [ByteString]
    hProcChunks [Handle]
    handles [ByteString]
    xs
                                 [ByteString] -> IO [ByteString]
    forall (m :: * -> *) a. Monad m => a -> m a
    return (ByteString
    x ByteString -> [ByteString] -> [ByteString]
    forall a. a -> [a] -> [a]
    : [ByteString]
    remainder)

    {- | Translate a character x to y, like:

    >tr 'e' 'f'

    Or, in sed,

    >y//
     -}
    tr :: Char -> Char -> String -> String
    tr :: Char -> Char -> FilePath -> FilePath
    tr a :: Char
    a b :: Char
    b = (Char -> Char) -> FilePath -> FilePath
    forall a b. (a -> b) -> [a] -> [b]
    map (\x :: Char
    x -> if Char
    x Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == Char
    a then Char
    b else Char
    x)

    {- | Delete specified character in a string. -}
    trd :: Char -> String -> String
    trd :: Char -> FilePath -> FilePath
    trd = (Char -> Bool) -> FilePath -> FilePath
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((Char -> Bool) -> FilePath -> FilePath)
    -> (Char -> Char -> Bool) -> Char -> FilePath -> FilePath
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    (/=)

    {- | Remove duplicate lines from a file (like Unix uniq).

    Takes a String representing a file or output and plugs it through lines and then nub to uniqify on a line basis. -}
    uniq :: String -> String
    uniq :: FilePath -> FilePath
    uniq = [FilePath] -> FilePath
    unlines ([FilePath] -> FilePath)
    -> (FilePath -> [FilePath]) -> FilePath -> FilePath
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [FilePath] -> [FilePath]
    forall a. Eq a => [a] -> [a]
    nub ([FilePath] -> [FilePath])
    -> (FilePath -> [FilePath]) -> FilePath -> [FilePath]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> [FilePath]
    lines

    {- | Double space a file; add an empty line between each line. -}
    space :: [String] -> [String]
    space :: [FilePath] -> [FilePath]
    space = FilePath -> [FilePath] -> [FilePath]
    forall a. a -> [a] -> [a]
    intersperse ""

    {- | Inverse of double 'space'; drop all empty lines. -}
    unspace :: [String] -> [String]
    unspace :: [FilePath] -> [FilePath]
    unspace = (FilePath -> Bool) -> [FilePath] -> [FilePath]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Bool -> Bool
    not (Bool -> Bool) -> (FilePath -> Bool) -> FilePath -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . FilePath -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null)

    {- | Convert a string to all lower case -}
    lower :: String -> String
    lower :: FilePath -> FilePath
    lower = (Char -> Char) -> FilePath -> FilePath
    forall a b. (a -> b) -> [a] -> [b]
    map Char -> Char
    toLower

    {- | Convert a string to all upper case -}
    upper :: String -> String
    upper :: FilePath -> FilePath
    upper = (Char -> Char) -> FilePath -> FilePath
    forall a b. (a -> b) -> [a] -> [b]
    map Char -> Char
    toUpper

    {- | Count number of lines.  Like wc -l -}
    wcL :: [String] -> [String]
    wcL :: [FilePath] -> [FilePath]
    wcL inp :: [FilePath]
    inp = [Integer -> FilePath
    forall a. Show a => a -> FilePath
    show ([FilePath] -> Integer
    forall i a. Num i => [a] -> i
    genericLength [FilePath]
    inp :: Integer)]

    {- | Count number of words in a file (like wc -w) -}
    wcW :: [String] -> [String]
    wcW :: [FilePath] -> [FilePath]
    wcW inp :: [FilePath]
    inp = [Integer -> FilePath
    forall a. Show a => a -> FilePath
    show (([FilePath] -> Integer
    forall i a. Num i => [a] -> i
    genericLength ([FilePath] -> Integer) -> [FilePath] -> Integer
    forall a b. (a -> b) -> a -> b
    $ FilePath -> [FilePath]
    words (FilePath -> [FilePath]) -> FilePath -> [FilePath]
    forall a b. (a -> b) -> a -> b
    $ [FilePath] -> FilePath
    unlines [FilePath]
    inp) :: Integer)]

    {- Utility function.
    > split ' ' "foo bar baz" -> ["foo","bar","baz"] -}
    split :: Char -> String -> [String]
    split :: Char -> FilePath -> [FilePath]
    split c :: Char
    c s :: FilePath
    s = case FilePath
    rest of
                  []     -> [FilePath
    chunk]
                  _:rst :: FilePath
    rst -> FilePath
    chunk FilePath -> [FilePath] -> [FilePath]
    forall a. a -> [a] -> [a]
    : Char -> FilePath -> [FilePath]
    split Char
    c FilePath
    rst
        where (chunk :: FilePath
    chunk, rest :: FilePath
    rest) = (Char -> Bool) -> FilePath -> (FilePath, FilePath)
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    break (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    ==Char
    c) FilePath
    s

    -- TODO: Perhaps simplify to make use of split
    splitpath :: String -> (String, String)
    splitpath :: FilePath -> (FilePath, FilePath)
    splitpath "" = (".", ".")
    splitpath "/" = ("/", "/")
    splitpath p :: FilePath
    p
        | FilePath -> Char
    forall a. [a] -> a
    last FilePath
    p Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == '/' = FilePath -> (FilePath, FilePath)
    splitpath (FilePath -> FilePath
    forall a. [a] -> [a]
    init FilePath
    p)
        | Bool -> Bool
    not ('/' Char -> FilePath -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `elem` FilePath
    p) = (".", FilePath
    p)
        | FilePath -> Char
    forall a. [a] -> a
    head FilePath
    p Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == '/' Bool -> Bool -> Bool
    && FilePath -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length ((Char -> Bool) -> FilePath -> FilePath
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == '/') FilePath
    p) Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == 1 = ("/", FilePath -> FilePath
    forall a. [a] -> [a]
    tail FilePath
    p)
        | Bool
    otherwise = (\(base :: FilePath
    base, dir :: FilePath
    dir) -> (FilePath -> FilePath
    forall a. [a] -> [a]
    reverse (FilePath -> FilePath
    forall a. [a] -> [a]
    tail FilePath
    dir), FilePath -> FilePath
    forall a. [a] -> [a]
    reverse FilePath
    base))
            ((Char -> Bool) -> FilePath -> (FilePath, FilePath)
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    break (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == '/') (FilePath -> FilePath
    forall a. [a] -> [a]
    reverse FilePath
    p))