Let's take the previous example from HLint's .ghci file:
let cmdHpc _ = return $ unlines [":!ghc --make -isrc -i. src/Main.hs -w -fhpc -odir .hpc -hidir .hpc -threaded -o .hpc/hlint-test",":!del hlint-test.tix",":!.hpc\\hlint-test --help",":!.hpc\\hlint-test --test",":!.hpc\\hlint-test src --report=.hpc\\hlint-test-report.html +RTS -N3",":!.hpc\\hlint-test data --report=.hpc\\hlint-test-report.html +RTS -N3",":!hpc.exe markup hlint-test.tix --destdir=.hpc",":!hpc.exe report hlint-test.tix",":!del hlint-test.tix",":!start .hpc\\hpc_index_fun.html"]
:def hpc cmdHpc
It work's, but it's ugly. However, it can be rewritten as:
:{
:def hpc const $ return $ unlines
[":!ghc --make -isrc -i. src/Main.hs -w -fhpc -odir .hpc -hidir .hpc -threaded -o .hpc/hlint-test"
,":!del hlint-test.tix"
,":!.hpc\\hlint-test --help"
,":!.hpc\\hlint-test --test"
,":!.hpc\\hlint-test src --report=.hpc\\hlint-test-report.html +RTS -N3"
,":!.hpc\\hlint-test data --report=.hpc\\hlint-test-report.html +RTS -N3"
,":!hpc.exe markup hlint-test.tix --destdir=.hpc"
,":!hpc.exe report hlint-test.tix"
,":!del hlint-test.tix"
,":!start .hpc\\hpc_index_fun.html"]
:}
The :{ :} notation allows multi-line input in GHCi. GHCi also allows full expressions after a :def. Combined, we now have a much more readable .ghci file.
Yes, I agree - it's very much easier to read .ghcis using that syntax. Hlint integration now becomes this:
ReplyDelete:{
let redir varcmd = case break Data.Char.isSpace varcmd of
(var,_:cmd) -> return $ unlines [":set -fno-print-bind-result",
"tmp <- System.Directory.getTemporaryDirectory",
"(f,h) <- System.IO.openTempFile tmp \"ghci\"",
"sto <- GHC.Handle.hDuplicate System.IO.stdout",
"GHC.Handle.hDuplicateTo h System.IO.stdout",
"System.IO.hClose h",
cmd,
"GHC.Handle.hDuplicateTo sto System.IO.stdout",
"let readFileNow f = readFile f >>=
\\t->Data.List.length t `seq` return t",
var++" <- readFileNow f",
"System.Directory.removeFile f"]
_ -> return "putStrLn \"usage: :redir var \""
:def redir cmdHelp redir ":redir var \t-- execute , redirecting stdout to var"
:}
--- Integration with the hlint code style tool
:{
:def hlint const $ return $ unlines [":unset +t +s",
":set -w",
":redir hlintvar1 :show modules",
":cmd return (\":! hlint \" ++ (concat $ Data.List.intersperse \" \"
(map (fst . break (==',') . Data.List.drop 2 . snd .
break (== '(')) $ lines hlintvar1)))",
":set +t +s -Wall"]
:}
Much lengthier vertically, but now one can actually hope to read it.
I must confess that before I had literally no idea what the hlint integration did, it really is much nicer now.
ReplyDeleteYup.
ReplyDeleteThe :show is reasonably clear; we get output like:
'Main ( ../.xmonad/xmonad.hs, interpreted )'
Note that we *don't* get any of the modules we've loaded like Control.Monad, but just files.
Or, if the file we loaded requires another file, we get something like:
'Mueval.Context ( Mueval/Context.hs, interpreted )
Mueval.ArgsParse ( Mueval/ArgsParse.hs, interpreted )'
Hence, we unlines it, then we use 'break' and 'snd' to discard everything up to the '('. (In retrospect, I probably could've used 'dropWhile'.) The 'drop 2' removes the '(' and the space, then we do the same thing (likewise, 'takeWhile').
At this point, we'll have ["Mueval/Context.hs", "Mueval/ArgsParse.hs"]. the 'concat . intersperse' turns this back into 'Mueval/Context.hs Mueval/ArgsParse.hs' (why didn't I use unlines?), and now we can pass it straight to the '!' command, which will invoke the shell to run 'hlint' on this string.
':redir' is still magic, though. :)
"It work's"
ReplyDeleteTsck, shame on you !
http://www.apostrophe.co.uk
Rake is pretty good at doing these kind of thing, also Nemesis if insist to program in Haskell.
ReplyDeleteI had some fun creating a continuous testing command:
ReplyDelete:{
:def test const $ return $ unlines
[":r"
,":main"
]
:}
:{
:def testMany const $ return $ unlines $
[ ":test"
, ":!sleep 5"
, ":testMany"
]
:}
Arms with this I just leave a terminal running and I can detect failures in near real time. It would be nice to have the tests run only if the reload picked up something new, then the polling interval could be dropped.
Well... you can use my trick to get the original filename and then check the last-modified time. I'm not sure if you can store that state in ghci, but you could always store it as a $variable or in a . file.
ReplyDelete