From cedc60f86a22eddef26d8db4cb3ec36d50a7fd75 Mon Sep 17 00:00:00 2001 From: Jed Barber Date: Sat, 14 Jan 2017 00:57:12 +1100 Subject: Basic command line interface done --- src/Election.hs | 31 ++++++++ src/main.hs | 223 ++++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 193 insertions(+), 61 deletions(-) create mode 100644 src/Election.hs diff --git a/src/Election.hs b/src/Election.hs new file mode 100644 index 0000000..082ee4c --- /dev/null +++ b/src/Election.hs @@ -0,0 +1,31 @@ +module Election( + Election, + + createElection, + doCount + ) where + + + + +import qualified Counter as Sen + + + + +data Election = Election String + + + + +createElection :: FilePath -> Sen.SenateCounter -> IO Election +createElection outDir counter = return (Election "testcode") + + + + +doCount :: Election -> Int -> IO () +doCount election numToElect = + putStrLn "run election here" + + diff --git a/src/main.hs b/src/main.hs index 9effc4a..ef7589e 100644 --- a/src/main.hs +++ b/src/main.hs @@ -1,75 +1,176 @@ import qualified System.Environment as Env -import qualified Counter as Sen -import qualified Candidate as Typ +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)) --- this is all messy test code - - - - --- maps for NT Senate data --- will be removed when candidate info parsing complete -above = [[1,2],[3,4],[5,6],[7,8],[9,10],[11,12],[13,14]] -below = [ "Pile, Jan" - , "Gimini, Jimmy" - , "Kavasilas, Andrew" - , "Jones, Timothy" - , "Campbell, Trudy" - , "Barry, Ian" - , "Connard, Michael" - , "Bannister, Kathy" - , "Scullion, Nigel" - , "Lillis, Jenni" - , "McCarthy, Malarndirri" - , "Honan, Pat" - , "Ordish, Carol" - , "Ordish, John" - , "Lee, TS" - , "Marshall, Tristan" - , "Ryan, Maurie Japarta" - , "MacDonald, Marney" - , "Strettles, Greg" ] - - -above2 = [[1,2],[3,4],[5,6],[7,8],[9,10],[11,12],[13,14],[15,16],[17,18],[19,20]] -below2 = [ "Donnelly, Matt" - , "Hennings, Cawley" - , "Edwards, David" - , "Mihaljevic, Denis" - , "Gallagher, Katy" - , "Smith, David" - , "O'Connor, Sandie" - , "Wyatt, Jess" - , "Haydon, John" - , "Tye, Martin" - , "Seselja, Zed" - , "Hiatt, Jane" - , "Field, Deborah" - , "Montagne, Jessica" - , "Hobbs, Christina" - , "Wareham, Sue" - , "Kim, David William" - , "Tadros, Elizabeth" - , "Bailey, Steven" - , "Swan, Robbie" - , "Hay, Michael Gerard" - , "Hanson, Anthony" ] main = do - args <- Env.getArgs - counter <- Sen.createSenateCounter (head args) above2 below2 - let testTraces = (map (:[]) (zip [1,1..] below2)) - results <- mapM (Sen.doCount counter) testTraces - let func (n,c) = putStrLn (c ++ " " ++ (show n)) - output = map func (zip results below2) - sequence_ output + 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 + + + -- run the show + Elt.doCount election numToElect + Ex.exitSuccess -- cgit