{-# LANGUAGE OverloadedStrings #-} module Walrus.Backend ( Backend(..), module Walrus.Backend.Metadata, parseBackend, runBackend ) where import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.Attoparsec.ByteString.Lazy as LA import Data.Attoparsec.ByteString.Char8.Extras import qualified Data.ByteString.Lazy as LBS import qualified Data.Char as C import Data.Text.IsText import Walrus.Backend.Metadata import Control.Applicative import Network import System.IO import qualified System.Process as P data Backend = TCPBackend String Int | UnixSocketBackend String | ExecBackend String [String] deriving (Show, Read, Ord, Eq) parseBackend :: A.Parser Backend parseBackend = do A.choice [ do tp "tcp" h <- tillSpace p <- A.decimal return $ TCPBackend h p, do tp "unix" tp "socket" p <- qStr return $ UnixSocketBackend p, do tp "exec" f <- qStr pp <- parseParameters return $ ExecBackend f pp ] where tp t = do skipHorizontalSpace A.stringCI t skipHorizontalSpace tillSpace :: A.Parser String tillSpace = fromText <$> A.takeTill C.isSpace qStr :: A.Parser String qStr = fromText <$> quotedString '\\' " " "" parseParameters = A.many' $ do p <- qStr skipHorizontalSpace return p runBackend :: Backend -> (Metadata, LBS.ByteString) -> IO (Either String (Metadata, LBS.ByteString)) runBackend b (m, qdt) = do let rm = renderMetadata m edt' <- intBk b $ LBS.concat [ fromText rm, "\r\n", qdt] case LA.parse repParse edt' of LA.Fail _ _ e -> return $ Left e LA.Done edt m' -> return $ Right (m', edt) where intBk :: Backend -> LBS.ByteString -> IO LBS.ByteString intBk (TCPBackend h p) = runTcp h p intBk (UnixSocketBackend f) = runUnix f intBk (ExecBackend f aa) = runExec f aa repParse = do m' <- parseMetadata A.endOfLine return m' runTcp :: String -> Int -> LBS.ByteString -> IO LBS.ByteString runTcp host port dt = do h <- connectTo host (PortNumber . fromIntegral $ port) bkgTrans h dt runUnix :: String -> LBS.ByteString -> IO LBS.ByteString runUnix path dt = do h <- connectTo "localhost" (UnixSocket path) bkgTrans h dt bkgTrans :: Handle -> LBS.ByteString -> IO LBS.ByteString bkgTrans h dt = do hSetNewlineMode h noNewlineTranslation LBS.hPut h dt hFlush h LBS.hGetContents h runExec :: String -> [String] -> LBS.ByteString -> IO LBS.ByteString runExec f args dt = do (Just i, Just o, _, _) <- P.createProcess ( (P.proc f args){P.std_in=P.CreatePipe}{ P.std_out=P.CreatePipe}{P.std_err=P.Inherit} ) mapM (\h -> hSetNewlineMode h noNewlineTranslation) [i, o] LBS.hPut i dt hFlush i LBS.hGetContents o