Commit a489c9bb authored by Andrew Gibiansky's avatar Andrew Gibiansky

Merge pull request #445 from gibiansky/format-all-src

Format all src
parents c19a3bf2 7009eb0e
...@@ -36,7 +36,7 @@ install: ...@@ -36,7 +36,7 @@ install:
if [ ${GHCVER%.*} = "7.8" ]; then if [ ${GHCVER%.*} = "7.8" ]; then
travis_retry cabal install arithmoi==0.4.* -fllvm travis_retry cabal install arithmoi==0.4.* -fllvm
travis_retry git clone http://www.github.com/gibiansky/hindent travis_retry git clone http://www.github.com/gibiansky/hindent
cd hindent && cabal install && cd .. cd hindent && travis_retry cabal install && cd ..
fi fi
# Here starts the actual work to be performed for the package under test; any command which exits with a non-zero exit code causes the build to fail. # Here starts the actual work to be performed for the package under test; any command which exits with a non-zero exit code causes the build to fail.
......
{-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, QuasiQuotes #-} {-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, QuasiQuotes #-}
module IHaskell.Display.Aeson () where module IHaskell.Display.Aeson () where
import ClassyPrelude import ClassyPrelude
......
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} {-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
module IHaskell.Display.Basic () where module IHaskell.Display.Basic () where
import IHaskell.Display import IHaskell.Display
...@@ -10,6 +11,9 @@ instance Show a => IHaskellDisplay (Maybe a) where ...@@ -10,6 +11,9 @@ instance Show a => IHaskellDisplay (Maybe a) where
where where
stringDisplay = plain (show just) stringDisplay = plain (show just)
htmlDisplay = html str htmlDisplay = html str
str = case just of str =
case just of
Nothing -> "<span style='color: red; font-weight: bold;'>Nothing</span>" Nothing -> "<span style='color: red; font-weight: bold;'>Nothing</span>"
Just x -> printf "<span style='color: green; font-weight: bold;'>Just</span><span style='font-family: monospace;'>%s</span>" (show x) Just x -> printf
"<span style='color: green; font-weight: bold;'>Just</span><span style='font-family: monospace;'>%s</span>"
(show x)
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} {-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
module IHaskell.Display.Blaze () where module IHaskell.Display.Blaze () where
import IHaskell.Display import IHaskell.Display
......
{-# LANGUAGE NoImplicitPrelude, CPP #-} {-# LANGUAGE NoImplicitPrelude, CPP #-}
module IHaskell.Display.Charts () where module IHaskell.Display.Charts () where
import ClassyPrelude import ClassyPrelude
...@@ -22,8 +23,8 @@ instance IHaskellDisplay (Renderable a) where ...@@ -22,8 +23,8 @@ instance IHaskellDisplay (Renderable a) where
display renderable = do display renderable = do
pngDisp <- chartData renderable PNG pngDisp <- chartData renderable PNG
-- We can add `svg svgDisplay` to the output of `display`, -- We can add `svg svgDisplay` to the output of `display`, but SVGs are not resizable in the IPython
-- but SVGs are not resizable in the IPython notebook. -- notebook.
svgDisp <- chartData renderable SVG svgDisp <- chartData renderable SVG
return $ Display [pngDisp, svgDisp] return $ Display [pngDisp, svgDisp]
...@@ -34,17 +35,17 @@ chartData renderable format = do ...@@ -34,17 +35,17 @@ chartData renderable format = do
-- Write the PNG image. -- Write the PNG image.
let filename = ".ihaskell-chart.png" let filename = ".ihaskell-chart.png"
opts = def{_fo_format = format, _fo_size = (width, height)} opts = def { _fo_format = format, _fo_size = (width, height) }
toFile = renderableToFile opts mkFile opts filename renderable
#if MIN_VERSION_Chart_cairo(1,3,0)
toFile filename renderable
#else
toFile renderable filename
#endif
-- Convert to base64. -- Convert to base64.
imgData <- readFile $ fpFromString filename imgData <- readFile $ fpFromString filename
return $ case format of return $
case format of
PNG -> png width height $ base64 imgData PNG -> png width height $ base64 imgData
SVG -> svg $ Char.unpack imgData SVG -> svg $ Char.unpack imgData
#if MIN_VERSION_Chart_cairo(1,3,0)
mkFile opts filename renderable = renderableToFile opts filename renderable
#else
mkFile opts filename renderable = renderableToFile opts renderable filename
#endif
{-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, FlexibleInstances #-} {-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, FlexibleInstances #-}
module IHaskell.Display.Diagrams (diagram, animation) where module IHaskell.Display.Diagrams (diagram, animation) where
import ClassyPrelude import ClassyPrelude
...@@ -36,11 +37,13 @@ diagramData renderable format = do ...@@ -36,11 +37,13 @@ diagramData renderable format = do
-- Convert to base64. -- Convert to base64.
imgData <- readFile $ fpFromString filename imgData <- readFile $ fpFromString filename
let value = case format of let value =
case format of
PNG -> png (floor imgWidth) (floor imgHeight) $ base64 imgData PNG -> png (floor imgWidth) (floor imgHeight) $ base64 imgData
SVG -> svg $ Char.unpack imgData SVG -> svg $ Char.unpack imgData
return value return value
where where
extension SVG = "svg" extension SVG = "svg"
extension PNG = "png" extension PNG = "png"
......
{-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, FlexibleInstances #-} {-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, FlexibleInstances #-}
module IHaskell.Display.Diagrams.Animation (animation) where module IHaskell.Display.Diagrams.Animation (animation) where
import ClassyPrelude hiding (filename) import ClassyPrelude hiding (filename)
import Diagrams.Prelude import Diagrams.Prelude
import Diagrams.Backend.Cairo import Diagrams.Backend.Cairo
import Diagrams.Backend.Cairo.CmdLine (GifOpts (..)) import Diagrams.Backend.Cairo.CmdLine (GifOpts(..))
import Diagrams.Backend.CmdLine (DiagramOpts (..), mainRender) import Diagrams.Backend.CmdLine (DiagramOpts(..), mainRender)
import IHaskell.Display import IHaskell.Display
...@@ -37,16 +38,12 @@ animationData renderable = do ...@@ -37,16 +38,12 @@ animationData renderable = do
-- Write the image. -- Write the image.
let filename = ".ihaskell-diagram.gif" let filename = ".ihaskell-diagram.gif"
diagOpts = DiagramOpts { diagOpts = DiagramOpts
_width = Just . ceiling $ imgWidth { _width = Just . ceiling $ imgWidth
, _height = Just . ceiling $ imgHeight , _height = Just . ceiling $ imgHeight
, _output = filename , _output = filename
} }
gifOpts = GifOpts { gifOpts = GifOpts { _dither = True, _noLooping = False, _loopRepeat = Nothing }
_dither = True
, _noLooping = False
, _loopRepeat = Nothing
}
mainRender (diagOpts, gifOpts) frameSet mainRender (diagOpts, gifOpts) frameSet
-- Convert to ascii represented base64 encoding -- Convert to ascii represented base64 encoding
......
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
-- | Provides 'IHaskellDisplay' instances for 'LaTeX' and 'LaTeXT'. -- | Provides 'IHaskellDisplay' instances for 'LaTeX' and 'LaTeXT'.
module IHaskell.Display.Hatex () where module IHaskell.Display.Hatex () where
......
{-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, FlexibleInstances #-} {-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, FlexibleInstances #-}
module IHaskell.Display.Juicypixels
( module IHaskell.Display module IHaskell.Display.Juicypixels (module IHaskell.Display, module Codec.Picture) where
, module Codec.Picture
) where
import Codec.Picture import Codec.Picture
import ClassyPrelude import ClassyPrelude
...@@ -11,20 +9,47 @@ import System.Directory ...@@ -11,20 +9,47 @@ import System.Directory
import System.IO.Unsafe import System.IO.Unsafe
-- instances -- instances
instance IHaskellDisplay DynamicImage where display = displayImageAsJpg instance IHaskellDisplay DynamicImage where
instance IHaskellDisplay (Image Pixel8) where display = displayImageAsJpg . ImageY8 display = displayImageAsJpg
instance IHaskellDisplay (Image Pixel16) where display = displayImageAsJpg . ImageY16
instance IHaskellDisplay (Image PixelF) where display = displayImageAsJpg . ImageYF instance IHaskellDisplay (Image Pixel8) where
instance IHaskellDisplay (Image PixelYA8) where display = displayImageAsJpg . ImageYA8 display = displayImageAsJpg . ImageY8
instance IHaskellDisplay (Image PixelYA16) where display = displayImageAsJpg . ImageYA16
instance IHaskellDisplay (Image PixelRGB8) where display = displayImageAsJpg . ImageRGB8 instance IHaskellDisplay (Image Pixel16) where
instance IHaskellDisplay (Image PixelRGB16) where display = displayImageAsJpg . ImageRGB16 display = displayImageAsJpg . ImageY16
instance IHaskellDisplay (Image PixelRGBF) where display = displayImageAsJpg . ImageRGBF
instance IHaskellDisplay (Image PixelRGBA8) where display = displayImageAsJpg . ImageRGBA8 instance IHaskellDisplay (Image PixelF) where
instance IHaskellDisplay (Image PixelRGBA16) where display = displayImageAsJpg . ImageRGBA16 display = displayImageAsJpg . ImageYF
instance IHaskellDisplay (Image PixelYCbCr8) where display = displayImageAsJpg . ImageYCbCr8
instance IHaskellDisplay (Image PixelCMYK8) where display = displayImageAsJpg . ImageCMYK8 instance IHaskellDisplay (Image PixelYA8) where
instance IHaskellDisplay (Image PixelCMYK16) where display = displayImageAsJpg . ImageCMYK16 display = displayImageAsJpg . ImageYA8
instance IHaskellDisplay (Image PixelYA16) where
display = displayImageAsJpg . ImageYA16
instance IHaskellDisplay (Image PixelRGB8) where
display = displayImageAsJpg . ImageRGB8
instance IHaskellDisplay (Image PixelRGB16) where
display = displayImageAsJpg . ImageRGB16
instance IHaskellDisplay (Image PixelRGBF) where
display = displayImageAsJpg . ImageRGBF
instance IHaskellDisplay (Image PixelRGBA8) where
display = displayImageAsJpg . ImageRGBA8
instance IHaskellDisplay (Image PixelRGBA16) where
display = displayImageAsJpg . ImageRGBA16
instance IHaskellDisplay (Image PixelYCbCr8) where
display = displayImageAsJpg . ImageYCbCr8
instance IHaskellDisplay (Image PixelCMYK8) where
display = displayImageAsJpg . ImageCMYK8
instance IHaskellDisplay (Image PixelCMYK16) where
display = displayImageAsJpg . ImageCMYK16
-- main rendering function -- main rendering function
displayImageAsJpg :: DynamicImage -> IO Display displayImageAsJpg :: DynamicImage -> IO Display
...@@ -41,11 +66,13 @@ displayImageAsJpg renderable = do ...@@ -41,11 +66,13 @@ displayImageAsJpg renderable = do
-- The type DynamicImage does not have a function to extract width and height -- The type DynamicImage does not have a function to extract width and height
imWidth :: DynamicImage -> Int imWidth :: DynamicImage -> Int
imWidth img = w imWidth img = w
where (w, h) = imWidthHeight img where
(w, h) = imWidthHeight img
imHeight :: DynamicImage -> Int imHeight :: DynamicImage -> Int
imHeight img = h imHeight img = h
where (w, h) = imWidthHeight img where
(w, h) = imWidthHeight img
-- Helper functions to pattern match on the DynamicImage Constructors -- Helper functions to pattern match on the DynamicImage Constructors
imWidthHeight :: DynamicImage -> (Int, Int) imWidthHeight :: DynamicImage -> (Int, Int)
...@@ -65,5 +92,3 @@ imWidthHeight (ImageCMYK16 im) = imWH im ...@@ -65,5 +92,3 @@ imWidthHeight (ImageCMYK16 im) = imWH im
imWH :: (Image a) -> (Int, Int) imWH :: (Image a) -> (Int, Int)
imWH im = (imageWidth im, imageHeight im) imWH im = (imageWidth im, imageHeight im)
{-# LANGUAGE ViewPatterns, TypeSynonymInstances, FlexibleInstances #-} {-# LANGUAGE ViewPatterns, TypeSynonymInstances, FlexibleInstances #-}
module IHaskell.Display.Magic () where module IHaskell.Display.Magic () where
import IHaskell.Display import IHaskell.Display
...@@ -54,17 +55,20 @@ JPG ...@@ -54,17 +55,20 @@ JPG
-} -}
parseMagic :: String -> MagicClass parseMagic :: String -> MagicClass
parseMagic f = case words f of parseMagic f =
"SVG" : _ -> SVG case words f of
"PNG" : _image : _data : "SVG":_ -> SVG
(readMaybe -> Just w) : _x : "PNG":_image:_data:(readMaybe -> Just w):_x:(readMaybe . takeWhile isDigit -> Just h):_ -> PNG w
(readMaybe . takeWhile isDigit -> Just h) : _ -> PNG w h h
"LaTeX" : _ -> LaTeX "LaTeX":_ -> LaTeX
"HTML" : _ -> HTML "HTML":_ -> HTML
"JPEG" : _ -> JPG "JPEG":_ -> JPG
_ -> Unknown _ -> Unknown
data MagicClass = SVG
data MagicClass = | PNG Int Int
SVG | PNG Int Int | JPG | HTML | LaTeX | Unknown | JPG
| HTML
| LaTeX
| Unknown
deriving Show deriving Show
{-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, QuasiQuotes, FlexibleInstances, OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude, TypeSynonymInstances, QuasiQuotes, FlexibleInstances, OverloadedStrings #-}
module IHaskell.Display.Parsec () where module IHaskell.Display.Parsec () where
import ClassyPrelude hiding (fromList) import ClassyPrelude hiding (fromList)
...@@ -29,21 +30,17 @@ instance FromJSON ParseText where ...@@ -29,21 +30,17 @@ instance FromJSON ParseText where
-- | Output of parsing. -- | Output of parsing.
instance Show a => ToJSON (Either ParseError a) where instance Show a => ToJSON (Either ParseError a) where
toJSON (Left err) = object [ toJSON (Left err) = object
"status" .= ("error" :: String), [ "status" .= ("error" :: String)
"line" .= sourceLine (errorPos err), , "line" .= sourceLine (errorPos err)
"col" .= sourceColumn (errorPos err), , "col" .= sourceColumn (errorPos err)
"msg" .= show err , "msg" .= show err
]
toJSON (Right result) = object [
"status" .= ("success" :: String),
"result" .= show result
] ]
toJSON (Right result) = object ["status" .= ("success" :: String), "result" .= show result]
instance Show a => IHaskellWidget (Parser a) where instance Show a => IHaskellWidget (Parser a) where
-- Name for this widget. -- Name for this widget.
targetName _ = "parsec" targetName _ = "parsec"
-- When we rece -- When we rece
comm widget (Object dict) publisher = do comm widget (Object dict) publisher = do
let key = "text" :: Text let key = "text" :: Text
......
{-# OPTIONS_GHC -fno-warn-missing-fields #-} {-# OPTIONS_GHC -fno-warn-missing-fields #-}
{-# LANGUAGE TupleSections, TemplateHaskell #-} {-# LANGUAGE TupleSections, TemplateHaskell #-}
module IHaskell.Display.Rlangqq
( module RlangQQ, module IHaskell.Display.Rlangqq (
module RlangQQ,
rDisp, rDisp,
rDisplayAll, rDisplayAll,
rOutputParsed, rOutputParsed,
...@@ -25,7 +26,6 @@ import IHaskell.Display ...@@ -25,7 +26,6 @@ import IHaskell.Display
import IHaskell.Display.Blaze () -- to confirm it's installed import IHaskell.Display.Blaze () -- to confirm it's installed
import qualified Text.Blaze.Html5 as H import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as H import qualified Text.Blaze.Html5.Attributes as H
import Data.Monoid
import Data.Char import Data.Char
import Control.Monad import Control.Monad
import Data.Ord import Data.Ord
...@@ -40,18 +40,17 @@ import Control.Concurrent.STM ...@@ -40,18 +40,17 @@ import Control.Concurrent.STM
import Language.Haskell.TH.Quote import Language.Haskell.TH.Quote
-- | same as 'RlangQQ.r', but displays plots at the end too -- | same as 'RlangQQ.r', but displays plots at the end too
rDisp = QuasiQuoter { quoteExp = \s -> [| do rDisp = QuasiQuoter { quoteExp = \s -> [|do
result <- $(quoteExp r s) result <- $(quoteExp r s)
p <- rDisplayAll p <- rDisplayAll
printDisplay p printDisplay p
return result return result|] }
|] }
rOutput :: IO [Int] rOutput :: IO [Int]
rOutput = do rOutput = do
fs <- mapMaybe (readMaybe <=< stripPrefix "raw" <=< stripSuffix ".md") fs <- mapMaybe (readMaybe <=< stripPrefix "raw" <=< stripSuffix ".md")
<$> getDirectoryContents "Rtmp" <$> getDirectoryContents "Rtmp"
fs' <- forM fs $ \f -> (,f) <$> getModificationTime (showf ("Rtmp/raw"%Int%".md") f) fs' <- forM fs $ \f -> (,f) <$> getModificationTime (showf ("Rtmp/raw" % Int % ".md") f)
return $ map snd $ sortBy (flip (comparing fst)) fs' return $ map snd $ sortBy (flip (comparing fst)) fs'
-- | like 'stripPrefix' except on the end -- | like 'stripPrefix' except on the end
...@@ -63,35 +62,33 @@ rOutputParsed = do ...@@ -63,35 +62,33 @@ rOutputParsed = do
ns <- rOutput ns <- rOutput
case ns of case ns of
[] -> return [] [] -> return []
n : _ -> parseKnitted <$> readFile (showf ("Rtmp/raw"%Int%".md") n) n:_ -> parseKnitted <$> readFile (showf ("Rtmp/raw" % Int % ".md") n)
getPlotNames :: IO [String] getPlotNames :: IO [String]
getPlotNames = do getPlotNames = do
interactions <- rOutputParsed interactions <- rOutputParsed
return [ p | KnitInteraction _ is <- interactions, KnitImage _ p <- is ] return [p | KnitInteraction _ is <- interactions
, KnitImage _ p <- is]
getCaptions :: IO [String] getCaptions :: IO [String]
getCaptions = do getCaptions = do
interactions <- rOutputParsed interactions <- rOutputParsed
return [ c | KnitInteraction _ is <- interactions, return
KnitImage c _ <- is, [c | KnitInteraction _ is <- interactions
not (isBoringCaption c) ] , KnitImage c _ <- is
, not (isBoringCaption c)]
-- | true when the caption name looks like one knitr will automatically -- | true when the caption name looks like one knitr will automatically generate
-- generate
isBoringCaption :: String -> Bool isBoringCaption :: String -> Bool
isBoringCaption s = maybe False isBoringCaption s = maybe False (all isDigit) (stripPrefix "plot of chunk unnamed-chunk-" s)
(all isDigit)
(stripPrefix "plot of chunk unnamed-chunk-" s)
rDisplayAll :: IO Display rDisplayAll :: IO Display
rDisplayAll = do rDisplayAll = do
ns <- rOutputParsed ns <- rOutputParsed
imgs <- sequence [ displayInteraction o | KnitInteraction _ os <- ns, o <- os] imgs <- sequence [displayInteraction o | KnitInteraction _ os <- ns
, o <- os]
display (mconcat imgs) display (mconcat imgs)
displayInteraction :: KnitOutput -> IO Display displayInteraction :: KnitOutput -> IO Display
displayInteraction (KnitPrint c) = display (plain c) displayInteraction (KnitPrint c) = display (plain c)
displayInteraction (KnitWarning c) = display (plain c) displayInteraction (KnitWarning c) = display (plain c)
...@@ -102,7 +99,8 @@ displayInteraction (KnitImage cap img) = do ...@@ -102,7 +99,8 @@ displayInteraction (KnitImage cap img) = do
| isBoringCaption cap = mempty | isBoringCaption cap = mempty
| otherwise = H.p (H.toMarkup cap) | otherwise = H.p (H.toMarkup cap)
encoded <- Base64.encode <$> B.readFile img encoded <- Base64.encode <$> B.readFile img
display $ H.img H.! H.src (H.unsafeByteStringValue display $ H.img H.! H.src
(H.unsafeByteStringValue
-- assumes you use the default device which is png -- assumes you use the default device which is png
(Char.pack "data:image/png;base64," <> encoded)) (Char.pack "data:image/png;base64," <> encoded))
<> caption <> caption
...@@ -23,10 +23,7 @@ getUniqueName = do ...@@ -23,10 +23,7 @@ getUniqueName = do
putMVar uniqueCounter val' putMVar uniqueCounter val'
return $ pack $ "ihaskellStaticCanvasUniqueID" ++ show val return $ pack $ "ihaskellStaticCanvasUniqueID" ++ show val
data Canvas = Canvas { width :: Int data Canvas = Canvas { width :: Int, height :: Int, canvas :: CanvasFree () }
, height :: Int
, canvas :: CanvasFree ()
}
instance IHaskellDisplay Canvas where instance IHaskellDisplay Canvas where
display cnv = do display cnv = do
......
{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}
module IHaskell.Display.Widgets () where module IHaskell.Display.Widgets () where
import ClassyPrelude import ClassyPrelude
...@@ -17,18 +18,19 @@ instance ToJSON WidgetName where ...@@ -17,18 +18,19 @@ instance ToJSON WidgetName where
toJSON ButtonWidget = "ButtonView" toJSON ButtonWidget = "ButtonView"
instance ToJSON WidgetMessage where instance ToJSON WidgetMessage where
toJSON DisplayWidget = object [ "method" .= str "display" ] toJSON DisplayWidget = object ["method" .= str "display"]
toJSON (InitialState name) = object [ toJSON (InitialState name) = object
"method" .= str "update", [ "method" .= str "update"
"state" .= object [ , "state" .= object
"_view_name" .= name, [ "_view_name" .= name
"visible" .= True, , "visible" .= True
"_css" .= object [], , "_css" .= object []
"msg_throttle" .= (3 :: Int), , "msg_throttle" .= (3 :: Int)
"disabled" .= False, , "disabled" .= False
"description" .= str "Button" , "description" .= str "Button"
] ]
] ]
str :: String -> String str :: String -> String
str = id str = id
...@@ -45,7 +47,6 @@ instance FromJSON ParseText where ...@@ -45,7 +47,6 @@ instance FromJSON ParseText where
instance IHaskellWidget Slider where instance IHaskellWidget Slider where
-- Name for this widget. -- Name for this widget.
targetName _ = "WidgetModel" targetName _ = "WidgetModel"
-- Start by sending messages to set up the widget. -- Start by sending messages to set up the widget.
open widget send = do open widget send = do
putStrLn "Sending widgets!" putStrLn "Sending widgets!"
......
module IHaskell.Widgets ( module IHaskell.Widgets (Slider(..)) where
Slider(..)
) where
data Slider = Slider data Slider = Slider
{-# LANGUAGE FlexibleContexts, FlexibleInstances, OverloadedStrings, MultiWayIf #-} {-# LANGUAGE FlexibleContexts, FlexibleInstances, OverloadedStrings, MultiWayIf #-}
module Main where module Main where
import Control.Applicative import Control.Applicative
...@@ -20,7 +21,8 @@ import IHaskell.IPython.EasyKernel (installProfile, easyKernel, KernelConfig(..) ...@@ -20,7 +21,8 @@ import IHaskell.IPython.EasyKernel (installProfile, easyKernel, KernelConfig(..)
import System.Environment (getArgs) import System.Environment (getArgs)
import System.FilePath ((</>)) 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 Text.Parsec.Token as P
import qualified Paths_ipython_kernel as Paths import qualified Paths_ipython_kernel as Paths
...@@ -28,21 +30,16 @@ import qualified Paths_ipython_kernel as Paths ...@@ -28,21 +30,16 @@ import qualified Paths_ipython_kernel as Paths
--------------------------------------------------------- ---------------------------------------------------------
-- Hutton's Razor, plus time delays, plus a global state -- Hutton's Razor, plus time delays, plus a global state
--------------------------------------------------------- ---------------------------------------------------------
--
-- | This language is Hutton's Razor with two added operations that -- | This language is Hutton's Razor with two added operations that are needed to demonstrate the
-- are needed to demonstrate the kernel features: a global state, -- kernel features: a global state, accessed and modified using Count, and a sleep operation.
-- accessed and modified using Count, and a sleep operation.
data Razor = I Integer data Razor = I Integer
| Plus Razor Razor | Plus Razor Razor
| SleepThen Double Razor | SleepThen Double Razor
| Count | Count
deriving (Read, Show, Eq) deriving (Read, Show, Eq)
-- ------- Parser -------
---------
-- Parser
---------
razorDef :: Monad m => P.GenLanguageDef String a m razorDef :: Monad m => P.GenLanguageDef String a m
razorDef = P.LanguageDef razorDef = P.LanguageDef
{ P.commentStart = "(*" { P.commentStart = "(*"
...@@ -83,7 +80,8 @@ literal :: Parsec String a Razor ...@@ -83,7 +80,8 @@ literal :: Parsec String a Razor
literal = I <$> integer literal = I <$> integer
sleepThen :: Parsec String a Razor sleepThen :: Parsec String a Razor
sleepThen = do keyword "sleep" sleepThen = do
keyword "sleep"
delay <- float <?> "seconds" delay <- float <?> "seconds"
keyword "then" keyword "then"
body <- expr body <- expr
...@@ -94,8 +92,11 @@ count :: Parsec String a Razor ...@@ -94,8 +92,11 @@ count :: Parsec String a Razor
count = keyword "count" >> return Count count = keyword "count" >> return Count
expr :: Parsec String a Razor expr :: Parsec String a Razor
expr = do one <- parens expr <|> literal <|> sleepThen <|> count expr = do
rest <- optionMaybe (do op <- operator one <- parens expr <|> literal <|> sleepThen <|> count
rest <- optionMaybe
(do
op <- operator
guard (op == "+") guard (op == "+")
expr) expr)
case rest of case rest of
...@@ -105,12 +106,7 @@ expr = do one <- parens expr <|> literal <|> sleepThen <|> count ...@@ -105,12 +106,7 @@ expr = do one <- parens expr <|> literal <|> sleepThen <|> count
parse :: String -> Either ParseError Razor parse :: String -> Either ParseError Razor
parse = runParser expr () "(input)" parse = runParser expr () "(input)"
-- -------------------- Language operations -------------------- | Completion
----------------------
-- Language operations
----------------------
-- | Completion
langCompletion :: T.Text -> T.Text -> Int -> Maybe ([T.Text], T.Text, T.Text) langCompletion :: T.Text -> T.Text -> Int -> Maybe ([T.Text], T.Text, T.Text)
langCompletion _code line col = langCompletion _code line col =
let (before, _) = T.splitAt col line let (before, _) = T.splitAt col line
...@@ -123,20 +119,18 @@ langCompletion _code line col = ...@@ -123,20 +119,18 @@ langCompletion _code line col =
lastMaybe (_:xs) = lastMaybe xs lastMaybe (_:xs) = lastMaybe xs
matchesFor :: String -> [String] matchesFor :: String -> [String]
matchesFor input = filter (isPrefixOf input) available 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 -- | Documentation lookup
langInfo :: T.Text -> Maybe (T.Text, T.Text, T.Text) langInfo :: T.Text -> Maybe (T.Text, T.Text, T.Text)
langInfo obj = langInfo obj =
if | any (T.isPrefixOf obj) ["sleep", "then", "end"] -> if | any (T.isPrefixOf obj) ["sleep", "then", "end"] -> Just (obj, sleepDocs, sleepType)
Just (obj, sleepDocs, sleepType) | T.isPrefixOf obj "count" -> Just (obj, countDocs, countType)
| T.isPrefixOf obj "count" ->
Just (obj, countDocs, countType)
| obj == "+" -> Just (obj, plusDocs, plusType) | obj == "+" -> Just (obj, plusDocs, plusType)
| T.all isDigit obj -> Just (obj, intDocs obj, intType) | T.all isDigit obj -> Just (obj, intDocs obj, intType)
| [x, y] <- T.splitOn "." obj, | [x, y] <- T.splitOn "." obj
T.all isDigit x, , T.all isDigit x
T.all isDigit y -> Just (obj, floatDocs obj, floatType) , T.all isDigit y -> Just (obj, floatDocs obj, floatType)
| otherwise -> Nothing | otherwise -> Nothing
where where
sleepDocs = "sleep DURATION then VALUE end: sleep DURATION seconds, then eval VALUE" sleepDocs = "sleep DURATION then VALUE end: sleep DURATION seconds, then eval VALUE"
...@@ -155,11 +149,11 @@ data IntermediateEvalRes = Got Razor Integer ...@@ -155,11 +149,11 @@ data IntermediateEvalRes = Got Razor Integer
| Waiting Double | Waiting Double
deriving Show deriving Show
-- | Cons for lists of trace elements - in this case, "sleeping" -- | Cons for lists of trace elements - in this case, "sleeping" messages should replace old ones to
-- messages should replace old ones to create a countdown effect. -- create a countdown effect.
consRes :: IntermediateEvalRes -> [IntermediateEvalRes] -> [IntermediateEvalRes] consRes :: IntermediateEvalRes -> [IntermediateEvalRes] -> [IntermediateEvalRes]
consRes r@(Waiting _) (Waiting _ : s) = r:s consRes r@(Waiting _) (Waiting _:s) = r : s
consRes r s = r:s consRes r s = r : s
-- | Execute an expression. -- | Execute an expression.
execRazor :: MVar Integer -- ^ The global counter state execRazor :: MVar Integer -- ^ The global counter state
...@@ -168,9 +162,10 @@ execRazor :: MVar Integer -- ^ The global counter state ...@@ -168,9 +162,10 @@ execRazor :: MVar Integer -- ^ The global counter state
-> ([IntermediateEvalRes] -> IO ()) -- ^ Callback for intermediate results -> ([IntermediateEvalRes] -> IO ()) -- ^ Callback for intermediate results
-> StateT ([IntermediateEvalRes], T.Text) IO Integer -> StateT ([IntermediateEvalRes], T.Text) IO Integer
execRazor _ x@(I i) _ _ = 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 = execRazor val tm@(Plus x y) clear send =
do modify (second (<> (T.pack (show tm)))) do
modify (second (<> T.pack (show tm)))
x' <- execRazor val x clear send x' <- execRazor val x clear send
modify (first $ consRes (Got x x')) modify (first $ consRes (Got x x'))
sendState sendState
...@@ -181,33 +176,39 @@ execRazor val tm@(Plus x y) clear send = ...@@ -181,33 +176,39 @@ execRazor val tm@(Plus x y) clear send =
modify (first $ consRes (Got tm res)) modify (first $ consRes (Got tm res))
sendState sendState
return res return res
where sendState = liftIO clear >> fst <$> get >>= liftIO . send
where
sendState = liftIO clear >> fst <$> get >>= liftIO . send
execRazor val (SleepThen delay body) clear send execRazor val (SleepThen delay body) clear send
| delay <= 0.0 = execRazor val body clear send | delay <= 0.0 = execRazor val body clear send
| delay > 0.1 = do modify (first $ consRes (Waiting delay)) | delay > 0.1 = do
modify (first $ consRes (Waiting delay))
sendState sendState
liftIO $ threadDelay 100000 liftIO $ threadDelay 100000
execRazor val (SleepThen (delay - 0.1) body) clear send execRazor val (SleepThen (delay - 0.1) body) clear send
| otherwise = do modify (first $ consRes (Waiting 0)) | otherwise = do
modify (first $ consRes (Waiting 0))
sendState sendState
liftIO $ threadDelay (floor (delay * 1000000)) liftIO $ threadDelay (floor (delay * 1000000))
execRazor val body clear send execRazor val body clear send
where sendState = liftIO clear >> fst <$> get >>= liftIO . send where
sendState = liftIO clear >> fst <$> get >>= liftIO . send
execRazor val Count clear send = do execRazor val Count clear send = do
i <- liftIO $ takeMVar val i <- liftIO $ takeMVar val
modify (first $ consRes (Got Count i)) modify (first $ consRes (Got Count i))
sendState sendState
liftIO $ putMVar val (i+1) liftIO $ putMVar val (i + 1)
return i 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 -- | Generate a language configuration for some initial state
mkConfig :: MVar Integer -- ^ The internal state of the execution mkConfig :: MVar Integer -- ^ The internal state of the execution
-> KernelConfig IO [IntermediateEvalRes] (Either ParseError Integer) -> KernelConfig IO [IntermediateEvalRes] (Either ParseError Integer)
mkConfig var = KernelConfig mkConfig var = KernelConfig
{ languageName = "expanded_huttons_razor" { languageName = "expanded_huttons_razor"
, languageVersion = [0,1,0] , languageVersion = [0, 1, 0]
, profileSource = Just . (</> "calc_profile.tar") <$> Paths.getDataDir , profileSource = Just . (</> "calc_profile.tar") <$> Paths.getDataDir
, displayResult = displayRes , displayResult = displayRes
, displayOutput = displayOut , displayOutput = displayOut
...@@ -235,7 +236,8 @@ mkConfig var = KernelConfig ...@@ -235,7 +236,8 @@ mkConfig var = KernelConfig
return (Right res, Ok, T.unpack pager) return (Right res, Ok, T.unpack pager)
main :: IO () main :: IO ()
main = do args <- getArgs main = do
args <- getArgs
val <- newMVar 1 val <- newMVar 1
case args of case args of
["kernel", profileFile] -> ["kernel", profileFile] ->
...@@ -246,4 +248,5 @@ main = do args <- getArgs ...@@ -246,4 +248,5 @@ main = do args <- getArgs
_ -> do _ -> do
putStrLn "Usage:" putStrLn "Usage:"
putStrLn "simple-calc-example setup -- set up the profile" 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" putStrLn
"simple-calc-example kernel FILE -- run a kernel with FILE for communication with the frontend"
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
-- | Description : Easy IPython kernels -- | Description : Easy IPython kernels = Overview This module provides automation for writing
-- = Overview -- simple IPython kernels. In particular, it provides a record type that defines configurations and
-- This module provides automation for writing simple IPython -- a function that interprets a configuration as an action in some monad that can do IO.
-- 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 configuration consists primarily of functions that implement the various features of a
-- the various features of a kernel, such as running code, looking up -- kernel, such as running code, looking up documentation, and performing completion. An example for
-- documentation, and performing completion. An example for a simple -- a simple language that nevertheless has side effects, global state, and timing effects is
-- language that nevertheless has side effects, global state, and -- included in the examples directory.
-- timing effects is included in the examples directory.
-- --
-- = Profiles -- = Profiles To run your kernel, you will need an IPython profile that causes the frontend to run
-- To run your kernel, you will need an IPython profile that causes -- it. To generate a fresh profile, run the command
-- the frontend to run it. To generate a fresh profile, run the command
-- --
-- > ipython profile create NAME -- > ipython profile create NAME
-- --
-- This will create a fresh IPython profile in @~\/.ipython\/profile_NAME@. -- This will create a fresh IPython profile in @~\/.ipython\/profile_NAME@. This profile must be
-- This profile must be modified in two ways: -- modified in two ways:
-- --
-- 1. It needs to run your kernel instead of the default ipython -- 1. It needs to run your kernel instead of the default ipython 2. It must have message signing
-- 2. It must have message signing turned off, because 'easyKernel' doesn't support it -- turned off, because 'easyKernel' doesn't support it
-- --
-- == Setting the executable -- == Setting the executable To set the executable, modify the configuration object's
-- To set the executable, modify the configuration object's
-- @KernelManager.kernel_cmd@ property. For example: -- @KernelManager.kernel_cmd@ property. For example:
-- --
-- > c.KernelManager.kernel_cmd = ['my_kernel', '{connection_file}'] -- > c.KernelManager.kernel_cmd = ['my_kernel', '{connection_file}']
...@@ -44,7 +38,6 @@ ...@@ -44,7 +38,6 @@
-- Consult the IPython documentation along with the generated profile -- Consult the IPython documentation along with the generated profile
-- source code for further configuration of the frontend, including -- source code for further configuration of the frontend, including
-- syntax highlighting, logos, help text, and so forth. -- syntax highlighting, logos, help text, and so forth.
module IHaskell.IPython.EasyKernel (easyKernel, installProfile, KernelConfig(..)) where module IHaskell.IPython.EasyKernel (easyKernel, installProfile, KernelConfig(..)) where
import Data.Aeson (decode) import Data.Aeson (decode)
...@@ -55,7 +48,7 @@ import qualified Codec.Archive.Tar as Tar ...@@ -55,7 +48,7 @@ import qualified Codec.Archive.Tar as Tar
import Control.Concurrent (MVar, readChan, writeChan, newMVar, readMVar, modifyMVar_) import Control.Concurrent (MVar, readChan, writeChan, newMVar, readMVar, modifyMVar_)
import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad (forever, when) import Control.Monad (forever, when, unless)
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
...@@ -64,59 +57,54 @@ import qualified Data.Text as T ...@@ -64,59 +57,54 @@ import qualified Data.Text as T
import IHaskell.IPython.Kernel import IHaskell.IPython.Kernel
import IHaskell.IPython.Message.UUID as UUID import IHaskell.IPython.Message.UUID as UUID
import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getHomeDirectory) import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist,
getHomeDirectory)
import System.FilePath ((</>)) import System.FilePath ((</>))
import System.Exit (exitSuccess) import System.Exit (exitSuccess)
import System.IO (openFile, IOMode(ReadMode)) import System.IO (openFile, IOMode(ReadMode))
-- | The kernel configuration specifies the behavior that is specific -- | The kernel configuration specifies the behavior that is specific to your language. The type
-- to your language. The type parameters provide the monad in which -- parameters provide the monad in which your kernel will run, the type of intermediate outputs from
-- your kernel will run, the type of intermediate outputs from running -- running cells, and the type of final results of cells, respectively.
-- cells, and the type of final results of cells, respectively. data KernelConfig m output result =
data KernelConfig m output result = KernelConfig KernelConfig
{ languageName :: String {
-- ^ The name of the language. This field is used to calculate -- | The name of the language. This field is used to calculate the name of the profile,
-- the name of the profile, so it should contain characters that -- so it should contain characters that are reasonable to have in file names.
-- are reasonable to have in file names. languageName :: String
, languageVersion :: [Int] -- ^ The version of the language -- | The version of the language
, profileSource :: IO (Maybe FilePath) , languageVersion :: [Int]
-- ^ Determine the source of a profile to install using -- | Determine the source of a profile to install using 'installProfile'. The source should be a
-- 'installProfile'. The source should be a tarball whose contents -- tarball whose contents will be unpacked directly into the profile directory. For example, the
-- will be unpacked directly into the profile directory. For -- file whose name is @ipython_config.py@ in the tar file for a language named @lang@ will end up in
-- 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@. -- @~/.ipython/profile_lang/ipython_config.py@.
, displayOutput :: output -> [DisplayData] -- ^ How to render intermediate output , profileSource :: IO (Maybe FilePath)
, displayResult :: result -> [DisplayData] -- ^ How to render final cell results -- | 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) , completion :: T.Text -> T.Text -> Int -> Maybe ([T.Text], T.Text, T.Text)
-- ^ Perform completion. The returned tuple consists of the matches, -- | Return the information or documentation for its argument. The returned tuple consists of the
-- the matched text, and the completion text. The arguments are the -- name, the documentation, and the type, respectively.
-- 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) , objectInfo :: T.Text -> Maybe (T.Text, T.Text, T.Text)
-- ^ Return the information or documentation for its argument. The -- | Execute a cell. The arguments are the contents of the cell, an IO action that will clear the
-- returned tuple consists of the name, the documentation, and the -- current intermediate output, and an IO action that will add a new item to the intermediate
-- type, respectively. -- 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) , 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 , debug :: Bool -- ^ Whether to print extra debugging information to
-- the console
} }
-- | Attempt to install the IPython profile from the .tar file -- the console | Attempt to install the IPython profile from the .tar file indicated by the
-- indicated by the 'profileSource' field of the configuration, if it -- 'profileSource' field of the configuration, if it is not already installed.
-- is not already installed.
installProfile :: MonadIO m => KernelConfig m output result -> m () installProfile :: MonadIO m => KernelConfig m output result -> m ()
installProfile config = do installProfile config = do
installed <- isInstalled installed <- isInstalled
when (not installed) $ do unless installed $ do
profSrc <- liftIO $ profileSource config profSrc <- liftIO $ profileSource config
case profSrc of case profSrc of
Nothing -> liftIO (putStrLn "No IPython profile is installed or specified") Nothing -> liftIO (putStrLn "No IPython profile is installed or specified")
...@@ -124,7 +112,8 @@ installProfile config = do ...@@ -124,7 +112,8 @@ installProfile config = do
profExists <- liftIO $ doesFileExist tar profExists <- liftIO $ doesFileExist tar
profTgt <- profDir profTgt <- profDir
if profExists if profExists
then do liftIO $ createDirectoryIfMissing True profTgt then do
liftIO $ createDirectoryIfMissing True profTgt
liftIO $ Tar.extract profTgt tar liftIO $ Tar.extract profTgt tar
else liftIO . putStrLn $ else liftIO . putStrLn $
"The supplied profile source '" ++ tar ++ "' does not exist" "The supplied profile source '" ++ tar ++ "' does not exist"
...@@ -153,28 +142,29 @@ createReplyHeader parent = do ...@@ -153,28 +142,29 @@ createReplyHeader parent = do
let repType = fromMaybe err (replyType $ msgType parent) let repType = fromMaybe err (replyType $ msgType parent)
err = error $ "No reply for message " ++ show (msgType parent) err = error $ "No reply for message " ++ show (msgType parent)
return MessageHeader { return
identifiers = identifiers parent, MessageHeader
parentHeader = Just parent, { identifiers = identifiers parent
metadata = Map.fromList [], , parentHeader = Just parent
messageId = newMessageId, , metadata = Map.fromList []
sessionId = sessionId parent, , messageId = newMessageId
username = username parent, , sessionId = sessionId parent
msgType = repType , username = username parent
, msgType = repType
} }
-- | Execute an IPython kernel for a config. Your 'main' action should call this as the last thing
-- it does.
-- | Execute an IPython kernel for a config. Your 'main' action should
-- call this as the last thing it does.
easyKernel :: (MonadIO m) easyKernel :: (MonadIO m)
=> FilePath -- ^ The connection file provided by the IPython frontend => 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 () -> m ()
easyKernel profileFile config = do easyKernel profileFile config = do
prof <- liftIO $ getProfile profileFile prof <- liftIO $ getProfile profileFile
zmq@(Channels shellReqChan shellRepChan ctrlReqChan ctrlRepChan iopubChan _) <- zmq@(Channels shellReqChan shellRepChan ctrlReqChan ctrlRepChan iopubChan _) <- liftIO $ serveProfile
liftIO $ serveProfile prof False prof
False
execCount <- liftIO $ newMVar 0 execCount <- liftIO $ newMVar 0
forever $ do forever $ do
req <- liftIO $ readChan shellReqChan req <- liftIO $ readChan shellReqChan
...@@ -183,7 +173,6 @@ easyKernel profileFile config = do ...@@ -183,7 +173,6 @@ easyKernel profileFile config = do
reply <- replyTo config execCount zmq req repHeader reply <- replyTo config execCount zmq req repHeader
liftIO $ writeChan shellRepChan reply liftIO $ writeChan shellRepChan reply
replyTo :: MonadIO m replyTo :: MonadIO m
=> KernelConfig m output result => KernelConfig m output result
-> MVar Integer -> MVar Integer
...@@ -192,28 +181,31 @@ replyTo :: MonadIO m ...@@ -192,28 +181,31 @@ replyTo :: MonadIO m
-> MessageHeader -> MessageHeader
-> m Message -> m Message
replyTo config _ _ KernelInfoRequest{} replyHeader = replyTo config _ _ KernelInfoRequest{} replyHeader =
return KernelInfoReply return
KernelInfoReply
{ header = replyHeader { header = replyHeader
, language = languageName config , language = languageName config
, versionList = languageVersion config , versionList = languageVersion config
} }
replyTo config _ interface ShutdownRequest{restartPending=pending} replyHeader = do replyTo config _ interface ShutdownRequest { restartPending = pending } replyHeader = do
liftIO $ writeChan (shellReplyChannel interface) $ ShutdownReply replyHeader pending liftIO $ writeChan (shellReplyChannel interface) $ ShutdownReply replyHeader pending
liftIO exitSuccess liftIO exitSuccess
replyTo config execCount interface req@ExecuteRequest { getCode = code } replyHeader = do 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 busyHeader <- dupHeader replyHeader StatusMessage
liftIO . send $ PublishStatus busyHeader Busy liftIO . send $ PublishStatus busyHeader Busy
outputHeader <- dupHeader replyHeader DisplayDataMessage outputHeader <- dupHeader replyHeader DisplayDataMessage
(res, replyStatus, pagerOut) <- (res, replyStatus, pagerOut) <- let clearOutput = do
let clearOutput = do clearHeader <- dupHeader replyHeader
clearHeader <- dupHeader replyHeader ClearOutputMessage ClearOutputMessage
send $ ClearOutput clearHeader False send $ ClearOutput clearHeader False
sendOutput x = sendOutput x =
send $ PublishDisplayData outputHeader (languageName config) send $ PublishDisplayData
outputHeader
(languageName config)
(displayOutput config x) (displayOutput config x)
in run config code clearOutput sendOutput in run config code clearOutput sendOutput
liftIO . send $ PublishDisplayData outputHeader (languageName config) (displayResult config res) liftIO . send $ PublishDisplayData outputHeader (languageName config) (displayResult config res)
...@@ -222,45 +214,24 @@ replyTo config execCount interface req@ExecuteRequest { getCode = code } replyHe ...@@ -222,45 +214,24 @@ replyTo config execCount interface req@ExecuteRequest { getCode = code } replyHe
idleHeader <- dupHeader replyHeader StatusMessage idleHeader <- dupHeader replyHeader StatusMessage
liftIO . send $ PublishStatus idleHeader Idle liftIO . send $ PublishStatus idleHeader Idle
liftIO $ modifyMVar_ execCount (return . (+1)) liftIO $ modifyMVar_ execCount (return . (+ 1))
counter <- liftIO $ readMVar execCount counter <- liftIO $ readMVar execCount
return ExecuteReply return
ExecuteReply
{ header = replyHeader { header = replyHeader
, pagerOutput = pagerOut , pagerOutput = pagerOut
, executionCounter = fromIntegral counter , executionCounter = fromIntegral counter
, status = replyStatus , status = replyStatus
} }
replyTo config _ _ req@CompleteRequest{} replyHeader = do replyTo config _ _ req@CompleteRequest{} replyHeader =
-- TODO: FIX -- TODO: FIX
error "Unimplemented in IPython 3.0" 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 = replyTo config _ _ ObjectInfoRequest { objectName = obj } replyHeader =
return $ case objectInfo config obj of return $
case objectInfo config obj of
Just (name, docs, ty) -> ObjectInfoReply Just (name, docs, ty) -> ObjectInfoReply
{ header = replyHeader { header = replyHeader
, objectName = obj , objectName = obj
...@@ -281,8 +252,8 @@ replyTo _ _ _ msg _ = do ...@@ -281,8 +252,8 @@ replyTo _ _ _ msg _ = do
liftIO $ print msg liftIO $ print msg
return msg return msg
dupHeader :: MonadIO m => MessageHeader -> MessageType -> m MessageHeader dupHeader :: MonadIO m => MessageHeader -> MessageType -> m MessageHeader
dupHeader hdr mtype = dupHeader hdr mtype =
do uuid <- liftIO UUID.random do
return hdr { messageId = uuid , msgType = mtype } uuid <- liftIO UUID.random
return hdr { messageId = uuid, msgType = mtype }
-- | This module exports all the types and functions necessary to create an -- | This module exports all the types and functions necessary to create an IPython language kernel
-- IPython language kernel that supports the @ipython console@ and @ipython -- that supports the @ipython console@ and @ipython notebook@ frontends.
-- notebook@ frontends. module IHaskell.IPython.Kernel (module X) where
module IHaskell.IPython.Kernel (
module X,
) where
import IHaskell.IPython.Types as X import IHaskell.IPython.Types as X
import IHaskell.IPython.Message.Writer as X import IHaskell.IPython.Message.Writer as X
......
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
-- | Description : Parsing messages received from IPython -- | Description : Parsing messages received from IPython
-- --
-- This module is responsible for converting from low-level ByteStrings -- This module is responsible for converting from low-level ByteStrings obtained from the 0MQ
-- obtained from the 0MQ sockets into Messages. The only exposed function is -- sockets into Messages. The only exposed function is `parseMessage`, which should only be used in
-- `parseMessage`, which should only be used in the low-level 0MQ interface. -- the low-level 0MQ interface.
module IHaskell.IPython.Message.Parser (parseMessage) where module IHaskell.IPython.Message.Parser (parseMessage) where
import Data.Aeson ((.:), decode, Result(..), Object) import Data.Aeson ((.:), decode, Result(..), Object)
...@@ -17,9 +18,7 @@ import IHaskell.IPython.Types ...@@ -17,9 +18,7 @@ import IHaskell.IPython.Types
type LByteString = Lazy.ByteString type LByteString = Lazy.ByteString
----- External interface ----- -- --- External interface ----- | Parse a message from its ByteString components into a Message.
-- | Parse a message from its ByteString components into a Message.
parseMessage :: [ByteString] -- ^ The list of identifiers sent with the message. parseMessage :: [ByteString] -- ^ The list of identifiers sent with the message.
-> ByteString -- ^ The header data. -> ByteString -- ^ The header data.
-> ByteString -- ^ The parent header, which is just "{}" if there is no header. -> ByteString -- ^ The parent header, which is just "{}" if there is no header.
...@@ -32,16 +31,15 @@ parseMessage idents headerData parentHeader metadata content = ...@@ -32,16 +31,15 @@ parseMessage idents headerData parentHeader metadata content =
messageWithoutHeader = parser messageType $ Lazy.fromStrict content messageWithoutHeader = parser messageType $ Lazy.fromStrict content
in messageWithoutHeader { header = header } in messageWithoutHeader { header = header }
----- Module internals ----- -- --- Module internals ----- | Parse a header from its ByteString components into a MessageHeader.
-- | Parse a header from its ByteString components into a MessageHeader.
parseHeader :: [ByteString] -- ^ The list of identifiers. parseHeader :: [ByteString] -- ^ The list of identifiers.
-> ByteString -- ^ The header data. -> ByteString -- ^ The header data.
-> ByteString -- ^ The parent header, or "{}" for Nothing. -> ByteString -- ^ The parent header, or "{}" for Nothing.
-> ByteString -- ^ The metadata, or "{}" for an empty map. -> ByteString -- ^ The metadata, or "{}" for an empty map.
-> MessageHeader -- The resulting message header. -> MessageHeader -- The resulting message header.
parseHeader idents headerData parentHeader metadata = parseHeader idents headerData parentHeader metadata =
MessageHeader { identifiers = idents MessageHeader
{ identifiers = idents
, parentHeader = parentResult , parentHeader = parentResult
, metadata = metadataMap , metadata = metadataMap
, messageId = messageUUID , messageId = messageUUID
...@@ -50,8 +48,8 @@ parseHeader idents headerData parentHeader metadata = ...@@ -50,8 +48,8 @@ parseHeader idents headerData parentHeader metadata =
, msgType = messageType , msgType = messageType
} }
where where
-- Decode the header data and the parent header data into JSON objects. -- Decode the header data and the parent header data into JSON objects. If the parent header data is
-- If the parent header data is absent, just have Nothing instead. -- absent, just have Nothing instead.
Just result = decode $ Lazy.fromStrict headerData :: Maybe Object Just result = decode $ Lazy.fromStrict headerData :: Maybe Object
parentResult = if parentHeader == "{}" parentResult = if parentHeader == "{}"
then Nothing then Nothing
...@@ -71,8 +69,8 @@ noHeader :: MessageHeader ...@@ -71,8 +69,8 @@ noHeader :: MessageHeader
noHeader = error "No header created" noHeader = error "No header created"
parser :: MessageType -- ^ The message type being parsed. parser :: MessageType -- ^ The message type being parsed.
-> LByteString -> Message -- ^ The parser that converts the body into a message. -> LByteString -> Message -- ^ The parser that converts the body into a message. This message
-- This message should have an undefined header. -- should have an undefined header.
parser KernelInfoRequestMessage = kernelInfoRequestParser parser KernelInfoRequestMessage = kernelInfoRequestParser
parser ExecuteRequestMessage = executeRequestParser parser ExecuteRequestMessage = executeRequestParser
parser CompleteRequestMessage = completeRequestParser parser CompleteRequestMessage = completeRequestParser
...@@ -85,13 +83,12 @@ parser CommCloseMessage = commCloseParser ...@@ -85,13 +83,12 @@ parser CommCloseMessage = commCloseParser
parser HistoryRequestMessage = historyRequestParser parser HistoryRequestMessage = historyRequestParser
parser other = error $ "Unknown message type " ++ show other parser other = error $ "Unknown message type " ++ show other
-- | Parse a kernel info request. -- | Parse a kernel info request. A kernel info request has no auxiliary information, so ignore the
-- A kernel info request has no auxiliary information, so ignore the body. -- body.
kernelInfoRequestParser :: LByteString -> Message kernelInfoRequestParser :: LByteString -> Message
kernelInfoRequestParser _ = KernelInfoRequest { header = noHeader } kernelInfoRequestParser _ = KernelInfoRequest { header = noHeader }
-- | Parse an execute request. -- | Parse an execute request. Fields used are:
-- Fields used are:
-- 1. "code": the code to execute. -- 1. "code": the code to execute.
-- 2. "silent": whether to execute silently. -- 2. "silent": whether to execute silently.
-- 3. "store_history": whether to include this in history. -- 3. "store_history": whether to include this in history.
...@@ -107,7 +104,8 @@ executeRequestParser content = ...@@ -107,7 +104,8 @@ executeRequestParser content =
return (code, silent, storeHistory, allowStdin) return (code, silent, storeHistory, allowStdin)
Just decoded = decode content Just decoded = decode content
Success (code, silent, storeHistory, allowStdin) = parse parser decoded Success (code, silent, storeHistory, allowStdin) = parse parser decoded
in ExecuteRequest { header = noHeader in ExecuteRequest
{ header = noHeader
, getCode = code , getCode = code
, getSilent = silent , getSilent = silent
, getAllowStdin = allowStdin , getAllowStdin = allowStdin
...@@ -147,7 +145,6 @@ objectInfoRequestParser = requestParser $ \obj -> do ...@@ -147,7 +145,6 @@ objectInfoRequestParser = requestParser $ \obj -> do
dlevel <- obj .: "detail_level" dlevel <- obj .: "detail_level"
return $ ObjectInfoRequest noHeader oname dlevel return $ ObjectInfoRequest noHeader oname dlevel
shutdownRequestParser :: LByteString -> Message shutdownRequestParser :: LByteString -> Message
shutdownRequestParser = requestParser $ \obj -> do shutdownRequestParser = requestParser $ \obj -> do
code <- obj .: "restart" code <- obj .: "restart"
......
-- | Description : UUID generator and data structure -- | Description : UUID generator and data structure
-- --
-- Generate, parse, and pretty print UUIDs for use with IPython. -- Generate, parse, and pretty print UUIDs for use with IPython.
module IHaskell.IPython.Message.UUID ( module IHaskell.IPython.Message.UUID (UUID, random, randoms) where
UUID,
random, randoms,
) where
import Control.Monad (mzero, replicateM) import Control.Monad (mzero, replicateM)
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
...@@ -12,15 +9,15 @@ import Data.Text (pack) ...@@ -12,15 +9,15 @@ import Data.Text (pack)
import Data.Aeson import Data.Aeson
import Data.UUID.V4 (nextRandom) 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.
-- | A UUID (universally unique identifier). -- | 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. -- | Generate a list of random UUIDs.
randoms :: Int -- ^ Number of UUIDs to generate. randoms :: Int -- ^ Number of UUIDs to generate.
......
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
-- | Description : @ToJSON@ for Messages -- | Description : @ToJSON@ for Messages
-- --
-- This module contains the @ToJSON@ instance for @Message@. -- This module contains the @ToJSON@ instance for @Message@.
module IHaskell.IPython.Message.Writer ( module IHaskell.IPython.Message.Writer (ToJSON(..)) where
ToJSON(..)
) where
import Data.Aeson import Data.Aeson
import Data.Map (Map) import Data.Map (Map)
...@@ -19,99 +18,86 @@ import IHaskell.IPython.Types ...@@ -19,99 +18,86 @@ import IHaskell.IPython.Types
-- Convert message bodies into JSON. -- Convert message bodies into JSON.
instance ToJSON Message where instance ToJSON Message where
toJSON KernelInfoReply{ versionList = vers, language = language } = object [ toJSON KernelInfoReply { versionList = vers, language = language } =
"protocol_version" .= string "5.0", -- current protocol version, major and minor object ["protocol_version" .= string "5.0" -- current protocol version, major and minor
"language_version" .= vers, , "language_version" .= vers, "language" .= language]
"language" .= language
] toJSON ExecuteReply { status = status, executionCounter = counter, pagerOutput = pager } =
object
toJSON ExecuteReply{ status = status, executionCounter = counter, pagerOutput = pager} = object [ [ "status" .= show status
"status" .= show status, , "execution_count" .= counter
"execution_count" .= counter, , "payload" .=
"payload" .=
if null pager if null pager
then [] then []
else [object [ else [object ["source" .= string "page", "text" .= pager]]
"source" .= string "page", , "user_variables" .= emptyMap
"text" .= pager , "user_expressions" .= emptyMap
]],
"user_variables" .= emptyMap,
"user_expressions" .= emptyMap
] ]
toJSON PublishStatus{ executionState = executionState } = object [ toJSON PublishStatus { executionState = executionState } =
"execution_state" .= 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 PublishStream{ streamType = streamType, streamContent = content } = object [ toJSON o@ObjectInfoReply{} =
"data" .= content, object
"name" .= streamType [ "oname" .=
] objectName o
toJSON PublishDisplayData{ source = src, displayData = datas } = object [ , "found" .= objectFound o
"source" .= src, , "ismagic" .= False
"metadata" .= object [], , "isalias" .= False
"data" .= object (map displayDataToJson datas) , "type_name" .= objectTypeString o
, "docstring" .= objectDocString o
] ]
toJSON PublishOutput{ executionCount = execCount, reprText = reprText } = object [ toJSON ShutdownReply { restartPending = restart } =
"data" .= object ["text/plain" .= reprText], object ["restart" .= restart]
"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 [ toJSON ClearOutput { wait = wait } =
"restart" .= restart object ["wait" .= wait]
]
toJSON ClearOutput{wait = wait} = object [ toJSON RequestInput { inputPrompt = prompt } =
"wait" .= wait object ["prompt" .= prompt]
]
toJSON RequestInput{inputPrompt = prompt} = object [
"prompt" .= prompt
]
toJSON req@CommOpen{} = object [ toJSON req@CommOpen{} =
"comm_id" .= commUuid req, object ["comm_id" .= commUuid req, "target_name" .= commTargetName req, "data" .= commData req]
"target_name" .= commTargetName req,
"data" .= commData req
]
toJSON req@CommData{} = object [ toJSON req@CommData{} =
"comm_id" .= commUuid req, object ["comm_id" .= commUuid req, "data" .= commData req]
"data" .= commData req
]
toJSON req@CommClose{} = object [ toJSON req@CommClose{} =
"comm_id" .= commUuid req, object ["comm_id" .= commUuid req, "data" .= commData req]
"data" .= commData req
]
toJSON req@HistoryReply{} = object [ "history" .= map tuplify (historyReply req) ] toJSON req@HistoryReply{} =
where tuplify (HistoryReplyElement sess linum res) = (sess, linum, case res of object ["history" .= map tuplify (historyReply req)]
where
tuplify (HistoryReplyElement sess linum res) = (sess, linum, case res of
Left inp -> toJSON inp Left inp -> toJSON inp
Right (inp, out) -> toJSON out) Right (inp, out) -> toJSON out)
toJSON body = error $ "Do not know how to convert to JSON for message " ++ show body toJSON body = error $ "Do not know how to convert to JSON for message " ++ show body
-- | Print an execution state as "busy", "idle", or "starting". -- | Print an execution state as "busy", "idle", or "starting".
instance ToJSON ExecutionState where instance ToJSON ExecutionState where
toJSON Busy = String "busy" toJSON Busy = String "busy"
...@@ -129,7 +115,6 @@ displayDataToJson (DisplayData mimeType dataStr) = ...@@ -129,7 +115,6 @@ displayDataToJson (DisplayData mimeType dataStr) =
pack (show mimeType) .= String dataStr pack (show mimeType) .= String dataStr
----- Constants ----- ----- Constants -----
emptyMap :: Map String String emptyMap :: Map String String
emptyMap = mempty emptyMap = mempty
......
{-# LANGUAGE OverloadedStrings, DoAndIfThenElse #-} {-# 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 -- | This module provides a way in which the Haskell standard input may be forwarded to the IPython
-- the standard input. -- frontend and thus allows the notebook to use the standard input.
-- --
-- This relies on the implementation of file handles in GHC, and is -- This relies on the implementation of file handles in GHC, and is generally unsafe and terrible.
-- generally unsafe and terrible. However, it is difficult to find another -- However, it is difficult to find another way to do it, as file handles are generally meant to
-- way to do it, as file handles are generally meant to point to streams -- point to streams and files, and not networked communication protocols.
-- and files, and not networked communication protocols.
-- --
-- In order to use this module, it must first be initialized with two -- In order to use this module, it must first be initialized with two things. First of all, in order
-- things. First of all, in order to know how to communicate with the -- to know how to communicate with the IPython frontend, it must know the kernel profile used for
-- IPython frontend, it must know the kernel profile used for -- communication. For this, use @recordKernelProfile@ once the profile is known. Both this and
-- communication. For this, use @recordKernelProfile@ once the profile is -- @recordParentHeader@ take a directory name where they can store this data.
-- 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 -- Finally, the module must know what @execute_request@ message is currently being replied to (which
-- currently being replied to (which will request the input). Thus, every -- will request the input). Thus, every time the language kernel receives an @execute_request@
-- time the language kernel receives an @execute_request@ message, it -- message, it should inform this module via @recordParentHeader@, so that the module may generate
-- should inform this module via @recordParentHeader@, so that the module -- messages with an appropriate parent header set. If this is not done, the IPython frontends will
-- may generate messages with an appropriate parent header set. If this is -- not recognize the target of the communication.
-- not done, the IPython frontends will not recognize the target of the
-- communication.
-- --
-- Finally, in order to activate this module, @fixStdin@ must be called -- Finally, in order to activate this module, @fixStdin@ must be called once. It must be passed the
-- once. It must be passed the same directory name as @recordParentHeader@ -- same directory name as @recordParentHeader@ and @recordKernelProfile@. Note that if this is being
-- and @recordKernelProfile@. Note that if this is being used from within -- used from within the GHC API, @fixStdin@ /must/ be called from within the GHC session not from
-- the GHC API, @fixStdin@ /must/ be called from within the GHC session -- the host code.
-- not from the host code. module IHaskell.IPython.Stdin (fixStdin, recordParentHeader, recordKernelProfile) where
module IHaskell.IPython.Stdin (
fixStdin,
recordParentHeader,
recordKernelProfile
) where
import Control.Concurrent import Control.Concurrent
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
...@@ -53,9 +43,8 @@ stdinInterface :: MVar ZeroMQStdin ...@@ -53,9 +43,8 @@ stdinInterface :: MVar ZeroMQStdin
{-# NOINLINE stdinInterface #-} {-# NOINLINE stdinInterface #-}
stdinInterface = unsafePerformIO newEmptyMVar stdinInterface = unsafePerformIO newEmptyMVar
-- | Manipulate standard input so that it is sourced from the IPython -- | Manipulate standard input so that it is sourced from the IPython frontend. This function is
-- frontend. This function is build on layers of deep magical hackery, so -- build on layers of deep magical hackery, so be careful modifying it.
-- be careful modifying it.
fixStdin :: String -> IO () fixStdin :: String -> IO ()
fixStdin dir = do fixStdin dir = do
-- Initialize the stdin interface. -- Initialize the stdin interface.
...@@ -78,6 +67,7 @@ stdinOnce dir = do ...@@ -78,6 +67,7 @@ stdinOnce dir = do
hDuplicateTo newStdin stdin hDuplicateTo newStdin stdin
loop stdinInput oldStdin newStdin loop stdinInput oldStdin newStdin
where where
loop stdinInput oldStdin newStdin = do loop stdinInput oldStdin newStdin = do
let FileHandle _ mvar = stdin let FileHandle _ mvar = stdin
...@@ -98,14 +88,14 @@ getInputLine dir = do ...@@ -98,14 +88,14 @@ getInputLine dir = do
-- Send a request for input. -- Send a request for input.
uuid <- UUID.random uuid <- UUID.random
parentHeader <- read <$> readFile (dir ++ "/.last-req-header") parentHeader <- read <$> readFile (dir ++ "/.last-req-header")
let header = MessageHeader { let header = MessageHeader
username = username parentHeader, { username = username parentHeader
identifiers = identifiers parentHeader, , identifiers = identifiers parentHeader
parentHeader = Just parentHeader, , parentHeader = Just parentHeader
messageId = uuid, , messageId = uuid
sessionId = sessionId parentHeader, , sessionId = sessionId parentHeader
metadata = Map.fromList [], , metadata = Map.fromList []
msgType = InputRequestMessage , msgType = InputRequestMessage
} }
let msg = RequestInput header "" let msg = RequestInput header ""
writeChan req msg writeChan req msg
......
{-# LANGUAGE OverloadedStrings, DeriveDataTypeable, DeriveGeneric #-} {-# 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 ( module IHaskell.IPython.Types (
-- * IPython kernel profile -- * IPython kernel profile
Profile(..), Profile(..),
...@@ -17,7 +17,8 @@ module IHaskell.IPython.Types ( ...@@ -17,7 +17,8 @@ module IHaskell.IPython.Types (
Username(..), Username(..),
Metadata(..), Metadata(..),
MessageType(..), MessageType(..),
Width(..), Height(..), Width(..),
Height(..),
StreamType(..), StreamType(..),
ExecutionState(..), ExecutionState(..),
ExecuteReplyStatus(..), ExecuteReplyStatus(..),
...@@ -28,8 +29,7 @@ module IHaskell.IPython.Types ( ...@@ -28,8 +29,7 @@ module IHaskell.IPython.Types (
-- ** IPython display data message -- ** IPython display data message
DisplayData(..), DisplayData(..),
MimeType(..), MimeType(..),
extractPlain extractPlain,
) where ) where
import Data.Aeson import Data.Aeson
...@@ -45,7 +45,8 @@ import Data.Typeable ...@@ -45,7 +45,8 @@ import Data.Typeable
import Data.List (find) import Data.List (find)
import Data.Map (Map) import Data.Map (Map)
-------------------- IPython Kernel Profile Types ---------------------- ------------------ IPython Kernel Profile Types ----------------------
--
-- | A TCP port. -- | A TCP port.
type Port = Int type Port = Int
...@@ -57,7 +58,9 @@ data Transport = TCP -- ^ Default transport mechanism via TCP. ...@@ -57,7 +58,9 @@ data Transport = TCP -- ^ Default transport mechanism via TCP.
deriving (Show, Read) deriving (Show, Read)
-- | A kernel profile, specifying how the kernel communicates. -- | A kernel profile, specifying how the kernel communicates.
data Profile = Profile { ip :: IP -- ^ The IP on which to listen. data Profile =
Profile
{ ip :: IP -- ^ The IP on which to listen.
, transport :: Transport -- ^ The transport mechanism. , transport :: Transport -- ^ The transport mechanism.
, stdinPort :: Port -- ^ The stdin channel port. , stdinPort :: Port -- ^ The stdin channel port.
, controlPort :: Port -- ^ The control channel port. , controlPort :: Port -- ^ The control channel port.
...@@ -107,15 +110,19 @@ instance FromJSON Transport where ...@@ -107,15 +110,19 @@ instance FromJSON Transport where
instance ToJSON Transport where instance ToJSON Transport where
toJSON TCP = String "tcp" toJSON TCP = String "tcp"
-------------------- IPython Kernelspec Types ---------------------- -------------------- IPython Kernelspec Types ----------------------
data KernelSpec = KernelSpec { data KernelSpec =
kernelDisplayName :: String, -- ^ Name shown to users to describe this kernel (e.g. "Haskell") KernelSpec
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 -- | Name shown to users to describe this kernel (e.g. "Haskell")
-- @"{connection_file}"@, which will be replaced by the path to a kernelDisplayName :: String
-- kernel profile file (see @Profile@) when the command is run. -- | Name for the kernel; unique kernel identifier (e.g. "haskell")
} deriving (Eq, Show) , 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 instance ToJSON KernelSpec where
toJSON kernelspec = object toJSON kernelspec = object
...@@ -124,28 +131,30 @@ instance ToJSON KernelSpec where ...@@ -124,28 +131,30 @@ instance ToJSON KernelSpec where
, "language" .= kernelLanguage kernelspec , "language" .= kernelLanguage kernelspec
] ]
-------------------- IPython Message Types ---------------------- ------------------ IPython Message Types --------------------
--
-- | A message header with some metadata. -- | A message header with some metadata.
data MessageHeader = MessageHeader { data MessageHeader =
identifiers :: [ByteString], -- ^ The identifiers sent with the message. MessageHeader
parentHeader :: Maybe MessageHeader, -- ^ The parent header, if present. { identifiers :: [ByteString] -- ^ The identifiers sent with the message.
metadata :: Metadata, -- ^ A dict of metadata. , parentHeader :: Maybe MessageHeader -- ^ The parent header, if present.
messageId :: UUID, -- ^ A unique message UUID. , metadata :: Metadata -- ^ A dict of metadata.
sessionId :: UUID, -- ^ A unique session UUID. , messageId :: UUID -- ^ A unique message UUID.
username :: Username, -- ^ The user who sent this message. , sessionId :: UUID -- ^ A unique session UUID.
msgType :: MessageType -- ^ The message type. , username :: Username -- ^ The user who sent this message.
} deriving (Show, Read) , msgType :: MessageType -- ^ The message type.
}
-- Convert a message header into the JSON field for the header. deriving (Show, Read)
-- This field does not actually have all the record fields.
-- 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 instance ToJSON MessageHeader where
toJSON header = object [ toJSON header = object
"msg_id" .= messageId header, [ "msg_id" .= messageId header
"session" .= sessionId header, , "session" .= sessionId header
"username" .= username header, , "username" .= username header
"version" .= ("5.0" :: String), , "version" .= ("5.0" :: String)
"msg_type" .= showMessageType (msgType header) , "msg_type" .= showMessageType (msgType header)
] ]
-- | A username for the source of a message. -- | A username for the source of a message.
...@@ -235,177 +244,161 @@ instance FromJSON MessageType where ...@@ -235,177 +244,161 @@ instance FromJSON MessageType where
_ -> fail ("Unknown message type: " ++ show s) _ -> fail ("Unknown message type: " ++ show s)
parseJSON _ = fail "Must be a string." parseJSON _ = fail "Must be a string."
-- | A message used to communicate with the IPython frontend. -- | A message used to communicate with the IPython frontend.
data Message data Message =
-- | A request from a frontend for information about the kernel. -- | A request from a frontend for information about the kernel.
= KernelInfoRequest { header :: MessageHeader } KernelInfoRequest { header :: MessageHeader }
|
-- | A response to a KernelInfoRequest. -- | A response to a KernelInfoRequest.
| KernelInfoReply { KernelInfoReply
header :: MessageHeader, { header :: MessageHeader
versionList :: [Int], -- ^ The version of the language, e.g. [7, 6, 3] for GHC 7.6.3 , versionList :: [Int] -- ^ The version of the language, e.g. [7, 6, 3] for GHC
language :: String -- ^ The language name, e.g. "haskell" -- 7.6.3
, language :: String -- ^ The language name, e.g. "haskell"
} }
|
-- | A request from a frontend to execute some code. -- | A request from a frontend to execute some code.
| ExecuteRequest { ExecuteRequest
header :: MessageHeader, { header :: MessageHeader
getCode :: Text, -- ^ The code string. , getCode :: Text -- ^ The code string.
getSilent :: Bool, -- ^ Whether this should be silently executed. , getSilent :: Bool -- ^ Whether this should be silently executed.
getStoreHistory :: Bool, -- ^ Whether to store this in history. , getStoreHistory :: Bool -- ^ Whether to store this in history.
getAllowStdin :: Bool, -- ^ Whether this code can use stdin. , getAllowStdin :: Bool -- ^ Whether this code can use stdin.
, getUserVariables :: [Text] -- ^ Unused.
getUserVariables :: [Text], -- ^ Unused. , getUserExpressions :: [Text] -- ^ Unused.
getUserExpressions :: [Text] -- ^ Unused.
} }
|
-- | A reply to an execute request. -- | A reply to an execute request.
| ExecuteReply { ExecuteReply
header :: MessageHeader, { header :: MessageHeader
status :: ExecuteReplyStatus, -- ^ The status of the output. , status :: ExecuteReplyStatus -- ^ The status of the output.
pagerOutput :: String, -- ^ The help string to show in the pager. , pagerOutput :: String -- ^ The help string to show in the pager.
executionCounter :: Int -- ^ The execution count, i.e. which output this is. , executionCounter :: Int -- ^ The execution count, i.e. which output this is.
} }
|
| PublishStatus { PublishStatus
header :: MessageHeader, { header :: MessageHeader
executionState :: ExecutionState -- ^ The execution state of the kernel. , executionState :: ExecutionState -- ^ The execution state of the kernel.
} }
|
| PublishStream { PublishStream
header :: MessageHeader, { header :: MessageHeader
streamType :: StreamType, -- ^ Which stream to publish to. , streamType :: StreamType -- ^ Which stream to publish to.
streamContent :: String -- ^ What to publish. , streamContent :: String -- ^ What to publish.
} }
|
| PublishDisplayData { PublishDisplayData
header :: MessageHeader, { header :: MessageHeader
source :: String, -- ^ The name of the data source. , source :: String -- ^ The name of the data source.
displayData :: [DisplayData] -- ^ A list of data representations. , displayData :: [DisplayData] -- ^ A list of data representations.
} }
|
| PublishOutput { PublishOutput
header :: MessageHeader, { header :: MessageHeader
reprText :: String, -- ^ Printed output text. , reprText :: String -- ^ Printed output text.
executionCount :: Int -- ^ Which output this is for. , executionCount :: Int -- ^ Which output this is for.
} }
|
| PublishInput { PublishInput
header :: MessageHeader, { header :: MessageHeader
inCode :: String, -- ^ Submitted input code. , inCode :: String -- ^ Submitted input code.
executionCount :: Int -- ^ Which input this is. , executionCount :: Int -- ^ Which input this is.
} }
|
| CompleteRequest { CompleteRequest
header :: MessageHeader, { header :: MessageHeader
getCode :: Text, {- ^ , getCode :: Text {- ^
The entire block of text where the line is. This may be useful in the 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 case of multiline completions where more context may be needed. Note: if
in practice this field proves unnecessary, remove it to lighten the in practice this field proves unnecessary, remove it to lighten the
messages. json field @code@ -} messages. json field @code@ -}
getCursorPos :: Int -- ^ Position of the cursor in unicode characters. json field @cursor_pos@ , getCursorPos :: Int -- ^ Position of the cursor in unicode characters. json field
-- @cursor_pos@
} }
|
| CompleteReply { CompleteReply
header :: MessageHeader, { header :: MessageHeader
completionMatches :: [Text], , completionMatches :: [Text]
completionCursorStart :: Int, , completionCursorStart :: Int
completionCursorEnd :: Int, , completionCursorEnd :: Int
completionMetadata :: Metadata, , completionMetadata :: Metadata
completionStatus :: Bool , completionStatus :: Bool
} }
|
| ObjectInfoRequest { ObjectInfoRequest
header :: MessageHeader, { header :: MessageHeader
objectName :: Text, -- ^ Name of object being searched for. -- | Name of object being searched for.
detailLevel :: Int -- ^ Level of detail desired (defaults to 0). , objectName :: Text
-- 0 is equivalent to foo?, 1 is equivalent -- | Level of detail desired (defaults to 0). 0 is equivalent to foo?, 1 is equivalent to foo??.
-- to foo??. , detailLevel :: Int
} }
|
| ObjectInfoReply { ObjectInfoReply
header :: MessageHeader, { header :: MessageHeader
objectName :: Text, -- ^ Name of object which was searched for. , objectName :: Text -- ^ Name of object which was searched for.
objectFound :: Bool, -- ^ Whether the object was found. , objectFound :: Bool -- ^ Whether the object was found.
objectTypeString :: Text, -- ^ Object type. , objectTypeString :: Text -- ^ Object type.
objectDocString :: Text , objectDocString :: Text
} }
|
| ShutdownRequest { ShutdownRequest
header :: MessageHeader, { header :: MessageHeader
restartPending :: Bool -- ^ Whether this shutdown precedes a restart. , restartPending :: Bool -- ^ Whether this shutdown precedes a restart.
}
| ShutdownReply {
header :: MessageHeader,
restartPending :: Bool -- ^ Whether this shutdown precedes a restart.
} }
|
| ClearOutput { ShutdownReply
header :: MessageHeader, { header :: MessageHeader
wait :: Bool -- ^ Whether to wait to redraw until there is more output. , restartPending :: Bool -- ^ Whether this shutdown precedes a restart.
} }
|
| RequestInput { ClearOutput
header :: MessageHeader, { header :: MessageHeader
inputPrompt :: String , wait :: Bool -- ^ Whether to wait to redraw until there is more output.
} }
| RequestInput { header :: MessageHeader, inputPrompt :: String }
| InputReply { | InputReply { header :: MessageHeader, inputValue :: String }
header :: MessageHeader, |
inputValue :: String CommOpen
{ header :: MessageHeader
, commTargetName :: String
, commUuid :: UUID
, commData :: Value
} }
| CommData { header :: MessageHeader, commUuid :: UUID, commData :: Value }
| CommOpen { | CommClose { header :: MessageHeader, commUuid :: UUID, commData :: Value }
header :: MessageHeader, |
commTargetName :: String, HistoryRequest
commUuid :: UUID, { header :: MessageHeader
commData :: Value , 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] }
| 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. | SendNothing -- Dummy message; nothing is sent.
deriving Show deriving Show
-- | Ways in which the frontend can request history. -- | Ways in which the frontend can request history. TODO: Implement fields as described in
-- TODO: Implement fields as described in messaging spec. -- messaging spec.
data HistoryAccessType = HistoryRange data HistoryAccessType = HistoryRange
| HistoryTail | HistoryTail
| HistorySearch | HistorySearch
deriving (Eq, Show) deriving (Eq, Show)
-- | Reply to history requests. -- | Reply to history requests.
data HistoryReplyElement = HistoryReplyElement { historyReplySession :: Int data HistoryReplyElement =
HistoryReplyElement
{ historyReplySession :: Int
, historyReplyLineNumber :: Int , historyReplyLineNumber :: Int
, historyReplyContent :: Either String (String, String) , historyReplyContent :: Either String (String, String)
} }
deriving (Eq, Show) deriving (Eq, Show)
-- | Possible statuses in the execution reply messages. -- | Possible statuses in the execution reply messages.
data ExecuteReplyStatus = Ok | Err | Abort data ExecuteReplyStatus = Ok
| Err
| Abort
instance Show ExecuteReplyStatus where instance Show ExecuteReplyStatus where
show Ok = "ok" show Ok = "ok"
...@@ -413,10 +406,15 @@ instance Show ExecuteReplyStatus where ...@@ -413,10 +406,15 @@ instance Show ExecuteReplyStatus where
show Abort = "abort" show Abort = "abort"
-- | The execution state of the kernel. -- | The execution state of the kernel.
data ExecutionState = Busy | Idle | Starting deriving Show data ExecutionState = Busy
| Idle
| Starting
deriving Show
-- | Input and output streams. -- | 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. -- | Get the reply message type for a request message type.
replyType :: MessageType -> Maybe MessageType replyType :: MessageType -> Maybe MessageType
...@@ -429,11 +427,11 @@ replyType HistoryRequestMessage = Just HistoryReplyMessage ...@@ -429,11 +427,11 @@ replyType HistoryRequestMessage = Just HistoryReplyMessage
replyType _ = Nothing replyType _ = Nothing
-- | Data for display: a string with associated MIME type. -- | 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 -- We can't print the actual data, otherwise this will be printed every time it gets computed
-- time it gets computed because of the way the evaluator is structured. -- because of the way the evaluator is structured. See how `displayExpr` is computed.
-- See how `displayExpr` is computed.
instance Show DisplayData where instance Show DisplayData where
show _ = "DisplayData" show _ = "DisplayData"
...@@ -441,12 +439,16 @@ instance Show DisplayData where ...@@ -441,12 +439,16 @@ instance Show DisplayData where
instance Serialize Text where instance Serialize Text where
put str = put (Text.encodeUtf8 str) put str = put (Text.encodeUtf8 str)
get = Text.decodeUtf8 <$> get get = Text.decodeUtf8 <$> get
instance Serialize DisplayData instance Serialize DisplayData
instance Serialize MimeType instance Serialize MimeType
-- | Possible MIME types for the display data. -- | Possible MIME types for the display data.
type Width = Int type Width = Int
type Height = Int type Height = Int
data MimeType = PlainText data MimeType = PlainText
| MimeHtml | MimeHtml
| MimePng Width Height | MimePng Width Height
......
{-# LANGUAGE OverloadedStrings, DoAndIfThenElse #-} {-# LANGUAGE OverloadedStrings, DoAndIfThenElse #-}
-- | Description : Low-level ZeroMQ communication wrapper. -- | Description : Low-level ZeroMQ communication wrapper.
-- --
-- The "ZeroMQ" module abstracts away the low-level 0MQ based interface with IPython, -- The "ZeroMQ" module abstracts away the low-level 0MQ based interface with IPython, replacing it
-- replacing it instead with a Haskell Channel based interface. The `serveProfile` function -- instead with a Haskell Channel based interface. The `serveProfile` function takes a IPython
-- takes a IPython profile specification and returns the channel interface to use. -- profile specification and returns the channel interface to use.
module IHaskell.IPython.ZeroMQ ( module IHaskell.IPython.ZeroMQ (ZeroMQInterface(..), ZeroMQStdin(..), serveProfile, serveStdin) where
ZeroMQInterface (..),
ZeroMQStdin(..),
serveProfile,
serveStdin,
) where
import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString.Lazy as LBS
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
...@@ -26,30 +22,37 @@ import IHaskell.IPython.Types ...@@ -26,30 +22,37 @@ import IHaskell.IPython.Types
import IHaskell.IPython.Message.Parser import IHaskell.IPython.Message.Parser
import IHaskell.IPython.Message.Writer import IHaskell.IPython.Message.Writer
-- | The channel interface to the ZeroMQ sockets. All communication is done via -- | The channel interface to the ZeroMQ sockets. All communication is done via Messages, which are
-- Messages, which are encoded and decoded into a lower level form before being -- encoded and decoded into a lower level form before being transmitted to IPython. These channels
-- transmitted to IPython. These channels should functionally serve as -- should functionally serve as high-level sockets which speak Messages instead of ByteStrings.
-- high-level sockets which speak Messages instead of ByteStrings.
data ZeroMQInterface = data ZeroMQInterface =
Channels { 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. -- | A channel populated with requests from the frontend.
controlRequestChannel :: Chan Message, -- ^ This channel is a duplicate of the shell request channel, shellRequestChannel :: Chan Message
-- though using a different backend socket. -- | Writing to this channel causes a reply to be sent to the frontend.
controlReplyChannel :: Chan Message, -- ^ This channel is a duplicate of the shell reply channel, , shellReplyChannel :: Chan Message
-- though using a different backend socket. -- | This channel is a duplicate of the shell request channel, though using a different backend
iopubChannel :: Chan Message, -- ^ Writing to this channel sends an iopub message to the frontend. -- socket.
hmacKey :: ByteString -- ^ Key used to sign messages. , 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 { data ZeroMQStdin =
stdinRequestChannel :: Chan Message, StdinChannel
stdinReplyChannel :: Chan Message { stdinRequestChannel :: Chan Message
, stdinReplyChannel :: Chan Message
} }
-- | Start responding on all ZeroMQ channels used to communicate with IPython -- | Start responding on all ZeroMQ channels used to communicate with IPython | via the provided
-- | via the provided profile. Return a set of channels which can be used to -- profile. Return a set of channels which can be used to | communicate with IPython in a more
-- | communicate with IPython in a more structured manner. -- structured manner.
serveProfile :: Profile -- ^ The profile specifying which ports and transport mechanisms to use. serveProfile :: Profile -- ^ The profile specifying which ports and transport mechanisms to use.
-> Bool -- ^ Print debug output -> Bool -- ^ Print debug output
-> IO ZeroMQInterface -- ^ The Message-channel based interface to the sockets. -> IO ZeroMQInterface -- ^ The Message-channel based interface to the sockets.
...@@ -63,18 +66,17 @@ serveProfile profile debug = do ...@@ -63,18 +66,17 @@ serveProfile profile debug = do
let channels = Channels shellReqChan shellRepChan controlReqChan controlRepChan iopubChan let channels = Channels shellReqChan shellRepChan controlReqChan controlRepChan iopubChan
(signatureKey profile) (signatureKey profile)
-- Create the context in a separate thread that never finishes. If -- Create the context in a separate thread that never finishes. If withContext or withSocket
-- withContext or withSocket complete, the context or socket become invalid. -- complete, the context or socket become invalid.
forkIO $ withContext $ \context -> do forkIO $ withContext $ \context -> do
-- Serve on all sockets. -- Serve on all sockets.
forkIO $ serveSocket context Rep (hbPort profile) $ heartbeat channels forkIO $ serveSocket context Rep (hbPort profile) $ heartbeat channels
forkIO $ serveSocket context Router (controlPort profile) $ control debug channels forkIO $ serveSocket context Router (controlPort profile) $ control debug channels
forkIO $ serveSocket context Router (shellPort profile) $ shell debug channels forkIO $ serveSocket context Router (shellPort profile) $ shell debug channels
-- The context is reference counted in this thread only. Thus, the last -- The context is reference counted in this thread only. Thus, the last serveSocket cannot be
-- serveSocket cannot be asynchronous, because otherwise context would -- asynchronous, because otherwise context would be garbage collectable - since it would only be
-- be garbage collectable - since it would only be used in other -- used in other threads. Thus, keep the last serveSocket in this thread.
-- threads. Thus, keep the last serveSocket in this thread.
serveSocket context Pub (iopubPort profile) $ iopub debug channels serveSocket context Pub (iopubPort profile) $ iopub debug channels
return channels return channels
...@@ -84,8 +86,8 @@ serveStdin profile = do ...@@ -84,8 +86,8 @@ serveStdin profile = do
reqChannel <- newChan reqChannel <- newChan
repChannel <- newChan repChannel <- newChan
-- Create the context in a separate thread that never finishes. If -- Create the context in a separate thread that never finishes. If withContext or withSocket
-- withContext or withSocket complete, the context or socket become invalid. -- complete, the context or socket become invalid.
forkIO $ withContext $ \context -> forkIO $ withContext $ \context ->
-- Serve on all sockets. -- Serve on all sockets.
serveSocket context Router (stdinPort profile) $ \socket -> do serveSocket context Router (stdinPort profile) $ \socket -> do
...@@ -97,9 +99,8 @@ serveStdin profile = do ...@@ -97,9 +99,8 @@ serveStdin profile = do
return $ StdinChannel reqChannel repChannel return $ StdinChannel reqChannel repChannel
-- | Serve on a given socket in a separate thread. Bind the socket in the -- | Serve on a given socket in a separate thread. Bind the socket in the | given context and then
-- | given context and then loop the provided action, which should listen -- loop the provided action, which should listen | on the socket and respond to any events.
-- | on the socket and respond to any events.
serveSocket :: SocketType a => Context -> a -> Port -> (Socket a -> IO b) -> IO () serveSocket :: SocketType a => Context -> a -> Port -> (Socket a -> IO b) -> IO ()
serveSocket context socketType port action = void $ serveSocket context socketType port action = void $
withSocket context socketType $ \socket -> do withSocket context socketType $ \socket -> do
...@@ -115,9 +116,9 @@ heartbeat _ socket = do ...@@ -115,9 +116,9 @@ heartbeat _ socket = do
-- Send it back. -- Send it back.
send socket [] request send socket [] request
-- | Listener on the shell port. Reads messages and writes them to -- | Listener on the shell port. Reads messages and writes them to | the shell request channel. For
-- | the shell request channel. For each message, reads a response from the -- each message, reads a response from the | shell reply channel of the interface and sends it back
-- | shell reply channel of the interface and sends it back to the frontend. -- to the frontend.
shell :: Bool -> ZeroMQInterface -> Socket Router -> IO () shell :: Bool -> ZeroMQInterface -> Socket Router -> IO ()
shell debug channels socket = do shell debug channels socket = do
-- Receive a message and write it to the interface channel. -- Receive a message and write it to the interface channel.
...@@ -130,9 +131,9 @@ shell debug channels socket = do ...@@ -130,9 +131,9 @@ shell debug channels socket = do
requestChannel = shellRequestChannel channels requestChannel = shellRequestChannel channels
replyChannel = shellReplyChannel channels replyChannel = shellReplyChannel channels
-- | Listener on the shell port. Reads messages and writes them to -- | Listener on the shell port. Reads messages and writes them to | the shell request channel. For
-- | the shell request channel. For each message, reads a response from the -- each message, reads a response from the | shell reply channel of the interface and sends it back
-- | shell reply channel of the interface and sends it back to the frontend. -- to the frontend.
control :: Bool -> ZeroMQInterface -> Socket Router -> IO () control :: Bool -> ZeroMQInterface -> Socket Router -> IO ()
control debug channels socket = do control debug channels socket = do
-- Receive a message and write it to the interface channel. -- Receive a message and write it to the interface channel.
...@@ -145,9 +146,8 @@ control debug channels socket = do ...@@ -145,9 +146,8 @@ control debug channels socket = do
requestChannel = controlRequestChannel channels requestChannel = controlRequestChannel channels
replyChannel = controlReplyChannel channels replyChannel = controlReplyChannel channels
-- | Send messages via the iopub channel. -- | Send messages via the iopub channel. | This reads messages from the ZeroMQ iopub interface
-- | This reads messages from the ZeroMQ iopub interface channel -- channel | and then writes the messages to the socket.
-- | and then writes the messages to the socket.
iopub :: Bool -> ZeroMQInterface -> Socket Pub -> IO () iopub :: Bool -> ZeroMQInterface -> Socket Pub -> IO ()
iopub debug channels socket = iopub debug channels socket =
readChan (iopubChannel channels) >>= sendMessage debug (hmacKey channels) socket readChan (iopubChannel channels) >>= sendMessage debug (hmacKey channels) socket
...@@ -179,8 +179,8 @@ receiveMessage debug socket = do ...@@ -179,8 +179,8 @@ receiveMessage debug socket = do
-- Receive the next piece of data from the socket. -- Receive the next piece of data from the socket.
next = receive socket next = receive socket
-- Read data from the socket until we hit an ending string. -- Read data from the socket until we hit an ending string. Return all data as a list, which does
-- Return all data as a list, which does not include the ending string. -- not include the ending string.
readUntil str = do readUntil str = do
line <- next line <- next
if line /= str if line /= str
...@@ -189,9 +189,8 @@ receiveMessage debug socket = do ...@@ -189,9 +189,8 @@ receiveMessage debug socket = do
return $ line : remaining return $ line : remaining
else return [] else return []
-- | Encode a message in the IPython ZeroMQ communication protocol -- | Encode a message in the IPython ZeroMQ communication protocol and send it through the provided
-- and send it through the provided socket. Sign it using HMAC -- socket. Sign it using HMAC with SHA-256 using the provided key.
-- with SHA-256 using the provided key.
sendMessage :: Sender a => Bool -> ByteString -> Socket a -> Message -> IO () sendMessage :: Sender a => Bool -> ByteString -> Socket a -> Message -> IO ()
sendMessage _ _ _ SendNothing = return () sendMessage _ _ _ SendNothing = return ()
sendMessage debug hmacKey socket message = do sendMessage debug hmacKey socket message = do
......
...@@ -44,9 +44,15 @@ except: ...@@ -44,9 +44,15 @@ except:
# Find all the source files # Find all the source files
sources = [] sources = []
for root, dirnames, filenames in os.walk("src"): for source_dir in ["src", "ipython-kernel", "ihaskell-display"]:
for root, dirnames, filenames in os.walk(source_dir):
# Skip cabal dist directories
if "dist" in root:
continue
for filename in filenames: for filename in filenames:
if filename.endswith(".hs"): # Take Haskell files, but ignore the Cabal Setup.hs
if filename.endswith(".hs") and filename != "Setup.hs":
sources.append(os.path.join(root, filename)) sources.append(os.path.join(root, filename))
......
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