import qualified System.Environment as Env import qualified System.Console.GetOpt as Opt import qualified System.Exit as Ex import qualified System.Directory as Dir import qualified Control.Monad as Con import qualified Data.Maybe as Maybe import qualified Counter as Sen import qualified Candidate as Cand import qualified Election as Elt data Options = Options { isVerbose :: Bool , isVersion :: Bool , isHelp :: Bool , getCandFile :: Maybe FilePath , getPrefFile :: Maybe FilePath , getOutDir :: Maybe FilePath , getNumToElect :: Maybe Int , getState :: Maybe String } deriving Show defaultOptions = Options { isVerbose = False , isVersion = False , isHelp = False , getCandFile = Nothing , getPrefFile = Nothing , getOutDir = Nothing , getNumToElect = Nothing , getState = Nothing } readMaybe :: Read a => String -> Maybe a readMaybe s = case reads s of [(val, "")] -> Just val _ -> Nothing electOpt :: String -> (Options -> Options) electOpt str = let r = readMaybe str :: Maybe Int jr = if (Maybe.isJust r && Maybe.fromJust r > 0) then r else Nothing in (\opts -> opts { getNumToElect = jr }) stateOpt :: String -> (Options -> Options) stateOpt str = let validStates = ["NSW", "VIC", "TAS", "QLD", "SA", "WA", "NT", "ACT"] sr = if (str `elem` validStates) then Just str else Nothing in (\opts -> opts { getState = sr } ) optionHeader = "Usage: stv [OPTION...]" optionData :: [Opt.OptDescr (Options -> Options)] optionData = [ Opt.Option ['v'] ["verbose"] (Opt.NoArg (\opts -> opts { isVerbose = True}) ) "chatty output on stderr" , Opt.Option ['V'] ["version"] (Opt.NoArg (\opts -> opts { isVersion = True}) ) "show version number" , Opt.Option ['h'] ["help"] (Opt.NoArg (\opts -> opts { isHelp = True }) ) "show this help information" , Opt.Option ['c'] ["candidates"] (Opt.ReqArg (\c opts -> opts { getCandFile = Just c }) "FILE") "file containing AEC candidate data" , Opt.Option ['p'] ["preferences"] (Opt.ReqArg (\p opts -> opts { getPrefFile = Just p}) "FILE") "file containing AEC formal preferences" , Opt.Option ['o'] ["outdir"] (Opt.ReqArg (\d opts -> opts { getOutDir = Just d}) "DIR") "directory to output count logging" , Opt.Option ['e'] ["elect"] (Opt.ReqArg electOpt "NUM") "number of candidates to elect" , Opt.Option ['s'] ["state"] (Opt.ReqArg stateOpt "STATE") "state or territory the data corresponds to" ] getOpts :: [String] -> IO (Options, [String]) getOpts argv = case Opt.getOpt Opt.Permute optionData argv of (o,n, [] ) -> return (foldl (flip id) defaultOptions o, n) (_,_,errs) -> ioError (userError (concat errs ++ Opt.usageInfo optionHeader optionData)) main = do rawArgs <- Env.getArgs (options, arguments) <- getOpts rawArgs -- options that abort the main program Con.when (isHelp options) $ do putStrLn (Opt.usageInfo optionHeader optionData) Ex.exitFailure Con.when (isVersion options) $ do putStrLn "Australian STV Counter v0.1" Ex.exitFailure -- check that all necessary parameters are -- both present and valid let candidateFile = Maybe.fromJust (getCandFile options) Con.when (Maybe.isNothing (getCandFile options)) $ Ex.die "Candidate data file not provided" doesExist <- Dir.doesFileExist candidateFile Con.when (not doesExist) $ Ex.die "Candidate data file does not exist" let preferenceFile = Maybe.fromJust (getPrefFile options) Con.when (Maybe.isNothing (getPrefFile options)) $ Ex.die "Formal preference data file not provided" doesExist <- Dir.doesFileExist preferenceFile Con.when (not doesExist) $ Ex.die "Formal preference data file does not exist" let outputDir = Maybe.fromJust (getOutDir options) Con.when (Maybe.isNothing (getOutDir options)) $ Ex.die "Output logging directory not provided" doesExist <- Dir.doesDirectoryExist outputDir Con.when doesExist $ Ex.die "Output directory already exists" let numToElect = Maybe.fromJust (getNumToElect options) Con.when (Maybe.isNothing (getNumToElect options)) $ Ex.die "Invalid number of candidates to elect or number not provided" let state = Maybe.fromJust (getState options) Con.when (Maybe.isNothing (getState options)) $ Ex.die "Invalid state/territory or state/territory not provided" -- set up the election processing (aboveBallot, belowBallot) <- Cand.readCandidates candidateFile state counter <- Sen.createSenateCounter preferenceFile aboveBallot belowBallot Dir.createDirectory outputDir election <- Elt.createElection outputDir counter numToElect -- run the show Elt.doCount election