diff --git a/Cargo.lock b/Cargo.lock index bc10c93..ac57375 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,41 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "822d7d63e0c0260a050f6b1f0d316f5c79b9eab830aca526ed904e1011bd64ca" +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "clap" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +dependencies = [ + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "gmp-mpfr-sys" version = "1.4.5" @@ -22,6 +57,37 @@ dependencies = [ "winapi", ] +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "indexmap" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.95" @@ -41,10 +107,59 @@ dependencies = [ name = "opentally" version = "0.1.0" dependencies = [ + "clap", "num-traits", "rug", ] +[[package]] +name = "os_str_bytes" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rug" version = "1.12.0" @@ -56,6 +171,56 @@ dependencies = [ "libc", ] +[[package]] +name = "syn" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "textwrap" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 4820012..571e2dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,8 @@ num-traits = "0.2" version = "1.12" default-features = false features = ["integer", "rational", "float"] + +[dependencies.clap] +version = "3.0.0-beta.2" +default-features = false +features = ["std", "derive"] diff --git a/src/main.rs b/src/main.rs index 7816085..ea73a82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,13 +22,33 @@ mod stv; use crate::election::{CandidateState, CountState, CountStateOrRef, Election, StageResult}; use crate::numbers::{Number, NumType}; -use std::env; +use clap::Clap; + use std::fs::File; use std::io::{self, BufRead}; -const DECIMAL_PLACES: usize = 0; +#[derive(Clap)] +struct Opts { + #[clap(subcommand)] + command: Command, +} -fn print_stage(stage_num: usize, result: &StageResult) { +#[derive(Clap)] +enum Command { + STV(STV), +} + +/// Count a single transferable vote (STV) election +#[derive(Clap)] +struct STV { + /// Path to the BLT file to be counted + filename: String, + /// Print votes to specified decimal places in results report + #[clap(long, default_value="2")] + pp_decimals: usize, +} + +fn print_stage(stage_num: usize, result: &StageResult, cmd_opts: &STV) { println!("{}. {}", stage_num, result.title); println!("{}", result.logs.join(" ")); @@ -41,27 +61,28 @@ fn print_stage(stage_num: usize, result: &StageResult) { //for (candidate, count_card) in candidates.into_iter().rev() { for (candidate, count_card) in candidates { if count_card.state == CandidateState::ELECTED { - println!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=DECIMAL_PLACES); + println!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=cmd_opts.pp_decimals); } else if count_card.state == CandidateState::EXCLUDED { - println!("- {}: {:.dps$} ({:.dps$}) - Excluded {}", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=DECIMAL_PLACES); + println!("- {}: {:.dps$} ({:.dps$}) - Excluded {}", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=cmd_opts.pp_decimals); } else { - println!("- {}: {:.dps$} ({:.dps$})", candidate.name, count_card.votes, count_card.transfers, dps=DECIMAL_PLACES); + println!("- {}: {:.dps$} ({:.dps$})", candidate.name, count_card.votes, count_card.transfers, dps=cmd_opts.pp_decimals); } } - println!("Quota: {:.dps$}", result.state.as_ref().quota, dps=DECIMAL_PLACES); + println!("Quota: {:.dps$}", result.state.as_ref().quota, dps=cmd_opts.pp_decimals); println!(""); } fn main() { - // Read arguments - let file_name = env::args().skip(1).next().expect("First argument must be path to BLT file"); - let should_clone_state = false; + // Read arguments + let opts: Opts = Opts::parse(); + let Command::STV(cmd_opts) = opts.command; + // Read BLT file - let file = File::open(file_name).expect("IO Error"); + let file = File::open(&cmd_opts.filename).expect("IO Error"); let lines = io::BufReader::new(file).lines(); let election: Election = Election::from_blt(lines); @@ -80,7 +101,7 @@ fn main() { logs: vec!["First preferences distributed."], state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) }, }; - print_stage(1, &result); + print_stage(1, &result, &cmd_opts); let mut stage_num = 1; @@ -101,7 +122,7 @@ fn main() { logs: vec!["Continuing exclusion."], state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) }, }; - print_stage(stage_num, &result); + print_stage(stage_num, &result, &cmd_opts); continue; } @@ -113,7 +134,7 @@ fn main() { logs: vec!["Surplus distributed."], state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) }, }; - print_stage(stage_num, &result); + print_stage(stage_num, &result, &cmd_opts); continue; } @@ -125,7 +146,7 @@ fn main() { logs: vec!["Bulk election."], state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) }, }; - print_stage(stage_num, &result); + print_stage(stage_num, &result, &cmd_opts); continue; } @@ -137,7 +158,7 @@ fn main() { logs: vec!["Candidate excluded."], state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) }, }; - print_stage(stage_num, &result); + print_stage(stage_num, &result, &cmd_opts); continue; }