Skip to content

Commit 852c6eb

Browse files
authored
Change to Reader Config IO (#53)
1 parent 758922f commit 852c6eb

File tree

8 files changed

+207
-183
lines changed

8 files changed

+207
-183
lines changed

app/Main.hs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module Main where
22

3-
import Data.Map (empty)
4-
import Data.Tuple.Extra (uncurry3)
3+
import Control.Monad.Reader
4+
import Data.Map (empty)
5+
import Data.Tuple.Extra (uncurry3)
56
import System.Environment
67
import System.IO
78

@@ -27,20 +28,20 @@ main = do
2728
Left NoStackPackageDbError -> putStrLn "Couldn't find an appropriate stack package DB!"
2829
Right paths -> do
2930
let config = uncurry3 ProgramConfig paths mainProjectExercisesDir stdin stdout stderr empty
30-
runCommand config (tail args) (head args)
31+
runReaderT (runCommand (tail args) (head args)) config
3132

32-
runCommand :: ProgramConfig -> [String] -> String -> IO ()
33-
runCommand config restArgs command = case command of
33+
runCommand :: [String] -> String -> ReaderT ProgramConfig IO ()
34+
runCommand restArgs command = case command of
3435
"run" -> if null restArgs
35-
then progPutStrLn config "Run command requires an exercise name!"
36-
else runExercise config (head restArgs)
37-
"watch" -> watchExercises config
36+
then progPutStrLn "Run command requires an exercise name!"
37+
else runExercise (head restArgs)
38+
"watch" -> watchExercises
3839
"exec" -> if null restArgs
39-
then progPutStrLn config "Exec command requires an exercise name!"
40-
else execExercise config (head restArgs)
41-
"version" -> putStrLn haskellingsVersion
42-
"list" -> listExercises config
40+
then progPutStrLn "Exec command requires an exercise name!"
41+
else execExercise (head restArgs)
42+
"version" -> progPutStrLn haskellingsVersion
43+
"list" -> listExercises
4344
"hint" -> if null restArgs
44-
then progPutStrLn config "Hint command requires an exercise name!"
45-
else hintExercise config (head restArgs)
46-
_ -> runHelp
45+
then progPutStrLn "Hint command requires an exercise name!"
46+
else hintExercise (head restArgs)
47+
_ -> lift runHelp

haskellings.cabal

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ library
4444
, extra
4545
, filepath
4646
, fsnotify
47+
, mtl
4748
, tasty
4849
, tasty-hunit
4950
, process
@@ -65,6 +66,7 @@ executable haskellings
6566
, containers
6667
, extra
6768
, haskellings
69+
, mtl
6870
default-language: Haskell2010
6971

7072
test-suite haskellings-tests
@@ -81,6 +83,7 @@ test-suite haskellings-tests
8183
, haskellings
8284
, hspec
8385
, HUnit
86+
, mtl
8487
, time
8588
default-language: Haskell2010
8689

src/DirectoryUtils.hs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
{- Utility functions for manipulating filepaths and directories.
2-
-}
1+
{- Utility functions for manipulating filepaths and directories. -}
2+
3+
{-# LANGUAGE FlexibleContexts #-}
4+
35
module DirectoryUtils where
46

57
import Control.Concurrent
6-
import Control.Exception (catch)
8+
import Control.Exception (catch)
9+
import Control.Monad.IO.Class
10+
import Control.Monad.Reader.Class
711
import Data.Char
8-
import Data.List (isSuffixOf)
9-
import Data.List.Extra (upper)
10-
import qualified Data.Map as M
11-
import qualified Data.Sequence as S
12+
import Data.List (isSuffixOf)
13+
import Data.List.Extra (upper)
14+
import qualified Data.Map as M
15+
import qualified Data.Sequence as S
1216
import System.Directory
13-
import System.FilePath (takeBaseName, takeFileName, (</>))
14-
import System.Info (os)
17+
import System.FilePath (takeBaseName, takeFileName, (</>))
18+
import System.Info (os)
1519

1620
import Types
1721

@@ -40,22 +44,24 @@ fullExerciseFp :: FilePath -> FilePath -> ExerciseInfo -> FilePath
4044
fullExerciseFp projectRoot exercisesExt (ExerciseInfo exName exDir _ _) =
4145
projectRoot </> exercisesExt </> exDir </> haskellFileName exName
4246

43-
withFileLock :: FilePath -> ProgramConfig -> IO a -> IO a
44-
withFileLock fp config action = case M.lookup fp (fileLocks config) of
45-
Nothing -> action
46-
Just lock -> do
47-
putMVar lock ()
48-
result <- action
49-
takeMVar lock
50-
return result
47+
withFileLock :: (MonadIO m, MonadReader ProgramConfig m) => FilePath -> m a -> m a
48+
withFileLock fp action = do
49+
maybeLock <- M.lookup fp <$> asks fileLocks
50+
case maybeLock of
51+
Nothing -> action
52+
Just lock -> do
53+
liftIO $ putMVar lock ()
54+
result <- action
55+
liftIO $ takeMVar lock
56+
return result
5157

5258
-- Create a directory. Run the action depending on that directory,
5359
-- and then clean the directory up.
54-
withDirectory :: FilePath -> IO a -> IO a
60+
withDirectory :: (MonadIO m) => FilePath -> m a -> m a
5561
withDirectory dirPath action = do
56-
createDirectoryIfMissing True dirPath
62+
liftIO $ createDirectoryIfMissing True dirPath
5763
res <- action
58-
removeDirectoryRecursive dirPath
64+
liftIO $ removeDirectoryRecursive dirPath
5965
return res
6066

6167
returnIfDirExists :: FilePath -> IO (Maybe FilePath)

src/Processor.hs

Lines changed: 57 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,40 @@
55
-}
66
module Processor where
77

8-
import Control.Monad (forM_, void, when)
9-
import Data.Maybe (fromJust, isJust)
8+
import Control.Monad.Reader
9+
import Data.Maybe (fromJust, isJust)
1010
import System.Exit
11-
import System.FilePath ((</>))
11+
import System.FilePath ((</>))
1212
import System.IO
1313
import System.Process
1414

1515
import DirectoryUtils
1616
import TerminalUtils
1717
import Types
1818

19-
executeExercise :: ProgramConfig -> ExerciseInfo -> IO ()
20-
executeExercise config exInfo@(ExerciseInfo exerciseName _ _ _) = do
21-
let (processSpec, genDirPath, genExecutablePath) = createExerciseProcess config exInfo
22-
let exFilename = haskellFileName exerciseName
19+
executeExercise :: ExerciseInfo -> ReaderT ProgramConfig IO ()
20+
executeExercise exInfo@(ExerciseInfo exerciseName _ _ _) = do
21+
config <- ask
22+
let (processSpec, genDirPath, genExecutablePath, exFilename) = createExerciseProcess config exInfo
2323
withDirectory genDirPath $ do
24-
(_, _, procStdErr, procHandle) <- createProcess (processSpec { std_out = CreatePipe, std_err = CreatePipe })
25-
exitCode <- waitForProcess procHandle
24+
(_, _, procStdErr, procHandle) <- lift $ createProcess (processSpec { std_out = CreatePipe, std_err = CreatePipe })
25+
exitCode <- lift $ waitForProcess procHandle
2626
case exitCode of
27-
ExitFailure code -> void $ onCompileFailure config exFilename procStdErr
27+
ExitFailure code -> void $ onCompileFailure exFilename procStdErr
2828
ExitSuccess -> do
29-
progPutStrLnSuccess config $ "Successfully compiled: " ++ exFilename
30-
progPutStrLn config $ "----- Executing file: " ++ exFilename ++ " -----"
29+
progPutStrLnSuccess $ "Successfully compiled: " ++ exFilename
30+
progPutStrLn $ "----- Executing file: " ++ exFilename ++ " -----"
3131
let execSpec = shell genExecutablePath
32-
(_, _, _, execProcHandle) <- createProcess execSpec
33-
void $ waitForProcess execProcHandle
32+
(_, _, _, execProcHandle) <- lift $ createProcess execSpec
33+
void $ lift $ waitForProcess execProcHandle
3434

3535
-- Produces 3 Elements for running our exercise:
3636
-- 1. The 'CreateProcess' that we can run for the compilation.
3737
-- 2. The directory path for the generated files
3838
-- 3. The path of the executable we would run (assuming the exercise is executable).
39-
createExerciseProcess :: ProgramConfig -> ExerciseInfo -> (CreateProcess, FilePath, FilePath)
39+
createExerciseProcess :: ProgramConfig -> ExerciseInfo -> (CreateProcess, FilePath, FilePath, FilePath)
4040
createExerciseProcess config (ExerciseInfo exerciseName exDirectory exType _) =
41-
(processSpec, genDirPath, genExecutablePath)
41+
(processSpec, genDirPath, genExecutablePath, haskellFileName exerciseName)
4242
where
4343
exIsRunnable = exType /= CompileOnly
4444
exFilename = haskellFileName exerciseName
@@ -51,88 +51,87 @@ createExerciseProcess config (ExerciseInfo exerciseName exDirectory exType _) =
5151
finalArgs = execArgs ++ ["-package-db", packageDb config]
5252
processSpec = proc (ghcPath config) finalArgs
5353

54-
onCompileFailure :: ProgramConfig -> String -> Maybe Handle -> IO RunResult
55-
onCompileFailure config exFilename errHandle = withTerminalFailure $ do
56-
progPutStrLn config $ "Couldn't compile : " ++ exFilename
54+
onCompileFailure :: String -> Maybe Handle -> ReaderT ProgramConfig IO RunResult
55+
onCompileFailure exFilename errHandle = withTerminalFailure $ do
56+
progPutStrLn $ "Couldn't compile : " ++ exFilename
5757
case errHandle of
5858
Nothing -> return ()
59-
Just h -> hGetContents h >>= progPutStrLn config
59+
Just h -> lift (hGetContents h) >>= progPutStrLn
6060
return CompileError
6161

62-
runUnitTestExercise :: ProgramConfig -> FilePath -> String -> IO RunResult
63-
runUnitTestExercise config genExecutablePath exFilename = do
62+
runUnitTestExercise :: FilePath -> String -> ReaderT ProgramConfig IO RunResult
63+
runUnitTestExercise genExecutablePath exFilename = do
6464
let execSpec = shell genExecutablePath
65-
(_, execStdOut, execStdErr, execProcHandle) <- createProcess (execSpec { std_out = CreatePipe, std_err = CreatePipe })
66-
execExit <- waitForProcess execProcHandle
65+
(_, execStdOut, execStdErr, execProcHandle) <- lift $ createProcess (execSpec { std_out = CreatePipe, std_err = CreatePipe })
66+
execExit <- lift $ waitForProcess execProcHandle
6767
case execExit of
6868
ExitFailure code -> withTerminalFailure $ do
69-
progPutStrLn config $ "Tests failed on exercise : " ++ exFilename
69+
progPutStrLn $ "Tests failed on exercise : " ++ exFilename
7070
case execStdErr of
7171
Nothing -> return ()
72-
Just h -> hGetContents h >>= progPutStrLn config
72+
Just h -> lift (hGetContents h) >>= progPutStrLn
7373
case execStdOut of
7474
Nothing -> return ()
75-
Just h -> hGetContents h >>= progPutStrLn config
75+
Just h -> lift (hGetContents h) >>= progPutStrLn
7676
return TestFailed
7777
ExitSuccess -> do
78-
progPutStrLnSuccess config $ "Successfully ran : " ++ exFilename
78+
progPutStrLnSuccess $ "Successfully ran : " ++ exFilename
7979
return RunSuccess
8080

8181
runExecutableExercise
82-
:: ProgramConfig
83-
-> FilePath
82+
:: FilePath
8483
-> String
8584
-> [String]
8685
-> ([String] -> Bool)
87-
-> IO RunResult
88-
runExecutableExercise config genExecutablePath exFilename inputs outputPred = do
86+
-> ReaderT ProgramConfig IO RunResult
87+
runExecutableExercise genExecutablePath exFilename inputs outputPred = do
8988
let execSpec = shell genExecutablePath
90-
(execStdIn, execStdOut, execStdErr, execProcHandle) <- createProcess
89+
(execStdIn, execStdOut, execStdErr, execProcHandle) <- lift $ createProcess
9190
(execSpec { std_out = CreatePipe, std_err = CreatePipe, std_in = CreatePipe })
92-
when (isJust execStdIn) $ forM_ inputs $ \i -> hPutStrLn (fromJust execStdIn) i
93-
execExit <- waitForProcess execProcHandle
91+
when (isJust execStdIn) $ forM_ inputs $ \i -> lift $ hPutStrLn (fromJust execStdIn) i
92+
execExit <- lift $ waitForProcess execProcHandle
9493
case execExit of
9594
ExitFailure code -> withTerminalFailure $ do
96-
progPutStrLn config $ "Encountered error running exercise: " ++ exFilename
95+
progPutStrLn $ "Encountered error running exercise: " ++ exFilename
9796
case execStdOut of
9897
Nothing -> return ()
99-
Just h -> hGetContents h >>= progPutStrLn config
98+
Just h -> lift (hGetContents h) >>= progPutStrLn
10099
case execStdErr of
101100
Nothing -> return ()
102-
Just h -> hGetContents h >>= progPutStrLn config
103-
progPutStrLn config "Check the Sample Input and Sample Output in the file."
104-
progPutStrLn config $ "Then try running it for yourself with 'haskellings exec" ++ haskellModuleName exFilename ++ "'."
101+
Just h -> lift (hGetContents h) >>= progPutStrLn
102+
progPutStrLn "Check the Sample Input and Sample Output in the file."
103+
progPutStrLn $ "Then try running it for yourself with 'haskellings exec" ++ haskellModuleName exFilename ++ "'."
105104
return TestFailed
106105
ExitSuccess -> do
107106
passes <- case execStdOut of
108107
Nothing -> return (outputPred [])
109-
Just h -> (lines <$> hGetContents h) >>= (return . outputPred)
108+
Just h -> (lines <$> lift (hGetContents h)) >>= (return . outputPred)
110109
if passes
111110
then withTerminalSuccess $ do
112-
progPutStrLn config $ "Successfully ran : " ++ exFilename
113-
progPutStrLn config $ "You can run this code for yourself with 'haskellings exec " ++ haskellModuleName exFilename ++ "'."
111+
progPutStrLn $ "Successfully ran : " ++ exFilename
112+
progPutStrLn $ "You can run this code for yourself with 'haskellings exec " ++ haskellModuleName exFilename ++ "'."
114113
return RunSuccess
115114
else withTerminalFailure $ do
116-
progPutStrLn config $ "Unexpected output for exercise: " ++ exFilename
117-
progPutStrLn config "Check the Sample Input and Sample Output in the file."
118-
progPutStrLn config $ "Then try running it for yourself with 'haskellings exec " ++ haskellModuleName exFilename ++ "'."
115+
progPutStrLn $ "Unexpected output for exercise: " ++ exFilename
116+
progPutStrLn "Check the Sample Input and Sample Output in the file."
117+
progPutStrLn $ "Then try running it for yourself with 'haskellings exec " ++ haskellModuleName exFilename ++ "'."
119118
return TestFailed
120119

121-
compileAndRunExercise :: ProgramConfig -> ExerciseInfo -> IO RunResult
122-
compileAndRunExercise config exInfo@(ExerciseInfo exerciseName exDirectory exType _) = do
123-
let (processSpec, genDirPath, genExecutablePath) = createExerciseProcess config exInfo
124-
let exFilename = haskellFileName exerciseName
120+
compileAndRunExercise :: ExerciseInfo -> ReaderT ProgramConfig IO RunResult
121+
compileAndRunExercise exInfo@(ExerciseInfo exerciseName exDirectory exType _) = do
122+
config <- ask
123+
let (processSpec, genDirPath, genExecutablePath, exFilename) = createExerciseProcess config exInfo
125124
withDirectory genDirPath $ do
126-
(_, _, procStdErr, procHandle) <- createProcess (processSpec { std_out = CreatePipe, std_err = CreatePipe })
127-
exitCode <- waitForProcess procHandle
125+
(_, _, procStdErr, procHandle) <- lift $ createProcess (processSpec { std_out = CreatePipe, std_err = CreatePipe })
126+
exitCode <- lift $ waitForProcess procHandle
128127
case exitCode of
129-
ExitFailure code -> onCompileFailure config exFilename procStdErr
128+
ExitFailure code -> onCompileFailure exFilename procStdErr
130129
ExitSuccess -> do
131-
progPutStrLnSuccess config $ "Successfully compiled : " ++ exFilename
130+
progPutStrLnSuccess $ "Successfully compiled : " ++ exFilename
132131
case exType of
133132
CompileOnly -> return RunSuccess
134-
UnitTests -> runUnitTestExercise config genExecutablePath exFilename
135-
Executable inputs outputPred -> runExecutableExercise config genExecutablePath exFilename inputs outputPred
133+
UnitTests -> runUnitTestExercise genExecutablePath exFilename
134+
Executable inputs outputPred -> runExecutableExercise genExecutablePath exFilename inputs outputPred
136135

137-
compileAndRunExercise_ :: ProgramConfig -> ExerciseInfo -> IO ()
138-
compileAndRunExercise_ config ex = void $ compileAndRunExercise config ex
136+
compileAndRunExercise_ :: ExerciseInfo -> ReaderT ProgramConfig IO ()
137+
compileAndRunExercise_ ex = void $ compileAndRunExercise ex

0 commit comments

Comments
 (0)