diff --git a/src/cli/stv.rs b/src/cli/stv.rs
index 4f5ea7d..27614c3 100644
--- a/src/cli/stv.rs
+++ b/src/cli/stv.rs
@@ -248,7 +248,7 @@ fn maybe_load_constraints(election: &mut Election, constraints: &O
if let Some(c) = constraints {
let file = File::open(c).expect("IO Error");
let lines = io::BufReader::new(file).lines();
- election.constraints = Some(Constraints::from_con(lines.map(|r| r.expect("IO Error").to_string()).into_iter()));
+ election.constraints = Some(Constraints::from_con(lines.map(|r| r.expect("IO Error"))));
}
}
@@ -267,7 +267,7 @@ where
cmd_opts.round_votes,
cmd_opts.round_quota,
cmd_opts.sum_surplus_transfers.into(),
- cmd_opts.meek_surplus_tolerance.into(),
+ cmd_opts.meek_surplus_tolerance,
cmd_opts.normalise_ballots,
cmd_opts.quota.into(),
cmd_opts.quota_criterion.into(),
@@ -324,7 +324,7 @@ where
let total_ballots = election.ballots.iter().fold(N::zero(), |acc, b| { acc + &b.orig_value });
print!("Count computed by OpenTally (revision {}). Read {:.0} ballots from \"{}\" for election \"{}\". There are {} candidates for {} vacancies. ", crate::VERSION, total_ballots, filename, election.name, election.candidates.len(), election.seats);
let opts_str = opts.describe::();
- if opts_str.len() > 0 {
+ if !opts_str.is_empty() {
println!("Counting using options \"{}\".", opts_str);
} else {
println!("Counting using default options.");
diff --git a/src/constraints.rs b/src/constraints.rs
index edf9080..e1219e8 100644
--- a/src/constraints.rs
+++ b/src/constraints.rs
@@ -40,47 +40,11 @@ impl Constraints {
let mut constraints = Constraints(Vec::new());
for line in lines {
- let mut bits = line.split(" ").peekable();
+ let mut bits = line.split(' ').peekable();
- // Read constraint category
- let mut constraint_name = String::new();
- let x = bits.next().expect("Syntax Error");
- if x.starts_with('"') {
- if x.ends_with('"') {
- constraint_name.push_str(&x[1..x.len()-1]);
- } else {
- constraint_name.push_str(&x[1..]);
- while !bits.peek().expect("Syntax Error").ends_with('"') {
- constraint_name.push_str(" ");
- constraint_name.push_str(bits.next().unwrap());
- }
- let x = bits.next().unwrap();
- constraint_name.push_str(" ");
- constraint_name.push_str(&x[..x.len()-1]);
- }
- } else {
- constraint_name.push_str(x);
- }
-
- // Read constraint group
- let mut group_name = String::new();
- let x = bits.next().expect("Syntax Error");
- if x.starts_with('"') {
- if x.ends_with('"') {
- group_name.push_str(&x[1..x.len()-1]);
- } else {
- group_name.push_str(&x[1..]);
- while !bits.peek().expect("Syntax Error").ends_with('"') {
- group_name.push_str(" ");
- group_name.push_str(bits.next().unwrap());
- }
- let x = bits.next().unwrap();
- group_name.push_str(" ");
- group_name.push_str(&x[..x.len()-1]);
- }
- } else {
- group_name.push_str(x);
- }
+ // Read constraint category and group
+ let constraint_name = read_quoted_string(&mut bits);
+ let group_name = read_quoted_string(&mut bits);
// Read min, max
let min: usize = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
@@ -93,7 +57,7 @@ impl Constraints {
}
// Insert constraint/group
- let constraint = match constraints.0.iter_mut().filter(|c| c.name == constraint_name).next() {
+ let constraint = match constraints.0.iter_mut().find(|c| c.name == constraint_name) {
Some(c) => { c }
None => {
let c = Constraint {
@@ -111,9 +75,9 @@ impl Constraints {
constraint.groups.push(ConstrainedGroup {
name: group_name,
- candidates: candidates,
- min: min,
- max: max,
+ candidates,
+ min,
+ max,
});
}
@@ -123,6 +87,39 @@ impl Constraints {
}
}
+/// Read an optionally quoted string, returning the string without quotes
+fn read_quoted_string<'a, I: Iterator- >(bits: &mut I) -> String {
+ let x = bits.next().expect("Syntax Error");
+ if let Some(x1) = x.strip_prefix('"') {
+ if let Some(x2) = x.strip_suffix('"') {
+ // Complete string
+ return String::from(x2);
+ } else {
+ // Incomplete string
+ let mut result = String::from(x1);
+
+ // Read until matching "
+ loop {
+ let x = bits.next().expect("Syntax Error");
+ result.push(' ');
+ if let Some(x1) = x.strip_suffix('"') {
+ // End of string
+ result.push_str(x1);
+ break;
+ } else {
+ // Middle of string
+ result.push_str(x);
+ }
+ }
+
+ return result;
+ }
+ } else {
+ // Unquoted string
+ return String::from(x);
+ }
+}
+
/// A single dimension of constraint
#[derive(Clone, Debug)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
@@ -264,7 +261,7 @@ impl ConstraintMatrix {
self.0[&idx].elected = 0;
// The axis along which to sum - if multiple, just pick the first, as these should agree
- let zero_axis = (0..idx.ndim()).filter(|d| idx[*d] == 0).next().unwrap();
+ let zero_axis = (0..idx.ndim()).find(|d| idx[*d] == 0).unwrap();
// Traverse along the axis and sum the candidates
let mut idx2 = idx.clone();
@@ -374,87 +371,87 @@ impl fmt::Display for ConstraintMatrix {
// TODO: >2 dimensions
if shape.len() == 1 {
- result.push_str("+");
+ result.push('+');
for _ in 0..shape[0] {
result.push_str("-------------+");
}
- result.push_str("\n");
+ result.push('\n');
- result.push_str("|");
+ result.push('|');
for x in 0..shape[0] {
result.push_str(&format!(" Elected: {:2}", self[&[x]].elected));
result.push_str(if x == 0 { " ‖" } else { " |" });
}
- result.push_str("\n");
+ result.push('\n');
- result.push_str("|");
+ result.push('|');
for x in 0..shape[0] {
result.push_str(&format!(" Min: {:2}", self[&[x]].min));
result.push_str(if x == 0 { " ‖" } else { " |" });
}
- result.push_str("\n");
+ result.push('\n');
- result.push_str("|");
+ result.push('|');
for x in 0..shape[0] {
result.push_str(&format!(" Max: {:2}", self[&[x]].max));
result.push_str(if x == 0 { " ‖" } else { " |" });
}
- result.push_str("\n");
+ result.push('\n');
- result.push_str("|");
+ result.push('|');
for x in 0..shape[0] {
result.push_str(&format!(" Cands: {:2}", self[&[x]].cands));
result.push_str(if x == 0 { " ‖" } else { " |" });
}
- result.push_str("\n");
+ result.push('\n');
- result.push_str("+");
+ result.push('+');
for _ in 0..shape[0] {
result.push_str("-------------+");
}
- result.push_str("\n");
+ result.push('\n');
} else if shape.len() == 2 {
for y in 0..shape[1] {
- result.push_str("+");
+ result.push('+');
for _ in 0..shape[0] {
result.push_str(if y == 1 { "=============+" } else { "-------------+" });
}
- result.push_str("\n");
+ result.push('\n');
- result.push_str("|");
+ result.push('|');
for x in 0..shape[0] {
result.push_str(&format!(" Elected: {:2}", self[&[x, y]].elected));
result.push_str(if x == 0 { " ‖" } else { " |" });
}
- result.push_str("\n");
+ result.push('\n');
- result.push_str("|");
+ result.push('|');
for x in 0..shape[0] {
result.push_str(&format!(" Min: {:2}", self[&[x, y]].min));
result.push_str(if x == 0 { " ‖" } else { " |" });
}
- result.push_str("\n");
+ result.push('\n');
- result.push_str("|");
+ result.push('|');
for x in 0..shape[0] {
result.push_str(&format!(" Max: {:2}", self[&[x, y]].max));
result.push_str(if x == 0 { " ‖" } else { " |" });
}
- result.push_str("\n");
+ result.push('\n');
- result.push_str("|");
+ result.push('|');
for x in 0..shape[0] {
result.push_str(&format!(" Cands: {:2}", self[&[x, y]].cands));
result.push_str(if x == 0 { " ‖" } else { " |" });
}
- result.push_str("\n");
+ result.push('\n');
}
- result.push_str("+");
+ result.push('+');
for _ in 0..shape[0] {
result.push_str("-------------+");
}
- result.push_str("\n");
+ result.push('\n');
} else {
todo!();
}
@@ -499,7 +496,7 @@ fn candidates_in_constraint_cell<'a, N: Number>(election: &'a Election, candi
}
/// Clone and update the constraints matrix, with the state of the given candidates set to candidate_state
-pub fn try_constraints(state: &CountState, candidates: &Vec<&Candidate>, candidate_state: CandidateState) -> Result<(), ConstraintError> {
+pub fn try_constraints(state: &CountState, candidates: &[&Candidate], candidate_state: CandidateState) -> Result<(), ConstraintError> {
if state.constraint_matrix.is_none() {
return Ok(());
}
@@ -507,11 +504,11 @@ pub fn try_constraints(state: &CountState, candidates: &Vec<&Candi
let mut trial_candidates = state.candidates.clone(); // TODO: Can probably be optimised by not cloning CountCard::parcels
for candidate in candidates {
- trial_candidates.get_mut(candidate).unwrap().state = candidate_state.clone();
+ trial_candidates.get_mut(candidate).unwrap().state = candidate_state;
}
// Update cands/elected
- cm.update_from_state(&state.election, &trial_candidates);
+ cm.update_from_state(state.election, &trial_candidates);
cm.recount_cands();
// Iterate for stable state
@@ -528,7 +525,7 @@ pub fn update_constraints(state: &mut CountState, opts: &STVOption
let cm = state.constraint_matrix.as_mut().unwrap();
// Update cands/elected
- cm.update_from_state(&state.election, &state.candidates);
+ cm.update_from_state(state.election, &state.candidates);
cm.recount_cands();
// Iterate for stable state
diff --git a/src/election.rs b/src/election.rs
index 53badf0..73cc170 100644
--- a/src/election.rs
+++ b/src/election.rs
@@ -149,7 +149,7 @@ impl<'a, N: Number> CountState<'a, N> {
/// Construct a new blank [CountState] for the given [Election]
pub fn new(election: &'a Election) -> Self {
let mut state = CountState {
- election: &election,
+ election,
candidates: HashMap::new(),
exhausted: CountCard::new(),
loss_fraction: CountCard::new(),
@@ -194,7 +194,7 @@ impl<'a, N: Number> CountState<'a, N> {
}
// Fill in grand total, etc.
- cm.update_from_state(&state.election, &state.candidates);
+ cm.update_from_state(state.election, &state.candidates);
cm.init();
//println!("{}", cm);
diff --git a/src/lib.rs b/src/lib.rs
index 91a21ac..405c7b5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,6 +16,7 @@
*/
#![warn(missing_docs)]
+#![allow(clippy::collapsible_else_if, clippy::collapsible_if, clippy::comparison_chain, clippy::derive_ord_xor_partial_ord, clippy::needless_bool, clippy::needless_return, clippy::new_without_default, clippy::too_many_arguments)]
//! Open source counting software for various preferential voting election systems
diff --git a/src/logger.rs b/src/logger.rs
index 4af14e4..07a19cb 100644
--- a/src/logger.rs
+++ b/src/logger.rs
@@ -27,10 +27,10 @@ impl<'a> Logger<'a> {
/// If consecutive smart log entries have the same templates, they will be merged
pub fn log(&mut self, entry: LogEntry<'a>) {
if let LogEntry::Smart(mut smart) = entry {
- if self.entries.len() > 0 {
+ if !self.entries.is_empty() {
if let LogEntry::Smart(last_smart) = self.entries.last_mut().unwrap() {
if last_smart.template1 == smart.template1 && last_smart.template2 == smart.template2 {
- &last_smart.data.append(&mut smart.data);
+ last_smart.data.append(&mut smart.data);
} else {
self.entries.push(LogEntry::Smart(smart));
}
@@ -55,9 +55,9 @@ impl<'a> Logger<'a> {
/// If consecutive smart log entries have the same templates, they will be merged
pub fn log_smart(&mut self, template1: &'a str, template2: &'a str, data: Vec<&'a str>) {
self.log(LogEntry::Smart(SmartLogEntry {
- template1: template1,
- template2: template2,
- data: data,
+ template1,
+ template2,
+ data,
}));
}
@@ -88,7 +88,7 @@ pub struct SmartLogEntry<'a> {
impl<'a> SmartLogEntry<'a> {
/// Render the [SmartLogEntry] to a [String]
pub fn render(&self) -> String {
- if self.data.len() == 0 {
+ if self.data.is_empty() {
panic!("Attempted to format smart log entry with no data");
} else if self.data.len() == 1 {
return String::from(self.template1).replace("{}", self.data.first().unwrap());
@@ -99,6 +99,7 @@ impl<'a> SmartLogEntry<'a> {
}
/// Join the given strings, with commas and terminal "and"
+#[allow(clippy::ptr_arg)]
pub fn smart_join(data: &Vec<&str>) -> String {
return format!("{} and {}", data[0..data.len()-1].join(", "), data.last().unwrap());
}
diff --git a/src/main.rs b/src/main.rs
index 9408571..3be616e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,6 +15,8 @@
* along with this program. If not, see .
*/
+#![allow(clippy::needless_return)]
+
use opentally::cli;
use clap::Clap;
@@ -27,10 +29,11 @@ struct Opts {
command: Command,
}
+#[allow(clippy::large_enum_variant)]
#[derive(Clap)]
enum Command {
Convert(cli::convert::SubcmdOptions),
- STV(cli::stv::SubcmdOptions),
+ Stv(cli::stv::SubcmdOptions),
}
fn main() {
@@ -48,6 +51,6 @@ fn main_() -> Result<(), i32> {
return match opts.command {
Command::Convert(cmd_opts) => cli::convert::main(cmd_opts),
- Command::STV(cmd_opts) => cli::stv::main(cmd_opts),
+ Command::Stv(cmd_opts) => cli::stv::main(cmd_opts),
};
}
diff --git a/src/numbers/mod.rs b/src/numbers/mod.rs
index acd2d68..f34f9c6 100644
--- a/src/numbers/mod.rs
+++ b/src/numbers/mod.rs
@@ -149,7 +149,7 @@ impl DeserializeWith, N, D> fo
impl DeserializeWith, Option, D> for SerializedOptionNumber where Archived: Deserialize {
fn deserialize_with(field: &Archived, deserializer: &mut D) -> Result
"#, opts_str))
} else {
result.push_str(r#"Counting using default options."#);
@@ -338,7 +335,7 @@ pub fn init_results_table(election: &Election, opts: &stv::STVOpti
let mut result = String::from(r#" |
"#);
if report_style == "ballots_votes" {
- result.push_str(&r#" |
"#);
+ result.push_str(r#" |
"#);
}
for candidate in election.candidates.iter() {
@@ -375,7 +372,7 @@ pub fn update_results_table(stage_num: usize, state: &CountState,
// Insert borders to left of new exclusions in Wright STV
let classes_o; // Outer version
let classes_i; // Inner version
- if opts.exclusion == stv::ExclusionMethod::Wright && (if let StageKind::ExclusionOf(_) = state.title { true } else { false }) {
+ if opts.exclusion == stv::ExclusionMethod::Wright && matches!(state.title, StageKind::ExclusionOf(_)) {
classes_o = r#" class="blw""#;
classes_i = r#"blw "#;
} else {
@@ -387,7 +384,7 @@ pub fn update_results_table(stage_num: usize, state: &CountState,
let hide_xfers_trsp;
if let StageKind::FirstPreferences = state.title {
hide_xfers_trsp = true;
- } else if opts.exclusion == stv::ExclusionMethod::Wright && (if let StageKind::ExclusionOf(_) = state.title { true } else { false }) {
+ } else if opts.exclusion == stv::ExclusionMethod::Wright && matches!(state.title, StageKind::ExclusionOf(_)) {
hide_xfers_trsp = true;
} else {
hide_xfers_trsp = false;
@@ -631,7 +628,7 @@ pub fn update_results_table(stage_num: usize, state: &CountState,
/// Get the comment for the current stage
pub fn update_stage_comments(state: &CountState, stage_num: usize) -> String {
let mut comments = state.logger.render().join(" ");
- if let Some(_) = state.transfer_table {
+ if state.transfer_table.is_some() {
comments.push_str(&format!(r##" [View detailed transfers]"##, stage_num));
}
return comments;
diff --git a/src/ties.rs b/src/ties.rs
index ced7b9d..701b70c 100644
--- a/src/ties.rs
+++ b/src/ties.rs
@@ -64,12 +64,12 @@ impl TieStrategy {
/// Break a tie between the given candidates, selecting the highest candidate
///
/// The given candidates are assumed to be tied in this round
- pub fn choose_highest<'c, N: Number>(&self, state: &mut CountState, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> {
+ pub fn choose_highest<'c, N: Number>(&self, state: &mut CountState, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
match self {
Self::Forwards => {
match &state.forwards_tiebreak {
Some(tb) => {
- let mut candidates = candidates.clone();
+ let mut candidates: Vec<&Candidate> = candidates.iter().copied().collect();
// Compare b to a to sort high-to-low
candidates.sort_unstable_by(|a, b| tb[b].cmp(&tb[a]));
if tb[candidates[0]] == tb[candidates[1]] {
@@ -88,7 +88,7 @@ impl TieStrategy {
Self::Backwards => {
match &state.backwards_tiebreak {
Some(tb) => {
- let mut candidates = candidates.clone();
+ let mut candidates: Vec<&Candidate> = candidates.iter().copied().collect();
candidates.sort_unstable_by(|a, b| tb[b].cmp(&tb[a]));
if tb[candidates[0]] == tb[candidates[1]] {
return Err(STVError::UnresolvedTie);
@@ -122,10 +122,10 @@ impl TieStrategy {
/// Break a tie between the given candidates, selecting the lowest candidate
///
/// The given candidates are assumed to be tied in this round
- pub fn choose_lowest<'c, N: Number>(&self, state: &mut CountState, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> {
+ pub fn choose_lowest<'c, N: Number>(&self, state: &mut CountState, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
match self {
Self::Forwards => {
- let mut candidates = candidates.clone();
+ let mut candidates: Vec<&Candidate> = candidates.iter().copied().collect();
candidates.sort_unstable_by(|a, b|
state.forwards_tiebreak.as_ref().unwrap()[a]
.cmp(&state.forwards_tiebreak.as_ref().unwrap()[b])
@@ -138,7 +138,7 @@ impl TieStrategy {
}
}
Self::Backwards => {
- let mut candidates = candidates.clone();
+ let mut candidates: Vec<&Candidate> = candidates.iter().copied().collect();
candidates.sort_unstable_by(|a, b|
state.backwards_tiebreak.as_ref().unwrap()[a]
.cmp(&state.backwards_tiebreak.as_ref().unwrap()[b])
@@ -161,7 +161,7 @@ impl TieStrategy {
}
/// Return all maximal items according to the given key
-pub fn multiple_max_by(items: &Vec, key: K) -> Vec
+pub fn multiple_max_by(items: &[E], key: K) -> Vec
where
K: Fn(&E) -> C
{
@@ -190,7 +190,7 @@ where
}
/// Return all minimal items according to the given key
-pub fn multiple_min_by(items: &Vec, key: K) -> Vec
+pub fn multiple_min_by(items: &[E], key: K) -> Vec
where
K: Fn(&E) -> C
{
@@ -220,7 +220,7 @@ where
/// Prompt the candidate for input, depending on CLI or WebAssembly target
#[cfg(not(target_arch = "wasm32"))]
-fn prompt<'c, N: Number>(state: &CountState, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> {
+fn prompt<'c, N: Number>(state: &CountState, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
// Show intrastage progress if required
if !state.logger.entries.is_empty() {
// Print stage details
@@ -233,7 +233,7 @@ fn prompt<'c, N: Number>(state: &CountState, opts: &STVOptions, candidates: &
// Print summary rows
print!("{}", state.describe_summary(opts));
- println!("");
+ println!();
}
println!("Multiple tied candidates:");
@@ -270,7 +270,7 @@ extern "C" {
}
#[cfg(target_arch = "wasm32")]
-fn prompt<'c, N: Number>(state: &CountState, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> {
+fn prompt<'c, N: Number>(state: &CountState, opts: &STVOptions, candidates: &[&'c Candidate], prompt_text: &str) -> Result<&'c Candidate, STVError> {
let mut message = String::new();
// Show intrastage progress if required
diff --git a/src/writer/bin.rs b/src/writer/bin.rs
index cc54bfc..fd59af7 100644
--- a/src/writer/bin.rs
+++ b/src/writer/bin.rs
@@ -31,5 +31,5 @@ pub fn write(election: Election, output: W) {
// Write output
let mut output = BufWriter::new(output);
- output.write(&buffer).expect("IO Error");
+ output.write_all(&buffer).expect("IO Error");
}
diff --git a/src/writer/blt.rs b/src/writer/blt.rs
index a41370a..10ada82 100644
--- a/src/writer/blt.rs
+++ b/src/writer/blt.rs
@@ -31,8 +31,8 @@ pub fn write(election: Election, output: W) {
// Write withdrawn candidates
if !election.withdrawn_candidates.is_empty() {
- output.write(election.withdrawn_candidates.into_iter().map(|idx| format!("-{}", idx + 1)).join(" ").as_bytes()).expect("IO Error");
- output.write(b"\n").expect("IO Error");
+ output.write_all(election.withdrawn_candidates.into_iter().map(|idx| format!("-{}", idx + 1)).join(" ").as_bytes()).expect("IO Error");
+ output.write_all(b"\n").expect("IO Error");
}
// Write ballots
@@ -43,10 +43,10 @@ pub fn write(election: Election, output: W) {
output.write_fmt(format_args!(" {}", preference.into_iter().map(|p| p + 1).join("="))).expect("IO Error");
}
- output.write(b" 0\n").expect("IO Error");
+ output.write_all(b" 0\n").expect("IO Error");
}
- output.write(b"0\n").expect("IO Error");
+ output.write_all(b"0\n").expect("IO Error");
// Write candidate names
for candidate in election.candidates {