with GNAT.Command_Line, GNAT.Strings, Ada.Command_Line, Ada.Text_IO, Ada.Directories, Ada.Strings.Unbounded, Simple_Time, CSV, Candidates.Containers, Preferences, Bundles.Containers, Election; use Ada.Text_IO; procedure STV is package ACom renames Ada.Command_Line; package GCom renames GNAT.Command_Line; package GStr renames GNAT.Strings; package File renames Ada.Directories; package SU renames Ada.Strings.Unbounded; use type File.File_Kind; use type SU.Unbounded_String; use type Simple_Time.Time; Config : GCom.Command_Line_Configuration; Further_Help : String := "Try ""stv --help"" for more information."; -- I'm not fond of accesses to string accesses. -- Would be nice if the GNAT.Strings package would use Unbounded_Strings instead. Verbose : aliased Boolean; Version : aliased Boolean; Help : aliased Boolean; Candidate_File : aliased GStr.String_Access; Preference_File : aliased GStr.String_Access; Output_Dir : aliased GStr.String_Access; Number_To_Elect : aliased Integer; State_String : aliased GStr.String_Access; State : Candidates.State_Name; Start_Time, Finish_Time : Simple_Time.Time; Main_Log, Log_Msg : SU.Unbounded_String; Log_File : File_Type; Candidate_Data : Candidates.Containers.Candidate_Vector; Above_Ballot : Candidates.Containers.Above_Line_Ballot; Below_Ballot : Candidates.Containers.Below_Line_Ballot; begin GCom.Define_Switch (Config => Config, Output => Verbose'Access, Switch => "-v", Long_Switch => "--verbose", Help => "chatty output on stderr"); GCom.Define_Switch (Config => Config, Output => Version'Access, Switch => "-V", Long_Switch => "--version", Help => "show version number"); -- Technically not required, but included so -- it will show up in the help output. GCom.Define_Switch (Config => Config, Output => Help'Access, Switch => "-h", Long_Switch => "--help", Help => "show this help information"); GCom.Define_Switch (Config => Config, Output => Candidate_File'Access, Switch => "-c:", Long_Switch => "--candidates=", Help => ".csv file containing AEC candidate data"); GCom.Define_Switch (Config => Config, Output => Preference_File'Access, Switch => "-p:", Long_Switch => "--preference=", Help => ".csv file containing AEC formal preferences"); GCom.Define_Switch (Config => Config, Output => Output_Dir'Access, Switch => "-o:", Long_Switch => "--outdir=", Help => "new directory to output count logging"); GCom.Define_Switch (Config => Config, Output => Number_To_Elect'Access, Switch => "-e:", Long_Switch => "--elect=", Help => "number of candidates to elect"); GCom.Define_Switch (Config => Config, Output => State_String'Access, Switch => "-s:", Long_Switch => "--state=", Help => "state or territory the data corresponds to"); GCom.Set_Usage (Config => Config, Usage => "[switches]", Help => "Note that the -c, -p, -o, -e, -s options are all" & ASCII.LF & "required for normal operation." & ASCII.LF); -- Parse options begin GCom.Getopt (Config); exception when GCom.Exit_From_Command_Line => ACom.Set_Exit_Status (ACom.Failure); return; when GCom.Invalid_Switch => ACom.Set_Exit_Status (ACom.Failure); return; end; -- Version display functionality if Version then Put_Line ("Australian STV Counter v0.2"); ACom.Set_Exit_Status (ACom.Failure); return; end if; -- Check candidate data option is valid if Candidate_File.all = "" then Put_Line ("Candidate data file not provided." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; if not File.Exists (Candidate_File.all) then Put_Line ("Candidate data file does not exist." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; if File.Kind (Candidate_File.all) /= File.Ordinary_File then Put_Line ("Candidate data file name appears to refer to a directory or special file." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; -- Check preference data option is valid if Preference_File.all = "" then Put_Line ("Preference data file not provided." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; if not File.Exists (Preference_File.all) then Put_Line ("Preference data file does not exist." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; if File.Kind (Preference_File.all) /= File.Ordinary_File then Put_Line ("Preference data file name appears to refer to a directory or special file." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; -- Check output directory option is valid if Output_Dir.all = "" then Put_Line ("Output logging directory not provided." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; if File.Exists (Output_Dir.all) then Put_Line ("Output logging directory already exists." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; -- Check number to elect option is valid if Number_To_Elect < 1 then Put_Line ("Number of candidates to be elected too low." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end if; -- Check state option is valid begin State := Candidates.State_Name'Value (State_String.all); exception when Constraint_Error => Put_Line ("Invalid State/Territory or State/Territory not provided." & ASCII.LF & Further_Help); ACom.Set_Exit_Status (ACom.Failure); return; end; -- Set up logging File.Create_Directory (Output_Dir.all); Start_Time := Simple_Time.Now; Main_Log := SU.To_Unbounded_String (Output_Dir.all & "/" & "log.txt"); Log_Msg := SU.To_Unbounded_String ("Started election count at " & Simple_Time.Image (Start_Time)); Create (Log_File, Append_File, SU.To_String (Main_Log)); Put_Line (Log_File, SU.To_String (Log_Msg)); Close (Log_File); if Verbose then Put_Line (Standard_Error, SU.To_String (Log_Msg)); end if; -- Read in candidate data, which is necessary for further setup if Verbose then Put_Line (Standard_Error, "Reading candidate data..."); end if; Candidates.Containers.Read_Candidates (Candidate_File.all, State, Candidate_Data); Candidates.Containers.Generate_Ballots (Candidate_Data, Above_Ballot, Below_Ballot); -- Set up and run the election singleton declare package Given_Prefs is new Preferences (Above_Ballot => Above_Ballot, Below_Ballot => Below_Ballot); package Vote_Bundles is new Bundles (Given_Prefs => Given_Prefs); package Vote_Bundle_Containers is new Vote_Bundles.Containers (Min_Valid => Candidate_Data.First_Index, Max_Valid => Candidate_Data.Last_Index); package This_Election is new Election (Given_Bundles => Vote_Bundles, Bundle_Containers => Vote_Bundle_Containers); begin This_Election.Setup (Candidate_Data => Candidate_Data, Preference_File => Preference_File.all, Output_Dir => Output_Dir.all, Main_Logfile => "log.txt", Number_To_Elect => Number_To_Elect, Is_Verbose => Verbose); This_Election.Run; end; -- Finish up logging Finish_Time := Simple_Time.Now; Log_Msg := SU.To_Unbounded_String ("Finished election count at " & Simple_Time.Image (Finish_Time) & ASCII.LF & Simple_Time.Image (Finish_Time - Start_Time) & " seconds elapsed."); Open (Log_File, Append_File, SU.To_String (Main_Log)); Put_Line (Log_File, SU.To_String (Log_Msg)); Close (Log_File); if Verbose then Put_Line (Standard_Error, ASCII.LF & SU.To_String (Log_Msg)); end if; end STV;