-- This source is licensed under Creative Commons CC0 v1.0. -- To read the full text, see license.txt in the main directory of this repository -- or go to https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt -- For a human readable summary, go to https://creativecommons.org/publicdomain/zero/1.0/ 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 System.IO as IO import qualified Control.Monad as Con import qualified Data.Time.Clock as Time import qualified Data.Maybe as Maybe import qualified Counter as Sen import qualified Candidate as Cand import qualified Election as Elt import qualified Miscellaneous as Misc 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 } electOpt :: String -> (Options -> Options) electOpt str = let r = Misc.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...]\n\n" ++ "Note that the -c, -p, -o, -e, -s options are all\n" ++ "required for normal operation.\n" furtherHelp = "Please be sure to provide all required options to run the election counter.\n" ++ "For further information consult '--help'.\n" 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") ".csv file containing AEC candidate data" , Opt.Option ['p'] ["preferences"] (Opt.ReqArg (\p opts -> opts { getPrefFile = Just p}) "FILE") ".csv file containing AEC formal preferences" , Opt.Option ['o'] ["outdir"] (Opt.ReqArg (\d opts -> opts { getOutDir = Just d}) "DIR") "new directory to output count logging" , Opt.Option ['e'] ["elect"] (Opt.ReqArg electOpt "INT") "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.\n\n" ++ furtherHelp) doesExist <- Dir.doesFileExist candidateFile Con.when (not doesExist) $ Ex.die ("Candidate data file does not exist.\n\n" ++ furtherHelp) let preferenceFile = Maybe.fromJust (getPrefFile options) Con.when (Maybe.isNothing (getPrefFile options)) $ Ex.die ("Formal preference data file not provided.\n\n" ++ furtherHelp) doesExist <- Dir.doesFileExist preferenceFile Con.when (not doesExist) $ Ex.die ("Formal preference data file does not exist.\n\n" ++ furtherHelp) let outputDir = Maybe.fromJust (getOutDir options) Con.when (Maybe.isNothing (getOutDir options)) $ Ex.die ("Output logging directory not provided.\n\n" ++ furtherHelp) doesExist <- Dir.doesDirectoryExist outputDir Con.when doesExist $ Ex.die ("Output directory already exists.\n\n" ++ furtherHelp) let numToElect = Maybe.fromJust (getNumToElect options) Con.when (Maybe.isNothing (getNumToElect options)) $ Ex.die ("Invalid number of candidates to elect or number not provided.\n\n" ++ furtherHelp) let state = Maybe.fromJust (getState options) Con.when (Maybe.isNothing (getState options)) $ Ex.die ("Invalid state/territory or state/territory not provided.\n\n" ++ furtherHelp) -- set up logging Dir.createDirectory outputDir startTime <- Time.getCurrentTime let mainLog = outputDir ++ "/" ++ "log.txt" startmsg = "Started election count at " ++ show startTime ++ "\n" IO.appendFile mainLog startmsg Con.when (isVerbose options) $ IO.hPutStrLn IO.stderr startmsg -- set up the election processing Con.when (isVerbose options) $ IO.hPutStrLn IO.stderr "Reading candidate data..." (aboveBallot, belowBallot) <- Cand.readCandidates candidateFile state Con.when (isVerbose options) $ IO.hPutStrLn IO.stderr "Reading preference data..." counter <- Sen.createSenateCounter preferenceFile aboveBallot belowBallot Con.when (isVerbose options) $ IO.hPutStrLn IO.stderr "Done.\n" Con.when (isVerbose options) $ IO.hPutStrLn IO.stderr "Setting up election..." election <- Elt.createElection outputDir mainLog counter numToElect (isVerbose options) Con.when (isVerbose options) $ IO.hPutStrLn IO.stderr "Done.\n" -- run the show Con.when (isVerbose options) $ IO.hPutStrLn IO.stderr "Running...\n" Elt.doCount election Con.when (isVerbose options) $ IO.hPutStr IO.stderr "\n" -- finish up logging endTime <- Time.getCurrentTime let endmsg = "Finished election count at " ++ show endTime ++ "\n" elapsedmsg = show (Time.diffUTCTime endTime startTime) ++ " elapsed\n" IO.appendFile mainLog (endmsg ++ elapsedmsg) Con.when (isVerbose options) $ IO.hPutStrLn IO.stderr (endmsg ++ elapsedmsg)