packages feed

YFrob-0.4: afp-demos/ITest.hs

--
-- ITest.hs -- An interactive command-line interpreter for running
-- a series of interactive tests.

module ITest where

import System.IO (hFlush, stdout)
import Data.List (sortBy, isPrefixOf)

-- type synonym for a test-case that is a simple IO action:
type IOTest=(String,IO ())

-- utility function:
fst3 (x,_,_) = x

-- testShell is the top-level command interpreter.
-- arguments:
--   tests -- a list of simple IOTests
--   prompt -- the prompt to display to the user
--   args -- command-line arguments
testShell :: [IOTest] -> String -> [String] -> IO ()
testShell tests prompt args =
  if ((length args)==0)
     then putStrLn "\":help\" for help." >> iShell tests prompt
     else loop args
  where loop [] = return ()
	loop (tname:ts) = do done <- runTest tests tname
			     if done then return () else loop ts

-- interactive shell:
iShell :: [IOTest] -> String -> IO ()
iShell tests prompt =
  do putStr prompt
     hFlush stdout
     line <- getLine
     done <- case words line of
	       ((':':cmd):args) -> handleBuiltin tests cmd args
	       (tname:args) -> runTest tests tname
	       _ -> return False
     if done 
	then return () 
	else iShell tests prompt

-- runSafe:  run a specific IO action, and catch any exceptions:
runSafe :: IO () -> IO ()
runSafe act =
  catch act (\e -> do putStrLn ("*** Exception running test: ")
	              putStrLn (show e)
                      putStrLn "***")

-- run a specific simple IO test
runTest :: [IOTest] -> String -> IO Bool
runTest tests tname =
  let ts = filter ((== tname) . fst) tests
  in do case ts of
          ((nm,tf):[]) -> do putStrLn ("executing test '" ++ nm++ "'")
			     runSafe tf
			     putStrLn ("(test complete)")
	  _ -> do putStrLn ("unknown or ambiguous test '" ++ tname ++ "'")
		  putStrLn ("Use ':list' to list available tests.")
        return False

builtins :: [(String,String,[IOTest] -> [String] -> IO Bool)]
builtins = [("quit","exit the test shell",(\_ _ -> return True))
	    , ("help","display this list of commands", binHelp)
	    , ("list","list available test cases", binList)
	   ]

-- help builtin:
binHelp :: [IOTest] -> [String] -> IO Bool
binHelp tests args =
  do putStrLn ("Command summary:")
     mapM_ putStrLn hlist
     return False
  where hlist = map aux (sortBy ccomp builtins)
	aux (cmd,desc,_) = ("  :" ++ cmd ++ " -- " ++ desc)
	ccomp (cmd1,_,_) (cmd2,_,_) = compare cmd1 cmd2

-- list builtin:
binList :: [IOTest] -> [String] -> IO Bool
binList tests args =
  do putStrLn ("Available Tests:")
     mapM_ putStrLn tnms
     return False
  where tnms = map aux (sortBy tcomp tests)
	aux (tnm,_) = "  " ++ tnm
	tcomp (tnm1,_) (tnm2,_) = compare tnm1 tnm2

-- process a built-in command
handleBuiltin :: [IOTest] -> String -> [String] -> IO Bool
handleBuiltin tstate cmd args = 
  let bins = filter (isPrefixOf cmd . fst3) builtins
  in case bins of
       ((_,_,cmdf):[]) -> cmdf tstate args
       _ -> binError cmd

binError :: String -> IO Bool
binError cmd =
  do putStrLn ("Unknown or ambiguous command ':" ++ cmd ++ "'")
     putStrLn ("Use ':help' for help.")
     return False