packages feed

amqp-utils 0.6.2.2 → 0.6.3.2

raw patch · 7 files changed

+238/−143 lines, 7 filesdep +filepath-bytestringdep +rawfilepathdep ~hinotifydep ~tlsdep ~unix

Dependencies added: filepath-bytestring, rawfilepath

Dependency ranges changed: hinotify, tls, unix

Files

ChangeLog.md view
@@ -1,5 +1,26 @@ # Revision history for haskell-amqp-utils +## 0.6.3.2  -- 2021-10-25++* update README++## 0.6.3.1  -- 2021-09-08++* avoid setCurrentDirectory which is not thread-safe+* start watch before initial scan+* put filename without directory into filename header++## 0.6.3.0  -- 2021-09-06++* watch multiple dirs in hotfolder mode+* drop hinotify < 0.3.10 compatibility+* use RawFilePath for file names+* fix handling of non-ascii filenames in agitprop hotfolder++## 0.6.2.4  -- 2021-08-30++* depend on tls >= 1.5.0 (thread safety for writes)+ ## 0.6.2.2  -- 2021-08-25  * agitprop: option to move sent file to another dir instead of just removing
Network/AMQP/Utils/Helpers.hs view
@@ -1,28 +1,32 @@ {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}  module Network.AMQP.Utils.Helpers where  import           Control.Concurrent-import qualified Control.Exception          as X+import qualified Control.Exception                as X import           Control.Monad-import qualified Data.ByteString.Lazy.Char8 as BL-import qualified Data.ByteString.UTF8       as BS-import           Data.Int                   (Int64)+import qualified Data.ByteString.Char8            as BS+import qualified Data.ByteString.Lazy.Char8       as BL+import qualified Data.ByteString.UTF8             as BU+import           Data.Int                         (Int64) import           Data.List-import qualified Data.Map                   as M+import qualified Data.Map                         as M import           Data.Maybe-import qualified Data.Text                  as T+import qualified Data.Text                        as T import           Data.Time import           Data.Time.Clock.POSIX-import           Data.Word                  (Word16)+import           Data.Word                        (Word16) import           Network.AMQP import           Network.AMQP.Types import           Network.AMQP.Utils.Options-import           Network.Socket             (PortNumber)-import           System.Directory           (removeFile)-import           System.Environment         (getEnvironment)+import           Network.Socket                   (PortNumber)+import           System.Directory                 (removeFile)+import           System.Environment               (getEnvironment) import           System.Exit+import           System.FilePath.Posix.ByteString (RawFilePath) import           System.IO+import           System.Posix.IO.ByteString import           System.Process  -- | print config parameters@@ -47,14 +51,30 @@   printparam _ Nothing  = return ()   printparam x (Just y) = printparam x y +instance Flexprint BS.ByteString where+  flexprint = BS.hPutStrLn stderr+  empty = BS.null+ instance Flexprint String where   flexprint = hPutStrLn stderr   empty = null +instance Flexprint [BS.ByteString] where+  flexprint = flexprint . BS.unwords+  empty = null+ instance Flexprint [String] where   flexprint = flexprint . unwords   empty = null +instance Flexprint [Maybe BS.ByteString] where+  flexprint = flexprint . catMaybes+  empty = null . catMaybes++instance Flexprint [Maybe String] where+  flexprint = flexprint . catMaybes+  empty = null . catMaybes+ instance Flexprint T.Text where   flexprint = flexprint . T.unpack   empty = T.null@@ -124,7 +144,7 @@  -- | showing a FieldValue valueshow :: FieldValue -> String-valueshow (FVString value)     = BS.toString value+valueshow (FVString value)     = BU.toString value valueshow (FVInt8 value)       = show value valueshow (FVInt16 value)      = show value valueshow (FVInt32 value)      = show value@@ -401,3 +421,21 @@     (forever (threadDelay 600000000) >>      return (X.toException $ X.ErrorCall "not reached"))     return++-- | extract first input file in case only one is needed+firstInputFile :: [(RawFilePath,String,String)] -> RawFilePath+firstInputFile []          = "-"+firstInputFile ((x,_,_):_) = x++-- | read RawFilePath to Lazy ByteString+readFileRawLazy :: RawFilePath -> IO BL.ByteString+readFileRawLazy path = do+  h <- openFd path ReadOnly Nothing defaultFlags >>= fdToHandle+  hSetBinaryMode h True+  BL.hGetContents h+  where+    defaultFlags = OpenFileFlags { System.Posix.IO.ByteString.append = False+                                 , exclusive = False+                                 , noctty = True+                                 , nonBlock = False+                                 , trunc = False }
Network/AMQP/Utils/Options.hs view
@@ -1,18 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}+ module Network.AMQP.Utils.Options where -import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Char8            as BS import           Data.Default.Class-import           Data.Int              (Int64)-import qualified Data.Map              as M+import           Data.Int                         (Int64)+import qualified Data.Map                         as M import           Data.Maybe-import           Data.Text             (Text, pack)-import           Data.Version          (showVersion)-import           Data.Word             (Word16)+import           Data.Text                        (Text, pack)+import           Data.Version                     (showVersion)+import           Data.Word                        (Word16) import           Network.AMQP import           Network.AMQP.Types-import           Network.Socket        (PortNumber)-import           Paths_amqp_utils      (version)+import           Network.Socket                   (PortNumber)+import           Paths_amqp_utils                 (version) import           System.Console.GetOpt+import           System.FilePath.Posix.ByteString (RawFilePath)  portnumber :: Args -> PortNumber portnumber a@@ -43,7 +46,7 @@     , additionalArgs  :: [String]     , connectionName  :: Maybe String     , tmpQName        :: String-    , inputFile       :: String+    , inputFiles      :: [(RawFilePath,String,String)]     , outputFile      :: String     , lineMode        :: Bool     , confirm         :: Bool@@ -60,7 +63,7 @@     , msgexp          :: Maybe Text     , msgheader       :: Maybe FieldTable     , fnheader        :: [String]-    , suffix          :: [String]+    , suffix          :: [BS.ByteString]     , magic           :: Bool     , persistent      :: Maybe DeliveryMode     , ack             :: Bool@@ -70,7 +73,7 @@     , simple          :: Bool     , cleanupTmpFile  :: Bool     , removeSentFile  :: Bool-    , moveSentFileTo  :: Maybe FilePath+    , moveSentFileTo  :: Maybe RawFilePath     , initialScan     :: Bool     } @@ -97,7 +100,7 @@       []       Nothing       ""-      "-"+      []       "-"       False       False@@ -199,8 +202,8 @@     , Option         ['f']         ["inputfile"]-        (ReqArg (\s o -> o {inputFile = s}) "INPUTFILE")-        ("Message input file (default: " ++ (inputFile def) ++ ")"))+        (ReqArg (\s o -> o {inputFiles = (BS.pack s,currentExchange o,rKey o):(inputFiles o)}) "INPUTFILE")+        ("Message input file (default: <stdin>)"))   , ( "p"     , Option         ['O']@@ -310,13 +313,13 @@     , Option         ['S']         ["suffix"]-        (ReqArg (\s o -> o {suffix = s : (suffix o)}) "SUFFIX")+        (ReqArg (\s o -> o {suffix = (BS.pack s) : (suffix o)}) "SUFFIX")         "Allowed file suffixes in hotfolder mode")   , ( "a"     , Option         ['u']         ["remove", "move"]-        (OptArg (\s o -> o {removeSentFile = True, moveSentFileTo = s}) "DIR")+        (OptArg (\s o -> o {removeSentFile = True, moveSentFileTo = fmap BS.pack s}) "DIR")         ("Remove (or move to DIR) sent file in hotfolder mode"))   , ( "a"     , Option
README.md view
@@ -15,7 +15,7 @@     Options:       -r BINDINGKEY  --bindingkey=BINDINGKEY          AMQP binding key       -X[EXE]        --execute[=EXE]                  Callback Script File (implies -t) (-X without arg: /usr/lib/haskell-amqp-utils/callback)-      -a ARG         --args=ARG                       additional argument for -X callback+      -a ARG         --args=ARG, --arg=ARG            additional argument for -X callback       -t[DIR]        --tempdir[=DIR], --target[=DIR]  tempdir (default: no file creation, -t without arg: /tmp)       -f INT         --prefetch=INT                   Prefetch count. (0=unlimited, 1=off, default: 1)       -A             --ack                            Toggle ack messages (default: True)@@ -78,7 +78,7 @@      Options:       -r ROUTINGKEY    --routingkey=ROUTINGKEY            AMQP routing key-      -f INPUTFILE     --inputfile=INPUTFILE              Message input file (default: -)+      -f INPUTFILE     --inputfile=INPUTFILE              Message input file (default: <stdin>)       -l               --linemode                         Toggle line-by-line mode (default: False)       -C               --confirm                          Toggle confirms (default: False)                        --msgid=ID                         Message ID@@ -123,6 +123,8 @@  Line-by-line mode sends one message per INPUTFILE line. +In "hotfolder mode" multiple directories can be watched and sent with distinct routingkeys.+ #### examples  Send a message containing a file and put the filename into a fileName@@ -130,32 +132,39 @@      agitprop -x amq.topic -r test -F fileName -f agitprop.hs +Watch two directories dir1 and dir2 and send files from dir1 with routingkey r1+and files from dir2 with routingkey r2, ignoring everything which is not *.txt+or *.zip, and set Content-Type and Content-Encoding automatically, and move all+sent files to subdir "done" (dir1/done and dir2/done, respectively):++    agitprop -x amq.topic -F fileName -m -S .txt -S .zip -d -udone -r r1 -f dir1 -r r2 -f dir2+ ### plane An RPC client.      plane [options]      Options:-      -r ROUTINGKEY    --routingkey=ROUTINGKEY    AMQP routing key-      -f INPUTFILE     --inputfile=INPUTFILE      Message input file (default: -)-      -O OUTPUTFILE    --outputfile=OUTPUTFILE    Message output file (default: -)-      -t SECONDS       --rpc_timeout=SECONDS      How long to wait for reply (default: 5.0)-                       --corrid=CORRID            Message CorrelationID-                       --exp=EXP                  Message Expiration-      -h HEADER=VALUE  --header=HEADER=VALUE      Message Headers-      -l INT           --charlimit=INT            limit number of shown body chars (default: unlimited)-      -x EXCHANGE      --exchange=EXCHANGE        AMQP Exchange (default: "")-      -o SERVER        --server=SERVER            AMQP Server (default: localhost)-      -y VHOST         --vhost=VHOST              AMQP Virtual Host (default: /)-      -p PORT          --port=PORT                Server Port Number (default: 5672)-      -T               --tls                      Toggle TLS (default: False)-      -c CERTFILE      --cert=CERTFILE            TLS Client Certificate File-      -k KEYFILE       --key=KEYFILE              TLS Client Private Key File-      -U USERNAME      --user=USERNAME            Username for Auth-      -P PASSWORD      --pass=PASSWORD            Password for Auth-      -s INT           --heartbeats=INT           heartbeat interval (0=disable, default: set by server)-      -n NAME          --name=NAME                connection name, will be shown in RabbitMQ web interface-      -w SECONDS       --connect_timeout=SECONDS  timeout for establishing initial connection (default: 600)+      -r ROUTINGKEY, -Q ROUTINGKEY  --routingkey=ROUTINGKEY, --qname=ROUTINGKEY  AMQP routing key+      -f INPUTFILE                  --inputfile=INPUTFILE                        Message input file (default: <stdin>)+      -O OUTPUTFILE                 --outputfile=OUTPUTFILE                      Message output file (default: -)+      -t SECONDS                    --rpc_timeout=SECONDS                        How long to wait for reply (default: 5.0)+                                    --corrid=CORRID                              Message CorrelationID+                                    --exp=EXP                                    Message Expiration+      -h HEADER=VALUE               --header=HEADER=VALUE                        Message Headers+      -l INT                        --charlimit=INT                              limit number of shown body chars (default: unlimited)+      -x EXCHANGE                   --exchange=EXCHANGE                          AMQP Exchange (default: "")+      -o SERVER                     --server=SERVER                              AMQP Server (default: localhost)+      -y VHOST                      --vhost=VHOST                                AMQP Virtual Host (default: /)+      -p PORT                       --port=PORT                                  Server Port Number (default: 5672)+      -T                            --tls                                        Toggle TLS (default: False)+      -c CERTFILE                   --cert=CERTFILE                              TLS Client Certificate File+      -k KEYFILE                    --key=KEYFILE                                TLS Client Private Key File+      -U USERNAME                   --user=USERNAME                              Username for Auth+      -P PASSWORD                   --pass=PASSWORD                              Password for Auth+      -s INT                        --heartbeats=INT                             heartbeat interval (0=disable, default: set by server)+      -n NAME                       --name=NAME                                  connection name, will be shown in RabbitMQ web interface+      -w SECONDS                    --connect_timeout=SECONDS                    timeout for establishing initial connection (default: 600)  #### examples @@ -170,7 +179,7 @@      Options:       -X[EXE]       --execute[=EXE]                  Callback Script File (implies -t) (-X without arg: /usr/lib/haskell-amqp-utils/callback)-      -a ARG        --args=ARG                       additional argument for -X callback+      -a ARG        --args=ARG, --arg=ARG            additional argument for -X callback       -t[DIR]       --tempdir[=DIR], --target[=DIR]  tempdir (default: no file creation, -t without arg: /tmp)       -f INT        --prefetch=INT                   Prefetch count. (0=unlimited, 1=off, default: 1)       -A            --ack                            Toggle ack messages (default: True)
agitprop.hs view
@@ -3,35 +3,32 @@  -- generic AMQP publisher import           Control.Concurrent-import qualified Control.Exception             as X-import qualified Data.ByteString.Lazy.Char8    as BL-#if linux_HOST_OS-#if MIN_VERSION_hinotify(0,3,10)-import qualified Data.ByteString.Char8         as BS-#endif-import           Data.List                     (isSuffixOf)-#endif+import qualified Control.Exception                as X+import           Control.Monad                    (forM_)+import qualified Data.ByteString.Lazy.Char8       as BL+import qualified RawFilePath.Directory            as RD+import qualified Data.ByteString.Char8            as BS+import qualified Data.Map                         as M import           Data.Maybe-import qualified Data.Text                     as T+import qualified Data.Text                        as T import           Data.Time import           Data.Time.Clock.POSIX-import           Data.Version                  (showVersion)-import           Data.Word                     (Word64)+import           Data.Version                     (showVersion)+import           Data.Word                        (Word64) import           Magic import           Network.AMQP import           Network.AMQP.Types import           Network.AMQP.Utils.Connection import           Network.AMQP.Utils.Helpers import           Network.AMQP.Utils.Options-import           Paths_amqp_utils              (version)-import           System.Directory+import           Paths_amqp_utils                 (version) import           System.Environment import           System.Exit-import           System.FilePath.Posix+import           System.FilePath.Posix.ByteString #if linux_HOST_OS import           System.INotify #endif-import qualified System.Posix.Files            as F+import qualified System.Posix.Files.ByteString    as F  main :: IO () main = do@@ -41,23 +38,6 @@   printparam "client version" ["amqp-utils", showVersion version]   printparam "routing key" $ rKey args   printparam "exchange" $ currentExchange args-  isDir <--    if inputFile args == "-"-      then return False-      else F.getFileStatus (inputFile args) >>= return . F.isDirectory-  if isDir-    then printparam "hotfolder" (inputFile args) >>-         printparam "initial scan" (initialScan args) >>-         if isNothing (moveSentFileTo args)-           then printparam "remove sent file" (removeSentFile args)-           else printparam "move sent file to" (moveSentFileTo args)-    else printparam-           "input file"-           [ inputFile args-           , if (lineMode args)-               then "(line-by-line)"-               else ""-           ]   (conn, chan) <- connect args   addChannelExceptionHandler chan (X.throwTo tid)   printparam "confirm mode" $ confirm args@@ -66,40 +46,49 @@       confirmSelect chan False       addConfirmationListener chan confirmCallback     else return ()+  let inputFile' = firstInputFile (inputFiles args)+  isDir <-+    if inputFile' == "-"+      then return False+      else F.getFileStatus inputFile' >>= return . F.isDirectory   let publishOneMsg =         publishOneMsg' chan args {removeSentFile = removeSentFile args && isDir}+  if isDir+    then do+      printparam "hotfolder mode" True+      printparam "initial scan" (initialScan args)+      if isNothing (moveSentFileTo args)+        then printparam "remove sent file" (removeSentFile args)+        else printparam "move sent file to" (moveSentFileTo args)+    else printparam+           "input file"+           [ inputFile'+           , if (lineMode args)+               then "(line-by-line)"+               else ""+           ]   X.catch     (if isDir        then do #if linux_HOST_OS-         setCurrentDirectory (inputFile args)-         if (initialScan args)-           then getDirectoryContents "." >>=-                mapM_ (\fn -> handleFile publishOneMsg (suffix args) fn)-           else return ()-         inotify <- initINotify-         wd <--           addWatch-             inotify-             [CloseWrite, MoveIn]-             "."-             (handleEvent publishOneMsg (suffix args))-         hr $ "BEGIN watching " ++ (inputFile args)+         wds <- mapM (watchHotfolder args publishOneMsg) (inputFiles args)          sleepingBeauty >>= printparam "exception"-         removeWatch wd-         hr $ "END watching " ++ (inputFile args)+         forM_ wds (\(wd,folder) -> do+           removeWatch wd+           printparam "END watching" folder+           ) #else          X.throw (X.ErrorCall "ERROR: watching a directory is only supported in Linux") #endif        else do          hr $ "BEGIN sending"          messageFile <--           if inputFile args == "-"+           if inputFile' == "-"              then BL.getContents-             else BL.readFile (inputFile args)+             else readFileRawLazy inputFile'          if (lineMode args)-           then mapM_ (publishOneMsg Nothing) (BL.lines messageFile)-           else publishOneMsg (Just (inputFile args)) messageFile+           then mapM_ (publishOneMsg (currentExchange args) (rKey args) Nothing) (BL.lines messageFile)+           else publishOneMsg (currentExchange args) (rKey args) (Just (inputFile')) messageFile          hr "END sending")     exceptionHandler   -- all done. wait and close.@@ -108,6 +97,30 @@     else return ()   X.catch (closeConnection conn) exceptionHandler +#if linux_HOST_OS+-- | watch a hotfolder+watchHotfolder ::+     Args+  -> (String -> String -> Maybe RawFilePath -> BL.ByteString -> IO ())+  -> (RawFilePath, String, String)+  -> IO (WatchDescriptor, RawFilePath)+watchHotfolder args publishOneMsg (folder, exchange, rkey) = do+  printparam "hotfolder" folder+  inotify <- initINotify+  wd <-+   addWatch+     inotify+     [CloseWrite, MoveIn]+     folder+     (handleEvent (publishOneMsg exchange rkey) (suffix args) folder)+  hr "BEGIN watching"+  if (initialScan args)+   then RD.listDirectory folder >>=+        mapM_ (\fn -> handleFile (publishOneMsg exchange rkey) (suffix args) (folder </> fn))+   else return ()+  return (wd,folder)+#endif+ -- | A handler for clean exit exceptionHandler :: AMQPException -> IO () exceptionHandler (ChannelClosedException Normal txt) =@@ -127,58 +140,57 @@         else "this"     , show ackType     ]- #if linux_HOST_OS -- | Hotfolder event handler handleEvent ::-     (Maybe String -> BL.ByteString -> IO ()) -> [String] -> Event -> IO ()+     (Maybe RawFilePath -> BL.ByteString -> IO ()) -> [BS.ByteString] -> RawFilePath -> Event -> IO () -- just handle closewrite and movedin events-#if MIN_VERSION_hinotify(0,3,10)-handleEvent func suffixes (Closed False (Just fileName) True) =-  handleFile func suffixes (BS.unpack fileName)-handleEvent func suffixes (MovedIn False fileName _) =-  handleFile func suffixes (BS.unpack fileName)-#else-handleEvent func suffixes (Closed False (Just fileName) True) =-  handleFile func suffixes fileName-handleEvent func suffixes (MovedIn False fileName _) =-  handleFile func suffixes fileName-#endif-handleEvent _ _ _ = return ()+handleEvent func suffixes folder (Closed False (Just fileName) True) =+  handleFile func suffixes (folder </> fileName)+handleEvent func suffixes folder (MovedIn False fileName _) =+  handleFile func suffixes (folder </> fileName)+handleEvent _ _ _ _ = return ()  -- | Hotfolder file handler handleFile ::-     (Maybe String -> BL.ByteString -> IO ()) -> [String] -> FilePath -> IO ()-handleFile _ _ ('.':_) = return () -- ignore hidden files+     (Maybe RawFilePath -> BL.ByteString -> IO ()) -> [BS.ByteString] -> RawFilePath -> IO () handleFile func suffixes@(_:_) fileName =-  if any (flip isSuffixOf fileName) suffixes+  if (any (flip BS.isSuffixOf fileName) suffixes) && not ("." `BS.isPrefixOf` fileName)     then handleFile func [] fileName     else return () handleFile func [] fileName =   X.catch-    (BL.readFile fileName >>= func (Just fileName))-    (\e -> printparam "exception in handleFile" (e :: X.IOException))+    (readFileRawLazy fileName >>= func (Just fileName))+    (\e -> printparam "exception while processing" fileName >> printparam "exception in handleFile" (e :: X.IOException)) #endif  -- | Publish one message with our settings-publishOneMsg' :: Channel -> Args -> Maybe FilePath -> BL.ByteString -> IO ()-publishOneMsg' chan a fn content = do+publishOneMsg' ::+     Channel+  -> Args+  -> String+  -> String+  -> Maybe RawFilePath+  -> BL.ByteString+  -> IO ()+publishOneMsg' chan a exchange rkey fn content = do   printparam "sending" fn   (mtype, mencoding) <--    if (magic a) && isJust fn+    if (magic a)       then do+        let firstchunk = if BL.null content then BS.empty else head $ BL.toChunks content         m <- magicOpen [MagicMimeType]         magicLoadDefault m-        t <- magicFile m (fromJust fn)+        t <- BS.useAsCStringLen firstchunk (magicCString m)         magicSetFlags m [MagicMimeEncoding]-        e <- magicFile m (fromJust fn)+        e <- BS.useAsCStringLen firstchunk (magicCString m)         return (Just (T.pack t), Just (T.pack e))       else return ((contenttype a), (contentencoding a))   now <- getCurrentTime >>= return . floor . utcTimeToPOSIXSeconds   publishMsg     chan-    (T.pack $ currentExchange a)-    (T.pack $ rKey a)+    (T.pack $ exchange)+    (T.pack $ rkey)     newMsg       { msgBody = content       , msgDeliveryMode = persistent a@@ -194,20 +206,25 @@       , msgPriority = prio a       , msgCorrelationID = corrid a       , msgExpiration = msgexp a-      , msgHeaders = substheader (fnheader a) fn $ msgheader a+      , msgHeaders = substheader (fnheader a) (fmap takeFileName fn) $ msgheader a       } >>=     printparam "sent"   removeSentFileIfRequested (removeSentFile a) (moveSentFileTo a) fn   where     substheader ::-         [String] -> Maybe String -> Maybe FieldTable -> Maybe FieldTable+         [String] -> Maybe BS.ByteString -> Maybe FieldTable -> Maybe FieldTable     substheader (s:r) (Just fname) old =-      substheader r (Just fname) (addheader old (s ++ "=" ++ fname))+      substheader r (Just fname) (addheader' old s fname)     substheader _ _ old = old     removeSentFileIfRequested False _ _ = return ()     removeSentFileIfRequested True _ Nothing = return ()     removeSentFileIfRequested True Nothing (Just fname) =-      printparam "removing" fname >> removeFile fname+      printparam "removing" fname >> RD.removeFile fname     removeSentFileIfRequested True (Just path) (Just fname) =-      printparam "moving" (fname ++ " to " ++ path) >>-      renameFile fname (replaceDirectory fname path)+      printparam "moving" [fname,"to",path] >>+      F.rename fname (replaceDirectory fname ((takeDirectory fname) </> path))+    addheader' :: Maybe FieldTable -> String -> BS.ByteString -> Maybe FieldTable+    addheader' Nothing k v =+      Just $ FieldTable $ M.singleton (T.pack k) (FVString v)+    addheader' (Just (FieldTable oldheader)) k v =+      Just $ FieldTable $ M.insert (T.pack k) (FVString v) oldheader
amqp-utils.cabal view
@@ -1,6 +1,6 @@ name:                amqp-utils -version:             0.6.2.2+version:             0.6.3.2  synopsis:            AMQP toolset for the command line @@ -45,10 +45,12 @@                        directory,                        bytestring,                        utf8-string,+                       filepath-bytestring,                        x509-system,                        network > 2.6,-                       tls >= 1.3.9,-                       amqp >=0.19+                       tls >= 1.5.0,+                       amqp >=0.19,+                       unix >= 2.7    ghc-options:         -threaded -Wall @@ -72,14 +74,16 @@                        filepath,                        bytestring,                        utf8-string,+                       rawfilepath,+                       filepath-bytestring,                        x509-system,                        network > 2.6,-                       tls >= 1.3.9,+                       tls >= 1.5.0,                        amqp >=0.19,                        unix >= 2.7,                        magic   if os(linux)-    build-depends:     hinotify >= 0.3.8+    build-depends:     hinotify >= 0.3.10    ghc-options:         -threaded -Wall @@ -102,9 +106,10 @@                        directory,                        bytestring,                        utf8-string,+                       filepath-bytestring,                        x509-system,                        network > 2.6,-                       tls >= 1.3.9,+                       tls >= 1.5.0,                        amqp >=0.19,                        unix >= 2.7 @@ -129,9 +134,10 @@                        directory,                        bytestring,                        utf8-string,+                       filepath-bytestring,                        x509-system,                        network > 2.6,-                       tls >= 1.3.9,+                       tls >= 1.5.0,                        amqp >=0.19,                        unix >= 2.7 
plane.hs view
@@ -36,11 +36,12 @@       printparam "exchange" $ currentExchange args       bindQueue chan q (T.pack $ currentExchange args) q     else return ()-  printparam "input file" $ inputFile args+  let inputFile' = firstInputFile (inputFiles args)+  printparam "input file" $ inputFile'   message <--    if inputFile args == "-"+    if inputFile' == "-"       then BL.getContents-      else BL.readFile (inputFile args)+      else readFileRawLazy inputFile'   printparam "output file" $ outputFile args   h <-     if outputFile args == "-"