use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Kind {
Network,
Volume,
Secret,
Image,
Container,
}
impl Kind {
pub fn noun(self) -> &'static str {
match self {
Kind::Network => "Network",
Kind::Volume => "Volume",
Kind::Secret => "Secret",
Kind::Image => "Image",
Kind::Container => "Container",
}
}
pub fn from_noun(noun: &str) -> Option<Self> {
match noun {
"Network" => Some(Kind::Network),
"Volume" => Some(Kind::Volume),
"Secret" => Some(Kind::Secret),
"Image" => Some(Kind::Image),
"Container" => Some(Kind::Container),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum State {
Pending,
Working(String),
Done(String),
}
#[derive(Debug, Clone)]
pub struct Row {
pub kind: Kind,
pub name: String,
pub state: State,
pub started: Option<Instant>,
pub elapsed: Option<Duration>,
}
impl Row {
pub fn duration(&self, now: Instant) -> Option<Duration> {
match (&self.state, self.elapsed, self.started) {
(State::Done(_), Some(d), _) => Some(d),
(State::Working(_), _, Some(start)) => Some(now.saturating_duration_since(start)),
_ => None,
}
}
}
#[derive(Debug, Default)]
pub struct Board {
rows: Vec<Row>,
flushed: usize,
}
impl Board {
pub fn new(resources: impl IntoIterator<Item = (Kind, String)>) -> Self {
Self {
rows: resources
.into_iter()
.map(|(kind, name)| Row {
kind,
name,
state: State::Pending,
started: None,
elapsed: None,
})
.collect(),
flushed: 0,
}
}
pub fn start(&mut self, kind: Kind, name: &str, verb: &str, now: Instant) {
let idx = self.index_of(kind, name).unwrap_or_else(|| {
self.rows.push(Row {
kind,
name: name.to_string(),
state: State::Pending,
started: None,
elapsed: None,
});
self.rows.len() - 1
});
let row = &mut self.rows[idx];
row.state = State::Working(verb.to_string());
row.started.get_or_insert(now);
}
pub fn finish(&mut self, kind: Kind, name: &str, verb: &str, now: Instant) {
let idx = self.index_of(kind, name).unwrap_or_else(|| {
self.rows.push(Row {
kind,
name: name.to_string(),
state: State::Pending,
started: None,
elapsed: None,
});
self.rows.len() - 1
});
let row = &mut self.rows[idx];
row.elapsed = row.started.map(|s| now.saturating_duration_since(s));
row.state = State::Done(verb.to_string());
}
pub fn take_completed_prefix(&mut self) -> Vec<Row> {
let mut out = Vec::new();
while let Some(row) = self.rows.get(self.flushed) {
if !matches!(row.state, State::Done(_)) {
break;
}
out.push(row.clone());
self.flushed += 1;
}
out
}
pub fn live_rows(&self) -> &[Row] {
&self.rows[self.flushed.min(self.rows.len())..]
}
pub fn tally(&self) -> (usize, usize) {
(
self.rows
.iter()
.filter(|r| matches!(r.state, State::Done(_)))
.count(),
self.rows.len(),
)
}
pub fn is_complete(&self) -> bool {
let (done, total) = self.tally();
done == total
}
fn index_of(&self, kind: Kind, name: &str) -> Option<usize> {
self.rows
.iter()
.position(|r| r.kind == kind && r.name == name)
}
}
#[cfg(test)]
#[path = "board_tests.rs"]
mod tests;