packages feed

rewrite-0.2: rewrite.hs

module Main (main) where

import Data.Either (partitionEithers)
import qualified System.Console.MultiArg as MA
import qualified System.IO as IO
import System.IO.Temp (withSystemTempDirectory)
import qualified System.Exit as Exit
import qualified System.Directory as D
import qualified System.Process as P

help :: String -> String
help pn = unlines
  [ "usage: " ++ pn ++ "[options] FILE PROGRAM [program options]"
  , "opens given FILE and feeds it to standard input of PROGRAM"
  , "and writes PROGRAM output back to FILE."
  , ""
  , "Aborts if PROGRAM exits with non-zero exit status."
  , ""
  , "Options:"
  , "  -h, --help          Show help and exit"
  , "  -b, --backup SUFFIX Back up FILE to file with given SUFFIX"
  , "                      before writing new file"
  ]

type Backup = String
type ProgramOpt = String
type ProgName = String
type InputFile = String

errExit :: String -> IO a
errExit msg = do
  pn <- MA.getProgName
  IO.hPutStrLn IO.stderr $ pn ++ ": error: " ++ msg
  Exit.exitFailure

-- | Parses command line arguments; returns whether to do backup and
-- positional arguments
parseArgs :: IO (Maybe Backup, InputFile, ProgName, [ProgramOpt])
parseArgs = do
  let opts = [ MA.OptSpec ["backup"] "b" (MA.OneArg Left) ]
  as <- MA.simpleWithHelp help MA.StopOptions opts (return . Right)
  let (baks, os) = partitionEithers as
  bak <- case baks of
    [] -> return Nothing
    x:[] -> if null x
            then errExit "empty backup suffix given"
            else return $ Just x
    _ -> errExit "multiple backup suffixes given"
  (input, name, progOpts) <- case os of
    [] -> errExit "no input file or program name given"
    _:[] -> errExit "no program name given"
    x:y:xs -> return (x, y, xs)
  return (bak, input, name, progOpts)


doBackup :: InputFile -> Backup -> IO ()
doBackup inf bak = D.copyFile inf (inf ++ "." ++ bak)


runProgram
  :: Maybe Backup

  -> InputFile
  -- ^ Name of input file

  -> ProgName

  -> [ProgramOpt]

  -> FilePath
  -- ^ Temporary directory

  -> IO ()
runProgram mayBak inFile pn opts tempPath =
  let outPath = tempPath ++ "/output"
  in IO.withFile outPath IO.WriteMode $ \outHandle ->
     IO.withFile inFile IO.ReadMode $ \inHandle -> do
      writeReadme tempPath
      let cp = P.CreateProcess
            { P.cmdspec = P.RawCommand pn opts
            , P.cwd = Nothing
            , P.env = Nothing
            , P.std_in = P.UseHandle inHandle
            , P.std_out = P.UseHandle outHandle
            , P.std_err = P.Inherit
            , P.close_fds = False
            , P.create_group = False }
      (_, _, _, procHndle) <- P.createProcess cp
      code <- P.waitForProcess procHndle
      _ <- case code of
        Exit.ExitSuccess -> return ()
        Exit.ExitFailure bad ->
          errExit $ "program " ++ pn ++ " exited with code "
                    ++ show bad
      _ <- case mayBak of
        Nothing -> return ()
        Just bak -> doBackup inFile bak
      D.copyFile outPath inFile


writeReadme
  :: FilePath
  -- ^ Temporary directory
  -> IO ()

writeReadme fp = writeFile (fp ++ "/README")
  "This directory created by the rewrite program."

main :: IO ()
main = do
  (mayBak, inf, pn, opts) <- parseArgs
  withSystemTempDirectory "rewrite" $ runProgram mayBak inf pn opts