Commit c53f70d8 authored by Andrew Gibiansky's avatar Andrew Gibiansky

Formatting ipython-kernel

parent 7ba7c4d1
{-# LANGUAGE FlexibleContexts, FlexibleInstances, OverloadedStrings, MultiWayIf #-}
module Main where
import Control.Applicative
import Control.Arrow
import Control.Applicative
import Control.Arrow
import Control.Concurrent (MVar, newMVar, takeMVar, putMVar, threadDelay)
import Control.Monad (guard)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.State.Strict (StateT, get, modify, runStateT)
import Control.Concurrent (MVar, newMVar, takeMVar, putMVar, threadDelay)
import Control.Monad (guard)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.State.Strict (StateT, get, modify, runStateT)
import Data.Char (isDigit)
import Data.List (isPrefixOf)
import Data.Monoid ((<>))
import Data.Char (isDigit)
import Data.List (isPrefixOf)
import Data.Monoid ((<>))
import qualified Data.Text as T
import IHaskell.IPython.Kernel
import IHaskell.IPython.EasyKernel (installProfile, easyKernel, KernelConfig(..))
import IHaskell.IPython.Kernel
import IHaskell.IPython.EasyKernel (installProfile, easyKernel, KernelConfig(..))
import System.Environment (getArgs)
import System.FilePath ((</>))
import System.Environment (getArgs)
import System.FilePath ((</>))
import Text.Parsec (Parsec, ParseError, alphaNum, char, letter, oneOf, optionMaybe, runParser, (<?>))
import Text.Parsec (Parsec, ParseError, alphaNum, char, letter, oneOf, optionMaybe,
runParser, (<?>))
import qualified Text.Parsec.Token as P
import qualified Paths_ipython_kernel as Paths
......@@ -28,34 +30,29 @@ import qualified Paths_ipython_kernel as Paths
---------------------------------------------------------
-- Hutton's Razor, plus time delays, plus a global state
---------------------------------------------------------
-- | This language is Hutton's Razor with two added operations that
-- are needed to demonstrate the kernel features: a global state,
-- accessed and modified using Count, and a sleep operation.
--
-- | This language is Hutton's Razor with two added operations that are needed to demonstrate the
-- kernel features: a global state, accessed and modified using Count, and a sleep operation.
data Razor = I Integer
| Plus Razor Razor
| SleepThen Double Razor
| Count
deriving (Read, Show, Eq)
---------
-- Parser
---------
-- ------- Parser -------
razorDef :: Monad m => P.GenLanguageDef String a m
razorDef = P.LanguageDef
{ P.commentStart = "(*"
, P.commentEnd = "*)"
, P.commentLine = "//"
, P.nestedComments = True
, P.identStart = letter <|> char '_'
, P.identLetter = alphaNum <|> char '_'
, P.opStart = oneOf "+"
, P.opLetter = oneOf "+"
, P.reservedNames = ["sleep", "then", "end", "count"]
{ P.commentStart = "(*"
, P.commentEnd = "*)"
, P.commentLine = "//"
, P.nestedComments = True
, P.identStart = letter <|> char '_'
, P.identLetter = alphaNum <|> char '_'
, P.opStart = oneOf "+"
, P.opLetter = oneOf "+"
, P.reservedNames = ["sleep", "then", "end", "count"]
, P.reservedOpNames = []
, P.caseSensitive = True
, P.caseSensitive = True
}
lexer :: Monad m => P.GenTokenParser String a m
......@@ -83,39 +80,38 @@ literal :: Parsec String a Razor
literal = I <$> integer
sleepThen :: Parsec String a Razor
sleepThen = do keyword "sleep"
delay <- float <?> "seconds"
keyword "then"
body <- expr
keyword "end" <?> ""
return $ SleepThen delay body
sleepThen = do
keyword "sleep"
delay <- float <?> "seconds"
keyword "then"
body <- expr
keyword "end" <?> ""
return $ SleepThen delay body
count :: Parsec String a Razor
count = keyword "count" >> return Count
expr :: Parsec String a Razor
expr = do one <- parens expr <|> literal <|> sleepThen <|> count
rest <- optionMaybe (do op <- operator
guard (op == "+")
expr)
case rest of
Nothing -> return one
Just other -> return $ Plus one other
expr = do
one <- parens expr <|> literal <|> sleepThen <|> count
rest <- optionMaybe
(do
op <- operator
guard (op == "+")
expr)
case rest of
Nothing -> return one
Just other -> return $ Plus one other
parse :: String -> Either ParseError Razor
parse = runParser expr () "(input)"
----------------------
-- Language operations
----------------------
-- | Completion
-- -------------------- Language operations -------------------- | Completion
langCompletion :: T.Text -> T.Text -> Int -> Maybe ([T.Text], T.Text, T.Text)
langCompletion _code line col =
let (before, _) = T.splitAt col line
in fmap (\word -> (map T.pack . matchesFor $ T.unpack word, word, word))
(lastMaybe (T.words before))
(lastMaybe (T.words before))
where
lastMaybe :: [a] -> Maybe a
lastMaybe [] = Nothing
......@@ -123,43 +119,41 @@ langCompletion _code line col =
lastMaybe (_:xs) = lastMaybe xs
matchesFor :: String -> [String]
matchesFor input = filter (isPrefixOf input) available
available = ["sleep", "then", "end", "count"] ++ map show [(-1000::Int)..1000]
available = ["sleep", "then", "end", "count"] ++ map show [(-1000 :: Int) .. 1000]
-- | Documentation lookup
langInfo :: T.Text -> Maybe (T.Text, T.Text, T.Text)
langInfo obj =
if | any (T.isPrefixOf obj) ["sleep", "then", "end"] ->
Just (obj, sleepDocs, sleepType)
| T.isPrefixOf obj "count" ->
Just (obj, countDocs, countType)
if | any (T.isPrefixOf obj) ["sleep", "then", "end"] -> Just (obj, sleepDocs, sleepType)
| T.isPrefixOf obj "count" -> Just (obj, countDocs, countType)
| obj == "+" -> Just (obj, plusDocs, plusType)
| T.all isDigit obj -> Just (obj, intDocs obj, intType)
| [x, y] <- T.splitOn "." obj,
T.all isDigit x,
T.all isDigit y -> Just (obj, floatDocs obj, floatType)
| [x, y] <- T.splitOn "." obj
, T.all isDigit x
, T.all isDigit y -> Just (obj, floatDocs obj, floatType)
| otherwise -> Nothing
where
sleepDocs = "sleep DURATION then VALUE end: sleep DURATION seconds, then eval VALUE"
sleepType = "sleep FLOAT then INT end"
plusDocs = "Perform addition"
plusType = "INT + INT"
intDocs i = "The integer " <> i
intType = "INT"
floatDocs f = "The floating point value " <> f
floatType = "FLOAT"
countDocs = "Increment and return the current counter"
countType = "INT"
sleepDocs = "sleep DURATION then VALUE end: sleep DURATION seconds, then eval VALUE"
sleepType = "sleep FLOAT then INT end"
plusDocs = "Perform addition"
plusType = "INT + INT"
intDocs i = "The integer " <> i
intType = "INT"
floatDocs f = "The floating point value " <> f
floatType = "FLOAT"
countDocs = "Increment and return the current counter"
countType = "INT"
-- | Messages sent to the frontend during evaluation will be lists of trace elements
data IntermediateEvalRes = Got Razor Integer
| Waiting Double
deriving Show
-- | Cons for lists of trace elements - in this case, "sleeping"
-- messages should replace old ones to create a countdown effect.
-- | Cons for lists of trace elements - in this case, "sleeping" messages should replace old ones to
-- create a countdown effect.
consRes :: IntermediateEvalRes -> [IntermediateEvalRes] -> [IntermediateEvalRes]
consRes r@(Waiting _) (Waiting _ : s) = r:s
consRes r s = r:s
consRes r@(Waiting _) (Waiting _:s) = r : s
consRes r s = r : s
-- | Execute an expression.
execRazor :: MVar Integer -- ^ The global counter state
......@@ -168,53 +162,60 @@ execRazor :: MVar Integer -- ^ The global counter state
-> ([IntermediateEvalRes] -> IO ()) -- ^ Callback for intermediate results
-> StateT ([IntermediateEvalRes], T.Text) IO Integer
execRazor _ x@(I i) _ _ =
modify (second (<> (T.pack (show x)))) >> return i
modify (second (<> T.pack (show x))) >> return i
execRazor val tm@(Plus x y) clear send =
do modify (second (<> (T.pack (show tm))))
x' <- execRazor val x clear send
modify (first $ consRes (Got x x'))
sendState
y' <- execRazor val y clear send
modify (first $ consRes (Got y y'))
sendState
let res = x' + y'
modify (first $ consRes (Got tm res))
sendState
return res
where sendState = liftIO clear >> fst <$> get >>= liftIO . send
do
modify (second (<> T.pack (show tm)))
x' <- execRazor val x clear send
modify (first $ consRes (Got x x'))
sendState
y' <- execRazor val y clear send
modify (first $ consRes (Got y y'))
sendState
let res = x' + y'
modify (first $ consRes (Got tm res))
sendState
return res
where
sendState = liftIO clear >> fst <$> get >>= liftIO . send
execRazor val (SleepThen delay body) clear send
| delay <= 0.0 = execRazor val body clear send
| delay > 0.1 = do modify (first $ consRes (Waiting delay))
sendState
liftIO $ threadDelay 100000
execRazor val (SleepThen (delay - 0.1) body) clear send
| otherwise = do modify (first $ consRes (Waiting 0))
sendState
liftIO $ threadDelay (floor (delay * 1000000))
execRazor val body clear send
where sendState = liftIO clear >> fst <$> get >>= liftIO . send
| delay > 0.1 = do
modify (first $ consRes (Waiting delay))
sendState
liftIO $ threadDelay 100000
execRazor val (SleepThen (delay - 0.1) body) clear send
| otherwise = do
modify (first $ consRes (Waiting 0))
sendState
liftIO $ threadDelay (floor (delay * 1000000))
execRazor val body clear send
where
sendState = liftIO clear >> fst <$> get >>= liftIO . send
execRazor val Count clear send = do
i <- liftIO $ takeMVar val
modify (first $ consRes (Got Count i))
sendState
liftIO $ putMVar val (i+1)
liftIO $ putMVar val (i + 1)
return i
where sendState = liftIO clear >> fst <$> get >>= liftIO . send
where
sendState = liftIO clear >> fst <$> get >>= liftIO . send
-- | Generate a language configuration for some initial state
mkConfig :: MVar Integer -- ^ The internal state of the execution
-> KernelConfig IO [IntermediateEvalRes] (Either ParseError Integer)
mkConfig var = KernelConfig
{ languageName = "expanded_huttons_razor"
, languageVersion = [0,1,0]
, profileSource = Just . (</> "calc_profile.tar") <$> Paths.getDataDir
, displayResult = displayRes
, displayOutput = displayOut
, completion = langCompletion
, objectInfo = langInfo
, run = parseAndRun
, debug = False
{ languageName = "expanded_huttons_razor"
, languageVersion = [0, 1, 0]
, profileSource = Just . (</> "calc_profile.tar") <$> Paths.getDataDir
, displayResult = displayRes
, displayOutput = displayOut
, completion = langCompletion
, objectInfo = langInfo
, run = parseAndRun
, debug = False
}
where
displayRes (Left err) =
......@@ -235,15 +236,17 @@ mkConfig var = KernelConfig
return (Right res, Ok, T.unpack pager)
main :: IO ()
main = do args <- getArgs
val <- newMVar 1
case args of
["kernel", profileFile] ->
easyKernel profileFile (mkConfig val)
["setup"] -> do
putStrLn "Installing profile..."
installProfile (mkConfig val)
_ -> do
putStrLn "Usage:"
putStrLn "simple-calc-example setup -- set up the profile"
putStrLn "simple-calc-example kernel FILE -- run a kernel with FILE for communication with the frontend"
main = do
args <- getArgs
val <- newMVar 1
case args of
["kernel", profileFile] ->
easyKernel profileFile (mkConfig val)
["setup"] -> do
putStrLn "Installing profile..."
installProfile (mkConfig val)
_ -> do
putStrLn "Usage:"
putStrLn "simple-calc-example setup -- set up the profile"
putStrLn
"simple-calc-example kernel FILE -- run a kernel with FILE for communication with the frontend"
{-# LANGUAGE OverloadedStrings #-}
-- | Description : Easy IPython kernels
-- = Overview
-- This module provides automation for writing simple IPython
-- kernels. In particular, it provides a record type that defines
-- configurations and a function that interprets a configuration as an
-- action in some monad that can do IO.
-- | Description : Easy IPython kernels = Overview This module provides automation for writing
-- simple IPython kernels. In particular, it provides a record type that defines configurations and
-- a function that interprets a configuration as an action in some monad that can do IO.
--
-- The configuration consists primarily of functions that implement
-- the various features of a kernel, such as running code, looking up
-- documentation, and performing completion. An example for a simple
-- language that nevertheless has side effects, global state, and
-- timing effects is included in the examples directory.
-- The configuration consists primarily of functions that implement the various features of a
-- kernel, such as running code, looking up documentation, and performing completion. An example for
-- a simple language that nevertheless has side effects, global state, and timing effects is
-- included in the examples directory.
--
-- = Profiles
-- To run your kernel, you will need an IPython profile that causes
-- the frontend to run it. To generate a fresh profile, run the command
-- = Profiles To run your kernel, you will need an IPython profile that causes the frontend to run
-- it. To generate a fresh profile, run the command
--
-- > ipython profile create NAME
--
-- This will create a fresh IPython profile in @~\/.ipython\/profile_NAME@.
-- This profile must be modified in two ways:
-- This will create a fresh IPython profile in @~\/.ipython\/profile_NAME@. This profile must be
-- modified in two ways:
--
-- 1. It needs to run your kernel instead of the default ipython
-- 2. It must have message signing turned off, because 'easyKernel' doesn't support it
-- 1. It needs to run your kernel instead of the default ipython 2. It must have message signing
-- turned off, because 'easyKernel' doesn't support it
--
-- == Setting the executable
-- To set the executable, modify the configuration object's
-- == Setting the executable To set the executable, modify the configuration object's
-- @KernelManager.kernel_cmd@ property. For example:
--
-- > c.KernelManager.kernel_cmd = ['my_kernel', '{connection_file}']
......@@ -44,79 +38,73 @@
-- Consult the IPython documentation along with the generated profile
-- source code for further configuration of the frontend, including
-- syntax highlighting, logos, help text, and so forth.
module IHaskell.IPython.EasyKernel (easyKernel, installProfile, KernelConfig(..)) where
import Data.Aeson (decode)
import Data.Aeson (decode)
import qualified Data.ByteString.Lazy as BL
import qualified Codec.Archive.Tar as Tar
import Control.Concurrent (MVar, readChan, writeChan, newMVar, readMVar, modifyMVar_)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad (forever, when)
import Control.Concurrent (MVar, readChan, writeChan, newMVar, readMVar, modifyMVar_)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad (forever, when)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import IHaskell.IPython.Kernel
import IHaskell.IPython.Message.UUID as UUID
import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getHomeDirectory)
import System.FilePath ((</>))
import System.Exit (exitSuccess)
import System.IO (openFile, IOMode(ReadMode))
-- | The kernel configuration specifies the behavior that is specific
-- to your language. The type parameters provide the monad in which
-- your kernel will run, the type of intermediate outputs from running
-- cells, and the type of final results of cells, respectively.
data KernelConfig m output result = KernelConfig
{ languageName :: String
-- ^ The name of the language. This field is used to calculate
-- the name of the profile, so it should contain characters that
-- are reasonable to have in file names.
, languageVersion :: [Int] -- ^ The version of the language
, profileSource :: IO (Maybe FilePath)
-- ^ Determine the source of a profile to install using
-- 'installProfile'. The source should be a tarball whose contents
-- will be unpacked directly into the profile directory. For
-- example, the file whose name is @ipython_config.py@ in the
-- tar file for a language named @lang@ will end up in
-- @~/.ipython/profile_lang/ipython_config.py@.
, displayOutput :: output -> [DisplayData] -- ^ How to render intermediate output
, displayResult :: result -> [DisplayData] -- ^ How to render final cell results
, completion :: T.Text -> T.Text -> Int -> Maybe ([T.Text], T.Text, T.Text)
-- ^ Perform completion. The returned tuple consists of the matches,
-- the matched text, and the completion text. The arguments are the
-- code in the cell, the current line as text, and the column at
-- which the cursor is placed.
, objectInfo :: T.Text -> Maybe (T.Text, T.Text, T.Text)
-- ^ Return the information or documentation for its argument. The
-- returned tuple consists of the name, the documentation, and the
-- type, respectively.
, run :: T.Text -> IO () -> (output -> IO ()) -> m (result, ExecuteReplyStatus, String)
-- ^ Execute a cell. The arguments are the contents of the cell, an
-- IO action that will clear the current intermediate output, and an
-- IO action that will add a new item to the intermediate
-- output. The result consists of the actual result, the status to
-- be sent to IPython, and the contents of the pager. Return the
-- empty string to indicate that there is no pager output. Errors
-- should be handled by defining an appropriate error constructor in
-- your result type.
, debug :: Bool -- ^ Whether to print extra debugging information to
-- the console
}
-- | Attempt to install the IPython profile from the .tar file
-- indicated by the 'profileSource' field of the configuration, if it
-- is not already installed.
import IHaskell.IPython.Kernel
import IHaskell.IPython.Message.UUID as UUID
import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist,
getHomeDirectory)
import System.FilePath ((</>))
import System.Exit (exitSuccess)
import System.IO (openFile, IOMode(ReadMode))
-- | The kernel configuration specifies the behavior that is specific to your language. The type
-- parameters provide the monad in which your kernel will run, the type of intermediate outputs from
-- running cells, and the type of final results of cells, respectively.
data KernelConfig m output result =
KernelConfig
{
-- | The name of the language. This field is used to calculate the name of the profile,
-- so it should contain characters that are reasonable to have in file names.
languageName :: String
-- | The version of the language
, languageVersion :: [Int]
-- | Determine the source of a profile to install using 'installProfile'. The source should be a
-- tarball whose contents will be unpacked directly into the profile directory. For example, the
-- file whose name is @ipython_config.py@ in the tar file for a language named @lang@ will end up in
-- @~/.ipython/profile_lang/ipython_config.py@.
, profileSource :: IO (Maybe FilePath)
-- | How to render intermediate output
, displayOutput :: output -> [DisplayData]
-- | How to render final cell results
, displayResult :: result -> [DisplayData]
-- | Perform completion. The returned tuple consists of the matches, the matched text, and the
-- completion text. The arguments are the code in the cell, the current line as text, and the column
-- at which the cursor is placed.
, completion :: T.Text -> T.Text -> Int -> Maybe ([T.Text], T.Text, T.Text)
-- | Return the information or documentation for its argument. The returned tuple consists of the
-- name, the documentation, and the type, respectively.
, objectInfo :: T.Text -> Maybe (T.Text, T.Text, T.Text)
-- | Execute a cell. The arguments are the contents of the cell, an IO action that will clear the
-- current intermediate output, and an IO action that will add a new item to the intermediate
-- output. The result consists of the actual result, the status to be sent to IPython, and the
-- contents of the pager. Return the empty string to indicate that there is no pager output. Errors
-- should be handled by defining an appropriate error constructor in your result type.
, run :: T.Text -> IO () -> (output -> IO ()) -> m (result, ExecuteReplyStatus, String)
, debug :: Bool -- ^ Whether to print extra debugging information to
}
-- the console | Attempt to install the IPython profile from the .tar file indicated by the
-- 'profileSource' field of the configuration, if it is not already installed.
installProfile :: MonadIO m => KernelConfig m output result -> m ()
installProfile config = do
installed <- isInstalled
when (not installed) $ do
unless installed $ do
profSrc <- liftIO $ profileSource config
case profSrc of
Nothing -> liftIO (putStrLn "No IPython profile is installed or specified")
......@@ -124,10 +112,11 @@ installProfile config = do
profExists <- liftIO $ doesFileExist tar
profTgt <- profDir
if profExists
then do liftIO $ createDirectoryIfMissing True profTgt
liftIO $ Tar.extract profTgt tar
else liftIO . putStrLn $
"The supplied profile source '" ++ tar ++ "' does not exist"
then do
liftIO $ createDirectoryIfMissing True profTgt
liftIO $ Tar.extract profTgt tar
else liftIO . putStrLn $
"The supplied profile source '" ++ tar ++ "' does not exist"
where
profDir = do
......@@ -153,28 +142,29 @@ createReplyHeader parent = do
let repType = fromMaybe err (replyType $ msgType parent)
err = error $ "No reply for message " ++ show (msgType parent)
return MessageHeader {
identifiers = identifiers parent,
parentHeader = Just parent,
metadata = Map.fromList [],
messageId = newMessageId,
sessionId = sessionId parent,
username = username parent,
msgType = repType
}
-- | Execute an IPython kernel for a config. Your 'main' action should
-- call this as the last thing it does.
return
MessageHeader
{ identifiers = identifiers parent
, parentHeader = Just parent
, metadata = Map.fromList []
, messageId = newMessageId
, sessionId = sessionId parent
, username = username parent
, msgType = repType
}
-- | Execute an IPython kernel for a config. Your 'main' action should call this as the last thing
-- it does.
easyKernel :: (MonadIO m)
=> FilePath -- ^ The connection file provided by the IPython frontend
-> KernelConfig m output result -- ^ The kernel configuration specifying how to react to messages
-> KernelConfig m output result -- ^ The kernel configuration specifying how to react to
-- messages
-> m ()
easyKernel profileFile config = do
prof <- liftIO $ getProfile profileFile
zmq@(Channels shellReqChan shellRepChan ctrlReqChan ctrlRepChan iopubChan _) <-
liftIO $ serveProfile prof False
zmq@(Channels shellReqChan shellRepChan ctrlReqChan ctrlRepChan iopubChan _) <- liftIO $ serveProfile
prof
False
execCount <- liftIO $ newMVar 0
forever $ do
req <- liftIO $ readChan shellReqChan
......@@ -183,7 +173,6 @@ easyKernel profileFile config = do
reply <- replyTo config execCount zmq req repHeader
liftIO $ writeChan shellRepChan reply
replyTo :: MonadIO m
=> KernelConfig m output result
-> MVar Integer
......@@ -192,97 +181,79 @@ replyTo :: MonadIO m
-> MessageHeader
-> m Message
replyTo config _ _ KernelInfoRequest{} replyHeader =
return KernelInfoReply
{ header = replyHeader
, language = languageName config
, versionList = languageVersion config
}
replyTo config _ interface ShutdownRequest{restartPending=pending} replyHeader = do
return
KernelInfoReply
{ header = replyHeader
, language = languageName config
, versionList = languageVersion config
}
replyTo config _ interface ShutdownRequest { restartPending = pending } replyHeader = do
liftIO $ writeChan (shellReplyChannel interface) $ ShutdownReply replyHeader pending
liftIO exitSuccess
replyTo config execCount interface req@ExecuteRequest { getCode = code } replyHeader = do
let send msg = writeChan (iopubChannel interface) msg
let send = writeChan (iopubChannel interface)
busyHeader <- dupHeader replyHeader StatusMessage
liftIO . send $ PublishStatus busyHeader Busy
outputHeader <- dupHeader replyHeader DisplayDataMessage
(res, replyStatus, pagerOut) <-
let clearOutput = do
clearHeader <- dupHeader replyHeader ClearOutputMessage
send $ ClearOutput clearHeader False
sendOutput x =
send $ PublishDisplayData outputHeader (languageName config)
(displayOutput config x)
in run config code clearOutput sendOutput
(res, replyStatus, pagerOut) <- let clearOutput = do
clearHeader <- dupHeader replyHeader
ClearOutputMessage
send $ ClearOutput clearHeader False
sendOutput x =
send $ PublishDisplayData
outputHeader
(languageName config)
(displayOutput config x)
in run config code clearOutput sendOutput
liftIO . send $ PublishDisplayData outputHeader (languageName config) (displayResult config res)
idleHeader <- dupHeader replyHeader StatusMessage
liftIO . send $ PublishStatus idleHeader Idle
liftIO $ modifyMVar_ execCount (return . (+1))
liftIO $ modifyMVar_ execCount (return . (+ 1))
counter <- liftIO $ readMVar execCount
return ExecuteReply
{ header = replyHeader
, pagerOutput = pagerOut
, executionCounter = fromIntegral counter
, status = replyStatus
}
return
ExecuteReply
{ header = replyHeader
, pagerOutput = pagerOut
, executionCounter = fromIntegral counter
, status = replyStatus
}
replyTo config _ _ req@CompleteRequest{} replyHeader = do
replyTo config _ _ req@CompleteRequest{} replyHeader =
-- TODO: FIX
error "Unimplemented in IPython 3.0"
{-
let code = getCode req
line = getCodeLine req
col = getCursorPos req
return $ case completion config code line col of
Nothing ->
CompleteReply
{ header = replyHeader
, completionMatches = []
, completionMatchedText = ""
, completionText = ""
, completionStatus = False
}
Just (matches, matchedText, cmplText) ->
CompleteReply
{ header = replyHeader
, completionMatches = matches
, completionMatchedText = matchedText
, completionText = cmplText
, completionStatus = True
}
-}
replyTo config _ _ ObjectInfoRequest { objectName = obj } replyHeader =
return $ case objectInfo config obj of
Just (name, docs, ty) -> ObjectInfoReply
{ header = replyHeader
, objectName = obj
, objectFound = True
, objectTypeString = ty
, objectDocString = docs
}
Nothing -> ObjectInfoReply
{ header = replyHeader
, objectName = obj
, objectFound = False
, objectTypeString = ""
, objectDocString = ""
}
return $
case objectInfo config obj of
Just (name, docs, ty) -> ObjectInfoReply
{ header = replyHeader
, objectName = obj
, objectFound = True
, objectTypeString = ty
, objectDocString = docs
}
Nothing -> ObjectInfoReply
{ header = replyHeader
, objectName = obj
, objectFound = False
, objectTypeString = ""
, objectDocString = ""
}
replyTo _ _ _ msg _ = do
liftIO $ putStrLn "Unknown message: "
liftIO $ print msg
return msg
dupHeader :: MonadIO m => MessageHeader -> MessageType -> m MessageHeader
dupHeader hdr mtype =
do uuid <- liftIO UUID.random
return hdr { messageId = uuid , msgType = mtype }
do
uuid <- liftIO UUID.random
return hdr { messageId = uuid, msgType = mtype }
-- | This module exports all the types and functions necessary to create an
-- IPython language kernel that supports the @ipython console@ and @ipython
-- notebook@ frontends.
module IHaskell.IPython.Kernel (
module X,
) where
-- | This module exports all the types and functions necessary to create an IPython language kernel
-- that supports the @ipython console@ and @ipython notebook@ frontends.
module IHaskell.IPython.Kernel (module X) where
import IHaskell.IPython.Types as X
import IHaskell.IPython.Message.Writer as X
import IHaskell.IPython.Message.Parser as X
import IHaskell.IPython.Message.UUID as X
import IHaskell.IPython.ZeroMQ as X
import IHaskell.IPython.Types as X
import IHaskell.IPython.Message.Writer as X
import IHaskell.IPython.Message.Parser as X
import IHaskell.IPython.Message.UUID as X
import IHaskell.IPython.ZeroMQ as X
{-# 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.
-- 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)
......@@ -17,9 +18,7 @@ import IHaskell.IPython.Types
type LByteString = Lazy.ByteString
----- External interface -----
-- | Parse a message from its ByteString components into a Message.
-- --- 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.
......@@ -32,26 +31,25 @@ parseMessage idents headerData parentHeader metadata content =
messageWithoutHeader = parser messageType $ Lazy.fromStrict content
in messageWithoutHeader { header = header }
----- Module internals -----
-- | Parse a header from its ByteString components into a MessageHeader.
-- --- 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
}
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.
-- 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
......@@ -71,27 +69,26 @@ 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.
-> 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 ExecuteRequestMessage = executeRequestParser
parser CompleteRequestMessage = completeRequestParser
parser ObjectInfoRequestMessage = objectInfoRequestParser
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.
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:
-- | 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.
......@@ -99,22 +96,23 @@ kernelInfoRequestParser _ = KernelInfoRequest { header = noHeader }
executeRequestParser :: LByteString -> Message
executeRequestParser content =
let parser obj = do
code <- obj .: "code"
silent <- obj .: "silent"
storeHistory <- obj .: "store_history"
allowStdin <- obj .: "allow_stdin"
code <- obj .: "code"
silent <- obj .: "silent"
storeHistory <- obj .: "store_history"
allowStdin <- obj .: "allow_stdin"
return (code, silent, storeHistory, allowStdin)
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 = []
}
in ExecuteRequest
{ header = noHeader
, getCode = code
, getSilent = silent
, getAllowStdin = allowStdin
, getStoreHistory = storeHistory
, getUserVariables = []
, getUserExpressions = []
}
requestParser parser content = parsed
where
......@@ -147,7 +145,6 @@ objectInfoRequestParser = requestParser $ \obj -> do
dlevel <- obj .: "detail_level"
return $ ObjectInfoRequest noHeader oname dlevel
shutdownRequestParser :: LByteString -> Message
shutdownRequestParser = requestParser $ \obj -> do
code <- obj .: "restart"
......
-- | Description : UUID generator and data structure
--
-- Generate, parse, and pretty print UUIDs for use with IPython.
module IHaskell.IPython.Message.UUID (
UUID,
random, randoms,
) where
module IHaskell.IPython.Message.UUID (UUID, random, randoms) where
import Control.Monad (mzero, replicateM)
import Control.Applicative ((<$>))
import Data.Text (pack)
import Data.Aeson
import Data.UUID.V4 (nextRandom)
-- We use an internal string representation because for the purposes of
-- IPython, it matters whether the letters are uppercase or lowercase and
-- whether the dashes are present in the correct locations. For the
-- purposes of new UUIDs, it does not matter, but IPython expects UUIDs
-- passed to kernels to be returned unchanged, so we cannot actually parse
-- them.
import Control.Monad (mzero, replicateM)
import Control.Applicative ((<$>))
import Data.Text (pack)
import Data.Aeson
import Data.UUID.V4 (nextRandom)
-- | A UUID (universally unique identifier).
data UUID = UUID String deriving (Show, Read, Eq, Ord)
data UUID =
-- We use an internal string representation because for the purposes of IPython, it
-- matters whether the letters are uppercase or lowercase and whether the dashes are
-- present in the correct locations. For the purposes of new UUIDs, it does not matter,
-- but IPython expects UUIDs passed to kernels to be returned unchanged, so we cannot
-- actually parse them.
UUID String
deriving (Show, Read, Eq, Ord)
-- | Generate a list of random UUIDs.
randoms :: Int -- ^ Number of UUIDs to generate.
......
{-# LANGUAGE OverloadedStrings #-}
-- | Description : @ToJSON@ for Messages
--
-- This module contains the @ToJSON@ instance for @Message@.
module IHaskell.IPython.Message.Writer (
ToJSON(..)
) where
module IHaskell.IPython.Message.Writer (ToJSON(..)) where
import Data.Aeson
import Data.Map (Map)
import Data.Text (Text, pack)
import Data.Monoid (mempty)
import Data.Aeson
import Data.Map (Map)
import Data.Text (Text, pack)
import Data.Monoid (mempty)
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B
import Data.Text.Encoding
import Data.Text.Encoding
import IHaskell.IPython.Types
import IHaskell.IPython.Types
-- Convert message bodies into JSON.
instance ToJSON Message where
toJSON KernelInfoReply{ versionList = vers, language = language } = object [
"protocol_version" .= string "5.0", -- current protocol version, major and minor
"language_version" .= vers,
"language" .= language
]
toJSON ExecuteReply{ status = status, executionCounter = counter, pagerOutput = pager} = object [
"status" .= show status,
"execution_count" .= counter,
"payload" .=
if null pager
then []
else [object [
"source" .= string "page",
"text" .= pager
]],
"user_variables" .= emptyMap,
"user_expressions" .= emptyMap
]
toJSON PublishStatus{ executionState = executionState } = object [
"execution_state" .= executionState
]
toJSON PublishStream{ streamType = streamType, streamContent = content } = object [
"data" .= content,
"name" .= streamType
]
toJSON PublishDisplayData{ source = src, displayData = datas } = object [
"source" .= src,
"metadata" .= object [],
"data" .= object (map displayDataToJson datas)
]
toJSON PublishOutput{ executionCount = execCount, reprText = reprText } = object [
"data" .= object ["text/plain" .= reprText],
"execution_count" .= execCount,
"metadata" .= object []
]
toJSON PublishInput{ executionCount = execCount, inCode = code } = object [
"execution_count" .= execCount,
"code" .= code
]
toJSON (CompleteReply _ matches start end metadata status) = object [
"matches" .= matches,
"cursor_start" .= start,
"cursor_end" .= end,
"metadata" .= metadata,
"status" .= if status then string "ok" else "error"
]
toJSON o@ObjectInfoReply{} = object [
"oname" .= objectName o,
"found" .= objectFound o,
"ismagic" .= False,
"isalias" .= False,
"type_name" .= objectTypeString o,
"docstring" .= objectDocString o
]
toJSON ShutdownReply{restartPending = restart} = object [
"restart" .= restart
]
toJSON ClearOutput{wait = wait} = object [
"wait" .= wait
]
toJSON RequestInput{inputPrompt = prompt} = object [
"prompt" .= prompt
]
toJSON req@CommOpen{} = object [
"comm_id" .= commUuid req,
"target_name" .= commTargetName req,
"data" .= commData req
]
toJSON req@CommData{} = object [
"comm_id" .= commUuid req,
"data" .= commData req
]
toJSON req@CommClose{} = object [
"comm_id" .= commUuid req,
"data" .= commData req
]
toJSON req@HistoryReply{} = object [ "history" .= map tuplify (historyReply req) ]
where tuplify (HistoryReplyElement sess linum res) = (sess, linum, case res of
Left inp -> toJSON inp
Right (inp, out) -> toJSON out)
toJSON KernelInfoReply { versionList = vers, language = language } =
object ["protocol_version" .= string "5.0" -- current protocol version, major and minor
, "language_version" .= vers, "language" .= language]
toJSON ExecuteReply { status = status, executionCounter = counter, pagerOutput = pager } =
object
[ "status" .= show status
, "execution_count" .= counter
, "payload" .=
if null pager
then []
else [object ["source" .= string "page", "text" .= pager]]
, "user_variables" .= emptyMap
, "user_expressions" .= emptyMap
]
toJSON PublishStatus { executionState = executionState } =
object ["execution_state" .= executionState]
toJSON PublishStream { streamType = streamType, streamContent = content } =
object ["data" .= content, "name" .= streamType]
toJSON PublishDisplayData { source = src, displayData = datas } =
object
["source" .= src, "metadata" .=
object [], "data" .=
object (map displayDataToJson datas)]
toJSON PublishOutput { executionCount = execCount, reprText = reprText } =
object
["data" .=
object ["text/plain" .= reprText], "execution_count" .= execCount, "metadata" .=
object []]
toJSON PublishInput { executionCount = execCount, inCode = code } =
object ["execution_count" .= execCount, "code" .= code]
toJSON (CompleteReply _ matches start end metadata status) =
object
[ "matches" .= matches
, "cursor_start" .= start
, "cursor_end" .= end
, "metadata" .= metadata
, "status" .= if status
then string "ok"
else "error"
]
toJSON o@ObjectInfoReply{} =
object
[ "oname" .=
objectName o
, "found" .= objectFound o
, "ismagic" .= False
, "isalias" .= False
, "type_name" .= objectTypeString o
, "docstring" .= objectDocString o
]
toJSON ShutdownReply { restartPending = restart } =
object ["restart" .= restart]
toJSON ClearOutput { wait = wait } =
object ["wait" .= wait]
toJSON RequestInput { inputPrompt = prompt } =
object ["prompt" .= prompt]
toJSON req@CommOpen{} =
object ["comm_id" .= commUuid req, "target_name" .= commTargetName req, "data" .= commData req]
toJSON req@CommData{} =
object ["comm_id" .= commUuid req, "data" .= commData req]
toJSON req@CommClose{} =
object ["comm_id" .= commUuid req, "data" .= commData req]
toJSON req@HistoryReply{} =
object ["history" .= map tuplify (historyReply req)]
where
tuplify (HistoryReplyElement sess linum res) = (sess, linum, case res of
Left inp -> toJSON inp
Right (inp, out) -> toJSON out)
toJSON body = error $ "Do not know how to convert to JSON for message " ++ show body
-- | Print an execution state as "busy", "idle", or "starting".
instance ToJSON ExecutionState where
toJSON Busy = String "busy"
toJSON Idle = String "idle"
toJSON Starting = String "starting"
toJSON Busy = String "busy"
toJSON Idle = String "idle"
toJSON Starting = String "starting"
-- | Print a stream as "stdin" or "stdout" strings.
instance ToJSON StreamType where
toJSON Stdin = String "stdin"
toJSON Stdout = String "stdout"
toJSON Stdin = String "stdin"
toJSON Stdout = String "stdout"
-- | Convert a MIME type and value into a JSON dictionary pair.
displayDataToJson :: DisplayData -> (Text, Value)
displayDataToJson :: DisplayData -> (Text, Value)
displayDataToJson (DisplayData mimeType dataStr) =
pack (show mimeType) .= String dataStr
pack (show mimeType) .= String dataStr
----- Constants -----
emptyMap :: Map String String
emptyMap = mempty
......
{-# LANGUAGE OverloadedStrings, DoAndIfThenElse #-}
-- | This module provides a way in which the Haskell standard input may be
-- forwarded to the IPython frontend and thus allows the notebook to use
-- the standard input.
-- | This module provides a way in which the Haskell standard input may be forwarded to the IPython
-- frontend and thus allows the notebook to use the standard input.
--
-- This relies on the implementation of file handles in GHC, and is
-- generally unsafe and terrible. However, it is difficult to find another
-- way to do it, as file handles are generally meant to point to streams
-- and files, and not networked communication protocols.
-- This relies on the implementation of file handles in GHC, and is generally unsafe and terrible.
-- However, it is difficult to find another way to do it, as file handles are generally meant to
-- point to streams and files, and not networked communication protocols.
--
-- In order to use this module, it must first be initialized with two
-- things. First of all, in order to know how to communicate with the
-- IPython frontend, it must know the kernel profile used for
-- communication. For this, use @recordKernelProfile@ once the profile is
-- known. Both this and @recordParentHeader@ take a directory name where
-- they can store this data.
-- In order to use this module, it must first be initialized with two things. First of all, in order
-- to know how to communicate with the IPython frontend, it must know the kernel profile used for
-- communication. For this, use @recordKernelProfile@ once the profile is known. Both this and
-- @recordParentHeader@ take a directory name where they can store this data.
--
-- Finally, the module must know what @execute_request@ message is
-- currently being replied to (which will request the input). Thus, every
-- time the language kernel receives an @execute_request@ message, it
-- should inform this module via @recordParentHeader@, so that the module
-- may generate messages with an appropriate parent header set. If this is
-- not done, the IPython frontends will not recognize the target of the
-- communication.
-- Finally, the module must know what @execute_request@ message is currently being replied to (which
-- will request the input). Thus, every time the language kernel receives an @execute_request@
-- message, it should inform this module via @recordParentHeader@, so that the module may generate
-- messages with an appropriate parent header set. If this is not done, the IPython frontends will
-- not recognize the target of the communication.
--
-- Finally, in order to activate this module, @fixStdin@ must be called
-- once. It must be passed the same directory name as @recordParentHeader@
-- and @recordKernelProfile@. Note that if this is being used from within
-- the GHC API, @fixStdin@ /must/ be called from within the GHC session
-- not from the host code.
module IHaskell.IPython.Stdin (
fixStdin,
recordParentHeader,
recordKernelProfile
) where
-- Finally, in order to activate this module, @fixStdin@ must be called once. It must be passed the
-- same directory name as @recordParentHeader@ and @recordKernelProfile@. Note that if this is being
-- used from within the GHC API, @fixStdin@ /must/ be called from within the GHC session not from
-- the host code.
module IHaskell.IPython.Stdin (fixStdin, recordParentHeader, recordKernelProfile) where
import Control.Concurrent
import Control.Applicative ((<$>))
import Control.Concurrent.Chan
import Control.Monad
import GHC.IO.Handle
import GHC.IO.Handle.Types
import System.IO
import System.Posix.IO
import System.IO.Unsafe
import qualified Data.Map as Map
import Control.Concurrent
import Control.Applicative ((<$>))
import Control.Concurrent.Chan
import Control.Monad
import GHC.IO.Handle
import GHC.IO.Handle.Types
import System.IO
import System.Posix.IO
import System.IO.Unsafe
import qualified Data.Map as Map
import IHaskell.IPython.Types
import IHaskell.IPython.ZeroMQ
import IHaskell.IPython.Message.UUID as UUID
import IHaskell.IPython.Types
import IHaskell.IPython.ZeroMQ
import IHaskell.IPython.Message.UUID as UUID
stdinInterface :: MVar ZeroMQStdin
{-# NOINLINE stdinInterface #-}
stdinInterface = unsafePerformIO newEmptyMVar
-- | Manipulate standard input so that it is sourced from the IPython
-- frontend. This function is build on layers of deep magical hackery, so
-- be careful modifying it.
-- | Manipulate standard input so that it is sourced from the IPython frontend. This function is
-- build on layers of deep magical hackery, so be careful modifying it.
fixStdin :: String -> IO ()
fixStdin dir = do
-- Initialize the stdin interface.
......@@ -78,17 +67,18 @@ stdinOnce dir = do
hDuplicateTo newStdin stdin
loop stdinInput oldStdin newStdin
where
loop stdinInput oldStdin newStdin = do
let FileHandle _ mvar = stdin
threadDelay $ 150 * 1000
empty <- isEmptyMVar mvar
if not empty
then loop stdinInput oldStdin newStdin
else do
line <- getInputLine dir
hPutStr stdinInput $ line ++ "\n"
loop stdinInput oldStdin newStdin
then loop stdinInput oldStdin newStdin
else do
line <- getInputLine dir
hPutStr stdinInput $ line ++ "\n"
loop stdinInput oldStdin newStdin
-- | Get a line of input from the IPython frontend.
getInputLine :: String -> IO String
......@@ -98,15 +88,15 @@ getInputLine dir = do
-- Send a request for input.
uuid <- UUID.random
parentHeader <- read <$> readFile (dir ++ "/.last-req-header")
let header = MessageHeader {
username = username parentHeader,
identifiers = identifiers parentHeader,
parentHeader = Just parentHeader,
messageId = uuid,
sessionId = sessionId parentHeader,
metadata = Map.fromList [],
msgType = InputRequestMessage
}
let header = MessageHeader
{ username = username parentHeader
, identifiers = identifiers parentHeader
, parentHeader = Just parentHeader
, messageId = uuid
, sessionId = sessionId parentHeader
, metadata = Map.fromList []
, msgType = InputRequestMessage
}
let msg = RequestInput header ""
writeChan req msg
......
{-# LANGUAGE OverloadedStrings, DeriveDataTypeable, DeriveGeneric #-}
-- | This module contains all types used to create an IPython language
-- kernel.
-- | This module contains all types used to create an IPython language kernel.
module IHaskell.IPython.Types (
-- * IPython kernel profile
Profile(..),
Transport(..),
Port(..),
IP(..),
-- * IPython kernelspecs
KernelSpec(..),
-- * IPython messaging protocol
Message(..),
MessageHeader(..),
Username(..),
Metadata(..),
MessageType(..),
Width(..), Height(..),
StreamType(..),
ExecutionState(..),
ExecuteReplyStatus(..),
HistoryAccessType(..),
HistoryReplyElement(..),
replyType,
-- ** IPython display data message
DisplayData(..),
MimeType(..),
extractPlain
) where
import Data.Aeson
import Control.Applicative ((<$>), (<*>))
import Data.ByteString (ByteString)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Data.Text (Text)
import Data.Serialize
import IHaskell.IPython.Message.UUID
import GHC.Generics (Generic)
import Data.Typeable
import Data.List (find)
import Data.Map (Map)
-------------------- IPython Kernel Profile Types ----------------------
-- * IPython kernel profile
Profile(..),
Transport(..),
Port(..),
IP(..),
-- * IPython kernelspecs
KernelSpec(..),
-- * IPython messaging protocol
Message(..),
MessageHeader(..),
Username(..),
Metadata(..),
MessageType(..),
Width(..),
Height(..),
StreamType(..),
ExecutionState(..),
ExecuteReplyStatus(..),
HistoryAccessType(..),
HistoryReplyElement(..),
replyType,
-- ** IPython display data message
DisplayData(..),
MimeType(..),
extractPlain,
) where
import Data.Aeson
import Control.Applicative ((<$>), (<*>))
import Data.ByteString (ByteString)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Data.Text (Text)
import Data.Serialize
import IHaskell.IPython.Message.UUID
import GHC.Generics (Generic)
import Data.Typeable
import Data.List (find)
import Data.Map (Map)
------------------ IPython Kernel Profile Types ----------------------
--
-- | A TCP port.
type Port = Int
......@@ -57,15 +58,17 @@ data Transport = TCP -- ^ Default transport mechanism via TCP.
deriving (Show, Read)
-- | A kernel profile, specifying how the kernel communicates.
data Profile = Profile { ip :: IP -- ^ The IP on which to listen.
, transport :: Transport -- ^ The transport mechanism.
, stdinPort :: Port -- ^ The stdin channel port.
, controlPort :: Port -- ^ The control channel port.
, hbPort :: Port -- ^ The heartbeat channel port.
, shellPort :: Port -- ^ The shell command port.
, iopubPort :: Port -- ^ The IOPub port.
, signatureKey :: ByteString -- ^ The HMAC encryption key.
}
data Profile =
Profile
{ ip :: IP -- ^ The IP on which to listen.
, transport :: Transport -- ^ The transport mechanism.
, stdinPort :: Port -- ^ The stdin channel port.
, controlPort :: Port -- ^ The control channel port.
, hbPort :: Port -- ^ The heartbeat channel port.
, shellPort :: Port -- ^ The shell command port.
, iopubPort :: Port -- ^ The IOPub port.
, signatureKey :: ByteString -- ^ The HMAC encryption key.
}
deriving (Show, Read)
-- Convert the kernel profile to and from JSON.
......@@ -87,35 +90,39 @@ instance FromJSON Profile where
instance ToJSON Profile where
toJSON profile = object
[ "ip" .= ip profile
, "transport" .= transport profile
, "stdin_port" .= stdinPort profile
[ "ip" .= ip profile
, "transport" .= transport profile
, "stdin_port" .= stdinPort profile
, "control_port" .= controlPort profile
, "hb_port" .= hbPort profile
, "shell_port" .= shellPort profile
, "iopub_port" .= iopubPort profile
, "key" .= Text.decodeUtf8 (signatureKey profile)
, "hb_port" .= hbPort profile
, "shell_port" .= shellPort profile
, "iopub_port" .= iopubPort profile
, "key" .= Text.decodeUtf8 (signatureKey profile)
]
instance FromJSON Transport where
parseJSON (String mech) =
case mech of
"tcp" -> return TCP
_ -> fail $ "Unknown transport mechanism " ++ Text.unpack mech
_ -> fail $ "Unknown transport mechanism " ++ Text.unpack mech
parseJSON _ = fail "Expected JSON string as transport."
instance ToJSON Transport where
toJSON TCP = String "tcp"
-------------------- IPython Kernelspec Types ----------------------
data KernelSpec = KernelSpec {
kernelDisplayName :: String, -- ^ Name shown to users to describe this kernel (e.g. "Haskell")
kernelLanguage :: String, -- ^ Name for the kernel; unique kernel identifier (e.g. "haskell")
kernelCommand :: [String] -- ^ Command to run to start the kernel. One of the strings may be
-- @"{connection_file}"@, which will be replaced by the path to a
-- kernel profile file (see @Profile@) when the command is run.
} deriving (Eq, Show)
data KernelSpec =
KernelSpec
{
-- | Name shown to users to describe this kernel (e.g. "Haskell")
kernelDisplayName :: String
-- | Name for the kernel; unique kernel identifier (e.g. "haskell")
, kernelLanguage :: String
-- | Command to run to start the kernel. One of the strings maybe @"{connection_file}"@, which will
-- be replaced by the path to a kernel profile file (see @Profile@) when the command is run.
, kernelCommand :: [String]
}
deriving (Eq, Show)
instance ToJSON KernelSpec where
toJSON kernelspec = object
......@@ -124,29 +131,31 @@ instance ToJSON KernelSpec where
, "language" .= kernelLanguage kernelspec
]
-------------------- IPython Message Types ----------------------
-- | A message header with some metadata.
data MessageHeader = MessageHeader {
identifiers :: [ByteString], -- ^ The identifiers sent with the message.
parentHeader :: Maybe MessageHeader, -- ^ The parent header, if present.
metadata :: Metadata, -- ^ A dict of metadata.
messageId :: UUID, -- ^ A unique message UUID.
sessionId :: UUID, -- ^ A unique session UUID.
username :: Username, -- ^ The user who sent this message.
msgType :: MessageType -- ^ The message type.
} deriving (Show, Read)
-- Convert a message header into the JSON field for the header.
-- This field does not actually have all the record fields.
------------------ IPython Message Types --------------------
--
-- | A message header with some metadata.
data MessageHeader =
MessageHeader
{ identifiers :: [ByteString] -- ^ The identifiers sent with the message.
, parentHeader :: Maybe MessageHeader -- ^ The parent header, if present.
, metadata :: Metadata -- ^ A dict of metadata.
, messageId :: UUID -- ^ A unique message UUID.
, sessionId :: UUID -- ^ A unique session UUID.
, username :: Username -- ^ The user who sent this message.
, msgType :: MessageType -- ^ The message type.
}
deriving (Show, Read)
-- Convert a message header into the JSON field for the header. This field does not actually have
-- all the record fields.
instance ToJSON MessageHeader where
toJSON header = object [
"msg_id" .= messageId header,
"session" .= sessionId header,
"username" .= username header,
"version" .= ("5.0" :: String),
"msg_type" .= showMessageType (msgType header)
]
toJSON header = object
[ "msg_id" .= messageId header
, "session" .= sessionId header
, "username" .= username header
, "version" .= ("5.0" :: String)
, "msg_type" .= showMessageType (msgType header)
]
-- | A username for the source of a message.
type Username = Text
......@@ -178,32 +187,32 @@ data MessageType = KernelInfoReplyMessage
| CommCloseMessage
| HistoryRequestMessage
| HistoryReplyMessage
deriving (Show, Read, Eq)
deriving (Show, Read, Eq)
showMessageType :: MessageType -> String
showMessageType KernelInfoReplyMessage = "kernel_info_reply"
showMessageType KernelInfoReplyMessage = "kernel_info_reply"
showMessageType KernelInfoRequestMessage = "kernel_info_request"
showMessageType ExecuteReplyMessage = "execute_reply"
showMessageType ExecuteRequestMessage = "execute_request"
showMessageType StatusMessage = "status"
showMessageType StreamMessage = "stream"
showMessageType DisplayDataMessage = "display_data"
showMessageType OutputMessage = "pyout"
showMessageType InputMessage = "pyin"
showMessageType CompleteRequestMessage = "complete_request"
showMessageType CompleteReplyMessage = "complete_reply"
showMessageType ExecuteReplyMessage = "execute_reply"
showMessageType ExecuteRequestMessage = "execute_request"
showMessageType StatusMessage = "status"
showMessageType StreamMessage = "stream"
showMessageType DisplayDataMessage = "display_data"
showMessageType OutputMessage = "pyout"
showMessageType InputMessage = "pyin"
showMessageType CompleteRequestMessage = "complete_request"
showMessageType CompleteReplyMessage = "complete_reply"
showMessageType ObjectInfoRequestMessage = "object_info_request"
showMessageType ObjectInfoReplyMessage = "object_info_reply"
showMessageType ShutdownRequestMessage = "shutdown_request"
showMessageType ShutdownReplyMessage = "shutdown_reply"
showMessageType ClearOutputMessage = "clear_output"
showMessageType InputRequestMessage = "input_request"
showMessageType InputReplyMessage = "input_reply"
showMessageType CommOpenMessage = "comm_open"
showMessageType CommDataMessage = "comm_msg"
showMessageType CommCloseMessage = "comm_close"
showMessageType HistoryRequestMessage = "history_request"
showMessageType HistoryReplyMessage = "history_reply"
showMessageType ObjectInfoReplyMessage = "object_info_reply"
showMessageType ShutdownRequestMessage = "shutdown_request"
showMessageType ShutdownReplyMessage = "shutdown_reply"
showMessageType ClearOutputMessage = "clear_output"
showMessageType InputRequestMessage = "input_request"
showMessageType InputReplyMessage = "input_reply"
showMessageType CommOpenMessage = "comm_open"
showMessageType CommDataMessage = "comm_msg"
showMessageType CommCloseMessage = "comm_close"
showMessageType HistoryRequestMessage = "history_request"
showMessageType HistoryReplyMessage = "history_reply"
instance FromJSON MessageType where
parseJSON (String s) =
......@@ -235,177 +244,161 @@ instance FromJSON MessageType where
_ -> fail ("Unknown message type: " ++ show s)
parseJSON _ = fail "Must be a string."
-- | A message used to communicate with the IPython frontend.
data Message
-- | A request from a frontend for information about the kernel.
= KernelInfoRequest { header :: MessageHeader }
-- | A response to a KernelInfoRequest.
| KernelInfoReply {
header :: MessageHeader,
versionList :: [Int], -- ^ The version of the language, e.g. [7, 6, 3] for GHC 7.6.3
language :: String -- ^ The language name, e.g. "haskell"
}
-- | A request from a frontend to execute some code.
| ExecuteRequest {
header :: MessageHeader,
getCode :: Text, -- ^ The code string.
getSilent :: Bool, -- ^ Whether this should be silently executed.
getStoreHistory :: Bool, -- ^ Whether to store this in history.
getAllowStdin :: Bool, -- ^ Whether this code can use stdin.
getUserVariables :: [Text], -- ^ Unused.
getUserExpressions :: [Text] -- ^ Unused.
}
-- | A reply to an execute request.
| ExecuteReply {
header :: MessageHeader,
status :: ExecuteReplyStatus, -- ^ The status of the output.
pagerOutput :: String, -- ^ The help string to show in the pager.
executionCounter :: Int -- ^ The execution count, i.e. which output this is.
}
| PublishStatus {
header :: MessageHeader,
executionState :: ExecutionState -- ^ The execution state of the kernel.
}
| PublishStream {
header :: MessageHeader,
streamType :: StreamType, -- ^ Which stream to publish to.
streamContent :: String -- ^ What to publish.
}
| PublishDisplayData {
header :: MessageHeader,
source :: String, -- ^ The name of the data source.
displayData :: [DisplayData] -- ^ A list of data representations.
}
| PublishOutput {
header :: MessageHeader,
reprText :: String, -- ^ Printed output text.
executionCount :: Int -- ^ Which output this is for.
}
| PublishInput {
header :: MessageHeader,
inCode :: String, -- ^ Submitted input code.
executionCount :: Int -- ^ Which input this is.
}
| CompleteRequest {
header :: MessageHeader,
getCode :: Text, {- ^
data Message =
-- | A request from a frontend for information about the kernel.
KernelInfoRequest { header :: MessageHeader }
|
-- | A response to a KernelInfoRequest.
KernelInfoReply
{ header :: MessageHeader
, versionList :: [Int] -- ^ The version of the language, e.g. [7, 6, 3] for GHC
-- 7.6.3
, language :: String -- ^ The language name, e.g. "haskell"
}
|
-- | A request from a frontend to execute some code.
ExecuteRequest
{ header :: MessageHeader
, getCode :: Text -- ^ The code string.
, getSilent :: Bool -- ^ Whether this should be silently executed.
, getStoreHistory :: Bool -- ^ Whether to store this in history.
, getAllowStdin :: Bool -- ^ Whether this code can use stdin.
, getUserVariables :: [Text] -- ^ Unused.
, getUserExpressions :: [Text] -- ^ Unused.
}
|
-- | A reply to an execute request.
ExecuteReply
{ header :: MessageHeader
, status :: ExecuteReplyStatus -- ^ The status of the output.
, pagerOutput :: String -- ^ The help string to show in the pager.
, executionCounter :: Int -- ^ The execution count, i.e. which output this is.
}
|
PublishStatus
{ header :: MessageHeader
, executionState :: ExecutionState -- ^ The execution state of the kernel.
}
|
PublishStream
{ header :: MessageHeader
, streamType :: StreamType -- ^ Which stream to publish to.
, streamContent :: String -- ^ What to publish.
}
|
PublishDisplayData
{ header :: MessageHeader
, source :: String -- ^ The name of the data source.
, displayData :: [DisplayData] -- ^ A list of data representations.
}
|
PublishOutput
{ header :: MessageHeader
, reprText :: String -- ^ Printed output text.
, executionCount :: Int -- ^ Which output this is for.
}
|
PublishInput
{ header :: MessageHeader
, inCode :: String -- ^ Submitted input code.
, executionCount :: Int -- ^ Which input this is.
}
|
CompleteRequest
{ header :: MessageHeader
, getCode :: Text {- ^
The entire block of text where the line is. This may be useful in the
case of multiline completions where more context may be needed. Note: if
in practice this field proves unnecessary, remove it to lighten the
messages. json field @code@ -}
getCursorPos :: Int -- ^ Position of the cursor in unicode characters. json field @cursor_pos@
}
| CompleteReply {
header :: MessageHeader,
completionMatches :: [Text],
completionCursorStart :: Int,
completionCursorEnd :: Int,
completionMetadata :: Metadata,
completionStatus :: Bool
}
| ObjectInfoRequest {
header :: MessageHeader,
objectName :: Text, -- ^ Name of object being searched for.
detailLevel :: Int -- ^ Level of detail desired (defaults to 0).
-- 0 is equivalent to foo?, 1 is equivalent
-- to foo??.
}
| ObjectInfoReply {
header :: MessageHeader,
objectName :: Text, -- ^ Name of object which was searched for.
objectFound :: Bool, -- ^ Whether the object was found.
objectTypeString :: Text, -- ^ Object type.
objectDocString :: Text
}
| ShutdownRequest {
header :: MessageHeader,
restartPending :: Bool -- ^ Whether this shutdown precedes a restart.
}
| ShutdownReply {
header :: MessageHeader,
restartPending :: Bool -- ^ Whether this shutdown precedes a restart.
}
| ClearOutput {
header :: MessageHeader,
wait :: Bool -- ^ Whether to wait to redraw until there is more output.
}
| RequestInput {
header :: MessageHeader,
inputPrompt :: String
}
| InputReply {
header :: MessageHeader,
inputValue :: String
}
| CommOpen {
header :: MessageHeader,
commTargetName :: String,
commUuid :: UUID,
commData :: Value
}
| CommData {
header :: MessageHeader,
commUuid :: UUID,
commData :: Value
}
| CommClose {
header :: MessageHeader,
commUuid :: UUID,
commData :: Value
}
| HistoryRequest {
header :: MessageHeader,
historyGetOutput :: Bool, -- ^ If True, also return output history in the resulting dict.
historyRaw :: Bool, -- ^ If True, return the raw input history, else the transformed input.
historyAccessType :: HistoryAccessType -- ^ What history is being requested.
}
| HistoryReply {
header :: MessageHeader,
historyReply :: [HistoryReplyElement]
}
| SendNothing -- Dummy message; nothing is sent.
deriving Show
-- | Ways in which the frontend can request history.
-- TODO: Implement fields as described in messaging spec.
, getCursorPos :: Int -- ^ Position of the cursor in unicode characters. json field
-- @cursor_pos@
}
|
CompleteReply
{ header :: MessageHeader
, completionMatches :: [Text]
, completionCursorStart :: Int
, completionCursorEnd :: Int
, completionMetadata :: Metadata
, completionStatus :: Bool
}
|
ObjectInfoRequest
{ header :: MessageHeader
-- | Name of object being searched for.
, objectName :: Text
-- | Level of detail desired (defaults to 0). 0 is equivalent to foo?, 1 is equivalent to foo??.
, detailLevel :: Int
}
|
ObjectInfoReply
{ header :: MessageHeader
, objectName :: Text -- ^ Name of object which was searched for.
, objectFound :: Bool -- ^ Whether the object was found.
, objectTypeString :: Text -- ^ Object type.
, objectDocString :: Text
}
|
ShutdownRequest
{ header :: MessageHeader
, restartPending :: Bool -- ^ Whether this shutdown precedes a restart.
}
|
ShutdownReply
{ header :: MessageHeader
, restartPending :: Bool -- ^ Whether this shutdown precedes a restart.
}
|
ClearOutput
{ header :: MessageHeader
, wait :: Bool -- ^ Whether to wait to redraw until there is more output.
}
| RequestInput { header :: MessageHeader, inputPrompt :: String }
| InputReply { header :: MessageHeader, inputValue :: String }
|
CommOpen
{ header :: MessageHeader
, commTargetName :: String
, commUuid :: UUID
, commData :: Value
}
| CommData { header :: MessageHeader, commUuid :: UUID, commData :: Value }
| CommClose { header :: MessageHeader, commUuid :: UUID, commData :: Value }
|
HistoryRequest
{ header :: MessageHeader
, historyGetOutput :: Bool -- ^ If True, also return output history in the resulting
-- dict.
, historyRaw :: Bool -- ^ If True, return the raw input history, else the
-- transformed input.
, historyAccessType :: HistoryAccessType -- ^ What history is being requested.
}
| HistoryReply { header :: MessageHeader, historyReply :: [HistoryReplyElement] }
| SendNothing -- Dummy message; nothing is sent.
deriving Show
-- | Ways in which the frontend can request history. TODO: Implement fields as described in
-- messaging spec.
data HistoryAccessType = HistoryRange
| HistoryTail
| HistorySearch
deriving (Eq, Show)
-- | Reply to history requests.
data HistoryReplyElement = HistoryReplyElement { historyReplySession :: Int
, historyReplyLineNumber :: Int
, historyReplyContent :: Either String (String, String)
}
data HistoryReplyElement =
HistoryReplyElement
{ historyReplySession :: Int
, historyReplyLineNumber :: Int
, historyReplyContent :: Either String (String, String)
}
deriving (Eq, Show)
-- | Possible statuses in the execution reply messages.
data ExecuteReplyStatus = Ok | Err | Abort
data ExecuteReplyStatus = Ok
| Err
| Abort
instance Show ExecuteReplyStatus where
show Ok = "ok"
......@@ -413,40 +406,49 @@ instance Show ExecuteReplyStatus where
show Abort = "abort"
-- | The execution state of the kernel.
data ExecutionState = Busy | Idle | Starting deriving Show
data ExecutionState = Busy
| Idle
| Starting
deriving Show
-- | Input and output streams.
data StreamType = Stdin | Stdout deriving Show
data StreamType = Stdin
| Stdout
deriving Show
-- | Get the reply message type for a request message type.
replyType :: MessageType -> Maybe MessageType
replyType KernelInfoRequestMessage = Just KernelInfoReplyMessage
replyType ExecuteRequestMessage = Just ExecuteReplyMessage
replyType CompleteRequestMessage = Just CompleteReplyMessage
replyType ExecuteRequestMessage = Just ExecuteReplyMessage
replyType CompleteRequestMessage = Just CompleteReplyMessage
replyType ObjectInfoRequestMessage = Just ObjectInfoReplyMessage
replyType ShutdownRequestMessage = Just ShutdownReplyMessage
replyType HistoryRequestMessage = Just HistoryReplyMessage
replyType _ = Nothing
replyType ShutdownRequestMessage = Just ShutdownReplyMessage
replyType HistoryRequestMessage = Just HistoryReplyMessage
replyType _ = Nothing
-- | Data for display: a string with associated MIME type.
data DisplayData = DisplayData MimeType Text deriving (Typeable, Generic)
data DisplayData = DisplayData MimeType Text
deriving (Typeable, Generic)
-- We can't print the actual data, otherwise this will be printed every
-- time it gets computed because of the way the evaluator is structured.
-- See how `displayExpr` is computed.
-- We can't print the actual data, otherwise this will be printed every time it gets computed
-- because of the way the evaluator is structured. See how `displayExpr` is computed.
instance Show DisplayData where
show _ = "DisplayData"
-- Allow DisplayData serialization
instance Serialize Text where
put str = put (Text.encodeUtf8 str)
get = Text.decodeUtf8 <$> get
put str = put (Text.encodeUtf8 str)
get = Text.decodeUtf8 <$> get
instance Serialize DisplayData
instance Serialize MimeType
-- | Possible MIME types for the display data.
type Width = Int
type Height = Int
data MimeType = PlainText
| MimeHtml
| MimePng Width Height
......@@ -454,22 +456,22 @@ data MimeType = PlainText
| MimeSvg
| MimeLatex
| MimeJavascript
deriving (Eq, Typeable, Generic)
deriving (Eq, Typeable, Generic)
-- Extract the plain text from a list of displays.
extractPlain :: [DisplayData] -> String
extractPlain disps =
case find isPlain disps of
Nothing -> ""
Nothing -> ""
Just (DisplayData PlainText bytestr) -> Text.unpack bytestr
where
isPlain (DisplayData mime _) = mime == PlainText
instance Show MimeType where
show PlainText = "text/plain"
show MimeHtml = "text/html"
show (MimePng _ _) = "image/png"
show (MimeJpg _ _) = "image/jpeg"
show MimeSvg = "image/svg+xml"
show MimeHtml = "text/html"
show (MimePng _ _) = "image/png"
show (MimeJpg _ _) = "image/jpeg"
show MimeSvg = "image/svg+xml"
show MimeLatex = "text/latex"
show MimeJavascript = "application/javascript"
{-# LANGUAGE OverloadedStrings, DoAndIfThenElse #-}
-- | Description : Low-level ZeroMQ communication wrapper.
--
-- The "ZeroMQ" module abstracts away the low-level 0MQ based interface with IPython,
-- replacing it instead with a Haskell Channel based interface. The `serveProfile` function
-- takes a IPython profile specification and returns the channel interface to use.
module IHaskell.IPython.ZeroMQ (
ZeroMQInterface (..),
ZeroMQStdin(..),
serveProfile,
serveStdin,
) where
-- The "ZeroMQ" module abstracts away the low-level 0MQ based interface with IPython, replacing it
-- instead with a Haskell Channel based interface. The `serveProfile` function takes a IPython
-- profile specification and returns the channel interface to use.
module IHaskell.IPython.ZeroMQ (ZeroMQInterface(..), ZeroMQStdin(..), serveProfile, serveStdin) where
import qualified Data.ByteString.Lazy as LBS
import Data.ByteString (ByteString)
......@@ -26,30 +22,37 @@ import IHaskell.IPython.Types
import IHaskell.IPython.Message.Parser
import IHaskell.IPython.Message.Writer
-- | The channel interface to the ZeroMQ sockets. All communication is done via
-- Messages, which are encoded and decoded into a lower level form before being
-- transmitted to IPython. These channels should functionally serve as
-- high-level sockets which speak Messages instead of ByteStrings.
data ZeroMQInterface =
Channels {
shellRequestChannel :: Chan Message, -- ^ A channel populated with requests from the frontend.
shellReplyChannel :: Chan Message, -- ^ Writing to this channel causes a reply to be sent to the frontend.
controlRequestChannel :: Chan Message, -- ^ This channel is a duplicate of the shell request channel,
-- though using a different backend socket.
controlReplyChannel :: Chan Message, -- ^ This channel is a duplicate of the shell reply channel,
-- though using a different backend socket.
iopubChannel :: Chan Message, -- ^ Writing to this channel sends an iopub message to the frontend.
hmacKey :: ByteString -- ^ Key used to sign messages.
}
data ZeroMQStdin = StdinChannel {
stdinRequestChannel :: Chan Message,
stdinReplyChannel :: Chan Message
}
-- | Start responding on all ZeroMQ channels used to communicate with IPython
-- | via the provided profile. Return a set of channels which can be used to
-- | communicate with IPython in a more structured manner.
-- | The channel interface to the ZeroMQ sockets. All communication is done via Messages, which are
-- encoded and decoded into a lower level form before being transmitted to IPython. These channels
-- should functionally serve as high-level sockets which speak Messages instead of ByteStrings.
data ZeroMQInterface =
Channels
{
-- | A channel populated with requests from the frontend.
shellRequestChannel :: Chan Message
-- | Writing to this channel causes a reply to be sent to the frontend.
, shellReplyChannel :: Chan Message
-- | This channel is a duplicate of the shell request channel, though using a different backend
-- socket.
, controlRequestChannel :: Chan Message
-- | This channel is a duplicate of the shell reply channel, though using a different backend
-- socket.
, controlReplyChannel :: Chan Message
-- | Writing to this channel sends an iopub message to the frontend.
, iopubChannel :: Chan Message
-- | Key used to sign messages.
, hmacKey :: ByteString
}
data ZeroMQStdin =
StdinChannel
{ stdinRequestChannel :: Chan Message
, stdinReplyChannel :: Chan Message
}
-- | Start responding on all ZeroMQ channels used to communicate with IPython | via the provided
-- profile. Return a set of channels which can be used to | communicate with IPython in a more
-- structured manner.
serveProfile :: Profile -- ^ The profile specifying which ports and transport mechanisms to use.
-> Bool -- ^ Print debug output
-> IO ZeroMQInterface -- ^ The Message-channel based interface to the sockets.
......@@ -63,29 +66,28 @@ serveProfile profile debug = do
let channels = Channels shellReqChan shellRepChan controlReqChan controlRepChan iopubChan
(signatureKey profile)
-- Create the context in a separate thread that never finishes. If
-- withContext or withSocket complete, the context or socket become invalid.
-- Create the context in a separate thread that never finishes. If withContext or withSocket
-- complete, the context or socket become invalid.
forkIO $ withContext $ \context -> do
-- Serve on all sockets.
forkIO $ serveSocket context Rep (hbPort profile) $ heartbeat channels
forkIO $ serveSocket context Router (controlPort profile) $ control debug channels
forkIO $ serveSocket context Router (shellPort profile) $ shell debug channels
-- The context is reference counted in this thread only. Thus, the last
-- serveSocket cannot be asynchronous, because otherwise context would
-- be garbage collectable - since it would only be used in other
-- threads. Thus, keep the last serveSocket in this thread.
-- The context is reference counted in this thread only. Thus, the last serveSocket cannot be
-- asynchronous, because otherwise context would be garbage collectable - since it would only be
-- used in other threads. Thus, keep the last serveSocket in this thread.
serveSocket context Pub (iopubPort profile) $ iopub debug channels
return channels
serveStdin :: Profile -> IO ZeroMQStdin
serveStdin :: Profile -> IO ZeroMQStdin
serveStdin profile = do
reqChannel <- newChan
repChannel <- newChan
-- Create the context in a separate thread that never finishes. If
-- withContext or withSocket complete, the context or socket become invalid.
-- Create the context in a separate thread that never finishes. If withContext or withSocket
-- complete, the context or socket become invalid.
forkIO $ withContext $ \context ->
-- Serve on all sockets.
serveSocket context Router (stdinPort profile) $ \socket -> do
......@@ -97,9 +99,8 @@ serveStdin profile = do
return $ StdinChannel reqChannel repChannel
-- | Serve on a given socket in a separate thread. Bind the socket in the
-- | given context and then loop the provided action, which should listen
-- | on the socket and respond to any events.
-- | Serve on a given socket in a separate thread. Bind the socket in the | given context and then
-- loop the provided action, which should listen | on the socket and respond to any events.
serveSocket :: SocketType a => Context -> a -> Port -> (Socket a -> IO b) -> IO ()
serveSocket context socketType port action = void $
withSocket context socketType $ \socket -> do
......@@ -115,9 +116,9 @@ heartbeat _ socket = do
-- Send it back.
send socket [] request
-- | Listener on the shell port. Reads messages and writes them to
-- | the shell request channel. For each message, reads a response from the
-- | shell reply channel of the interface and sends it back to the frontend.
-- | Listener on the shell port. Reads messages and writes them to | the shell request channel. For
-- each message, reads a response from the | shell reply channel of the interface and sends it back
-- to the frontend.
shell :: Bool -> ZeroMQInterface -> Socket Router -> IO ()
shell debug channels socket = do
-- Receive a message and write it to the interface channel.
......@@ -130,9 +131,9 @@ shell debug channels socket = do
requestChannel = shellRequestChannel channels
replyChannel = shellReplyChannel channels
-- | Listener on the shell port. Reads messages and writes them to
-- | the shell request channel. For each message, reads a response from the
-- | shell reply channel of the interface and sends it back to the frontend.
-- | Listener on the shell port. Reads messages and writes them to | the shell request channel. For
-- each message, reads a response from the | shell reply channel of the interface and sends it back
-- to the frontend.
control :: Bool -> ZeroMQInterface -> Socket Router -> IO ()
control debug channels socket = do
-- Receive a message and write it to the interface channel.
......@@ -143,11 +144,10 @@ control debug channels socket = do
where
requestChannel = controlRequestChannel channels
replyChannel = controlReplyChannel channels
replyChannel = controlReplyChannel channels
-- | Send messages via the iopub channel.
-- | This reads messages from the ZeroMQ iopub interface channel
-- | and then writes the messages to the socket.
-- | Send messages via the iopub channel. | This reads messages from the ZeroMQ iopub interface
-- channel | and then writes the messages to the socket.
iopub :: Bool -> ZeroMQInterface -> Socket Pub -> IO ()
iopub debug channels socket =
readChan (iopubChannel channels) >>= sendMessage debug (hmacKey channels) socket
......@@ -179,19 +179,18 @@ receiveMessage debug socket = do
-- Receive the next piece of data from the socket.
next = receive socket
-- Read data from the socket until we hit an ending string.
-- Return all data as a list, which does not include the ending string.
-- Read data from the socket until we hit an ending string. Return all data as a list, which does
-- not include the ending string.
readUntil str = do
line <- next
if line /= str
then do
remaining <- readUntil str
return $ line : remaining
else return []
-- | Encode a message in the IPython ZeroMQ communication protocol
-- and send it through the provided socket. Sign it using HMAC
-- with SHA-256 using the provided key.
then do
remaining <- readUntil str
return $ line : remaining
else return []
-- | Encode a message in the IPython ZeroMQ communication protocol and send it through the provided
-- socket. Sign it using HMAC with SHA-256 using the provided key.
sendMessage :: Sender a => Bool -> ByteString -> Socket a -> Message -> IO ()
sendMessage _ _ _ SendNothing = return ()
sendMessage debug hmacKey socket message = do
......
......@@ -44,10 +44,15 @@ except:
# Find all the source files
sources = []
for root, dirnames, filenames in os.walk("src"):
for filename in filenames:
if filename.endswith(".hs"):
sources.append(os.path.join(root, filename))
for source_dir in ["src", "ipython-kernel"]:
for root, dirnames, filenames in os.walk(source_dir):
# Skip cabal dist directories
if "dist" in root:
continue
for filename in filenames:
if filename.endswith(".hs"):
sources.append(os.path.join(root, filename))
hindent_outputs = {}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment