ihaskell-0.2.0.0: IHaskell/IPython.hs
{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}
{-# LANGUAGE DoAndIfThenElse #-}
-- | Description : Shell scripting wrapper using @Shelly@ for the @notebook@, @setup@, and
-- @console@ commands.
module IHaskell.IPython (
runIHaskell,
setupIPythonProfile,
ipythonVersion,
parseVersion
) where
import ClassyPrelude
import Prelude (read, reads)
import Shelly hiding (find, trace)
import System.Argv0
import System.Directory
import qualified Filesystem.Path.CurrentOS as FS
import Data.List.Utils (split)
import Data.String.Utils (rstrip)
import qualified System.IO.Strict as StrictIO
import qualified IHaskell.Config as Config
-- | Run IPython with any arguments.
ipython :: Bool -- ^ Whether to suppress output.
-> [Text] -- ^ IPython command line arguments.
-> Sh String -- ^ IPython output.
ipython suppress args = do
path <- which "ipython"
case path of
Nothing -> do
putStrLn "Could not find `ipython` executable."
fail "`ipython` not on $PATH."
Just ipythonPath -> runHandles ipythonPath args handles doNothing
where handles = [InHandle Inherit, outHandle suppress, ErrorHandle Inherit]
outHandle True = OutHandle CreatePipe
outHandle False = OutHandle Inherit
doNothing _ stdout _ = if suppress
then liftIO $ StrictIO.hGetContents stdout
else return ""
-- | Use the `ipython --version` command to figure out the version.
-- Return a tuple with (major, minor, patch).
ipythonVersion :: IO (Int, Int, Int)
ipythonVersion = shelly $ do
[major, minor, patch] <- parseVersion <$> ipython True ["--version"]
return (major, minor, patch)
-- | Parse an IPython version string into a list of integers.
parseVersion :: String -> [Int]
parseVersion versionStr = map read' $ split "." versionStr
where read' x = case reads x of
[(n, _)] -> n
_ -> error $ "cannot parse version: "++ versionStr
-- | Run an IHaskell application using the given profile.
runIHaskell :: String -- ^ IHaskell profile name.
-> String -- ^ IPython app name.
-> [String] -- ^ Arguments to IPython.
-> IO ()
runIHaskell profile app args = void . shelly . ipython False $ [pack app, "--profile", pack profile] ++ map pack args
-- | Create the IPython profile.
setupIPythonProfile :: String -- ^ IHaskell profile name.
-> IO ()
setupIPythonProfile profile = shelly $ do
-- Create the IPython profile.
void $ ipython False ["profile", "create", pack profile]
-- Find the IPython profile directory. Make sure to get rid of trailing
-- newlines from the output of the `ipython locate` call.
ipythonDir <- pack <$> rstrip <$> ipython True ["locate"]
let profileDir = ipythonDir ++ "/profile_" ++ pack profile ++ "/"
path <- getIHaskellPath
writeConfigFilesTo profileDir path
-- | Write IPython configuration files to the profile directory.
writeConfigFilesTo :: Text -- ^ Profile directory to write to. Must have a trailing slash.
-> String -- ^ Path to IHaskell executable.
-> Sh ()
writeConfigFilesTo profileDir ihaskellPath = do
writeFile (conf "ipython_config.py") $ Config.ipython ihaskellPath
writeFile (conf "ipython_notebook_config.py") Config.notebook
writeFile (conf "ipython_console_config.py") Config.console
writeFile (conf "ipython_qtconsole_config.py") Config.qtconsole
-- The custom directory many not exist, in which case we'll create it.
mkdir_p (conf "static/custom/")
writeFile (conf "static/custom/custom.js") Config.customjs
-- Make directory for images.
mkdir_p (conf "static/base/images")
-- The notebook/js directory many not exist, in which case we'll create it.
mkdir_p (conf "static/notebook/")
mkdir_p (conf "static/notebook/js")
forM_ Config.notebookJavascript $ \(file, content) ->
writeFile (conf "static/notebook/js/" ++ file) content
where
conf filename = fromText $ profileDir ++ filename
-- | Get the absolute path to this IHaskell executable.
getIHaskellPath :: Sh String
getIHaskellPath = do
-- Get the absolute filepath to the argument.
f <- liftIO getArgv0
-- If we have an absolute path, that's the IHaskell we're interested in.
if FS.absolute f
then return $ FS.encodeString f
else
-- Check whether this is a relative path, or just 'IHaskell' with $PATH
-- resolution done by the shell. If it's just 'IHaskell', use the $PATH
-- variable to find where IHaskell lives.
if FS.filename f == f
then do
ihaskellPath <- which "IHaskell"
case ihaskellPath of
Nothing -> error "IHaskell not on $PATH and not referenced relative to directory."
Just path -> return $ FS.encodeString path
else do
-- If it's actually a relative path, make it absolute.
cd <- liftIO getCurrentDirectory
return $ FS.encodeString $ FS.decodeString cd FS.</> f