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 +21/−0
- Network/AMQP/Utils/Helpers.hs +49/−11
- Network/AMQP/Utils/Options.hs +19/−16
- README.md +32/−23
- agitprop.hs +100/−83
- amqp-utils.cabal +13/−7
- plane.hs +4/−3
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 == "-"