1 -- SPDX-FileCopyrightText: 2022 Frank Doepper
3 -- SPDX-License-Identifier: GPL-3.0-only
5 {-# LANGUAGE OverloadedStrings #-}
7 module Network.AMQP.Utils.Options where
9 import qualified Data.ByteString.Char8 as BS
10 import Data.Default.Class
11 import Data.Int (Int64)
12 import qualified Data.Map as M
14 import Data.Text (Text, pack)
15 import Data.Version (showVersion)
16 import Data.Word (Word16)
18 import Network.AMQP.Types
19 import Network.Socket (PortNumber)
20 import Paths_amqp_utils (version)
21 import System.Console.GetOpt
22 import System.FilePath.Posix.ByteString (RawFilePath)
23 import Text.Read (readMaybe)
25 portnumber :: Args -> PortNumber
27 | (port a) == Nothing && (tls a) = 5671
28 | (port a) == Nothing = 5672
29 | otherwise = fromJust (port a)
31 -- | A data type for our options
35 , port :: Maybe PortNumber
38 , currentExchange :: String
39 , bindings :: [(String, String)]
41 , anRiss :: Maybe Int64
42 , fileProcess :: Maybe String
43 , qName :: Maybe String
44 , cert :: Maybe String
49 , heartBeat :: Maybe Word16
50 , tempDir :: Maybe String
51 , additionalArgs :: [String]
52 , connectionName :: Maybe String
54 , inputFiles :: [(RawFilePath,String,String)]
55 , outputFile :: String
59 , msgtype :: Maybe Text
60 , userid :: Maybe Text
62 , clusterid :: Maybe Text
63 , contenttype :: Maybe Text
64 , contentencoding :: Maybe Text
65 , replyto :: Maybe Text
67 , corrid :: Maybe Text
68 , msgexp :: Maybe Text
69 , msgheader :: Maybe FieldTable
70 , fnheader :: [String]
71 , suffix :: [BS.ByteString]
73 , persistent :: Maybe DeliveryMode
76 , rpc_timeout :: Double
77 , connect_timeout :: Int
79 , cleanupTmpFile :: Bool
80 , removeSentFile :: Bool
81 , moveSentFileTo :: Maybe RawFilePath
83 , streamoffset :: FieldTable
87 instance Default Args where
143 allOptions :: [(String, OptDescr (Args -> Args))]
150 (\s o -> o {bindings = (currentExchange o, s) : (bindings o)})
152 ("AMQP binding key"))
160 { fileProcess = Just (fromMaybe callback s)
161 , tempDir = Just (fromMaybe "/tmp" (tempDir o))
164 ("Callback Script File (implies -t) (-X without arg: " ++
170 (ReqArg (\s o -> o {additionalArgs = s : (additionalArgs o)}) "ARG")
171 "additional argument for -X callback")
175 ["tempdir", "target"]
176 (OptArg (\s o -> o {tempDir = Just (fromMaybe "/tmp" s)}) "DIR")
177 "tempdir (default: no file creation, -t without arg: /tmp)")
182 (ReqArg (\s o -> o {preFetch = read s}) "INT")
183 ("Prefetch count. (0=unlimited, 1=off, default: " ++
184 show (preFetch def) ++ ")"))
189 (NoArg (\o -> o {ack = not (ack o)}))
190 ("Toggle ack messages (default: " ++ show (ack def) ++ ")"))
195 (NoArg (\o -> o {requeuenack = not (requeuenack o)}))
196 ("Toggle requeue when rejected (default: " ++
197 show (requeuenack def) ++ ")"))
202 (ReqArg (\s o -> o {rKey = s}) "ROUTINGKEY")
207 ["routingkey", "qname"]
208 (ReqArg (\s o -> o {rKey = s}) "ROUTINGKEY")
214 (ReqArg (\s o -> o {inputFiles = (BS.pack s,currentExchange o,rKey o):(inputFiles o)}) "INPUTFILE")
215 ("Message input file (default: <stdin>)"))
220 (ReqArg (\s o -> o {outputFile = s}) "OUTPUTFILE")
221 ("Message output file (default: " ++ (outputFile def) ++ ")"))
226 (NoArg (\o -> o {lineMode = not (lineMode o)}))
227 ("Toggle line-by-line mode (default: " ++ show (lineMode def) ++ ")"))
232 (NoArg (\o -> o {confirm = not (confirm o)}))
233 ("Toggle confirms (default: " ++ show (confirm def) ++ ")"))
238 (ReqArg (\s o -> o {msgid = Just $ pack s}) "ID")
244 (ReqArg (\s o -> o {msgtype = Just $ pack s}) "TYPE")
250 (ReqArg (\s o -> o {userid = Just $ pack s}) "USERID")
256 (ReqArg (\s o -> o {appid = Just $ pack s}) "APPID")
262 (ReqArg (\s o -> o {clusterid = Just $ pack s}) "CLUSTERID")
263 "Message Cluster-ID")
268 (ReqArg (\s o -> o {contenttype = Just $ pack s}) "CONTENTTYPE")
269 "Message Content-Type")
274 (ReqArg (\s o -> o {contentencoding = Just $ pack s}) "CONTENTENCODING")
275 "Message Content-Encoding")
280 (ReqArg (\s o -> o {replyto = Just $ pack s}) "REPLYTO")
286 (ReqArg (\s o -> o {rpc_timeout = read s}) "SECONDS")
287 ("How long to wait for reply (default: " ++
288 show (rpc_timeout def) ++ ")"))
293 (ReqArg (\s o -> o {prio = Just $ read s}) "PRIO")
299 (ReqArg (\s o -> o {corrid = Just $ pack s}) "CORRID")
300 "Message CorrelationID")
305 (ReqArg (\s o -> o {msgexp = Just $ pack s}) "EXP")
306 "Message Expiration")
312 (\s o -> o {msgheader = addheader (msgheader o) s})
320 (\s o -> o {streamoffset = mkStreamOffset s})
322 "x-stream-offset consumer argument")
327 (ReqArg (\s o -> o {fnheader = s : (fnheader o)}) "HEADERNAME")
328 "Put filename into this header")
333 (ReqArg (\s o -> o {suffix = (BS.pack s) : (suffix o)}) "SUFFIX")
334 "Allowed file suffixes in hotfolder mode")
339 (OptArg (\s o -> o {removeSentFile = True, moveSentFileTo = fmap BS.pack s}) "DIR")
340 ("Remove (or move to DIR) sent file in hotfolder mode"))
345 (NoArg (\o -> o {initialScan = not (initialScan o)}))
346 ("Toggle initial directory scan in hotfolder mode (default: " ++
347 show (initialScan def) ++ ")"))
352 (NoArg (\o -> o {magic = not (magic o)}))
353 ("Toggle setting content-type and -encoding from file contents (default: " ++
354 show (magic def) ++ ")"))
359 (NoArg (\o -> o {persistent = Just Persistent}))
360 "Set persistent delivery")
365 (NoArg (\o -> o {persistent = Just NonPersistent}))
366 "Set nonpersistent delivery")
371 (ReqArg (\s o -> o {anRiss = Just (read s)}) "INT")
372 "limit number of shown body chars (default: unlimited)")
377 (ReqArg (\s o -> o {qName = Just s}) "QUEUENAME")
378 "Ignore Exchange and bind to existing Queue")
384 (\o -> o {simple = True, cleanupTmpFile = not (cleanupTmpFile o)}))
385 "call callback with one arg (filename) only")
390 (NoArg (\o -> o {cleanupTmpFile = not (cleanupTmpFile o)}))
391 "Toggle remove tempfile after script call. Default False, but default True if --simple (-i)")
396 (ReqArg (\s o -> o {tmpQName = s}) "TEMPQNAME")
397 "Name for temporary exclusive Queue")
402 (ReqArg (\s o -> o {currentExchange = s}) "EXCHANGE")
403 ("AMQP Exchange (default: \"\")"))
408 (ReqArg (\s o -> o {server = s}) "SERVER")
409 ("AMQP Server (default: " ++ server def ++ ")"))
414 (ReqArg (\s o -> o {vHost = s}) "VHOST")
415 ("AMQP Virtual Host (default: " ++ vHost def ++ ")"))
420 (ReqArg (\s o -> o {port = Just (read s)}) "PORT")
421 ("Server Port Number (default: " ++ show (portnumber def) ++ ")"))
426 (NoArg (\o -> o {tls = not (tls o)}))
427 ("Toggle TLS (default: " ++ show (tls def) ++ ")"))
432 (ReqArg (\s o -> o {cert = Just s}) "CERTFILE")
433 ("TLS Client Certificate File"))
438 (ReqArg (\s o -> o {key = Just s}) "KEYFILE")
439 ("TLS Client Private Key File"))
444 (ReqArg (\s o -> o {user = s}) "USERNAME")
445 ("Username for Auth"))
450 (ReqArg (\s o -> o {pass = s}) "PASSWORD")
451 ("Password for Auth"))
456 (ReqArg (\s o -> o {heartBeat = (Just (read s))}) "INT")
457 "heartbeat interval (0=disable, default: set by server)")
462 (ReqArg (\s o -> o {connectionName = Just s}) "NAME")
463 "connection name, will be shown in RabbitMQ web interface")
468 (ReqArg (\s o -> o {connect_timeout = read s}) "SECONDS")
469 ("timeout for establishing initial connection (default: " ++
470 show (connect_timeout def) ++ ")"))
475 (ReqArg (\s o -> o {delaynack = read s}) "SECONDS")
476 ("delay negative acknowledgements (default: " ++
477 show (delaynack def) ++ ")"))
480 -- | Options for the executables
481 options :: Char -> [OptDescr (Args -> Args)]
482 options exename = map snd $ filter ((elem exename) . fst) allOptions
484 -- | Add a header with a String value
485 addheader :: Maybe FieldTable -> String -> Maybe FieldTable
486 addheader Nothing string =
487 Just $ FieldTable $ M.singleton (getkey string) (getval string)
488 addheader (Just (FieldTable oldheader)) string =
489 Just $ FieldTable $ M.insert (getkey string) (getval string) oldheader
491 getkey :: String -> Text
492 getkey s = pack $ takeWhile (/= '=') s
494 getval :: String -> FieldValue
495 getval s = FVString $ BS.pack $ tail $ dropWhile (/= '=') s
497 -- | Parse streamoffset argument as number or string
498 mkStreamOffset :: String -> FieldTable
499 mkStreamOffset s = FieldTable $ M.singleton (pack "x-stream-offset") value
501 value = maybe (FVString $ BS.pack s) FVInt64 $ readMaybe s
503 -- | 'parseargs' exename argstring
504 -- applies options onto argstring
505 parseargs :: Char -> [String] -> IO Args
506 parseargs exename argstring =
507 case getOpt Permute opt argstring of
508 (o, [], []) -> return $ foldl (flip id) def o
510 ioError $ userError $ concat errs ++ usageInfo (usage exename) opt
512 opt = options exename
514 -- | the default callback for the -X option
516 callback = "/usr/lib/haskell-amqp-utils/callback"
518 usage :: Char -> String
522 (showVersion version) ++
525 (longname exename) ++
529 longname :: Char -> String
530 longname 'a' = "agitprop"
531 longname 'k' = "konsum"
532 longname 'r' = "arbeite"
533 longname 'p' = "plane"
534 longname _ = "command"