/* 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 . */ 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 #[clap(help_heading=Some("INPUT"))] infile: String, /// Format of input file #[clap(help_heading=Some("INPUT"), short, long, possible_values=&["bin", "blt", "csp"], value_name="format")] r#in: Option, /// Path to the output data file #[clap(help_heading=Some("OUTPUT"))] outfile: String, /// Format of output file #[clap(help_heading=Some("OUTPUT"), short, long, possible_values=&["bin", "blt", "csp"], value_name="format")] out: Option, /// Number of seats #[clap(help_heading=Some("ELECTION SPECIFICATION"), long)] seats: Option, /// 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, /// Require strict ordering of preferences (disallow equal rankings) #[clap(help_heading=Some("PREFERENCE VALIDATION"), long)] require_strict_order: bool, /// Do not output wholly informal ballots #[clap(help_heading=Some("PREFERENCE VALIDATION"), long)] omit_informal: bool, } /// Entrypoint for subcommand pub fn main(mut cmd_opts: SubcmdOptions) -> Result<(), i32> { // Auto-detect input/output formats if cmd_opts.r#in == None { if cmd_opts.infile.ends_with(".bin") { cmd_opts.r#in = Some("bin".to_string()); } else if cmd_opts.infile.ends_with(".blt") { 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 { if cmd_opts.outfile.ends_with(".bin") { cmd_opts.out = Some("bin".to_string()); } else if cmd_opts.outfile.ends_with(".blt") { 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 let mut election: Election; match cmd_opts.r#in.as_deref().unwrap() { "bin" => { election = parser::bin::parse_path(cmd_opts.infile); } "blt" => { match parser::blt::parse_path(cmd_opts.infile) { Ok(e) => { election = e; } Err(err) => { println!("Syntax Error: {}", err); return Err(1); } } } "csp" => { let file = File::open(cmd_opts.infile).expect("IO Error"); election = parser::csp::parse_reader(file, cmd_opts.require_1, cmd_opts.require_sequential, cmd_opts.require_strict_order).expect("Syntax Error"); } _ => unreachable!() }; 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); } } } if cmd_opts.omit_informal { // Remove wholly informal ballots from output election.ballots.retain(|b| !b.preferences.is_empty()); } // Write output file let output = File::create(cmd_opts.outfile).expect("IO Error"); match cmd_opts.out.as_deref().unwrap() { "bin" => { writer::bin::write(election, output); } "blt" => { writer::blt::write(election, output); } "csp" => { writer::csp::write(election, output); } _ => unreachable!() } return Ok(()); }