Commit e09132a0 authored by Andrew Gibiansky's avatar Andrew Gibiansky

added shelling out directive

parent 7d78c6a1
...@@ -48,6 +48,7 @@ data-files: ...@@ -48,6 +48,7 @@ data-files:
library library
hs-source-dirs: src hs-source-dirs: src
build-depends: base ==4.6.*, build-depends: base ==4.6.*,
process >= 1.1,
hlint, hlint,
cmdargs >= 0.10, cmdargs >= 0.10,
tar, tar,
...@@ -106,6 +107,7 @@ executable IHaskell ...@@ -106,6 +107,7 @@ executable IHaskell
-- Other library packages from which modules are imported. -- Other library packages from which modules are imported.
build-depends: base ==4.6.*, build-depends: base ==4.6.*,
process >= 1.1,
hlint, hlint,
cmdargs >= 0.10, cmdargs >= 0.10,
tar, tar,
...@@ -139,6 +141,7 @@ Test-Suite hspec ...@@ -139,6 +141,7 @@ Test-Suite hspec
Ghc-Options: -threaded Ghc-Options: -threaded
Main-Is: Hspec.hs Main-Is: Hspec.hs
build-depends: base ==4.6.*, build-depends: base ==4.6.*,
process >= 1.1,
hlint, hlint,
cmdargs >= 0.10, cmdargs >= 0.10,
tar, tar,
......
...@@ -25,6 +25,9 @@ import System.IO (hGetChar, hFlush) ...@@ -25,6 +25,9 @@ import System.IO (hGetChar, hFlush)
import System.Random (getStdGen, randomRs) import System.Random (getStdGen, randomRs)
import Unsafe.Coerce import Unsafe.Coerce
import Control.Monad (guard) import Control.Monad (guard)
import System.Process
import System.Exit
import Data.Maybe (fromJust)
import NameSet import NameSet
import Name import Name
...@@ -394,6 +397,73 @@ evalCommand _ (Directive SetOpt option) state = do ...@@ -394,6 +397,73 @@ evalCommand _ (Directive SetOpt option) state = do
setOpt _ _ = Nothing setOpt _ _ = Nothing
evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ liftIO $
case words cmd of
"cd":dirs ->
let directory = unwords dirs in do
setCurrentDirectory directory
return []
cmd -> do
(readEnd, writeEnd) <- createPipe
handle <- fdToHandle writeEnd
pipe <- fdToHandle readEnd
let initProcSpec = shell $ unwords cmd
procSpec = initProcSpec {
std_in = Inherit,
std_out = UseHandle handle,
std_err = UseHandle handle
}
(_, _, _, process) <- createProcess procSpec
-- Accumulate output from the process.
outputAccum <- liftIO $ newMVar ""
-- Start a loop to publish intermediate results.
let
-- Compute how long to wait between reading pieces of the output.
-- `threadDelay` takes an argument of microseconds.
ms = 1000
delay = 100 * ms
-- Maximum size of the output (after which we truncate).
maxSize = 100 * 1000
incSize = 200
output str = publish False [plain str]
loop = do
-- Wait and then check if the computation is done.
threadDelay delay
-- Read next chunk and append to accumulator.
nextChunk <- readChars pipe "\n" incSize
modifyMVar_ outputAccum (return . (++ nextChunk))
-- Check if we're done.
exitCode <- getProcessExitCode process
let computationDone = isJust exitCode
when computationDone $ do
nextChunk <- readChars pipe "" maxSize
modifyMVar_ outputAccum (return . (++ nextChunk))
if not computationDone
then do
-- Write to frontend and repeat.
readMVar outputAccum >>= output
loop
else do
out <- readMVar outputAccum
case fromJust exitCode of
ExitSuccess -> return [plain out]
ExitFailure code -> do
let errMsg = "Process exited with error code " ++ show code
htmlErr = printf "<span class='err-msg'>%s</span>" errMsg
return [plain $ out ++ "\n" ++ errMsg,
html $ printf "<span class='mono'>%s</span>" out ++ htmlErr]
loop
-- This is taken largely from GHCi's info section in InteractiveUI. -- This is taken largely from GHCi's info section in InteractiveUI.
evalCommand _ (Directive GetHelp _) state = do evalCommand _ (Directive GetHelp _) state = do
write "Help via :help or :?." write "Help via :help or :?."
...@@ -626,6 +696,29 @@ evalCommand _ (ParseError loc err) state = do ...@@ -626,6 +696,29 @@ evalCommand _ (ParseError loc err) state = do
evalState = state evalState = state
} }
-- Read from a file handle until we hit a delimiter or until we've read
-- as many characters as requested
readChars :: Handle -> String -> Int -> IO String
-- If we're done reading, return nothing.
readChars handle delims 0 = return []
readChars handle delims nchars = do
-- Try reading a single character. It will throw an exception if the
-- handle is already closed.
tryRead <- gtry $ hGetChar handle :: IO (Either SomeException Char)
case tryRead of
Right char ->
-- If this is a delimiter, stop reading.
if char `elem` delims
then return [char]
else do
next <- readChars handle delims (nchars - 1)
return $ char:next
-- An error occurs at the end of the stream, so just stop reading.
Left _ -> return []
doLoadModule :: String -> String -> Ghc [DisplayData] doLoadModule :: String -> String -> Ghc [DisplayData]
doLoadModule name modName = flip gcatch unload $ do doLoadModule name modName = flip gcatch unload $ do
-- Compile loaded modules. -- Compile loaded modules.
...@@ -730,29 +823,6 @@ capturedStatement output stmt = do ...@@ -730,29 +823,6 @@ capturedStatement output stmt = do
fd <- head <$> unsafeCoerce hValues fd <- head <$> unsafeCoerce hValues
fdToHandle fd fdToHandle fd
-- Read from a file handle until we hit a delimiter or until we've read
-- as many characters as requested
let
readChars :: Handle -> String -> Int -> IO String
-- If we're done reading, return nothing.
readChars handle delims 0 = return []
readChars handle delims nchars = do
-- Try reading a single character. It will throw an exception if the
-- handle is already closed.
tryRead <- gtry $ hGetChar handle :: IO (Either SomeException Char)
case tryRead of
Right char ->
-- If this is a delimiter, stop reading.
if char `elem` delims
then return [char]
else do
next <- readChars handle delims (nchars - 1)
return $ char:next
-- An error occurs at the end of the stream, so just stop reading.
Left _ -> return []
-- Keep track of whether execution has completed. -- Keep track of whether execution has completed.
completed <- liftIO $ newMVar False completed <- liftIO $ newMVar False
finishedReading <- liftIO newEmptyMVar finishedReading <- liftIO newEmptyMVar
......
...@@ -62,6 +62,7 @@ data DirectiveType ...@@ -62,6 +62,7 @@ data DirectiveType
| SetExtension -- ^ Enable or disable an extension via ':extension' (or prefixes) | SetExtension -- ^ Enable or disable an extension via ':extension' (or prefixes)
| LoadFile -- ^ Load a Haskell module. | LoadFile -- ^ Load a Haskell module.
| SetOpt -- ^ Set various options. | SetOpt -- ^ Set various options.
| ShellCmd -- ^ Execute a shell command.
| GetHelp -- ^ General help via ':?' or ':help'. | GetHelp -- ^ General help via ':?' or ':help'.
deriving (Show, Eq) deriving (Show, Eq)
...@@ -219,6 +220,8 @@ joinFunctions [] = [] ...@@ -219,6 +220,8 @@ joinFunctions [] = []
parseDirective :: String -- ^ Directive string. parseDirective :: String -- ^ Directive string.
-> Int -- ^ Line number at which the directive appears. -> Int -- ^ Line number at which the directive appears.
-> CodeBlock -- ^ Directive code block or a parse error. -> CodeBlock -- ^ Directive code block or a parse error.
parseDirective (':':'!':directive) line = Directive ShellCmd $ '!':directive
parseDirective (':':directive) line = case find rightDirective directives of parseDirective (':':directive) line = case find rightDirective directives of
Just (directiveType, _) -> Directive directiveType arg Just (directiveType, _) -> Directive directiveType arg
where arg = unwords restLine where arg = unwords restLine
...@@ -292,6 +295,9 @@ joinLines = intercalate "\n" ...@@ -292,6 +295,9 @@ joinLines = intercalate "\n"
dropComments :: String -> String dropComments :: String -> String
dropComments = removeOneLineComments . removeMultilineComments dropComments = removeOneLineComments . removeMultilineComments
where where
-- Don't remove comments after cmd directives
removeOneLineComments (':':'!':remaining) = ":!" ++ takeWhile (/= '\n') remaining ++
removeOneLineComments (dropWhile (/= '\n') remaining)
removeOneLineComments ('-':'-':remaining) = removeOneLineComments (dropWhile (/= '\n') remaining) removeOneLineComments ('-':'-':remaining) = removeOneLineComments (dropWhile (/= '\n') remaining)
removeOneLineComments (x:xs) = x:removeOneLineComments xs removeOneLineComments (x:xs) = x:removeOneLineComments xs
removeOneLineComments x = x removeOneLineComments x = x
......
...@@ -74,7 +74,6 @@ instance ToJSON Profile where ...@@ -74,7 +74,6 @@ instance ToJSON Profile where
data KernelState = KernelState data KernelState = KernelState
{ getExecutionCounter :: Int, { getExecutionCounter :: Int,
getLintStatus :: LintStatus, -- Whether to use hlint, and what arguments to pass it. getLintStatus :: LintStatus, -- Whether to use hlint, and what arguments to pass it.
getCwd :: String,
useSvg :: Bool, useSvg :: Bool,
useShowErrors :: Bool, useShowErrors :: Bool,
useShowTypes :: Bool useShowTypes :: Bool
...@@ -85,7 +84,6 @@ defaultKernelState :: KernelState ...@@ -85,7 +84,6 @@ defaultKernelState :: KernelState
defaultKernelState = KernelState defaultKernelState = KernelState
{ getExecutionCounter = 1, { getExecutionCounter = 1,
getLintStatus = LintOn, getLintStatus = LintOn,
getCwd = ".",
useSvg = True, useSvg = True,
useShowErrors = False, useShowErrors = False,
useShowTypes = False useShowTypes = False
......
...@@ -223,8 +223,6 @@ runKernel profileSrc initInfo = do ...@@ -223,8 +223,6 @@ runKernel profileSrc initInfo = do
-- Create initial state in the directory the kernel *should* be in. -- Create initial state in the directory the kernel *should* be in.
state <- initialKernelState state <- initialKernelState
modifyMVar_ state $ \initState ->
return initState { getCwd = initDir initInfo }
-- Receive and reply to all messages on the shell socket. -- Receive and reply to all messages on the shell socket.
interpret $ do interpret $ do
......
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