diff --git a/ipython-kernel/example-data/calc_profile.tar b/ipython-kernel/example-data/calc_profile.tar new file mode 100644 index 00000000..65c10475 Binary files /dev/null and b/ipython-kernel/example-data/calc_profile.tar differ diff --git a/ipython-kernel/examples/Calc.hs b/ipython-kernel/examples/Calc.hs index ba4cf1f8..a7bd11e2 100644 --- a/ipython-kernel/examples/Calc.hs +++ b/ipython-kernel/examples/Calc.hs @@ -15,13 +15,16 @@ import Data.Monoid ((<>)) import qualified Data.Text as T import IHaskell.IPython.Kernel -import IHaskell.IPython.EasyKernel (easyKernel, KernelConfig(..)) +import IHaskell.IPython.EasyKernel (installProfile, easyKernel, KernelConfig(..)) import System.Environment (getArgs) +import System.FilePath (()) import Text.Parsec (Parsec, ParseError, alphaNum, char, letter, oneOf, optionMaybe, runParser, ()) import qualified Text.Parsec.Token as P +import qualified Paths_ipython_kernel as Paths + --------------------------------------------------------- -- Hutton's Razor, plus time delays, plus a global state --------------------------------------------------------- @@ -203,14 +206,15 @@ execRazor val Count clear send = do mkConfig :: MVar Integer -- ^ The internal state of the execution -> KernelConfig IO [IntermediateEvalRes] (Either ParseError Integer) mkConfig var = KernelConfig - { languageName = "Hutton's Razor + extra" - , languageVersion = [0,1,0] - , displayResult = displayRes - , displayOutput = displayOut - , completion = langCompletion - , objectInfo = langInfo - , run = parseAndRun - , debug = False + { languageName = "expanded_huttons_razor" + , languageVersion = [0,1,0] + , profileSource = Just . ( "calc_profile.tar") <$> Paths.getDataDir + , displayResult = displayRes + , displayOutput = displayOut + , completion = langCompletion + , objectInfo = langInfo + , run = parseAndRun + , debug = False } where displayRes (Left err) = @@ -231,6 +235,15 @@ mkConfig var = KernelConfig return (Right res, Ok, T.unpack pager) main :: IO () -main = do ["kernel", profileFile] <- getArgs +main = do args <- getArgs val <- newMVar 1 - easyKernel profileFile (mkConfig val) + case args of + ["kernel", profileFile] -> + easyKernel profileFile (mkConfig val) + ["setup"] -> do + putStrLn "Installing profile..." + installProfile (mkConfig val) + _ -> do + putStrLn "Usage:" + putStrLn "simple-calc-example setup -- set up the profile" + putStrLn "simple-calc-example kernel FILE -- run a kernel with FILE for communication with the frontend" diff --git a/ipython-kernel/ipython-kernel.cabal b/ipython-kernel/ipython-kernel.cabal index 7ca7ccfb..19f3dbe3 100644 --- a/ipython-kernel/ipython-kernel.cabal +++ b/ipython-kernel/ipython-kernel.cabal @@ -14,10 +14,15 @@ build-type: Simple cabal-version: >=1.16 +data-dir: example-data +data-files: calc_profile.tar + + flag examples description: Build example programs default: False + library exposed-modules: IHaskell.IPython.Kernel IHaskell.IPython.Types @@ -36,22 +41,27 @@ library bytestring >=0.10, cereal >=0.3, containers >=0.5, + directory >=1.1, + filepath >=1.2, mtl >=2.1, + tar >=0.4.0.1, text >=0.11, transformers >=0.3, unix >=2.6, uuid >=1.3, zeromq4-haskell >=0.1 + -- Example program executable simple-calc-example hs-source-dirs: examples main-is: Calc.hs build-depends: ipython-kernel, - base >=4.6 && < 4.8, + base >=4.6 && <4.8, + filepath >=1.2, mtl >=2.1, parsec >=3.1, - text >= 0.11, + text >=0.11, transformers >=0.3 if !flag(examples) diff --git a/ipython-kernel/src/IHaskell/IPython/EasyKernel.hs b/ipython-kernel/src/IHaskell/IPython/EasyKernel.hs index b5b50f70..e17df8dd 100644 --- a/ipython-kernel/src/IHaskell/IPython/EasyKernel.hs +++ b/ipython-kernel/src/IHaskell/IPython/EasyKernel.hs @@ -45,12 +45,14 @@ -- source code for further configuration of the frontend, including -- syntax highlighting, logos, help text, and so forth. -module IHaskell.IPython.EasyKernel (easyKernel, KernelConfig(..)) where +module IHaskell.IPython.EasyKernel (easyKernel, installProfile, KernelConfig(..)) where import Data.Aeson (decode) import qualified Data.ByteString.Lazy as BL +import qualified Codec.Archive.Tar as Tar + import Control.Concurrent (MVar, readChan, writeChan, newMVar, readMVar, modifyMVar_) import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad (forever, when) @@ -62,7 +64,8 @@ import qualified Data.Text as T import IHaskell.IPython.Kernel import IHaskell.IPython.Message.UUID as UUID - +import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getHomeDirectory) +import System.FilePath (()) import System.Exit (exitSuccess) import System.IO (openFile, IOMode(ReadMode)) @@ -71,8 +74,18 @@ import System.IO (openFile, IOMode(ReadMode)) -- your kernel will run, the type of intermediate outputs from running -- cells, and the type of final results of cells, respectively. data KernelConfig m output result = KernelConfig - { languageName :: String -- ^ The name of the language + { languageName :: String + -- ^ The name of the language. This field is used to calculate + -- the name of the profile, so it should contain characters that + -- are reasonable to have in file names. , languageVersion :: [Int] -- ^ The version of the language + , profileSource :: IO (Maybe FilePath) + -- ^ Determine the source of a profile to install using + -- 'installProfile'. The source should be a tarball whose contents + -- will be unpacked directly into the profile directory. For + -- example, the file whose name is @ipython_config.py@ in the + -- tar file for a language named @lang@ will end up in + -- @~/.ipython/profile_lang/ipython_config.py@. , displayOutput :: output -> [DisplayData] -- ^ How to render intermediate output , displayResult :: result -> [DisplayData] -- ^ How to render final cell results , completion :: T.Text -> T.Text -> Int -> Maybe ([T.Text], T.Text, T.Text) @@ -97,6 +110,35 @@ data KernelConfig m output result = KernelConfig -- the console } +-- | Attempt to install the IPython profile from the .tar file +-- indicated by the 'profileSource' field of the configuration, if it +-- is not already installed. +installProfile :: MonadIO m => KernelConfig m output result -> m () +installProfile config = do + installed <- isInstalled + when (not installed) $ do + profSrc <- liftIO $ profileSource config + case profSrc of + Nothing -> liftIO (putStrLn "No IPython profile is installed or specified") + Just tar -> do + profExists <- liftIO $ doesFileExist tar + profTgt <- profDir + if profExists + then do liftIO $ createDirectoryIfMissing True profTgt + liftIO $ Tar.extract profTgt tar + else liftIO . putStrLn $ + "The supplied profile source '" ++ tar ++ "' does not exist" + + where + profDir = do + home <- liftIO getHomeDirectory + return $ home ".ipython" ("profile_" ++ languageName config) + isInstalled = do + prof <- profDir + dirThere <- liftIO $ doesDirectoryExist prof + isProf <- liftIO . doesFileExist $ prof "ipython_config.py" + return $ dirThere && isProf + getProfile :: FilePath -> IO Profile getProfile fn = do profData <- openFile fn ReadMode >>= BL.hGetContents