1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
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
|