{-# LANGUAGE LambdaCase #-} module Main where import Control.DeepSeq (force) import Control.Exception import Control.Monad import Data.ByteString.Lazy.Char8 (ByteString,pack) import Shh import System.IO import System.IO.Error import System.Environment import System.Exit import System.IO.Temp import System.Directory import System.Posix.Process defaultShell = "\ \{-# LANGUAGE TemplateHaskell #-}\n\ \module Shell where\n\ \import Shh\n\ \$(loadEnv SearchPath)\n\ \" defaultInitGhci = "\ \:seti -XNoOverloadedLists\n\ \import Shh\n\ \" extraInitGhci = "\ \import Shh.Prompt\n\ \:set prompt-function formatPrompt \"\\n\\ESC[1;32m[%u@%h:%w]λ \\ESC[0m\"\n\ \:set prompt-cont \"| \"\n\ \" defaultWrapper = "\ \#! /usr/bin/env sh\n\ \exec \"$@\"\n\ \" doIfMissing :: FilePath -> IO () -> IO () doIfMissing fp a = do doesFileExist fp >>= \case True -> pure () False -> a writeIfMissing :: FilePath -> String -> IO () writeIfMissing fp s = doIfMissing fp (writeFile fp s) main :: IO () main = do a <- getArgs shhDir <- lookupEnv "SHH_DIR" >>= \case Nothing -> lookupEnv "HOME" >>= \case Nothing -> error "Please specify HOME or SHH_DIR environment variables" Just h -> pure $ h <> "/.shh" Just s -> pure s let wrapper :: String wrapper = shhDir <> "/wrapper" let debug = elem "--debug" a when debug $ putStrLn $ "Shh home is: " <> shhDir case a of ["--rebuild"] -> do removeFile "Shell.hi" removeFile "Shell.o" ["--help"] -> do putStrLn "usage: shh [--rebuild][--debug]" exitSuccess ["--debug"] -> pure () [] -> pure () _ -> error $ "Unknown arguments: " ++ show a createDirectoryIfMissing False shhDir withCurrentDirectory shhDir $ do writeIfMissing "wrapper" defaultWrapper setPermissions "wrapper" $ setOwnerExecutable True $ setOwnerReadable True $ setOwnerWritable True $ emptyPermissions doIfMissing "init.ghci" $ do putStrLn "Generating init.ghci..." putStrLn " ... checking for shh..." tryFailure (exe (pack wrapper) "ghc" (if debug then "" else "-v0") "-e" "import Shh") >>= \case Left _ -> do putStrLn "Please make the shh and shh-extras packages available in the shh" putStrLn "environment (install it globally or modify the wrapper, see docs)." putStrLn "Aborting" exitFailure Right _ -> writeFile "init.ghci" defaultInitGhci putStrLn " ... checking for shh-extras..." tryFailure (exe (pack wrapper) "ghc" (if debug then "" else "-v0") "-e" "import Shh.Prompt") >>= \case Left _ -> do putStrLn "## WARNING ##########################################################" putStrLn "# You do not have the shh-extras library installed, and so we are" putStrLn "# generating a reduced functionality init.ghci file. To restore full" putStrLn "# functionality, install shh-extras and re-generate your init.ghci" putStrLn $ "# file by deleting " <> shhDir <> "/init.ghci and re-running shh" putStrLn "#####################################################################" Right _ -> appendFile "init.ghci" extraInitGhci writeIfMissing "Shell.hs" defaultShell writeIfMissing "paths" "" pp <- readFile "paths" cp <- show <$> pathBins pathDiff <- evaluate $ force pp /= cp outdated <- catch (do shellMod <- getModificationTime "Shell.hs" hiMod <- getModificationTime "Shell.hi" pure (shellMod > hiMod) ) (\e -> if isDoesNotExistError e then pure True else throwIO e) when (outdated || pathDiff) $ do putStrLn "Rebuilding Shell.hs..." writeFile "paths" cp -- Use absolute path of Shell.hs so that GHCi doesn't recompile. exe (pack wrapper) "ghc" "-c" "-dynamic" (shhDir <> "/Shell.hs") (if debug then "" else "-v0") executeFile wrapper False (["ghci", "-ghci-script", shhDir <> "/init.ghci", shhDir <> "/Shell.hs"] <> if debug then [] else ["-v0"]) Nothing