packages feed

msgpack-arbitrary-0.1.2: src/Test/MessagePack/Parser.hs

{-# LANGUAGE Safe #-}
-- | A MessagePack parser.
--
-- Example usage:
--   $ echo -ne "\x94\x01\xa1\x32\xa1\x33\xa4\x50\x6f\x6f\x66" | ./msgpack-parser
-- or
--   $ echo 'ObjectArray [ObjectInt 97, ObjectStr "test",  ObjectBool True]' | ./msgpack-parser
--
-- This tool performs two symmetrical functions:
--   1. It can decode binary data representing a
--      Data.MessagePack.Object into a human-readable string.
--   2. It can do the reverse: encode a human-readable string into
--      a binary representation of Data.MessagePack.Object.
--
-- No flags are required as it automatically detects which of these
-- two functions it should perform.  This is done by first assuming
-- the input is human readable.  If it fails to parse it, it then
-- considers it as binary data.
--
-- Therefore, given a valid input, the tool has the following property
--   $ ./msgpack-parser < input.bin | ./msgpack-parser
-- will output back the contents of input.bin.
--
-- In case the input is impossible to parse, nothing is output.
--
-- Known bugs:
--   - If no input is given, the tool exits with
--     "Data.Binary.Get.runGet at position 0: not enough bytes"
--   - The tool does not check that all the input is parsed.
--     Therefore, "abc" is interpreted as just "ObjectInt 97".
--
module Test.MessagePack.Parser (parse) where

import           Control.Applicative        ((<|>))
import qualified Data.ByteString.Lazy       as L
import qualified Data.ByteString.Lazy.Char8 as L8
import           Data.Int                   (Int64)
import           Data.Maybe                 (fromMaybe)
import           Data.MessagePack.Types     (Object)
import           Data.Time.Clock            (diffUTCTime, getCurrentTime)
import           System.IO                  (hPutStr, hPutStrLn, stderr)
import           Text.Read                  (readMaybe)


display :: Int64 -> Object -> String
display len | len > 10 * 1024 = const $ show len <> " bytes (too large to display)"
display _   = show


parseBidirectional
    :: (Object -> L.ByteString)
    -> (L.ByteString -> Maybe Object)
    -> L.ByteString
    -> L.ByteString
parseBidirectional pack unpack str = fromMaybe L.empty $
    pack <$> readMaybe (L8.unpack str)
    <|>
    L8.pack . flip (++) "\n" . display (L.length str) <$> unpack str


showSpeed :: Int64 -> Double -> String
showSpeed size time =
    show (fromIntegral size / (1024 * 1024) / time) <> " MiB/s"


parse :: (Object -> L.ByteString) -> (L.ByteString -> Maybe Object) -> IO ()
parse pack unpack = do
    start <- getCurrentTime
    packed <- L.getContents
    hPutStr stderr $ "Read " <> show (L.length packed) <> " bytes"
    readTime <- getCurrentTime
    hPutStrLn stderr $ " in " <> show (diffUTCTime readTime start)

    let parsed = parseBidirectional pack unpack packed
    hPutStr stderr $ "Parsed into " <> show (L.length parsed) <> " bytes"
    unpackTime <- getCurrentTime
    hPutStrLn stderr $ " in " <> show (diffUTCTime unpackTime readTime)

    hPutStrLn stderr $ "Unpacking speed: " <> showSpeed (L.length packed) (realToFrac (diffUTCTime unpackTime readTime))

    L.putStr parsed