{-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Highlight.Pipes where

import Prelude ()
import Prelude.Compat

import Control.Exception (throwIO, try)
import Control.Monad.IO.Class (MonadIO(liftIO))
import Data.ByteString (ByteString, hGetLine, hPutStr)
import Foreign.C.Error (Errno(Errno), ePIPE)
import GHC.IO.Exception
       (IOException(IOError), IOErrorType(ResourceVanished), ioe_errno,
        ioe_type)
import Pipes (Consumer', Producer', Proxy, await, each, yield)
import System.Directory (getDirectoryContents)
import System.FilePath ((</>))
import System.IO (Handle, stderr, stdin)

import Highlight.Util
       (closeHandleIfEOFOrThrow, openFilePathForReading)

-- | Read input from a 'Handle', split it into lines, and return each of those
-- lines as a 'ByteString' in a 'Producer'.
--
-- This function will close the 'Handle' if the end of the file is reached.
-- However, if an error was thrown while reading input from the 'Handle', the
-- 'Handle' is not closed.
--
-- Setup for examples:
--
-- >>> import Pipes.Prelude (toListM)
-- >>> import System.IO (IOMode(ReadMode), openBinaryFile)
-- >>> let goodFilePath = "test/golden/test-files/file2"
--
-- Examples:
--
-- >>> handle <- openBinaryFile goodFilePath ReadMode
-- >>> fmap head . toListM $ fromHandleLines handle
-- "Proud Pour is a wine company that funds solutions to local environmental"
fromHandleLines :: forall m. MonadIO m => Handle -> Producer' ByteString m ()
fromHandleLines :: Handle -> Producer' ByteString m ()
fromHandleLines Handle
handle = Proxy x' x () ByteString m ()
Producer' ByteString m ()
go
  where
    go :: Producer' ByteString m ()
    go :: Proxy x' x () ByteString m ()
go = do
      Either IOException ByteString
eitherLine <- IO (Either IOException ByteString)
-> Proxy x' x () ByteString m (Either IOException ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either IOException ByteString)
 -> Proxy x' x () ByteString m (Either IOException ByteString))
-> (IO ByteString -> IO (Either IOException ByteString))
-> IO ByteString
-> Proxy x' x () ByteString m (Either IOException ByteString)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO ByteString -> IO (Either IOException ByteString)
forall e a. Exception e => IO a -> IO (Either e a)
try (IO ByteString
 -> Proxy x' x () ByteString m (Either IOException ByteString))
-> IO ByteString
-> Proxy x' x () ByteString m (Either IOException ByteString)
forall a b. (a -> b) -> a -> b
$ Handle -> IO ByteString
hGetLine Handle
handle
      case Either IOException ByteString
eitherLine of
        Left IOException
ioerr -> Handle -> IOException -> Proxy x' x () ByteString m ()
forall (m :: * -> *). MonadIO m => Handle -> IOException -> m ()
closeHandleIfEOFOrThrow Handle
handle IOException
ioerr
        Right ByteString
line -> ByteString -> Proxy x' x () ByteString m ()
forall (m :: * -> *) a x' x. Functor m => a -> Proxy x' x () a m ()
yield ByteString
line Proxy x' x () ByteString m ()
-> Proxy x' x () ByteString m () -> Proxy x' x () ByteString m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Proxy x' x () ByteString m ()
Producer' ByteString m ()
go
{-# INLINABLE fromHandleLines #-}

-- | Call 'fromHandleLines' on 'stdin'.
stdinLines :: forall m. MonadIO m => Producer' ByteString m ()
stdinLines :: Producer' ByteString m ()
stdinLines = Handle -> Producer' ByteString m ()
forall (m :: * -> *).
MonadIO m =>
Handle -> Producer' ByteString m ()
fromHandleLines Handle
stdin
{-# INLINABLE stdinLines #-}

-- | Try calling 'fromHandleLines' on the 'Handle' obtained from
-- 'openFilePathForReading'.
--
-- Setup for examples:
--
-- >>> import Pipes (Producer)
-- >>> import Pipes.Prelude (toListM)
--
-- >>> let t a = a :: IO (Either IOException (Producer ByteString IO ()))
-- >>> let goodFilePath = "test/golden/test-files/file2"
-- >>> let badFilePath = "thisfiledoesnotexist"
-- >>> let handleErr err = error $ "got following error: " `mappend` show err
--
-- Example:
--
-- >>> eitherProducer <- t $ fromFileLines goodFilePath
-- >>> let producer = either handleErr id eitherProducer
-- >>> fmap head $ toListM producer
-- "Proud Pour is a wine company that funds solutions to local environmental"
--
-- Returns 'IOException' if there was an error when opening the file.
--
-- >>> eitherProducer <- t $ fromFileLines badFilePath
-- >>> either print (const $ return ()) eitherProducer
-- thisfiledoesnotexist: openBinaryFile: does not exist ...
fromFileLines
  :: forall m n x' x.
     (MonadIO m, MonadIO n)
  => FilePath
  -> m (Either IOException (Proxy x' x () ByteString n ()))
fromFileLines :: FilePath -> m (Either IOException (Proxy x' x () ByteString n ()))
fromFileLines FilePath
filePath = do
  Either IOException Handle
eitherHandle <- FilePath -> m (Either IOException Handle)
forall (m :: * -> *).
MonadIO m =>
FilePath -> m (Either IOException Handle)
openFilePathForReading FilePath
filePath
  case Either IOException Handle
eitherHandle of
    Left IOException
ioerr -> Either IOException (Proxy x' x () ByteString n ())
-> m (Either IOException (Proxy x' x () ByteString n ()))
forall (m :: * -> *) a. Monad m => a -> m a
return (Either IOException (Proxy x' x () ByteString n ())
 -> m (Either IOException (Proxy x' x () ByteString n ())))
-> Either IOException (Proxy x' x () ByteString n ())
-> m (Either IOException (Proxy x' x () ByteString n ()))
forall a b. (a -> b) -> a -> b
$ IOException -> Either IOException (Proxy x' x () ByteString n ())
forall a b. a -> Either a b
Left IOException
ioerr
    Right Handle
handle -> Either IOException (Proxy x' x () ByteString n ())
-> m (Either IOException (Proxy x' x () ByteString n ()))
forall (m :: * -> *) a. Monad m => a -> m a
return (Either IOException (Proxy x' x () ByteString n ())
 -> m (Either IOException (Proxy x' x () ByteString n ())))
-> (Proxy x' x () ByteString n ()
    -> Either IOException (Proxy x' x () ByteString n ()))
-> Proxy x' x () ByteString n ()
-> m (Either IOException (Proxy x' x () ByteString n ()))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Proxy x' x () ByteString n ()
-> Either IOException (Proxy x' x () ByteString n ())
forall a b. b -> Either a b
Right (Proxy x' x () ByteString n ()
 -> m (Either IOException (Proxy x' x () ByteString n ())))
-> Proxy x' x () ByteString n ()
-> m (Either IOException (Proxy x' x () ByteString n ()))
forall a b. (a -> b) -> a -> b
$ Handle -> Producer' ByteString n ()
forall (m :: * -> *).
MonadIO m =>
Handle -> Producer' ByteString m ()
fromHandleLines Handle
handle
{-# INLINABLE fromFileLines #-}

-- | Output 'ByteString's to 'stderr'.
--
-- If an 'ePIPE' error is thrown, then just 'return' @()@.  If any other error
-- is thrown, rethrow the error.
--
-- Setup for examples:
--
-- >>> :set -XOverloadedStrings
-- >>> import Pipes ((>->), runEffect)
--
-- Example:
--
-- >>> runEffect $ yield "hello" >-> stderrConsumer
-- hello
stderrConsumer :: forall m. MonadIO m => Consumer' ByteString m ()
stderrConsumer :: Consumer' ByteString m ()
stderrConsumer = Proxy () ByteString y' y m ()
Consumer' ByteString m ()
go
  where
    go :: Consumer' ByteString m ()
    go :: Proxy () ByteString y' y m ()
go = do
      ByteString
bs <- Proxy () ByteString y' y m ByteString
forall (m :: * -> *) a. Functor m => Consumer' a m a
await
      Either IOException ()
x  <- IO (Either IOException ())
-> Proxy () ByteString y' y m (Either IOException ())
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either IOException ())
 -> Proxy () ByteString y' y m (Either IOException ()))
-> IO (Either IOException ())
-> Proxy () ByteString y' y m (Either IOException ())
forall a b. (a -> b) -> a -> b
$ IO () -> IO (Either IOException ())
forall e a. Exception e => IO a -> IO (Either e a)
try (Handle -> ByteString -> IO ()
hPutStr Handle
stderr ByteString
bs)
      case Either IOException ()
x of
        Left IOError { ioe_type :: IOException -> IOErrorType
ioe_type = IOErrorType
ResourceVanished, ioe_errno :: IOException -> Maybe CInt
ioe_errno = Just CInt
ioe }
          | CInt -> Errno
Errno CInt
ioe Errno -> Errno -> Bool
forall a. Eq a => a -> a -> Bool
== Errno
ePIPE -> () -> Proxy () ByteString y' y m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Left  IOException
e  -> IO () -> Proxy () ByteString y' y m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> Proxy () ByteString y' y m ())
-> IO () -> Proxy () ByteString y' y m ()
forall a b. (a -> b) -> a -> b
$ IOException -> IO ()
forall e a. Exception e => e -> IO a
throwIO IOException
e
        Right () -> Proxy () ByteString y' y m ()
Consumer' ByteString m ()
go
{-# INLINABLE stderrConsumer #-}

-- | Select all immediate children of the given directory, ignoring @\".\"@ and
-- @\"..\"@.
--
-- Throws an 'IOException' if the directory is not readable or (on Windows) if
-- the directory is actually a reparse point.
--
-- Setup for examples:
--
-- >>> import Data.List (sort)
-- >>> import Pipes.Prelude (toListM)
--
-- Examples:
--
-- >>> fmap (head . sort) . toListM $ childOf "test/golden/test-files"
-- "test/golden/test-files/dir1"
--
-- TODO: This could be rewritten to be faster by using the Windows- and
-- Linux-specific functions to only read one file from a directory at a time
-- like the actual
-- <https://hackage.haskell.org/package/dirstream-1.0.3/docs/Data-DirStream.html#v:childOf childOf>
-- function.
childOf :: MonadIO m => FilePath -> Producer' FilePath m ()
childOf :: FilePath -> Producer' FilePath m ()
childOf FilePath
path = do
  [FilePath]
files <- IO [FilePath] -> Proxy x' x () FilePath m [FilePath]
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [FilePath] -> Proxy x' x () FilePath m [FilePath])
-> IO [FilePath] -> Proxy x' x () FilePath m [FilePath]
forall a b. (a -> b) -> a -> b
$ FilePath -> IO [FilePath]
getDirectoryContents FilePath
path
  let filteredFiles :: [FilePath]
filteredFiles = (FilePath -> Bool) -> [FilePath] -> [FilePath]
forall a. (a -> Bool) -> [a] -> [a]
filter FilePath -> Bool
isNormalFile [FilePath]
files
      fullFiles :: [FilePath]
fullFiles = (FilePath -> FilePath) -> [FilePath] -> [FilePath]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (FilePath
path FilePath -> FilePath -> FilePath
</>) [FilePath]
filteredFiles
  [FilePath] -> Proxy x' x () FilePath m ()
forall (m :: * -> *) (f :: * -> *) a x' x.
(Functor m, Foldable f) =>
f a -> Proxy x' x () a m ()
each [FilePath]
fullFiles
  where
    isNormalFile :: FilePath -> Bool
    isNormalFile :: FilePath -> Bool
isNormalFile FilePath
file = FilePath
file FilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
/= FilePath
"." Bool -> Bool -> Bool
&& FilePath
file FilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
/= FilePath
".."
{-# INLINABLE childOf #-}