{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}

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

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

-- |
-- Module      :  Distribution.Simple.Program.ResponseFile
-- Copyright   :  (c) Sergey Vinokurov 2017
-- License     :  BSD3-style
--
-- Maintainer  :  cabal-devel@haskell.org
-- Created     :  23 July 2017
module Distribution.Simple.Program.ResponseFile (withResponseFile) where

import System.IO (TextEncoding, hClose, hPutStr, hSetEncoding)
import Prelude ()

import Distribution.Compat.Prelude
import Distribution.Simple.Utils (TempFileOptions, debug, withTempFileEx)
import Distribution.Verbosity

withResponseFile
  :: Verbosity
  -> TempFileOptions
  -> FilePath
  -- ^ Working directory to create response file in.
  -> FilePath
  -- ^ Template for response file name.
  -> Maybe TextEncoding
  -- ^ Encoding to use for response file contents.
  -> [String]
  -- ^ Arguments to put into response file.
  -> (FilePath -> IO a)
  -> IO a
withResponseFile :: forall a.
Verbosity
-> TempFileOptions
-> FilePath
-> FilePath
-> Maybe TextEncoding
-> [FilePath]
-> (FilePath -> IO a)
-> IO a
withResponseFile Verbosity
verbosity TempFileOptions
tmpFileOpts FilePath
workDir FilePath
fileNameTemplate Maybe TextEncoding
encoding [FilePath]
arguments FilePath -> IO a
f =
  TempFileOptions
-> FilePath -> FilePath -> (FilePath -> Handle -> IO a) -> IO a
forall a.
TempFileOptions
-> FilePath -> FilePath -> (FilePath -> Handle -> IO a) -> IO a
withTempFileEx TempFileOptions
tmpFileOpts FilePath
workDir FilePath
fileNameTemplate ((FilePath -> Handle -> IO a) -> IO a)
-> (FilePath -> Handle -> IO a) -> IO a
forall a b. (a -> b) -> a -> b
$ \FilePath
responseFileName Handle
hf -> do
    (TextEncoding -> IO ()) -> Maybe TextEncoding -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ (Handle -> TextEncoding -> IO ()
hSetEncoding Handle
hf) Maybe TextEncoding
encoding
    let responseContents :: FilePath
responseContents = [FilePath] -> FilePath
unlines ([FilePath] -> FilePath) -> [FilePath] -> FilePath
forall a b. (a -> b) -> a -> b
$ (FilePath -> FilePath) -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map FilePath -> FilePath
escapeResponseFileArg [FilePath]
arguments
    Handle -> FilePath -> IO ()
hPutStr Handle
hf FilePath
responseContents
    Handle -> IO ()
hClose Handle
hf
    Verbosity -> FilePath -> IO ()
debug Verbosity
verbosity (FilePath -> IO ()) -> FilePath -> IO ()
forall a b. (a -> b) -> a -> b
$ FilePath
responseFileName FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ FilePath
" contents: <<<"
    Verbosity -> FilePath -> IO ()
debug Verbosity
verbosity FilePath
responseContents
    Verbosity -> FilePath -> IO ()
debug Verbosity
verbosity (FilePath -> IO ()) -> FilePath -> IO ()
forall a b. (a -> b) -> a -> b
$ FilePath
">>> " FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ FilePath
responseFileName
    FilePath -> IO a
f FilePath
responseFileName

-- Support a gcc-like response file syntax.  Each separate
-- argument and its possible parameter(s), will be separated in the
-- response file by an actual newline; all other whitespace,
-- single quotes, double quotes, and the character used for escaping
-- (backslash) are escaped.  The called program will need to do a similar
-- inverse operation to de-escape and re-constitute the argument list.
escapeResponseFileArg :: String -> String
escapeResponseFileArg :: FilePath -> FilePath
escapeResponseFileArg = FilePath -> FilePath
forall a. [a] -> [a]
reverse (FilePath -> FilePath)
-> (FilePath -> FilePath) -> FilePath -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (FilePath -> Char -> FilePath) -> FilePath -> FilePath -> FilePath
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' FilePath -> Char -> FilePath
escape []
  where
    escape :: String -> Char -> String
    escape :: FilePath -> Char -> FilePath
escape FilePath
cs Char
c =
      case Char
c of
        Char
'\\' -> Char
c Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: Char
'\\' Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: FilePath
cs
        Char
'\'' -> Char
c Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: Char
'\\' Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: FilePath
cs
        Char
'"' -> Char
c Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: Char
'\\' Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: FilePath
cs
        Char
_
          | Char -> Bool
isSpace Char
c -> Char
c Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: Char
'\\' Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: FilePath
cs
          | Bool
otherwise -> Char
c Char -> FilePath -> FilePath
forall a. a -> [a] -> [a]
: FilePath
cs