diff --git a/README.md b/README.md index 98013d3..91db59e 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,6 @@ A (currently in development) to do app for the command line. - Options for editing additional config - `config` - `editor` subcommand for setting default text editor -- Editing individual tasks directly (command: `edit`) - - Create temporary file for the data - - Fork process to open the text editor - - Wait for process to return - - Open, read and then delete the temporary file - - Deserialize as a map so each value can be checked and useful errors reported - Listing tasks in vault (command: `list`) - Options for which field to order by, and how to order (ascending or descending) - Options for which columns to include diff --git a/src/edit.rs b/src/edit.rs new file mode 100644 index 0000000..6761bce --- /dev/null +++ b/src/edit.rs @@ -0,0 +1,61 @@ +use std::fs; +use std::mem; +use std::path; +use std::process; + +use crate::tasks; +use crate::error; +use crate::tasks::Id; + +pub fn edit_raw(id : Id, vault_folder : path::PathBuf) -> Result<(), error::Error> { + + let mut task = tasks::Task::load(id, vault_folder.clone(), false)?; + + let temp_path = vault_folder.join("temp.toml"); + + fs::copy(task.path(), &temp_path)?; + + // This will be a matter of configuration later on. + let mut command = process::Command::new("nvim"); + + command + .current_dir(&vault_folder) + .args(vec![&temp_path]); + + let mut child = command.spawn()?; + + let status = child.wait()?; + + if !status.success() { + match status.code() { + Some(code) => Err(error::Error::Generic(format!("Process responded with a non-zero status code: {}", code))), + None => Err(error::Error::Generic(String::from("Process was interrupted by signal"))), + } + } + else { + let file_contents = fs::read_to_string(&temp_path)?; + + let mut edited_task = tasks::Task::load_direct(temp_path.clone(), true)?; + + if edited_task.data.id != task.data.id { + Err(error::Error::Generic(String::from("You cannot change the ID of a task in a direct edit"))) + } + else { + if edited_task.data.dependencies != task.data.dependencies { + // This is where the other dependencies graph needs to be updated. + } + if edited_task.data.name != task.data.name { + // This is where the hashmap from id to string needs to be updated. + } + + mem::swap(&mut edited_task.data, &mut task.data); + mem::drop(edited_task); + + task.save()?; + + fs::remove_file(&temp_path)?; + + Ok(()) + } + } +} diff --git a/src/main.rs b/src/main.rs index 8463790..28e73fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![allow(dead_code, unused_variables)] mod git; +mod edit; mod vault; mod error; mod tasks; @@ -8,6 +9,8 @@ mod state; mod config; mod colour; +use tasks::Id; + use std::path; #[derive(clap::Parser, Debug)] @@ -28,25 +31,28 @@ enum Command { #[clap(short, long)] tags : Vec, #[clap(short, long)] - dependencies : Vec, + dependencies : Vec, #[clap(short, long, value_enum)] priority : Option, }, /// Displays the specified task in detail. View { - id : tasks::Id, + id : Id, + }, + Edit { + id : Id, }, /// Delete a task completely. Delete { - id : tasks::Id, + id : Id, }, /// Discard a task without deleting the underlying file. Discard { - id : tasks::Id, + id : Id, }, /// Mark a task as complete. Complete { - id : tasks::Id, + id : Id, }, /// Run Git commands at the root of the vault. #[clap(trailing_var_arg=true)] @@ -160,7 +166,11 @@ fn program() -> Result<(), error::Error> { View { id } => { let task = tasks::Task::load(id, vault_folder.clone(), true)?; task.display()?; - } + }, + Edit { id } => { + edit::edit_raw(id, vault_folder.clone())?; + println!("Updated task {}", colour::id(&id.to_string())); + }, Discard { id } => { let mut task = tasks::Task::load(id, vault_folder.clone(), false)?; task.data.discarded = true; diff --git a/src/tasks.rs b/src/tasks.rs index b8bec81..4233807 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -110,12 +110,11 @@ impl Task { }) } - /// The read_only flag is so that the file will not be truncated, and therefore doesn't need to - /// be saved when finished. - pub fn load(id : Id, vault_folder : path::PathBuf, read_only : bool) -> Result { - let path = Task::check_exists(id, &vault_folder)?; - + /// Less graceful error handling on this for task not existing. Only use this externally when + /// in edit mode. + pub fn load_direct(path : path::PathBuf, read_only : bool) -> Result { let file_contents = fs::read_to_string(&path)?; + let file = if read_only { fs::File::open(&path)? } @@ -135,6 +134,18 @@ impl Task { }) } + /// The read_only flag is so that the file will not be truncated, and therefore doesn't need to + /// be saved when finished. + pub fn load(id : Id, vault_folder : path::PathBuf, read_only : bool) -> Result { + let path = Task::check_exists(id, &vault_folder)?; + + Task::load_direct(path, read_only) + } + + pub fn path(&self) -> &path::Path { + &self.path + } + pub fn check_exists(id : Id, vault_folder : &path::Path) -> Result { let path = vault_folder.join("notes").join(format!("{}.toml", id)); if path.exists() && path.is_file() { @@ -243,14 +254,16 @@ pub fn list(vault_folder : &path::Path) -> Result<(), error::Error> { for id in ids { let task = Task::load(id, vault_folder.to_path_buf(), true)?; - table.add_row( - vec![ - task.data.id.to_string(), - task.data.name, - format_hash_set(&task.data.tags)?, - task.data.priority.to_string() - ] - ); + if !task.data.discarded && !task.data.complete { + table.add_row( + vec![ + task.data.id.to_string(), + task.data.name, + format_hash_set(&task.data.tags)?, + task.data.priority.to_string() + ] + ); + } } println!("{}", table);