2021-08-20 02:16:54 +10:00
|
|
|
/* OpenTally: Open-source election vote counting
|
|
|
|
* Copyright © 2021 Lee Yingtong Li (RunasSudo)
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
use crate::election::Election;
|
|
|
|
use crate::numbers::Rational;
|
|
|
|
use crate::parser;
|
|
|
|
use crate::writer;
|
|
|
|
|
|
|
|
use clap::{AppSettings, Clap};
|
|
|
|
|
|
|
|
use std::fs::File;
|
|
|
|
|
|
|
|
/// Convert between different ballot data formats
|
|
|
|
#[derive(Clap)]
|
|
|
|
#[clap(setting=AppSettings::DeriveDisplayOrder)]
|
|
|
|
pub struct SubcmdOptions {
|
|
|
|
/// Path to the input data file
|
2021-09-02 22:35:10 +10:00
|
|
|
#[clap(help_heading=Some("INPUT"))]
|
2021-08-20 02:16:54 +10:00
|
|
|
infile: String,
|
|
|
|
|
|
|
|
/// Format of input file
|
2021-09-02 22:35:10 +10:00
|
|
|
#[clap(help_heading=Some("INPUT"), short, long, possible_values=&["bin", "blt", "csp"], value_name="format")]
|
2021-08-20 02:16:54 +10:00
|
|
|
r#in: Option<String>,
|
|
|
|
|
2021-09-02 22:35:10 +10:00
|
|
|
/// Path to the output data file
|
|
|
|
#[clap(help_heading=Some("OUTPUT"))]
|
|
|
|
outfile: String,
|
|
|
|
|
2021-08-20 02:16:54 +10:00
|
|
|
/// Format of output file
|
2021-09-02 22:35:10 +10:00
|
|
|
#[clap(help_heading=Some("OUTPUT"), short, long, possible_values=&["bin", "blt", "csp"], value_name="format")]
|
2021-08-20 02:16:54 +10:00
|
|
|
out: Option<String>,
|
|
|
|
|
|
|
|
/// Number of seats
|
|
|
|
#[clap(help_heading=Some("ELECTION SPECIFICATION"), long)]
|
|
|
|
seats: Option<usize>,
|
2021-09-30 00:48:57 +10:00
|
|
|
|
|
|
|
/// Require 1st preference
|
|
|
|
#[clap(help_heading=Some("PREFERENCE VALIDATION"), long)]
|
|
|
|
require_1: bool,
|
|
|
|
|
|
|
|
/// Require sequential preferences
|
|
|
|
#[clap(help_heading=Some("PREFERENCE VALIDATION"), long)]
|
|
|
|
require_sequential: bool,
|
|
|
|
|
2021-10-17 16:32:35 +11:00
|
|
|
/// Require strict ordering of preferences (disallow equal rankings)
|
2021-09-30 00:48:57 +10:00
|
|
|
#[clap(help_heading=Some("PREFERENCE VALIDATION"), long)]
|
|
|
|
require_strict_order: bool,
|
2021-10-17 16:32:35 +11:00
|
|
|
|
|
|
|
/// Do not output wholly informal ballots
|
|
|
|
#[clap(help_heading=Some("PREFERENCE VALIDATION"), long)]
|
|
|
|
omit_informal: bool,
|
2021-08-20 02:16:54 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Entrypoint for subcommand
|
|
|
|
pub fn main(mut cmd_opts: SubcmdOptions) -> Result<(), i32> {
|
|
|
|
// Auto-detect input/output formats
|
|
|
|
if cmd_opts.r#in == None {
|
2021-09-02 17:17:45 +10:00
|
|
|
if cmd_opts.infile.ends_with(".bin") {
|
|
|
|
cmd_opts.r#in = Some("bin".to_string());
|
|
|
|
} else if cmd_opts.infile.ends_with(".blt") {
|
2021-08-20 02:16:54 +10:00
|
|
|
cmd_opts.r#in = Some("blt".to_string());
|
|
|
|
} else if cmd_opts.infile.ends_with(".csp") {
|
|
|
|
cmd_opts.r#in = Some("csp".to_string());
|
|
|
|
} else {
|
|
|
|
println!("Error: --in not specified and format cannot be determined from input filename");
|
|
|
|
return Err(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if cmd_opts.out == None {
|
2021-09-02 17:17:45 +10:00
|
|
|
if cmd_opts.outfile.ends_with(".bin") {
|
|
|
|
cmd_opts.out = Some("bin".to_string());
|
|
|
|
} else if cmd_opts.outfile.ends_with(".blt") {
|
2021-08-20 02:16:54 +10:00
|
|
|
cmd_opts.out = Some("blt".to_string());
|
|
|
|
} else if cmd_opts.outfile.ends_with(".csp") {
|
|
|
|
cmd_opts.out = Some("csp".to_string());
|
|
|
|
} else {
|
|
|
|
println!("Error: --out not specified and format cannot be determined from output filename");
|
|
|
|
return Err(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read input file
|
2021-08-21 01:19:54 +10:00
|
|
|
let mut election: Election<Rational>;
|
2021-08-20 02:16:54 +10:00
|
|
|
|
|
|
|
match cmd_opts.r#in.as_deref().unwrap() {
|
2021-09-02 17:17:45 +10:00
|
|
|
"bin" => {
|
|
|
|
election = parser::bin::parse_path(cmd_opts.infile);
|
|
|
|
}
|
2021-08-20 02:16:54 +10:00
|
|
|
"blt" => {
|
|
|
|
match parser::blt::parse_path(cmd_opts.infile) {
|
|
|
|
Ok(e) => {
|
|
|
|
election = e;
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
println!("Syntax Error: {}", err);
|
|
|
|
return Err(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"csp" => {
|
2021-08-21 01:19:54 +10:00
|
|
|
let file = File::open(cmd_opts.infile).expect("IO Error");
|
2021-10-17 17:00:24 +11:00
|
|
|
election = parser::csp::parse_reader(file, cmd_opts.require_1, cmd_opts.require_sequential, cmd_opts.require_strict_order).expect("Syntax Error");
|
2021-08-20 02:16:54 +10:00
|
|
|
}
|
|
|
|
_ => unreachable!()
|
|
|
|
};
|
|
|
|
|
2021-08-21 01:19:54 +10:00
|
|
|
match cmd_opts.seats {
|
|
|
|
Some(seats) => {
|
|
|
|
election.seats = seats;
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
if election.seats == 0 {
|
|
|
|
println!("Error: --seats must be specified with CSP input");
|
|
|
|
return Err(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-17 16:32:35 +11:00
|
|
|
if cmd_opts.omit_informal {
|
|
|
|
// Remove wholly informal ballots from output
|
|
|
|
election.ballots.retain(|b| !b.preferences.is_empty());
|
|
|
|
}
|
|
|
|
|
2021-08-20 02:16:54 +10:00
|
|
|
// Write output file
|
|
|
|
let output = File::create(cmd_opts.outfile).expect("IO Error");
|
|
|
|
|
|
|
|
match cmd_opts.out.as_deref().unwrap() {
|
2021-09-02 17:17:45 +10:00
|
|
|
"bin" => {
|
|
|
|
writer::bin::write(election, output);
|
|
|
|
}
|
2021-08-20 02:16:54 +10:00
|
|
|
"blt" => {
|
|
|
|
writer::blt::write(election, output);
|
|
|
|
}
|
|
|
|
"csp" => {
|
|
|
|
writer::csp::write(election, output);
|
|
|
|
}
|
|
|
|
_ => unreachable!()
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
}
|