pub mod errors;
use serde::{Deserialize, Serialize};
use serde_json::ser::to_string as to_json_string;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::io::{self, Write};
#[derive(Serialize, Deserialize, Debug, PartialEq, Hash, Eq, Ord, PartialOrd)]
pub struct Task {
pub name: String,
pub progress: Status,
}
impl Task {
pub fn new(name: impl Into<String>) -> Self {
Task {
name: name.into(),
progress: Status::Pending { total: None },
}
}
pub fn total(mut self, new_total: usize) -> Self {
self.progress = Status::Pending {
total: Some(new_total),
};
self
}
pub fn name(mut self, new_name: impl Into<String>) -> Self {
self.name = new_name.into();
self
}
}
impl From<String> for Task {
fn from(name: String) -> Self {
Task::new(name)
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Hash, Eq, Ord, PartialOrd)]
#[serde(tag = "status")]
pub enum Status {
#[serde(rename = "pending")]
Pending { total: Option<usize> },
#[serde(rename = "error")]
Error { message: String },
#[serde(rename = "finished")]
Finished,
#[serde(rename = "running")]
Running,
#[serde(rename = "in_progress")]
InProgress {
done: usize,
total: usize,
},
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct TaskManager {
pub tasks: HashMap<String, Task>,
pub task_list: Vec<String>,
pub task_counter: usize,
silent: bool,
}
impl TaskManager {
#[inline]
fn output(&self) {
if self.silent {
return;
}
println!(
"{}",
to_json_string(&self.tasks.values().collect::<Vec<_>>()).expect("Should never happen")
);
io::stdout().flush().unwrap();
}
pub fn new() -> Self {
Self {
tasks: HashMap::new(),
task_list: Vec::new(),
task_counter: 0,
silent: false,
}
}
pub fn add_task(&mut self, task: Task) -> Result<(), errors::InterprogError> {
let name = task.name.clone();
match self.tasks.entry(name.clone()) {
Entry::Occupied(_) => return Err(errors::InterprogError::TaskAlreadyExists),
Entry::Vacant(entry) => entry.insert(task),
};
self.task_list.push(name);
Ok(())
}
pub fn start_task(&mut self, task_name: impl AsRef<str>) -> Result<(), errors::InterprogError> {
let task = &mut self
.tasks
.get_mut(task_name.as_ref())
.ok_or(errors::InterprogError::NonexistentTask)?;
if let Status::Pending { total } = &task.progress {
match total {
Some(total) => {
task.progress = Status::InProgress {
done: 0,
total: *total,
};
}
None => task.progress = Status::Running,
}
} else {
return Err(errors::InterprogError::TaskAlreadyStarted);
}
self.output();
Ok(())
}
pub fn start(&mut self) -> Result<(), errors::InterprogError> {
let task_name: String = self
.task_list
.get(self.task_counter)
.ok_or(errors::InterprogError::NonexistentTask)?
.clone();
self.start_task(&task_name)
}
pub fn increment_task(
&mut self,
task_name: impl AsRef<str>,
by: usize,
) -> Result<(), errors::InterprogError> {
let task = &mut self
.tasks
.get_mut(task_name.as_ref())
.ok_or(errors::InterprogError::NonexistentTask)?;
match &task.progress {
Status::Pending { total: Some(total) } => {
task.progress = Status::InProgress {
done: 1,
total: *total,
};
}
Status::InProgress {
done,
total,
} => {
if done >= total {
return Err(errors::InterprogError::TaskAlreadyFinished);
}
task.progress = Status::InProgress {
done: done + by,
total: *total,
};
}
Status::Running | Status::Pending { total: None } => {
return Err(errors::InterprogError::InvalidTaskType)
}
Status::Finished | Status::Error { message: _ } => {
return Err(errors::InterprogError::TaskAlreadyFinished)
}
}
self.output();
Ok(())
}
pub fn increment(&mut self, by: usize) -> Result<(), errors::InterprogError> {
let task_name: String = self
.task_list
.get(self.task_counter)
.ok_or(errors::InterprogError::NonexistentTask)?
.clone();
self.increment_task(&task_name, by)
}
pub fn finish_task(
&mut self,
task_name: impl AsRef<str>,
) -> Result<(), errors::InterprogError> {
let task = &mut self
.tasks
.get_mut(task_name.as_ref())
.ok_or(errors::InterprogError::NonexistentTask)?;
task.progress = Status::Finished;
self.task_counter += 1;
self.output();
Ok(())
}
pub fn finish(&mut self) -> Result<(), errors::InterprogError> {
let task_name: String = self
.task_list
.get(self.task_counter)
.ok_or(errors::InterprogError::NonexistentTask)?
.clone();
self.finish_task(&task_name)
}
pub fn error_task(
&mut self,
task_name: impl AsRef<str>,
message: impl Into<String>,
) -> Result<(), errors::InterprogError> {
let task = &mut self
.tasks
.get_mut(task_name.as_ref())
.ok_or(errors::InterprogError::NonexistentTask)?;
task.progress = Status::Error {
message: message.into(),
};
self.task_counter += 1;
self.output();
Ok(())
}
pub fn error(&mut self, message: impl Into<String>) -> Result<(), errors::InterprogError> {
let task_name: String = self
.task_list
.get(self.task_counter)
.ok_or(errors::InterprogError::NonexistentTask)?
.clone();
self.error_task(&task_name, message)
}
}
impl Default for TaskManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use crate::{Task, TaskManager};
#[test]
fn it_works() {
let mut manager = TaskManager::new();
manager.add_task(Task::new("name")).unwrap();
manager.start().unwrap();
manager.finish().unwrap();
}
#[test]
fn real_example() {
let mut manager = TaskManager::new();
manager.add_task(Task::new("Log in")).unwrap();
manager.start().unwrap();
manager.finish().unwrap();
let classes = vec!["English", "History", "Science", "Math"];
for class in &classes {
manager
.add_task(Task::new(format!("Scraping {class}")).total(4))
.unwrap();
}
for _ in 0..4 {
for class in &classes {
manager
.increment_task(format!("Scraping {class}"), 1)
.unwrap();
}
}
}
#[test]
fn static_names() {
let mut manager = TaskManager::new();
manager.add_task(Task::new("Log in")).unwrap();
manager.start_task("Log in").unwrap();
manager.finish().unwrap();
}
}