ipython-kernel-0.8.1.0: src/IHaskell/IPython/Message/Parser.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Description : Parsing messages received from IPython
--
-- This module is responsible for converting from low-level ByteStrings obtained from the 0MQ
-- sockets into Messages. The only exposed function is `parseMessage`, which should only be used in
-- the low-level 0MQ interface.
module IHaskell.IPython.Message.Parser (parseMessage) where
import Data.Aeson ((.:), (.:?), (.!=), decode, Result(..), Object)
import Control.Applicative ((<|>), (<$>), (<*>))
import Data.Aeson.Types (parse)
import Data.ByteString
import Data.Map (Map)
import Data.Text (Text)
import qualified Data.ByteString.Lazy as Lazy
import IHaskell.IPython.Types
type LByteString = Lazy.ByteString
-- --- External interface ----- | Parse a message from its ByteString components into a Message.
parseMessage :: [ByteString] -- ^ The list of identifiers sent with the message.
-> ByteString -- ^ The header data.
-> ByteString -- ^ The parent header, which is just "{}" if there is no header.
-> ByteString -- ^ The metadata map, also "{}" for an empty map.
-> ByteString -- ^ The message content.
-> Message -- ^ A parsed message.
parseMessage idents headerData parentHeader metadata content =
let header = parseHeader idents headerData parentHeader metadata
messageType = msgType header
messageWithoutHeader = parser messageType $ Lazy.fromStrict content
in messageWithoutHeader { header = header }
-- --- Module internals ----- | Parse a header from its ByteString components into a MessageHeader.
parseHeader :: [ByteString] -- ^ The list of identifiers.
-> ByteString -- ^ The header data.
-> ByteString -- ^ The parent header, or "{}" for Nothing.
-> ByteString -- ^ The metadata, or "{}" for an empty map.
-> MessageHeader -- The resulting message header.
parseHeader idents headerData parentHeader metadata =
MessageHeader
{ identifiers = idents
, parentHeader = parentResult
, metadata = metadataMap
, messageId = messageUUID
, sessionId = sessionUUID
, username = username
, msgType = messageType
}
where
-- Decode the header data and the parent header data into JSON objects. If the parent header data is
-- absent, just have Nothing instead.
Just result = decode $ Lazy.fromStrict headerData :: Maybe Object
parentResult = if parentHeader == "{}"
then Nothing
else Just $ parseHeader idents parentHeader "{}" metadata
Success (messageType, username, messageUUID, sessionUUID) = flip parse result $ \obj -> do
messType <- obj .: "msg_type"
username <- obj .: "username"
message <- obj .: "msg_id"
session <- obj .: "session"
return (messType, username, message, session)
-- Get metadata as a simple map.
Just metadataMap = decode $ Lazy.fromStrict metadata :: Maybe (Map Text Text)
noHeader :: MessageHeader
noHeader = error "No header created"
parser :: MessageType -- ^ The message type being parsed.
-> LByteString -> Message -- ^ The parser that converts the body into a message. This message
-- should have an undefined header.
parser KernelInfoRequestMessage = kernelInfoRequestParser
parser ExecuteRequestMessage = executeRequestParser
parser CompleteRequestMessage = completeRequestParser
parser InspectRequestMessage = inspectRequestParser
parser ShutdownRequestMessage = shutdownRequestParser
parser InputReplyMessage = inputReplyParser
parser CommOpenMessage = commOpenParser
parser CommDataMessage = commDataParser
parser CommCloseMessage = commCloseParser
parser HistoryRequestMessage = historyRequestParser
parser other = error $ "Unknown message type " ++ show other
-- | Parse a kernel info request. A kernel info request has no auxiliary information, so ignore the
-- body.
kernelInfoRequestParser :: LByteString -> Message
kernelInfoRequestParser _ = KernelInfoRequest { header = noHeader }
-- | Parse an execute request. Fields used are:
-- 1. "code": the code to execute.
-- 2. "silent": whether to execute silently.
-- 3. "store_history": whether to include this in history.
-- 4. "allow_stdin": whether to allow reading from stdin for this code.
executeRequestParser :: LByteString -> Message
executeRequestParser content =
let parser obj = do
code <- obj .: "code"
silent <- obj .: "silent"
storeHistory <- obj .: "store_history"
allowStdin <- obj .: "allow_stdin"
return (code, silent, storeHistory, allowStdin)
Just decoded = decode content
Success (code, silent, storeHistory, allowStdin) = parse parser decoded
in ExecuteRequest
{ header = noHeader
, getCode = code
, getSilent = silent
, getAllowStdin = allowStdin
, getStoreHistory = storeHistory
, getUserVariables = []
, getUserExpressions = []
}
requestParser parser content = parsed
where
Success parsed = parse parser decoded
Just decoded = decode content
historyRequestParser :: LByteString -> Message
historyRequestParser = requestParser $ \obj ->
HistoryRequest noHeader <$> obj .: "output" <*> obj .: "raw" <*> historyAccessType obj
where
-- TODO: Implement full history access type parsing from message spec
historyAccessType obj = do
accessTypeStr <- obj .: "hist_access_type"
return $
case accessTypeStr of
"range" -> HistoryRange
"tail" -> HistoryTail
"search" -> HistorySearch
str -> error $ "Unknown history access type: " ++ str
completeRequestParser :: LByteString -> Message
completeRequestParser = requestParser $ \obj -> do
code <- obj .: "code"
pos <- obj .: "cursor_pos"
return $ CompleteRequest noHeader code pos
inspectRequestParser :: LByteString -> Message
inspectRequestParser = requestParser $ \obj -> do
code <- obj .: "code"
pos <- obj .: "cursor_pos"
dlevel <- obj .: "detail_level"
return $ InspectRequest noHeader code pos dlevel
shutdownRequestParser :: LByteString -> Message
shutdownRequestParser = requestParser $ \obj -> do
code <- obj .: "restart"
return $ ShutdownRequest noHeader code
inputReplyParser :: LByteString -> Message
inputReplyParser = requestParser $ \obj -> do
value <- obj .: "value"
return $ InputReply noHeader value
commOpenParser :: LByteString -> Message
commOpenParser = requestParser $ \obj -> do
uuid <- obj .: "comm_id"
targetName <- obj .: "target_name"
targetModule <- obj .:? "target_module" .!= ""
value <- obj .: "data"
return $ CommOpen noHeader targetName targetModule uuid value
commDataParser :: LByteString -> Message
commDataParser = requestParser $ \obj -> do
uuid <- obj .: "comm_id"
value <- obj .: "data"
return $ CommData noHeader uuid value
commCloseParser :: LByteString -> Message
commCloseParser = requestParser $ \obj -> do
uuid <- obj .: "comm_id"
value <- obj .: "data"
return $ CommClose noHeader uuid value