packages feed

llsd-0.1.0.1: TestMain.hs

-- |
-- Module      :  Main - Test LLSD Utility
-- Copyright   :  (c) Linden Lab 2009
-- License     :  MIT
-- Maintainer  :  bos@lindenlab.com
-- Stability   :  provisional
-- Portability :  portable

module Main where

import Control.Monad
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Data.List (foldl')
import Data.Serialize
import Data.Word
import Network.Format.LLSD
import qualified Network.Format.LLSD.Test as Test
import qualified Network.Format.LLSD.TestIDL as TestIDL
import System.Console.GetOpt
import System.Environment
import System.Exit
import System.IO
import TestData

data Operation = Test | Help | Generate | Read

data Mode = Mode {
    modeOperation   :: Operation,
    modeCount       :: Int,
    modeGroup       :: Int,
    modeReportEvery :: Int,
    modeQuiet       :: Bool,
    modeParse       :: B.ByteString -> Either String LLSD,
    modeFormat      :: LLSD -> L.ByteString,
    modeDataFile    :: Maybe String
    }

defaultMode :: Mode
defaultMode = Mode {
    modeOperation   = Help,
    modeCount       = 0,
    modeGroup       = 1,
    modeReportEvery = 1000,
    modeQuiet       = False,
    modeParse       = parseXML,
    modeFormat      = formatXML,
    modeDataFile    = Nothing
    }

setOperation :: Operation -> Mode -> Mode
setOperation op mode = mode { modeOperation = op }

setTest, setHelp, setRead :: Mode -> Mode
setGenerate :: String -> Mode -> Mode
setTest       = setOperation Test
setHelp       = setOperation Help
setGenerate n = setOperation Generate . setCount n
setRead       = setOperation Read

setCount, setGroup, setReportEvery, setDataFile :: String -> Mode -> Mode
setCount n mode       = mode { modeCount = read n }
setGroup n mode       = mode { modeGroup = read n }
setReportEvery n mode = mode { modeReportEvery = read n }
    -- FIXME: should do something better than read, which can error
setDataFile f mode    = mode { modeDataFile = Just f }

setXML, setBinary, setQuiet :: Mode -> Mode
setXML mode    = mode { modeParse = parseXML,    modeFormat = formatXML }
setBinary mode = mode { modeParse = parseBinary, modeFormat = formatBinary }
setQuiet mode  = mode { modeQuiet = True }

options :: [OptDescr (Mode -> Mode)]
options = [
    Option ['t'] ["test", "tests"]      (NoArg setTest)
                                        "run unit tests and checks",

    Option ['g'] ["gen", "generate"]    (ReqArg setGenerate "N")
                                        "generate N packets of LLSD output",
    Option ['c'] ["count"]              (ReqArg setGroup "M")
                                        "generate M records per packet",

    Option ['r'] ["read"]               (NoArg setRead)
                                        "read packets of LLSD",
    Option ['e'] ["every"]              (ReqArg setReportEvery "N")
                                        "report stats every N packets",

    Option ['b'] ["binary"]             (NoArg setBinary)
                                        "binary encoded LLSD",
    Option ['x'] ["xml", "XML"]         (NoArg setXML)
                                        "XML encoded LLSD",

    Option ['d'] ["data"]               (ReqArg setDataFile "F")
                                        "read/write data file F",
    Option ['q'] ["quiet"]              (NoArg setQuiet)
                                        "supress message output",

    Option ['?'] ["help"]               (NoArg setHelp)
                                        "print help summary"
    ]

usage :: [String] -> Handle -> IO ()
usage errs h = do
        progName <- getProgName
        forM_ errs (hPutStr h)
        hPutStr h $ usageInfo (header progName) options
    where header progName = "Usage: " ++ progName ++ " [options]"

usageFailure :: [String] -> IO a
usageFailure errs = usage errs stderr >> exitFailure

parseOptions :: IO (Mode, [String])
parseOptions = do
    argv <- getArgs
    case getOpt Permute options argv of
        (opts, args,   []) -> return (foldl' (flip ($)) defaultMode opts, args)
        (   _,    _, errs) -> usageFailure errs

runtimeFailure :: [String] -> IO a
runtimeFailure msgs = do
    forM_ msgs (\m -> hPutStrLn stderr m)
    exitFailure





hEncode :: (Serialize a) => Handle -> a -> IO ()
hEncode h = B.hPut h . encode

hDecode :: (Serialize a) => Handle -> String -> Int -> IO a
hDecode h msg n = do
    buf <- B.hGet h n
    case decode buf of
        Left err -> runtimeFailure [msg, err]
        Right a  -> return a

hPutInt32 :: (Integral a) => Handle -> a -> IO ()
hPutInt32 h i = hEncode h (fromIntegral i :: Word32)
hGetInt32 :: Handle -> String -> IO Int
hGetInt32 h msg = (hDecode h msg 4 :: IO Word32) >>= return . fromIntegral


report :: (Show a) => Mode -> Handle -> a -> IO ()
report mode h a =
    when (not $ modeQuiet mode) $ hPutStrLn h (show a)

reportCount :: Mode -> Int -> IO ()
reportCount mode i =
        when (i `mod` (modeReportEvery mode) == 0) $ report mode stderr i

generateData :: Mode -> Handle -> IO ()
generateData mode h = do
    let count = modeCount mode
        group = modeGroup mode
        formatter = modeFormat mode
    hSetBinaryMode h True
    -- write format indicator
    hPutInt32 h count
    forM_ [1..count] $ \i -> do
        items <- makeLogItems group
        let bs = (formatter $ toLLSD items)
        hPutInt32 h $ L.length bs
        L.hPut h bs
        reportCount mode i

readData :: Mode -> Handle -> IO ()
readData mode h = do
    hSetBinaryMode h True
    -- read format indicator
    count <- hGetInt32 h "packet count"
    foldM readPacket zeroStats [1..count] >>= report mode stdout
    where readPacket :: LogStats -> Int -> IO LogStats
          readPacket s0 i = s0 `seq` do -- this force of s0 is important
                bs <- hGetInt32 h "length" >>= B.hGet h
                reportCount mode i
                case (modeParse mode $ bs) of
                    Right l -> return $ foldl' (flip note) s0 (fromLLSD l :: [LogItem])
                    Left err -> runtimeFailure ["packet decode error", err]


runAllTests :: IO()
runAllTests = do
    Test.runTests
    Test.runChecks
    TestIDL.runTests
    TestIDL.runChecks
    

withDataHandle :: IOMode -> Handle -> Mode -> (Handle -> IO a) -> IO a
withDataHandle iomode defHandle mode act =
    maybe (act defHandle) (\f -> withFile f iomode act) $ modeDataFile mode

withInputHandle :: Mode -> (Handle -> IO a) -> IO a
withInputHandle = withDataHandle ReadMode stdin

withOutputHandle :: Mode -> (Handle -> IO a) -> IO a
withOutputHandle = withDataHandle WriteMode stdout
    

main :: IO ()
main = do
    (mode, _files) <- parseOptions
    case modeOperation mode of
        Help     -> usage [] stdout
        Test     -> runAllTests
        Generate -> withOutputHandle mode $ generateData mode
        Read     -> withInputHandle mode $ readData mode