From cca097f9430eb9eb10e2e8de492a59db73bcddd9 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Fri, 23 Jul 2021 16:45:54 +1000 Subject: [PATCH] Use Pest-based parser for BLT files Support comments, optional newlines, etc. --- Cargo.lock | 136 +++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 + docs/blt.md | 16 ++--- src/election.rs | 75 +++++++++++------------- src/lib.rs | 4 ++ src/main.rs | 13 ++--- src/parser/blt.pest | 36 ++++++++++++ src/parser/blt.rs | 44 ++++++++++++++ src/parser/mod.rs | 19 ++++++ src/stv/wasm.rs | 2 +- tests/aec.rs | 10 ++-- tests/constraints.rs | 20 +++---- tests/meek.rs | 17 ++---- tests/scotland.rs | 12 +--- tests/utils/mod.rs | 9 +-- 15 files changed, 309 insertions(+), 106 deletions(-) create mode 100644 src/parser/blt.pest create mode 100644 src/parser/blt.rs create mode 100644 src/parser/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 6da0914..6d5d79e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,13 +47,34 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", ] [[package]] @@ -74,6 +95,12 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.4.3" @@ -198,13 +225,22 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.4", ] [[package]] @@ -219,6 +255,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + [[package]] name = "flate2" version = "1.0.20" @@ -240,6 +282,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.4" @@ -365,6 +416,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "matrixmultiply" version = "0.3.1" @@ -460,6 +517,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -485,6 +548,8 @@ dependencies = [ "num-rational", "num-traits", "paste", + "pest", + "pest_derive", "predicates", "rug", "sha2", @@ -504,6 +569,49 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + [[package]] name = "predicates" version = "1.0.8" @@ -651,17 +759,29 @@ version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + [[package]] name = "sha2" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest", - "opaque-debug", + "digest 0.9.0", + "opaque-debug 0.3.0", ] [[package]] @@ -699,6 +819,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicode-segmentation" version = "1.7.1" diff --git a/Cargo.toml b/Cargo.toml index 6a4c09d..efec249 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ git-version = "0.3.4" ibig = "0.3.2" itertools = "0.10.1" ndarray = "0.15.3" +pest = "2.1.3" +pest_derive = "2.1.0" predicates = "1.0.8" num-traits = "0.2" sha2 = "0.9.5" diff --git a/docs/blt.md b/docs/blt.md index 985c82d..12db92a 100644 --- a/docs/blt.md +++ b/docs/blt.md @@ -1,6 +1,6 @@ # BLT file format -OpenTally accepts ballot data in the BLT file format, as described by [Hill, Wichmann & Woodall](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) for their implementation of Meek STV. The BLT file format is also used by [OpenSTV/OpaVote](https://www.opavote.com/openstv), Lundell's [Droop](https://github.com/jklundell/droop/wiki/BltFileFormat) and Otten's [eSTV](https://web.archive.org/web/20020606014623/http://estv.otten.co.uk/) (where it is known as a DAT data transfer file). +OpenTally accepts ballot data in the BLT file format, as described by [Hill, Wichmann & Woodall](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) for their implementation of Meek STV. The BLT file format is also used by [OpenSTV/OpaVote](https://www.opavote.com/help/overview#blt-file-format), Lundell's [Droop](https://github.com/jklundell/droop/wiki/BltFileFormat) and Otten's [eSTV](https://web.archive.org/web/20020606014623/http://estv.otten.co.uk/) (where it is known as a DAT data transfer file). The file format is as follows: @@ -21,14 +21,16 @@ The file format is as follows: "Title" ``` -The first line (`4 2`) indicates that there are 4 candidates for 2 vacancies. This must be on its own line. +The first line (`4 2`) indicates that there are 4 candidates for 2 vacancies. -The second line (`-2`), which is optional, indicates that the 2nd candidate (Basil) has withdrawn. Multiple withdrawn candidates may be specified on this line, e.g. `-2 -3 -4`. This must, if present, be on its own line. +The second line (`-2`), which is optional, indicates that the 2nd candidate (Basil) has withdrawn. Multiple withdrawn candidates may be specified on this line, e.g. `-2 -3 -4`. -The third line (second, if there are no withdrawn candidates) begins the ballot data. `3 1 3 4 0` indicates that there were 3 ballots which voted, in order of preference, for the 1st candidate (Adam), then the 3rd candidate (Charlotte), then the 4th candidate (Donald). A `0` optionally indicates the end of the list of preferences. Each such set of ballots must be on its own line. +The third line (second, if there are no withdrawn candidates) begins the ballot data. `3 1 3 4 0` indicates that there were 3 ballots which voted, in order of preference, for the 1st candidate (Adam), then the 3rd candidate (Charlotte), then the 4th candidate (Donald). A `0` optionally indicates the end of the list of preferences. -The end of the list of ballots must be indicated with a single `0`, which must be on its own line. +The end of the list of ballots must be indicated with a single `0`. -The next lines give the names of the candidates, up to the number of candidates specified on the first line (in this case, 4). Each candidate's name must be surrounded by quotation marks, and must appear on its own line. +The next lines give the names of the candidates, up to the number of candidates specified on the first line (in this case, 4). Each candidate's name, unless a single word, must be surrounded by quotation marks. -The final line gives the name of the election, which must appear on its own line. +The final line gives the name of the election, which, unless a single word, must be surrounded by quotation marks. + +Newlines are optional, but if not provided, the `0` at the end of each ballot's preferences is mandatory. diff --git a/src/election.rs b/src/election.rs index 2521ec4..d7e4d22 100644 --- a/src/election.rs +++ b/src/election.rs @@ -18,8 +18,11 @@ use crate::constraints::{Constraints, ConstraintMatrix}; use crate::logger::Logger; use crate::numbers::Number; +use crate::parser::blt; use crate::sharandom::SHARandom; +use pest::Parser; + use std::collections::HashMap; /// An election to be counted @@ -40,12 +43,12 @@ pub struct Election { impl Election { /// Parse the given BLT file and return an [Election] - pub fn from_blt>(mut lines: I) -> Self { - // Read first line - let line = lines.next().expect("Unexpected EOF"); - let mut bits = line.split(" "); - let num_candidates = bits.next().expect("Syntax Error").parse().expect("Syntax Error"); - let seats: usize = bits.next().expect("Syntax Error").parse().expect("Syntax Error"); + pub fn from_blt(input: &str) -> Self { + let mut main = blt::BLTParser::parse(blt::Rule::main, input).expect("Syntax Error").next().unwrap().into_inner(); + + let mut header = main.next().unwrap().into_inner(); + let num_candidates = header.next().unwrap().as_str().parse().unwrap(); + let seats = header.next().unwrap().as_str().parse().unwrap(); // Initialise the Election object let mut election = Election { @@ -57,57 +60,47 @@ impl Election { constraints: None, }; + // Read withdrawn candidates + let withdrawn_pairs = main.next().unwrap().into_inner(); + for withdrawn_cand in withdrawn_pairs { + let val = withdrawn_cand.as_str(); + let val: usize = val[1..val.len()].parse().unwrap(); + election.withdrawn_candidates.push(val - 1); + } + // Read ballots - for line in &mut lines { - if line == "0" { - break; - } + let ballot_list = main.next().unwrap().into_inner(); + for ballot in ballot_list { + let mut ballot_pairs = ballot.into_inner(); - let mut bits = line.split(" "); - - if line.starts_with("-") { - // Withdrawn candidates - for bit in bits.into_iter() { - let val = bit[1..bit.len()].parse::().expect("Syntax Error"); - election.withdrawn_candidates.push(val - 1); - } - continue; - } - - let value = N::parse(bits.next().expect("Syntax Error")); + let value = N::parse(ballot_pairs.next().unwrap().as_str()); let mut ballot = Ballot { orig_value: value, preferences: Vec::new(), }; - for preference in bits { - if preference != "0" { - let preference = preference.parse::().expect("Syntax Error"); - ballot.preferences.push(preference - 1); - } + let ballot_preferences = ballot_pairs.next().unwrap().into_inner(); + for preference in ballot_preferences { + let preference: usize = preference.as_str().parse().unwrap(); + ballot.preferences.push(preference - 1); } election.ballots.push(ballot); } + // Zero line + main.next(); + // Read candidates - for line in lines.by_ref().take(num_candidates) { - let mut line = &line[..]; - if line.starts_with("\"") && line.ends_with("\"") { - line = &line[1..line.len()-1]; - } - - election.candidates.push(Candidate { name: line.to_string() }); + for _ in 0..num_candidates { + let string = main.next().expect("Expected candidate name"); + election.candidates.push(Candidate { name: blt::unwrap_string(string).to_string() }); } // Read name - let line = lines.next().expect("Syntax Error"); - let mut line = &line[..]; - if line.starts_with("\"") && line.ends_with("\"") { - line = &line[1..line.len()-1]; - } - election.name.push_str(line); + let string = main.next().expect("Expected election title"); + election.name.push_str(blt::unwrap_string(string)); return election; } @@ -321,7 +314,7 @@ impl<'a, N: Number> CountCard<'a, N> { pub struct Parcel<'a, N> { /// [Vote]s in this parcel pub votes: Vec>, - /// Order for sorting with [opentally::stv::ExclusionMethod::BySource] + /// Order for sorting with [crate::stv::ExclusionMethod::BySource] pub source_order: usize, } diff --git a/src/lib.rs b/src/lib.rs index fe69e89..cffff22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,8 @@ //! Open source counting software for various preferential voting election systems +/// File parsers +pub mod parser; /// Data types and logic for constraints on elections pub mod constraints; /// Data types for representing abstract elections @@ -36,6 +38,8 @@ pub mod ties; use git_version::git_version; use wasm_bindgen::prelude::wasm_bindgen; +#[macro_use] +extern crate pest_derive; /// The git revision of this OpenTally build pub const VERSION: &str = git_version!(args=["--always", "--dirty=-dev"], fallback="unknown"); diff --git a/src/main.rs b/src/main.rs index 7a8df45..d951093 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ use opentally::stv; use clap::{AppSettings, Clap}; use std::cmp::max; -use std::fs::File; +use std::fs::{self, File}; use std::io::{self, BufRead}; use std::ops; @@ -190,29 +190,28 @@ fn main() { let Command::STV(cmd_opts) = opts.command; // Read BLT file - let file = File::open(&cmd_opts.filename).expect("IO Error"); - let lines = io::BufReader::new(file).lines(); + let file = fs::read_to_string(&cmd_opts.filename).expect("IO Error"); // Create and count election according to --numbers if cmd_opts.numbers == "rational" { - let mut election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let mut election: Election = Election::from_blt(&file); maybe_load_constraints(&mut election, &cmd_opts.constraints); // Must specify :: here and in a few other places because ndarray causes E0275 otherwise count_election::(election, cmd_opts); } else if cmd_opts.numbers == "float64" { - let mut election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let mut election: Election = Election::from_blt(&file); maybe_load_constraints(&mut election, &cmd_opts.constraints); count_election::(election, cmd_opts); } else if cmd_opts.numbers == "fixed" { Fixed::set_dps(cmd_opts.decimals); - let mut election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let mut election: Election = Election::from_blt(&file); maybe_load_constraints(&mut election, &cmd_opts.constraints); count_election::(election, cmd_opts); } else if cmd_opts.numbers == "gfixed" { GuardedFixed::set_dps(cmd_opts.decimals); - let mut election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let mut election: Election = Election::from_blt(&file); maybe_load_constraints(&mut election, &cmd_opts.constraints); count_election::(election, cmd_opts); } diff --git a/src/parser/blt.pest b/src/parser/blt.pest new file mode 100644 index 0000000..0446d8c --- /dev/null +++ b/src/parser/blt.pest @@ -0,0 +1,36 @@ +main = { + SOI ~ NEWLINE* + ~ header ~ NEWLINE* + ~ withdrawn ~ NEWLINE* + ~ ballot_list + ~ zero ~ NEWLINE* + ~ (string ~ NEWLINE*)+ + ~ EOI +} + +header = { + integer ~ integer +} + +withdrawn = { + (withdrawn_cand ~ NEWLINE*)* +} +withdrawn_cand = @{ "-" ~ integer } + +ballot_list = { (ballot ~ NEWLINE*)* } +ballot = { + number ~ ballot_preferences ~ (zero ~ NEWLINE? | NEWLINE) +} +ballot_preferences = { integer* } + +zero = { "0" } +integer = @{ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* } +decimal = @{ "0." ~ ASCII_DIGIT+ | integer ~ "." ~ ASCII_DIGIT+ } +number = { decimal | integer } + +quoted_string = @{ "\"" ~ (!"\"" ~ !NEWLINE ~ ANY)* ~ "\"" } +raw_string = @{ (!" " ~ !NEWLINE ~ ANY)+ } +string = { quoted_string | raw_string } + +WHITESPACE = _{ " " } +COMMENT = _{ "#" ~ (!NEWLINE ~ ANY)* ~ (&NEWLINE | &EOI) } diff --git a/src/parser/blt.rs b/src/parser/blt.rs new file mode 100644 index 0000000..10050e9 --- /dev/null +++ b/src/parser/blt.rs @@ -0,0 +1,44 @@ +/* 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 . + */ + +#![allow(missing_docs)] + +use pest::iterators::Pair; + +#[derive(Parser)] +#[grammar = "parser/blt.pest"] +pub struct BLTParser; + +/// Convert the specified [Pair] into a &str +pub fn unwrap_string(string: Pair) -> &str { + match string.as_rule() { + Rule::string => { + let string = string.into_inner().next().unwrap(); + match string.as_rule() { + Rule::raw_string => { + return string.as_str(); + } + Rule::quoted_string => { + let string = string.as_str(); + return &string[1..string.len()-1]; + } + _ => unreachable!() + } + } + _ => panic!("Non-string passed to unwrap_string") + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..3ea5b5b --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,19 @@ +/* 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 . + */ + +/// BLT file parser +pub mod blt; diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs index 7211fda..37cd359 100644 --- a/src/stv/wasm.rs +++ b/src/stv/wasm.rs @@ -56,7 +56,7 @@ macro_rules! impl_type { // Install panic! hook console_error_panic_hook::set_once(); - let election: Election<$type> = Election::from_blt(text.lines().map(|s| s.to_string()).into_iter()); + let election: Election<$type> = Election::from_blt(&text); return [](election); } diff --git a/tests/aec.rs b/tests/aec.rs index 4bc3333..3ff13b8 100644 --- a/tests/aec.rs +++ b/tests/aec.rs @@ -25,7 +25,7 @@ use csv::StringRecord; use flate2::bufread::GzDecoder; use std::fs::File; -use std::io::{self, BufRead}; +use std::io::{self, Read}; #[test] fn aec_tas19_rational() { @@ -45,12 +45,12 @@ fn aec_tas19_rational() { // Decompress BLT let file = File::open("tests/data/aec-senate-formalpreferences-24310-TAS.blt.gz").expect("IO Error"); let file_reader = io::BufReader::new(file); - let gz_decoder = GzDecoder::new(file_reader); - let gz_reader = io::BufReader::new(gz_decoder); - let lines = gz_reader.lines(); + let mut gz_decoder = GzDecoder::new(file_reader); + let mut input = String::new(); + gz_decoder.read_to_string(&mut input).expect("IO Error"); // Read BLT - let election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let election: Election = Election::from_blt(&input); // Validate candidate names for (i, candidate) in candidates.iter().enumerate() { diff --git a/tests/constraints.rs b/tests/constraints.rs index c219a1c..f774792 100644 --- a/tests/constraints.rs +++ b/tests/constraints.rs @@ -22,7 +22,7 @@ use opentally::election::{CandidateState, CountState, Election}; use opentally::numbers::Rational; use opentally::stv; -use std::fs::File; +use std::fs::{self, File}; use std::io::{self, BufRead}; #[test] @@ -30,10 +30,8 @@ fn prsa1_constr1_rational() { // FIXME: This is unvalidated! // Read BLT - let file = File::open("tests/data/prsa1.blt").expect("IO Error"); - let file_reader = io::BufReader::new(file); - let lines = file_reader.lines(); - let mut election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error"); + let mut election: Election = Election::from_blt(&file); // Read CON let file = File::open("tests/data/prsa1_constr1.con").expect("IO Error"); @@ -94,10 +92,8 @@ fn prsa1_constr2_rational() { // FIXME: This is unvalidated! // Read BLT - let file = File::open("tests/data/prsa1.blt").expect("IO Error"); - let file_reader = io::BufReader::new(file); - let lines = file_reader.lines(); - let mut election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error"); + let mut election: Election = Election::from_blt(&file); // Read CON let file = File::open("tests/data/prsa1_constr2.con").expect("IO Error"); @@ -158,10 +154,8 @@ fn prsa1_constr3_rational() { // FIXME: This is unvalidated! // Read BLT - let file = File::open("tests/data/prsa1.blt").expect("IO Error"); - let file_reader = io::BufReader::new(file); - let lines = file_reader.lines(); - let mut election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let file = fs::read_to_string("tests/data/prsa1.blt").expect("IO Error"); + let mut election: Election = Election::from_blt(&file); // Read CON let file = File::open("tests/data/prsa1_constr3.con").expect("IO Error"); diff --git a/tests/meek.rs b/tests/meek.rs index 336e4f4..37e0bf8 100644 --- a/tests/meek.rs +++ b/tests/meek.rs @@ -21,8 +21,7 @@ use opentally::election::{CandidateState, CountState, Election}; use opentally::numbers::{Fixed, NativeFloat64, Number}; use opentally::stv; -use std::io::{self, BufRead}; -use std::fs::File; +use std::fs; // Compare ers97.blt count with result produced by 1987 Hill–Wichmann–Woodall reference implementation #[test] @@ -86,11 +85,8 @@ fn meek06_ers97_fixed12() { Fixed::set_dps(12); // Read BLT - let file = File::open("tests/data/ers97.blt").expect("IO Error"); - let file_reader = io::BufReader::new(file); - let lines = file_reader.lines(); - - let election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let file = fs::read_to_string("tests/data/ers97.blt").expect("IO Error"); + let election: Election = Election::from_blt(&file); // Initialise count state let mut state = CountState::new(&election); @@ -162,11 +158,8 @@ fn meeknz_ers97_fixed12() { Fixed::set_dps(12); // Read BLT - let file = File::open("tests/data/ers97.blt").expect("IO Error"); - let file_reader = io::BufReader::new(file); - let lines = file_reader.lines(); - - let election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let file = fs::read_to_string("tests/data/ers97.blt").expect("IO Error"); + let election: Election = Election::from_blt(&file); // Initialise count state let mut state = CountState::new(&election); diff --git a/tests/scotland.rs b/tests/scotland.rs index 6ee5b1d..a5e97d4 100644 --- a/tests/scotland.rs +++ b/tests/scotland.rs @@ -15,16 +15,13 @@ * along with this program. If not, see . */ -// https://web.archive.org/web/20121004213938/http://www.glasgow.gov.uk/en/YourCouncil/Elections_Voting/Election_Results/ElectionScotland2007/LGWardResults.htm?ward=1&wardname=1%20-%20Linn - use opentally::election::{CandidateState, CountState, Election}; use opentally::numbers::{Fixed, GuardedFixed, Number}; use opentally::stv; use xmltree::Element; -use std::io::{self, BufRead}; -use std::fs::File; +use std::fs::{self, File}; use std::ops; #[test] @@ -114,11 +111,8 @@ where let num_stages = root.get_child("headerrow").expect("Syntax Error").children.len(); // Read BLT - let file = File::open("tests/data/linn07.blt").expect("IO Error"); - let file_reader = io::BufReader::new(file); - let lines = file_reader.lines(); - - let mut election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let file = fs::read_to_string("tests/data/linn07.blt").expect("IO Error"); + let mut election: Election = Election::from_blt(&file); // !!! FOR SCOTTISH STV !!! election.normalise_ballots(); diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index f4722a0..cc68275 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -21,8 +21,7 @@ use opentally::stv; use csv::StringRecord; -use std::fs::File; -use std::io::{self, BufRead}; +use std::fs; use std::ops; #[allow(dead_code)] // Suppress false positive @@ -48,11 +47,9 @@ where let stages: Vec = records.first().unwrap().iter().skip(1).step_by(2).map(|s| s.parse().unwrap()).collect(); // Read BLT - let file = File::open(blt_file).expect("IO Error"); - let file_reader = io::BufReader::new(file); - let lines = file_reader.lines(); + let file = fs::read_to_string(blt_file).expect("IO Error"); - let election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let election: Election = Election::from_blt(&file); // Validate candidate names for (i, candidate) in candidates.iter().enumerate() {