pub mod vote_matrix;
pub mod vote_tree;
pub use self::vote_matrix::VoteMatrix;
pub use self::vote_tree::{Transfer, VoteTree};
use std::error::Error;
use std::io;
pub enum VoteToken {
DeclareCandidates(u32),
DeclareSeats(u32),
WithdrawCandidate(u32),
BallotRepeat(u64),
Vote(i32, u32),
CandidateName(u32, String),
ElectionTitle(String),
EndBallot,
EndBallots,
ReadFailure(VoteReadError),
}
pub enum VoteReadError {
InvalidToken(usize, String),
CannotParse(usize, String),
WrongSyntax(usize),
InvalidValue(usize, String),
EqualVote,
InvalidSeats(u32),
InvalidMeta,
IOError(io::Error),
}
impl Error for VoteReadError {}
impl std::convert::From<std::io::Error> for VoteReadError {
fn from(x: std::io::Error) -> Self {
VoteReadError::IOError(x)
}
}
use std::fmt;
impl fmt::Debug for VoteReadError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl fmt::Display for VoteReadError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::VoteReadError::*;
match self {
InvalidToken(line, token) => {
write!(f, "Unexpected token >>{}<< on line {}", token, line)
}
CannotParse(line, token) => write!(f, "Cannot parse >>{}<< on line {}", token, line),
WrongSyntax(line) => write!(f, "Wrong syntax on line {}", line),
InvalidValue(line, token) => write!(f, "Invalid value >>{}<< on line {}", token, line),
EqualVote => write!(f, "Equal vote in a count that does not support such"),
InvalidSeats(value) => write!(f, "Invalid seats number ({})", value),
InvalidMeta => write!(f, "Invalid metadata"),
IOError(x) => write!(f, "{}", x),
}
}
}
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
#[derive(Debug, Clone)]
pub struct Metadata {
seats: Option<u32>,
withdrawn: HashSet<u32>,
candidate_names: HashMap<u32, String>,
title: Option<String>,
}
impl Metadata {
pub fn new(
seats: Option<u32>,
withdrawn: HashSet<u32>,
candidate_names: HashMap<u32, String>,
title: Option<String>,
) -> Metadata {
Metadata {
seats,
withdrawn,
candidate_names,
title,
}
}
pub fn empty() -> Metadata {
Metadata::new(None, HashSet::new(), HashMap::new(), None)
}
pub fn get_name(&self, who: u32) -> Option<&String> {
self.candidate_names.get(&who)
}
pub fn get_seats(&self) -> u32 {
self.seats.unwrap_or(0)
}
pub fn get_title(&self) -> Option<&String> {
self.title.as_ref()
}
pub fn map_withdrawn<F: FnMut(u32)>(&self, mut f: F) {
for e in self.withdrawn.iter() {
f(*e)
}
}
}
pub trait Tally: Sized {
fn get_meta(&self) -> &'_ Metadata;
fn get_seats(&self) -> u32 {
self.get_meta().get_seats()
}
fn get_candidates(&self) -> u32;
fn name_candidate(&self, id: u32) -> Option<&String> {
self.get_meta().get_name(id)
}
fn map_withdrawn<F: FnMut(u32)>(&self, f: F) {
self.get_meta().map_withdrawn(f)
}
fn from_blt_file(name: &str) -> Result<Self, VoteReadError>
where
Result<Self, VoteReadError>: FromIterator<VoteToken>,
{
use crate::io::BltReader;
use std::fs::File;
use std::io::BufReader;
BltReader::new(&mut BufReader::new(File::open(name)?)).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_file_errors() {
use crate::VoteMatrix;
let x: Result<VoteMatrix, _> = Tally::from_blt_file("non-existent");
assert!(x.is_err());
}
#[test]
fn file_read() {
use crate::Irv;
let x: Result<VoteTree, _> = Tally::from_blt_file("examples/a123.blt");
assert!(Irv::new(&x.unwrap()).run().is_ok());
}
}