-----------------------------------------------------------------------------
-- |
-- Module      :  Distribution.Simple.Program.Script
-- Copyright   :  Duncan Coutts 2009
--
-- Maintainer  :  cabal-devel@haskell.org
-- Portability :  portable
--
-- This module provides an library interface to the @hc-pkg@ program.
-- Currently only GHC and LHC have hc-pkg programs.

module Distribution.Simple.Program.Script (

    invocationAsSystemScript,
    invocationAsShellScript,
    invocationAsBatchFile,
  ) where

import Distribution.Simple.Program.Run
         ( ProgramInvocation(..) )
import Distribution.System
         ( OS(..) )

import Data.Maybe
         ( maybeToList )

-- | Generate a system script, either POSIX shell script or Windows batch file
-- as appropriate for the given system.
--
invocationAsSystemScript :: OS -> ProgramInvocation -> String
invocationAsSystemScript Windows = invocationAsBatchFile
invocationAsSystemScript _       = invocationAsShellScript


-- | Generate a POSIX shell script that invokes a program.
--
invocationAsShellScript :: ProgramInvocation -> String
invocationAsShellScript
  ProgramInvocation {
    progInvokePath  = path,
    progInvokeArgs  = args,
    progInvokeEnv   = envExtra,
    progInvokeCwd   = mcwd,
    progInvokeInput = minput
  } = unlines $
          [ "#!/bin/sh" ]
       ++ concatMap setEnv envExtra
       ++ [ "cd " ++ quote cwd | cwd <- maybeToList mcwd ]
       ++ [ (case minput of
              Nothing    -> ""
              Just input -> "echo " ++ quote input ++ " | ")
         ++ unwords (map quote $ path : args) ++ " \"$@\""]

  where
    setEnv (var, Nothing)  = ["unset " ++ var, "export " ++ var]
    setEnv (var, Just val) = ["export " ++ var ++ "=" ++ quote val]

    quote :: String -> String
    quote s = "'" ++ escape s ++ "'"

    escape []        = []
    escape ('\'':cs) = "'\\''" ++ escape cs
    escape (c   :cs) = c        : escape cs


-- | Generate a Windows batch file that invokes a program.
--
invocationAsBatchFile :: ProgramInvocation -> String
invocationAsBatchFile
  ProgramInvocation {
    progInvokePath  = path,
    progInvokeArgs  = args,
    progInvokeEnv   = envExtra,
    progInvokeCwd   = mcwd,
    progInvokeInput = minput
  } = unlines $
          [ "@echo off" ]
       ++ map setEnv envExtra
       ++ [ "cd \"" ++ cwd ++ "\"" | cwd <- maybeToList mcwd ]
       ++ case minput of
            Nothing    ->
                [ path ++ concatMap (' ':) args ]

            Just input ->
                [ "(" ]
             ++ [ "echo " ++ escape line | line <- lines input ]
             ++ [ ") | "
               ++ "\"" ++ path ++ "\""
               ++ concatMap (\arg -> ' ':quote arg) args ]

  where
    setEnv (var, Nothing)  = "set " ++ var ++ "="
    setEnv (var, Just val) = "set " ++ var ++ "=" ++ escape val

    quote :: String -> String
    quote s = "\"" ++ escapeQ s ++ "\""

    escapeQ []       = []
    escapeQ ('"':cs) = "\"\"\"" ++ escapeQ cs
    escapeQ (c  :cs) = c         : escapeQ cs

    escape []        = []
    escape ('|':cs) = "^|" ++ escape cs
    escape ('<':cs) = "^<" ++ escape cs
    escape ('>':cs) = "^>" ++ escape cs
    escape ('&':cs) = "^&" ++ escape cs
    escape ('(':cs) = "^(" ++ escape cs
    escape (')':cs) = "^)" ++ escape cs
    escape ('^':cs) = "^^" ++ escape cs
    escape (c  :cs) = c     : escape cs
