lhc-0.6.20081127: src/Util/Interact.hs
module Util.Interact(
Interact(..),
InteractCommand(..),
beginInteraction,
runInteraction,
runInteractions,
emptyInteract
) where
import IO
import Char
import List
import qualified Data.Map as Map
import Control.Monad.Identity
import Control.Monad.Trans
import System
import System.Console.Haskeline
import System.Directory
import GenUtil
commands = [
(":quit","quit interactive session"),
(":version","print out version number"),
(":cd", "change directory to argument"),
(":pwd", "show current directory"),
(":set", "set options"),
(":unset", "unset options"),
(":execfile", "run sequence of commands from a file"),
-- (":execfile!", "run sequence of commands from a file if it exists"),
(":echo", "echo argument to screen"),
(":command", "enter command mode"),
(":normal", "enter normal mode"),
(":help", "print help table")
]
extra_help = [
("!command", "run shell command")
]
basicParse :: Maybe String -> String -> Either (String,String) String
basicParse comm s = f (cleanupWhitespace s) where
f xs | Just c <- comm, c `isPrefixOf` xs = Right ""
f (':':rs) = Left (':':dropWhile (== ':') (map toLower as),dropWhile isSpace rest) where
(as,rest) = span isAlpha rs
f s = Right s
data InteractCommand = InteractCommand {
commandName :: String,
commandHelp :: String,
commandAction :: Interact -> String -> String -> IO Interact
}
data Interact = Interact {
interactPrompt :: String, -- ^ the prompt to use
interactCommands :: [InteractCommand], -- ^ a list of commands
interactSettables :: [String], -- ^ possible things that may be set
interactVersion :: String, -- ^ version string to print
interactSet :: Map.Map String String, -- ^ vars that are actually set
interactExpr :: Interact -> String -> IO Interact, -- ^ what to run on a bare expression
interactRC :: [String], -- ^ commands to run at startup
interactWords :: [String], -- ^ list of words to autocomplete
interactEcho :: Bool, -- ^ whether to echo commands
interactCommandMode :: Bool, -- ^ whether we are in command mode
interactComment :: Maybe String -- ^ comment initializer
}
emptyInteract = Interact {
interactPrompt = "> ",
interactCommands = [],
interactSettables = [],
interactVersion = "(none)",
interactSet = Map.empty,
interactExpr = \i s -> putStrLn ("Unknown Command: " ++ s) >> return i,
interactRC = [],
interactWords = [],
interactEcho = False,
interactCommandMode = False,
interactComment = Nothing
}
cleanupWhitespace s = reverse $ dropWhile isSpace (reverse $ dropWhile isSpace s)
runInteractions :: Interact -> [String] -> IO Interact
runInteractions act [] = return act
runInteractions act (x:xs) = do
act' <- runInteraction act x
runInteractions act' xs
thePrompt Interact { interactCommandMode = False, interactPrompt = p } = p
thePrompt Interact { interactCommandMode = True } = ":"
-- | run a command as if typed at prompt
runInteraction :: Interact -> String -> IO Interact
runInteraction act s = do
act <- runInteractions act { interactRC = [] } (interactRC act)
let commands' = commands ++ [ (n,h) | InteractCommand { commandName = n, commandHelp = h } <- interactCommands act ]
help_text = unlines $ buildTableLL (commands' ++ extra_help)
let args s = [ bb | bb@(n,_) <- commands', s `isPrefixOf` n ]
expand s = fsts (args s) ++ filter (isPrefixOf s) (interactSettables act)
let showSet
| null $ interactSettables act = putStrLn "Nothing may be set"
| otherwise = do
let set = [ " " ++ if null b then a else a ++ "=" ++ b | (a,b) <- Map.toList $ interactSet act]
setable = [ " " ++ a | a <- sort $ interactSettables act, not $ a `Map.member` interactSet act]
when (not $ null set) $ putStrLn "Set options:" >> putStr (unlines set)
when (not $ null setable) $ putStrLn "Setable options:" >> putStr (unlines setable)
case basicParse (interactComment act) (if interactCommandMode act then ':':s else s) of
Right "" -> return act
Right ('!':rest) -> System.system rest >> return act
Right s -> do
when (interactEcho act) $ putStrLn $ (interactPrompt act) ++ s
act' <- interactExpr act act s
return act'
Left (cmd,arg) -> case fsts $ args cmd of
[":quit"] -> putStrLn "Bye!" >> exitSuccess
[":help"] -> putStrLn help_text >> return act
[":version"] -> putStrLn (interactVersion act) >> return act
[":echo"] -> putStrLn arg >> return act
[":cd"] -> IO.catch (setCurrentDirectory arg) (\_ -> putStrLn $ "Could not change to directory: " ++ arg) >> return act
[":pwd"] -> (IO.catch getCurrentDirectory (\_ -> putStrLn "Could not get current directory." >> return "") >>= putStrLn) >> return act
[":set"] -> case simpleUnquote arg of
[] -> showSet >> return act
rs -> do
let ts = [ let (a,b) = span (/= '=') x in (cleanupWhitespace a,drop 1 b) | x <- rs ]
sequence_ [ putStrLn $ "Unknown option: " ++ a | (a,_) <- ts, a `notElem` interactSettables act]
return act { interactSet = Map.fromList [ x | x@(a,_) <- ts, a `elem` interactSettables act ] `Map.union` interactSet act }
[":unset"] -> return act { interactSet = interactSet act Map.\\ Map.fromList [ (cleanupWhitespace rs,"") | rs <- simpleUnquote arg] }
[":execfile"] -> do
fc <- IO.catch (readFile arg) (\_ -> putStrLn ("Could not read file: " ++ arg) >> return "")
act <- runInteractions act { interactEcho = True } (lines fc)
return act { interactEcho = False }
[":execfile!"] -> do
fc <- IO.catch (readFile arg) (\_ -> return "")
runInteractions act { interactEcho = True } (lines fc)
[":command"] -> return act { interactCommandMode = True }
[":normal"] -> return act {interactCommandMode = False }
[m] -> let [a] = [ a | InteractCommand { commandName = n, commandAction = a } <- interactCommands act, n == m] in do
act' <- a act m arg
return act'
(_:_:_) -> putStrLn "Ambiguous command, possibilites are:" >> putStr (unlines $ buildTableLL $ args cmd) >> return act
[] -> (putStrLn $ "Unknown command (use :help for help): " ++ cmd) >> return act
-- | begin interactive interaction
beginInteraction :: Interact -> IO ()
beginInteraction act = runInputT defaultSettings (loop act)
where loop act = do s <- getInputLine (thePrompt act)
case s of
Nothing -> outputStrLn "Bye!" >> liftIO exitSuccess
Just cs | all isSpace cs -> return ()
Just s -> liftIO (runInteraction act s) >>= loop