use anyhow::Context;
use anyhow::{bail, Result};
use std::fs;
use std::path::PathBuf;
#[derive(Debug)]
pub struct Project {
pub path: PathBuf,
pub steps: usize,
pub data: ProjectData,
}
impl Project {
pub const fn new(project_file: PathBuf, steps: usize, name: String) -> Self {
Self {
path: project_file,
data: ProjectData {
name,
tasks: Vec::new(),
},
steps,
}
}
pub fn load(project_file: PathBuf, steps: usize) -> Result<Self> {
let file_content =
fs::read_to_string(project_file.as_path()).context("unable to read project file")?;
let data: ProjectData =
toml::from_str(file_content.as_str()).context("invalid project file syntax")?;
Ok(Self {
path: project_file,
data,
steps,
})
}
pub fn save(&mut self) -> Result<()> {
let serialized = toml::to_string_pretty(&self.data)?;
fs::write(self.path.as_path(), serialized).context("unable to write project file")?;
Ok(())
}
pub fn get_task_mut(&mut self, index: usize) -> Result<&mut Task> {
for t in &mut self.data.tasks {
if t.index == index {
return Ok(t);
}
}
bail!("no task with index {}", &index)
}
pub fn add(&mut self, name: String, completed: bool) {
self.data
.tasks
.push(Task::new(name, completed, self.next_index()))
}
pub fn remove(&mut self, index: usize) {
self.data.tasks.retain(|t| t.index != index);
}
pub fn remove_all(&mut self) {
self.data.tasks.clear();
}
pub fn remove_completed(&mut self) {
self.data.tasks.retain(|t| !t.completed);
}
pub fn mark_completion_all(&mut self, completed: bool) {
for mut t in &mut self.data.tasks {
t.completed = completed;
}
}
pub fn mark_completion(&mut self, index: usize, completed: bool) -> Result<()> {
let task = self.get_task_mut(index)?;
task.completed = completed;
Ok(())
}
pub fn next_index(&self) -> usize {
if self.data.tasks.is_empty() {
return 0;
}
let mut highest = 0;
for t in &self.data.tasks {
if t.index > highest {
highest = t.index;
}
}
if highest >= 999 {
0
} else {
highest + 1
}
}
}
#[derive(Debug)]
pub struct ProjectData {
pub name: String,
pub tasks: Vec<Task>,
}
#[derive(Debug)]
pub struct Task {
pub desc: String,
pub index: usize,
pub completed: bool,
}
impl Task {
pub fn new(name: impl Into<String>, completed: bool, index: usize) -> Self {
Self {
desc: name.into(),
completed,
index,
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::{Project, Task};
#[test]
fn next_index() {
let mut project = Project::new(PathBuf::new(), 0, String::from("dummy"));
project.data.tasks.push(Task::new("a", false, 5));
project.data.tasks.push(Task::new("a", false, 16));
assert_eq!(project.next_index(), 17);
}
#[test]
fn wrap_index() {
let mut project = Project::new(PathBuf::new(), 0, String::from("dummy"));
project.data.tasks.push(Task::new("a", false, 999));
project.data.tasks.push(Task::new("a", false, 3));
assert_eq!(project.next_index(), 0);
}
}