
Written by Ian Lynagh <igloo@debian.org>.
Copyright (C) 2003, 2004 Ian Lynagh.
Released under the GNU GPL version 2.

\begin{code}
module Main (main) where

import System (getArgs, ExitCode(ExitSuccess, ExitFailure), exitWith, system)
import IO (hPutStrLn, stderr)
import Directory (doesFileExist, removeFile)
import Char (toUpper)
import List (partition)
import Monad (when)

type State = ([Compiler], [Trigger])
type Err = Bool

data Compiler = Compiler FilePath CType Version
    deriving (Show, Read)

data Trigger = Trigger TrigIdent When CType TrigStr
    deriving (Show, Read)

data CType = GHC | NHC | HUGS | Other
    deriving (Show, Read, Eq)

data When = OnAdd | OnRemove
    deriving (Show, Read, Eq)

type Version = String
type TrigIdent = String
type TrigStr = String

fail_args, fail_bad_ctype, fail_del_compiler, fail_del_trig, fail_parse :: Int
fail_args = 1
fail_bad_ctype = 2
fail_del_compiler = 3
fail_del_trig = 4
fail_parse = 5

main :: IO ()
main = do args <- getArgs
          let (err, args') = case args of
                                 "-e":xs -> (True, xs)
                                 _ -> (False, args)
          case args' of
              ["--help"] -> usage
              ["-h"]     -> usage
              ["--version"] -> show_version
              ["-V"]        -> show_version
              ["--add-compiler", path, ctype, version] ->
                  add_compiler err path ctype version
              ["--remove-compiler", path] ->
                  remove_compiler err path
              ["--add-trigger", ident, ctype, trigstr] ->
                  add_trigger err ident ctype trigstr
              ["--add-untrigger", ident, ctype, trigstr] ->
                  add_untrigger ident ctype trigstr
              ["--remove-triggers", ident] ->
                  remove_triggers err ident
              _ -> do hPutStrLn stderr $ "Invalid args: " ++ show args
                      usage
                      exitWith (ExitFailure fail_args)

compiler_file :: String
compiler_file = "@localstatedir@/haskell-utils/compilers"

usage :: IO ()
usage = do putStrLn "Usage: haskell-utils [ --help | -h | --version | -V ]"
           putStrLn "       haskell-utils [ -e ] --add-compiler /path/to/compiler TYPE VERSION"
           putStrLn "       haskell-utils [ -e ] --remove-compiler /path/to/compiler"
           putStrLn "       haskell-utils [ -e ] --add-trigger IDENT TYPE TRIGGER"
           putStrLn "       haskell-utils [ -e ] --add-untrigger IDENT TYPE TRIGGER"
           putStrLn "       haskell-utils [ -e ] --remove-triggers IDENT"
           putStrLn "   TYPE is GHC | NHC | HUGS | Other."
           putStrLn "   IDENT is a string uniquely identifying the trigger owner."
           putStrLn "   TRIGGER is the command to be run when the trigger happens."
           putStrLn "           %% is replaced with % and %p with the path to the compiler."
           putStrLn "   If -e is given then haskell-utils will fail if anything external does."
           putStrLn ""
           putStrLn "haskell-utils allows compilers and tools that like to know about compilers to"
           putStrLn "register themselves so the latter can be informed about the addition and"
           putStrLn "removal of the former."
           putStrLn ""

show_version :: IO ()
show_version = do putStrLn "haskell-utils @version@"
                  putStrLn "Written by Ian Lynagh."
                  putStrLn "Copyright (C) 2003, 2004 Ian Lynagh."

get_current :: IO State
get_current =
 do exists <- doesFileExist compiler_file
    if exists
     then do contents <- readFile compiler_file
             case reads contents of
                 [(cs, "")] -> return cs
                 _ -> do hPutStrLn stderr ("Failed to parse " ++ compiler_file)
                         exitWith (ExitFailure fail_parse)
     else return ([], [])

put_new :: State -> IO ()
put_new ([], []) = removeFile compiler_file
put_new cs = writeFile compiler_file $ show cs

del_compiler :: FilePath -> State -> Maybe ([Compiler], State)
del_compiler p (cs, ts) = case partition is_at_p cs of
                              ([], _) -> Nothing
                              (cs_removed, cs') -> Just (cs_removed, (cs', ts))
    where is_at_p (Compiler q _ _) = p == q

del_triggers :: TrigIdent -> State -> Maybe State
del_triggers ti (cs, ts) = case partition is_a_ti ts of
                              ([], _) -> Nothing
                              (_, ts') -> Just (cs, ts')
    where is_a_ti (Trigger ti' _ _ _) = ti == ti'

add_compiler :: Err -> FilePath -> String -> Version -> IO ()
add_compiler err path ctype version
    = case lookup (map toUpper ctype) ctypes of
          Just ct -> do let c = Compiler path ct version
                        cur <- get_current
                        case del_compiler path cur of
                            Nothing -> put_new (push_compiler c cur)
                            Just (cs, cur') ->
                                do putStrLn "Overwriting old entry"
                                   mapM_ (trigger err cur OnRemove) cs
                                   put_new (push_compiler c cur')
                        trigger err cur OnAdd c
          Nothing -> do hPutStrLn stderr ("Bad TYPE " ++ show ctype)
                        usage
                        exitWith (ExitFailure fail_bad_ctype)

remove_compiler :: Err -> FilePath -> IO ()
remove_compiler err path
    = do cur <- get_current
         case del_compiler path cur of
             Nothing -> do hPutStrLn stderr ("Can't find " ++ path)
                           when err $ exitWith (ExitFailure fail_del_compiler)
             Just (cs, cur') -> do mapM_ (trigger err cur OnRemove) cs
                                   put_new cur'

add_trigger :: Err -> TrigIdent -> String -> TrigStr -> IO ()
add_trigger err ident ctype trigstr
    = case lookup (map toUpper ctype) ctypes of
          Just ct -> do let t = Trigger ident OnAdd ct trigstr
                        cur <- get_current
                        mapM_ (do_trigger err trigstr)
                              [ fp | Compiler fp c_ct _ <- get_compilers cur,
                                     c_ct == ct ]
                        put_new (push_trigger t cur)
          Nothing -> do hPutStrLn stderr ("Bad TYPE " ++ show ctype)
                        usage
                        exitWith (ExitFailure fail_bad_ctype)

add_untrigger :: TrigIdent -> String -> TrigStr -> IO ()
add_untrigger ident ctype trigstr
    = case lookup (map toUpper ctype) ctypes of
          Just ct -> do let t = Trigger ident OnRemove ct trigstr
                        cur <- get_current
                        put_new (push_trigger t cur)
          Nothing -> do hPutStrLn stderr ("Bad TYPE " ++ show ctype)
                        usage
                        exitWith (ExitFailure fail_bad_ctype)

remove_triggers :: Err -> TrigIdent -> IO ()
remove_triggers err ti
    = do cur <- get_current
         case del_triggers ti cur of
             Nothing -> do hPutStrLn stderr ("Can't find " ++ ti)
                           when err $ exitWith (ExitFailure fail_del_trig)
             Just cur' -> put_new cur'

trigger :: Err -> State -> When -> Compiler -> IO ()
trigger err (_, ts) trig_when c = mapM_ (trig c) ts
    where trig (Compiler p ct1 _) (Trigger _ w ct2 trigstr)
           | ct1 == ct2 && w == trig_when = do_trigger err trigstr p
          trig _ _ = return ()

do_trigger :: Err -> TrigStr -> FilePath -> IO ()
do_trigger err ts p
    = do r <- system (subst ts)
         case r of
             ExitSuccess -> return ()
             f -> do hPutStrLn stderr ("Trigger failed: " ++ show (ts, p))
                     when err $ exitWith f
    where subst "" = ""
          subst ('%':'%':xs) = '%':subst xs
          subst ('%':'p':xs) = p ++ subst xs
          subst (x:xs) = x:subst xs

get_compilers :: State -> [Compiler]
get_compilers (cs, _) = cs

push_compiler :: Compiler -> State -> State
push_compiler c (cs, ts) = (c:cs, ts)

push_trigger :: Trigger -> State -> State
push_trigger t (cs, ts) = (cs, t:ts)

ctypes :: [(String, CType)]
ctypes = [("GHC", GHC), ("NHC", NHC), ("HUGS", HUGS), ("OTHER", Other)]
\end{code}

