1d1054faa6
Added -z option to git-annex commands that use --batch, useful for supporting filenames containing newlines. It only controls input to --batch, the output will still be line delimited unless --json or etc is used to get some other output. While git often makes -z affect both input and output, I don't like trying them together, and making it affect output would have been a significant complication, and also git-annex output is generally not intended to be machine parsed, unless using --json or a format option. Commands that take pairs like "file key" still separate them with a space in --batch mode. All such commands take care to support filenames with spaces when parsing that, so there was no need to change it, and it would have needed significant changes to the batch machinery to separate tose with a null. To make fromkey and registerurl support -z, I had to give them a --batch option. The implicit batch mode they enter when not provided with input parameters does not support -z as that would have complicated option parsing. Seemed better to move these toward using the same --batch as everything else, though the implicit batch mode can still be used. This commit was sponsored by Ole-Morten Duesund on Patreon.
108 lines
3.2 KiB
Haskell
108 lines
3.2 KiB
Haskell
{- git-annex batch commands
|
|
-
|
|
- Copyright 2015 Joey Hess <id@joeyh.name>
|
|
-
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
-}
|
|
|
|
module CmdLine.Batch where
|
|
|
|
import Annex.Common
|
|
import Types.Command
|
|
import CmdLine.Action
|
|
import CmdLine.GitAnnex.Options
|
|
import Options.Applicative
|
|
import Limit
|
|
import Types.FileMatcher
|
|
|
|
data BatchMode = Batch BatchFormat | NoBatch
|
|
|
|
data BatchFormat = BatchLine | BatchNull
|
|
|
|
parseBatchOption :: Parser BatchMode
|
|
parseBatchOption = go
|
|
<$> switch
|
|
( long "batch"
|
|
<> help "enable batch mode"
|
|
)
|
|
<*> switch
|
|
( short 'z'
|
|
<> help "null delimited batch input"
|
|
)
|
|
where
|
|
go True False = Batch BatchLine
|
|
go True True = Batch BatchNull
|
|
go False _ = NoBatch
|
|
|
|
-- A batchable command can run in batch mode, or not.
|
|
-- In batch mode, one line at a time is read, parsed, and a reply output to
|
|
-- stdout. In non batch mode, the command's parameters are parsed and
|
|
-- a reply output for each.
|
|
batchable :: (opts -> String -> Annex Bool) -> Parser opts -> CmdParamsDesc -> CommandParser
|
|
batchable handler parser paramdesc = batchseeker <$> batchparser
|
|
where
|
|
batchparser = (,,)
|
|
<$> parser
|
|
<*> parseBatchOption
|
|
<*> cmdParams paramdesc
|
|
|
|
batchseeker (opts, NoBatch, params) =
|
|
mapM_ (go NoBatch opts) params
|
|
batchseeker (opts, batchmode@(Batch fmt), _) =
|
|
batchInput fmt Right (go batchmode opts)
|
|
|
|
go batchmode opts p =
|
|
unlessM (handler opts p) $
|
|
batchBadInput batchmode
|
|
|
|
-- bad input is indicated by an empty line in batch mode. In non batch
|
|
-- mode, exit on bad input.
|
|
batchBadInput :: BatchMode -> Annex ()
|
|
batchBadInput NoBatch = liftIO exitFailure
|
|
batchBadInput (Batch _) = liftIO $ putStrLn ""
|
|
|
|
-- Reads lines of batch mode input and passes to the action to handle.
|
|
batchInput :: BatchFormat -> (String -> Either String a) -> (a -> Annex ()) -> Annex ()
|
|
batchInput fmt parser a = go =<< batchLines fmt
|
|
where
|
|
go [] = return ()
|
|
go (l:rest) = do
|
|
either parseerr a (parser l)
|
|
go rest
|
|
parseerr s = giveup $ "Batch input parse failure: " ++ s
|
|
|
|
batchLines :: BatchFormat -> Annex [String]
|
|
batchLines fmt = liftIO $ splitter <$> getContents
|
|
where
|
|
splitter = case fmt of
|
|
BatchLine -> lines
|
|
BatchNull -> splitc '\0'
|
|
|
|
-- Runs a CommandStart in batch mode.
|
|
--
|
|
-- The batch mode user expects to read a line of output, and it's up to the
|
|
-- CommandStart to generate that output as it succeeds or fails to do its
|
|
-- job. However, if it stops without doing anything, it won't generate
|
|
-- any output, so in that case, batchBadInput is used to provide the caller
|
|
-- with an empty line.
|
|
batchCommandAction :: CommandStart -> Annex ()
|
|
batchCommandAction a = maybe (batchBadInput (Batch BatchLine)) (const noop)
|
|
=<< callCommandAction' a
|
|
|
|
-- Reads lines of batch input and passes the filepaths to a CommandStart
|
|
-- to handle them.
|
|
--
|
|
-- File matching options are not checked.
|
|
allBatchFiles :: BatchFormat -> (FilePath -> CommandStart) -> Annex ()
|
|
allBatchFiles fmt a = batchInput fmt Right $ batchCommandAction . a
|
|
|
|
-- Like allBatchFiles, but checks the file matching options
|
|
-- and skips non-matching files.
|
|
batchFilesMatching :: BatchFormat -> (FilePath -> CommandStart) -> Annex ()
|
|
batchFilesMatching fmt a = do
|
|
matcher <- getMatcher
|
|
allBatchFiles fmt $ \f ->
|
|
ifM (matcher $ MatchingFile $ FileInfo f f)
|
|
( a f
|
|
, return Nothing
|
|
)
|