packages feed

data-embed-0.1.0.0: embedtool.hs

-- | Main module for @embedtool@, a command line interface to @data-embed@.
module Main where
import Control.Monad
import Data.Embed.File
import System.Console.GetOpt
import System.Environment
import System.Exit
import System.IO

data Overwrite = Append | Replace | DontOverwrite
  deriving Show

data Option
  = Write                  -- Write a bundle to an executable
  | Erase                  -- Erase an existing bundle
  | Check                  -- Check if a file contains a bundle or not
  | List                   -- List all files in a bundle
  | PrintHelp              -- Print help message and exit
  | PrintUsage             -- Print usage message and exit
  | SetOverwrite Overwrite -- Set overwrite mode
  | SetStrip Int           -- Set the number of leading directories to strip
                           -- from file names added to bundle
    deriving Show

optspec :: [OptDescr Option]
optspec =
  [ Option "w" ["write"]     (NoArg Write) $
    "Write a bundle to a file. Do nothing if the file already has a bundle."
  , Option "r" ["replace"]   (NoArg (SetOverwrite Replace)) $
    "When writing a bundle to a file, overwrite any previously existing " ++
    "bundle."
  , Option "a" ["append"]    (NoArg (SetOverwrite Append)) $
    "When writing a bundle to a file, leave the old one in place but " ++
    "append the new one at the end of the file."
  , Option "p" ["strip"]     (ReqArg (SetStrip . read) "NUM") $
    "Strip up to NUM leading directories from file names added to bundle.\n" ++
    "`embedtool -p1 -w outfile dir/infile' will add `dir/infile' to the " ++
    "bundle with the name `infile'."
  , Option "ed" ["erase"]    (NoArg Erase) $
    "Erase previously created bundles from any of the given files."
  , Option "l" ["list"]      (NoArg List) $
    "List all files in the given bundle."
  , Option "c" ["check"]     (NoArg Check) $
    "Check whether the given file contains a bundle or not."
  , Option "?h" ["help"]     (NoArg PrintHelp) $
    "Print this message and exit."
  ]

helpHeader :: String
helpHeader = concat
  [ "embedtool creates, modifies and inspects file bundles for use with the "
  , "`data-bundle' library. It accepts the following options:"
  ]

main :: IO ()
main = do
  args <- getArgs
  case getOpt Permute optspec args of
    (opts, nonopts, []) -> runAct (getAction opts) (writeOpts opts) nonopts
    (_, _, errs)        -> mapM_ (hPutStr stderr) errs >> exitFailure

-- | Extract write options from the given list of options.
writeOpts :: [Option] -> (Overwrite, Int)
writeOpts = foldl writeOpt (DontOverwrite, 0)
  where
    writeOpt (_, s)   (SetOverwrite ovr) = (ovr, s)
    writeOpt (ovr, _) (SetStrip s)       = (ovr, s)
    writeOpt acc      _                     = acc

-- | Get the action to perform from the given list of options.
getAction :: [Option] -> Option
getAction = foldl getAct PrintUsage
  where
    getAct acc (SetOverwrite _) = acc
    getAct acc (SetStrip _)     = acc
    getAct _   act              = act

runAct :: Option -> (Overwrite, Int) -> [String] -> IO ()
runAct Write (ovr, s) fs = do
  case fs of
    (outf : infs) | not (null infs) -> do
      alreadyHasBundle <- hasBundle outf
      when alreadyHasBundle $ do
        case ovr of
          DontOverwrite -> do
            hPutStrLn stderr $ "file `" ++ outf ++ "' already has a " ++
                               "bundle; aborting"
            exitFailure
          Replace ->
            eraseBundle outf
          _ ->
            return ()
      appendBundle outf (map (FilePath s) infs)
    _ -> do
      hPutStrLn stderr $ "need an output file and at least one input file " ++
                         "to create a bundle"
      hPutStrLn stderr $ "try `embedtool -w outfile infile1 [infile2 ...]'"
      exitFailure
runAct Erase _ outfs = do
  when (null outfs) $ do
    hPutStrLn stderr $ "need at least one file to erase bundle from"
    hPutStrLn stderr $ "try `embedtool -e file1 [file2 ...]'"
    exitFailure
  mapM_ eraseBundle outfs
runAct Check _ infs = do
  case infs of
    [inf] -> do
      ok <- hasBundle inf
      putStrLn $ if ok then "yes" else "no"
    _ -> do
      hPutStrLn stderr $ "need exactly one file to check for bundles"
      hPutStrLn stderr $ "try `embedtool -c file'"
      exitFailure
runAct List _ infs = do
  case infs of
    [inf] -> do
      res <- withBundle inf (mapM_ putStrLn . listBundleFiles)
      case res of
        Right _ -> return ()
        Left e  -> do
          hPutStrLn stderr $ "failed to read bundle: " ++ e
          exitFailure
    _ -> do
      hPutStrLn stderr $ "need exactly one file to list files from"
      hPutStrLn stderr $ "try `embedtool -l file'"
      exitFailure
runAct PrintHelp _ _ = do
  putStr $ usageInfo helpHeader optspec
runAct PrintUsage _ _ = do
  putStrLn "usage: embedtool OPTIONS FILE [FILES]"
  putStrLn "try `embedtool --help' for more information"
  exitFailure
runAct opt _ _ = do
  error $ "BUG: option `" ++ show opt ++ "' is not an action!"