diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/commands/add.rs b/src/commands/add.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/commands/delete.rs b/src/commands/delete.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/commands/list.rs b/src/commands/list.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/commands/update.rs b/src/commands/update.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.rs b/src/main.rs index ad107ff..8ddad01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,149 @@ mod task; -fn main() { - let t = task::Task::from_string("1. [>] \"Task 1\"; due: 2020-01-21T00:00; priority: 1; tags: tag1, tag2".to_string()).unwrap(); - let is_valid = t.validate(); - println!("{}", is_valid); - println!("{}", t); +mod storage; +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser)] +#[command(name = "citrine", version = "0.1.0")] +#[command(author = "syntaxbullet ")] +#[command(about = "A simple task manager following the unix philosophy")] + +pub struct Cli { + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand)] + +enum Commands { + Add(Add), + Update(Update), + Delete(Delete), + List(List), +} + +#[derive(Args)] +/// Add a new task to the task list +struct Add { + /// The title of the task + title: String, + /// The due date of the task in rfc3339 format (e.g. 2021-01-01T00:00:00+00:00) + #[arg(short = 'd', long = "due")] + due_date: Option, + /// The priority of the task [0-9] + #[arg(short = 'p', long = "priority")] + priority: Option, + /// The tags of the task , must be a comma separated list + #[arg(short = 't', long = "tags")] + tags: Option>, } +#[derive(Args)] +/// Update an existing task in the task list +struct Update { + /// The id of the task to update + id: u8, + /// The due date of the task in rfc3339 format (e.g. 2021-01-01T00:00:00+00:00) + #[arg(short = 'd', long = "due")] + due_date: Option, + /// The priority of the task (0-9) + #[arg(short = 'p', long = "priority")] + priority: Option, + /// The tags of the task, must be a comma separated list + #[arg(short = 't', long = "tags")] + tags: Option>, + /// The status of the task + #[arg(short = 's', long = "status")] + status: Option, + /// The title of the task + #[arg(short = 'm', long = "message")] + title: Option, + /// remove tags from the task + #[arg(short = 'r', long = "remove-tags")] + remove_tag: Option, + /// append tags to the task + #[arg(short = 'a', long = "append-tags")] + append_tag: Option, +} + +#[derive(Args)] +/// Delete an existing task from the task list +struct Delete { + /// The id of the task to remove + id: u8, +} + +#[derive(Args)] +/// List all tasks in the task list filtered by the given options +struct List {} + +fn main() { + let cli = Cli::parse(); + + match cli.command { + Some(Commands::Add(add)) => { + let last_id = storage::get_last_id().unwrap_or(0); + let task = task::Task { + id: last_id + 1, + title: add.title, + status: ' ', + due_date: add.due_date, + priority: add.priority, + tags: add.tags.unwrap_or(Vec::new()), + }; + // validate the task + if !task.validate() { + println!("Invalid task"); + return; + } + // append the task to the task list + storage::append_task(task).unwrap(); + + + } + Some(Commands::Update(update)) => { + // get the task to update + let mut task = storage::read_task(update.id).unwrap(); + // update the task + if let Some(due_date) = update.due_date { + task.due_date = Some(due_date); + } + if let Some(priority) = update.priority { + task.priority = Some(priority); + } + if let Some(status) = update.status { + task.status = status.chars().next().unwrap(); + } + if let Some(title) = update.title { + task.title = title; + } + if let Some(append_tag) = update.append_tag { + task.tags.push(append_tag); + } + if let Some(remove_tag) = update.remove_tag { + task.tags.retain(|tag| tag != &remove_tag); + } + // validate the task + if !task.validate() { + println!("Invalid task"); + return; + } + // update the task in the task list + storage::update_task(update.id, task).unwrap(); + + } + Some(Commands::Delete(delete)) => { + // delete the task from the task list + storage::delete_task(delete.id).unwrap(); + } + Some(Commands::List(_list)) => { + // get all tasks from the task list + let tasks = storage::read_tasks().unwrap(); + // print all tasks + for task in tasks { + println!("{}", task); + } + } + None => { + println!("No command given"); + } + } +} \ No newline at end of file diff --git a/src/storage.rs b/src/storage.rs index e69de29..fb462a6 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -0,0 +1,84 @@ + +use std::error::Error; +use std::fs::{File, OpenOptions}; + +use crate::task::Task; +use std::io::Write; +use std::io::Read; + +pub fn append_task(task: Task) -> Result<(), Box> { + let mut file = OpenOptions::new() + .append(true) + .create(true) + .open(".citrine")?; + // validate the task + if !task.validate() { + println!("Invalid task"); + return Ok(()); + } + writeln!(file, "{}", task)?; + Ok(()) +} +pub fn read_task(id: u8) -> Result> { + let mut file = File::open(".citrine")?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + for line in contents.lines() { + let task = Task::from_string(line.to_string()).unwrap(); + if task.id == id { + return Ok(task); + } + } + Err("Task not found".into()) +} +pub fn read_tasks() -> Result, Box> { + let mut file = File::open(".citrine")?; + // if the file is empty, return an empty vector + if file.metadata()?.len() == 0 { + return Ok(Vec::new()); + } + // if the file does not exist, return an empty vector + if !std::path::Path::new(".citrine").exists() { + return Ok(Vec::new()); + } + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let mut tasks = Vec::new(); + for line in contents.lines() { + // skip unparseable lines + if let Some(task) = Task::from_string(line.to_string()) { + tasks.push(task); + } + } + Ok(tasks) +} +pub fn write_tasks(tasks: Vec) -> Result<(), Box> { + let mut file = File::create(".citrine")?; + for task in tasks { + writeln!(file, "{}", task)?; + } + Ok(()) +} +pub fn delete_task(id: u8) -> Result<(), Box> { + let mut tasks = read_tasks()?; + tasks.retain(|task| task.id != id); + write_tasks(tasks)?; + Ok(()) +} +pub fn update_task(id: u8, task: Task) -> Result<(), Box> { + let mut tasks = read_tasks()?; + tasks.retain(|t| t.id != id); + tasks.push(task); + write_tasks(tasks)?; + Ok(()) +} +pub fn get_last_id() -> Result> { + let tasks = read_tasks()?; + let mut last_id = 0; + for task in tasks { + if task.id > last_id { + last_id = task.id; + } + } + Ok(last_id) +} diff --git a/src/task.rs b/src/task.rs index 1c9d192..8d34bab 100644 --- a/src/task.rs +++ b/src/task.rs @@ -74,6 +74,10 @@ impl Task { _ => (), } } + // validate the task + if !result.validate() { + return None; + } Some(result) } pub fn validate(&self) -> bool { diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index e69de29..0000000