Commit c53f70d8 authored by Andrew Gibiansky's avatar Andrew Gibiansky

Formatting ipython-kernel

parent 7ba7c4d1
This diff is collapsed.
-- | 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
import IHaskell.IPython.Message.Parser as X import IHaskell.IPython.Message.Parser as X
import IHaskell.IPython.Message.UUID as X import IHaskell.IPython.Message.UUID as X
import IHaskell.IPython.ZeroMQ as X import IHaskell.IPython.ZeroMQ 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,26 +31,25 @@ parseMessage idents headerData parentHeader metadata content = ...@@ -32,26 +31,25 @@ 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
, parentHeader = parentResult { identifiers = idents
, metadata = metadataMap , parentHeader = parentResult
, messageId = messageUUID , metadata = metadataMap
, sessionId = sessionUUID , messageId = messageUUID
, username = username , sessionId = sessionUUID
, msgType = messageType , username = username
} , 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,27 +69,26 @@ noHeader :: MessageHeader ...@@ -71,27 +69,26 @@ 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
parser ObjectInfoRequestMessage = objectInfoRequestParser parser ObjectInfoRequestMessage = objectInfoRequestParser
parser ShutdownRequestMessage = shutdownRequestParser parser ShutdownRequestMessage = shutdownRequestParser
parser InputReplyMessage = inputReplyParser parser InputReplyMessage = inputReplyParser
parser CommOpenMessage = commOpenParser parser CommOpenMessage = commOpenParser
parser CommDataMessage = commDataParser parser CommDataMessage = commDataParser
parser CommCloseMessage = commCloseParser 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.
...@@ -99,22 +96,23 @@ kernelInfoRequestParser _ = KernelInfoRequest { header = noHeader } ...@@ -99,22 +96,23 @@ kernelInfoRequestParser _ = KernelInfoRequest { header = noHeader }
executeRequestParser :: LByteString -> Message executeRequestParser :: LByteString -> Message
executeRequestParser content = executeRequestParser content =
let parser obj = do let parser obj = do
code <- obj .: "code" code <- obj .: "code"
silent <- obj .: "silent" silent <- obj .: "silent"
storeHistory <- obj .: "store_history" storeHistory <- obj .: "store_history"
allowStdin <- obj .: "allow_stdin" allowStdin <- obj .: "allow_stdin"
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
, getCode = code { header = noHeader
, getSilent = silent , getCode = code
, getAllowStdin = allowStdin , getSilent = silent
, getStoreHistory = storeHistory , getAllowStdin = allowStdin
, getUserVariables = [] , getStoreHistory = storeHistory
, getUserExpressions = [] , getUserVariables = []
} , getUserExpressions = []
}
requestParser parser content = parsed requestParser parser content = parsed
where where
...@@ -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 ((<$>))
import Data.Text (pack) 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)
import Data.Text (Text, pack) import Data.Text (Text, pack)
import Data.Monoid (mempty) import Data.Monoid (mempty)
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B import qualified Data.ByteString as B
import Data.Text.Encoding import Data.Text.Encoding
import IHaskell.IPython.Types import IHaskell.IPython.Types
-- Convert message bodies into JSON. -- 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 ["source" .= string "page", "text" .= pager]]
else [object [ , "user_variables" .= emptyMap
"source" .= string "page", , "user_expressions" .= emptyMap
"text" .= pager ]
]], toJSON PublishStatus { executionState = executionState } =
"user_variables" .= emptyMap, object ["execution_state" .= executionState]
"user_expressions" .= emptyMap toJSON PublishStream { streamType = streamType, streamContent = content } =
] object ["data" .= content, "name" .= streamType]
toJSON PublishStatus{ executionState = executionState } = object [ toJSON PublishDisplayData { source = src, displayData = datas } =
"execution_state" .= executionState object
] ["source" .= src, "metadata" .=
toJSON PublishStream{ streamType = streamType, streamContent = content } = object [ object [], "data" .=
"data" .= content, object (map displayDataToJson datas)]
"name" .= streamType
] toJSON PublishOutput { executionCount = execCount, reprText = reprText } =
toJSON PublishDisplayData{ source = src, displayData = datas } = object [ object
"source" .= src, ["data" .=
"metadata" .= object [], object ["text/plain" .= reprText], "execution_count" .= execCount, "metadata" .=
"data" .= object (map displayDataToJson datas) object []]
] toJSON PublishInput { executionCount = execCount, inCode = code } =
object ["execution_count" .= execCount, "code" .= code]
toJSON PublishOutput{ executionCount = execCount, reprText = reprText } = object [ toJSON (CompleteReply _ matches start end metadata status) =
"data" .= object ["text/plain" .= reprText], object
"execution_count" .= execCount, [ "matches" .= matches
"metadata" .= object [] , "cursor_start" .= start
] , "cursor_end" .= end
toJSON PublishInput{ executionCount = execCount, inCode = code } = object [ , "metadata" .= metadata
"execution_count" .= execCount, , "status" .= if status
"code" .= code then string "ok"
] else "error"
toJSON (CompleteReply _ matches start end metadata status) = object [ ]
"matches" .= matches, toJSON o@ObjectInfoReply{} =
"cursor_start" .= start, object
"cursor_end" .= end, [ "oname" .=
"metadata" .= metadata, objectName o
"status" .= if status then string "ok" else "error" , "found" .= objectFound o
] , "ismagic" .= False
toJSON o@ObjectInfoReply{} = object [ , "isalias" .= False
"oname" .= objectName o, , "type_name" .= objectTypeString o
"found" .= objectFound o, , "docstring" .= objectDocString o
"ismagic" .= False, ]
"isalias" .= False,
"type_name" .= objectTypeString o, toJSON ShutdownReply { restartPending = restart } =
"docstring" .= objectDocString o object ["restart" .= restart]
]
toJSON ClearOutput { wait = wait } =
toJSON ShutdownReply{restartPending = restart} = object [ object ["wait" .= wait]
"restart" .= restart
] toJSON RequestInput { inputPrompt = prompt } =
object ["prompt" .= prompt]
toJSON ClearOutput{wait = wait} = object [
"wait" .= wait toJSON req@CommOpen{} =
] object ["comm_id" .= commUuid req, "target_name" .= commTargetName req, "data" .= commData req]
toJSON RequestInput{inputPrompt = prompt} = object [ toJSON req@CommData{} =
"prompt" .= prompt object ["comm_id" .= commUuid req, "data" .= commData req]
]
toJSON req@CommClose{} =
toJSON req@CommOpen{} = object [ object ["comm_id" .= commUuid req, "data" .= commData req]
"comm_id" .= commUuid req,
"target_name" .= commTargetName req, toJSON req@HistoryReply{} =
"data" .= commData req object ["history" .= map tuplify (historyReply req)]
] where
tuplify (HistoryReplyElement sess linum res) = (sess, linum, case res of
toJSON req@CommData{} = object [ Left inp -> toJSON inp
"comm_id" .= commUuid req, Right (inp, out) -> toJSON out)
"data" .= commData req
]
toJSON req@CommClose{} = object [
"comm_id" .= commUuid req,
"data" .= commData req
]
toJSON req@HistoryReply{} = object [ "history" .= map tuplify (historyReply req) ]
where tuplify (HistoryReplyElement sess linum res) = (sess, linum, case res of
Left inp -> toJSON inp
Right (inp, out) -> toJSON out)
toJSON body = error $ "Do not know how to convert to JSON for message " ++ show body 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"
toJSON Idle = String "idle" toJSON Idle = String "idle"
toJSON Starting = String "starting" toJSON Starting = String "starting"
-- | Print a stream as "stdin" or "stdout" strings. -- | Print a stream as "stdin" or "stdout" strings.
instance ToJSON StreamType where instance ToJSON StreamType where
toJSON Stdin = String "stdin" toJSON Stdin = String "stdin"
toJSON Stdout = String "stdout" toJSON Stdout = String "stdout"
-- | Convert a MIME type and value into a JSON dictionary pair. -- | Convert a MIME type and value into a JSON dictionary pair.
displayDataToJson :: DisplayData -> (Text, Value) displayDataToJson :: DisplayData -> (Text, Value)
displayDataToJson (DisplayData mimeType dataStr) = 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 ((<$>))
import Control.Concurrent.Chan import Control.Concurrent.Chan
import Control.Monad import Control.Monad
import GHC.IO.Handle import GHC.IO.Handle
import GHC.IO.Handle.Types import GHC.IO.Handle.Types
import System.IO import System.IO
import System.Posix.IO import System.Posix.IO
import System.IO.Unsafe import System.IO.Unsafe
import qualified Data.Map as Map import qualified Data.Map as Map
import IHaskell.IPython.Types import IHaskell.IPython.Types
import IHaskell.IPython.ZeroMQ import IHaskell.IPython.ZeroMQ
import IHaskell.IPython.Message.UUID as UUID import IHaskell.IPython.Message.UUID as UUID
stdinInterface :: MVar ZeroMQStdin 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,17 +67,18 @@ stdinOnce dir = do ...@@ -78,17 +67,18 @@ 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
threadDelay $ 150 * 1000 threadDelay $ 150 * 1000
empty <- isEmptyMVar mvar empty <- isEmptyMVar mvar
if not empty if not empty
then loop stdinInput oldStdin newStdin then loop stdinInput oldStdin newStdin
else do else do
line <- getInputLine dir line <- getInputLine dir
hPutStr stdinInput $ line ++ "\n" hPutStr stdinInput $ line ++ "\n"
loop stdinInput oldStdin newStdin loop stdinInput oldStdin newStdin
-- | Get a line of input from the IPython frontend. -- | Get a line of input from the IPython frontend.
getInputLine :: String -> IO String getInputLine :: String -> IO String
...@@ -98,15 +88,15 @@ getInputLine dir = do ...@@ -98,15 +88,15 @@ 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
......
...@@ -44,10 +44,15 @@ except: ...@@ -44,10 +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"]:
for filename in filenames: for root, dirnames, filenames in os.walk(source_dir):
if filename.endswith(".hs"): # Skip cabal dist directories
sources.append(os.path.join(root, filename)) if "dist" in root:
continue
for filename in filenames:
if filename.endswith(".hs"):
sources.append(os.path.join(root, filename))
hindent_outputs = {} hindent_outputs = {}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment