fn tally_votes(dir: &Path) -> std::io::Result<HashMap<String, HashSet<String>>> {
let mut tally: HashMap<String, HashSet<String>> = HashMap::new();
- if let Ok(vfile) = std::fs::File::open(dir.to_owned().join("votes")) {
- for liner in std::io::BufReader::new(vfile).lines() {
- let line = liner?;
- if let Some((voter, datum)) = line.split_once(' ') {
- if voter.len() == COOKIE_LENGTH {
- if let Some((vote, candidate)) = datum.split_once(' ') {
- if vote == "0" {
- if let Some(entry) = tally.get_mut(candidate) {
- entry.remove(voter);
+ match std::fs::File::open(dir.to_owned().join("votes")) {
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(tally),
+ Err(e) => Err(e),
+ Ok(vfile) => {
+ for liner in std::io::BufReader::new(vfile).lines() {
+ let line = liner?;
+ if let Some((voter, datum)) = line.split_once(' ') {
+ if voter.len() == COOKIE_LENGTH {
+ if let Some((vote, candidate)) = datum.split_once(' ') {
+ if vote == "0" {
+ if let Some(entry) = tally.get_mut(candidate) {
+ entry.remove(voter);
+ }
+ } else if vote == "1" {
+ tally
+ .entry(candidate.to_owned())
+ .or_default()
+ .insert(voter.to_owned());
}
- } else if vote == "1" {
- tally
- .entry(candidate.to_owned())
- .or_default()
- .insert(voter.to_owned());
}
}
}
}
+ Ok(tally)
}
}
- Ok(tally)
+}
+
+fn read_elim_list(dir: &Path) -> std::io::Result<HashSet<String>> {
+ match std::fs::File::open(dir.join("eliminated")) {
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(HashSet::new()),
+ Err(e) => Err(e),
+ Ok(elimfile) => std::io::BufReader::new(elimfile).lines().collect(),
+ }
}
fn valid_id_char(c: u8) -> bool {
- (b'A'..=b'Z').contains(&c) || (b'a'..=b'z').contains(&c) || (b'0'..=b'9').contains(&c)
+ c.is_ascii_alphanumeric()
}
fn make_random_id() -> [u8; COOKIE_LENGTH] {
<style>
th { font-size: 70%; text-align: left }
input { transform: scale(1.5) }
+ .eliminated { text-decoration: line-through; }
div { animation: 2s infinite linear spin }
@keyframes spin {
from { transform:rotate(0) }
<script>
window.onload = function() {
for (cb of document.getElementsByTagName('input')) {
+ if (cb.parentElement.parentElement.classList.contains('eliminated'))
+ continue;
cb.addEventListener('click', (function(cb) {
return function() {
cb.style.display = 'none'
None
};
let tally = tally_votes(dir).map_err(|_| cgi::text_response(503, "Couldn't tally votes"))?;
+ let elim =
+ read_elim_list(dir).map_err(|_| cgi::text_response(503, "Couldn't read eliminations"))?;
let cfile = std::fs::File::open(dir.join("candidates"))
.map_err(|_| cgi::text_response(503, "No candidates"))?;
let mut response = cgi::html_response(
} else {
""
};
+ let class = if elim.contains(&c) { "eliminated" } else { "" };
format!(
- "<tr>
+ "<tr class=\"{class}\">
<td>{count}</td>
<td><input type=\"checkbox\" autocomplete=\"off\" {checked} disabled></td>
<td>{c}</td>