From 77378d3939f7f603a90234704fb551bbd005fcb8 Mon Sep 17 00:00:00 2001 From: Patrick Auernig Date: Mon, 6 Jan 2025 22:18:27 +0100 Subject: [PATCH] feat(core): Add basic versioning for projects file --- src/cli.rs | 6 +-- src/main.rs | 4 +- src/projects.rs | 91 ++++++++++++++++++---------------------------- src/projects/v1.rs | 33 ++++++++++------- src/projects/v2.rs | 62 +++++++++++++++++++++++++++++++ src/tui.rs | 57 +++++++++++++++++++++++++---- 6 files changed, 172 insertions(+), 81 deletions(-) create mode 100644 src/projects/v2.rs diff --git a/src/cli.rs b/src/cli.rs index 549777e..9de86a3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,7 +4,7 @@ use anyhow::{ensure, Result}; use clap::Subcommand; use crate::dirs; -use crate::projects::{write_projects_file, Project, Projects}; +use crate::projects::{Project, Projects}; #[derive(Debug, Clone, clap::ValueEnum)] @@ -86,7 +86,7 @@ where ); projects.add(project); - write_projects_file(projects)?; + projects.write_file()?; println!("Added {}", projects.list().last().unwrap().path().display()); Ok(()) @@ -117,7 +117,7 @@ where if let Some(idx) = idx { let proj = projects.remove(idx); - write_projects_file(projects)?; + projects.write_file()?; println!("Removed {}", proj.path().display()); } diff --git a/src/main.rs b/src/main.rs index 10504b2..71e40d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ mod tui; use anyhow::Result; use clap::Parser; use cli::Command; -use projects::read_projects_file; +use projects::Projects; #[derive(Debug, Parser)] @@ -22,7 +22,7 @@ fn main() -> Result<()> { let args = Args::parse(); - let projects = read_projects_file()?; + let projects = Projects::read_file()?; match &args.cmd { Some(cmd) => cmd.execute(projects)?, diff --git a/src/projects.rs b/src/projects.rs index a176d40..15e2bfa 100644 --- a/src/projects.rs +++ b/src/projects.rs @@ -1,10 +1,10 @@ mod v1; +mod v2; use std::fs; -use std::io::Write; use anyhow::Result; -pub use v1::Project; +pub use v2::Project; use crate::dirs; @@ -30,6 +30,39 @@ impl Projects { pub fn add(&mut self, project: Project) { self.list.push(project); } + + pub fn get_mut(&mut self, idx: usize) -> Option<&mut Project> { + self.list.get_mut(idx) + } + + pub fn read_file() -> Result { + let projects_file = dirs::data_path().join("projects"); + + if !projects_file.exists() { + return Ok(Projects::default()); + } + + let list = match v2::deserialize_project_list(&projects_file) { + Ok(list) => list, + Err(_) => v1::deserialize_project_list(&projects_file)?, + }; + + Ok(Projects { list }) + } + + pub fn write_file(&self) -> Result<()> { + let projects_file = dirs::data_path().join("projects"); + + let file = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(projects_file)?; + + bincode::serialize_into(file, &self.list)?; + + Ok(()) + } } @@ -41,57 +74,3 @@ impl<'a> IntoIterator for &'a Projects { self.list.iter() } } - - -pub fn read_projects_file() -> Result { - let projects_file = dirs::data_path().join("projects"); - - if !projects_file.exists() { - return Ok(Projects::default()); - } - - let file = std::fs::File::open(projects_file)?; - let projects = bincode::deserialize_from(file)?; - - Ok(projects) -} - - -pub fn write_projects_file(projects: &Projects) -> Result<()> { - let projects_file = dirs::data_path().join("projects"); - - let file = fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(projects_file)?; - - bincode::serialize_into(file, projects)?; - - Ok(()) -} - - -pub fn clear_selected_project_file() -> Result<()> { - let cache_file = dirs::selected_project_file(); - - if fs::exists(cache_file)? { - fs::remove_file(cache_file)?; - } - - Ok(()) -} - -pub fn write_selected_project_file(project: &Project) -> Result<()> { - let cache_file = dirs::selected_project_file(); - let path = project.path(); - - let mut file = fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(cache_file)?; - write!(file, "{}", path.display())?; - - Ok(()) -} diff --git a/src/projects/v1.rs b/src/projects/v1.rs index 7cc8ec4..8642086 100644 --- a/src/projects/v1.rs +++ b/src/projects/v1.rs @@ -1,20 +1,12 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; + +use anyhow::Result; +use tracing::trace; #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Project { - path: PathBuf, -} - -impl Project { - pub fn path(&self) -> &PathBuf { - &self.path - } - - pub fn name(&self) -> String { - let name = self.path.file_name().unwrap(); - name.to_string_lossy().to_string() - } + pub path: PathBuf, } impl From for Project { @@ -22,3 +14,18 @@ impl From for Project { Self { path } } } + + +#[tracing::instrument(skip_all)] +pub fn deserialize_project_list

(path: P) -> Result> +where + P: AsRef, +{ + let file = std::fs::File::open(path)?; + trace!("Deserializing and migrating V1 project list"); + + let list = bincode::deserialize_from::<_, Vec>(file) + .map(|projects| projects.into_iter().map(From::from).collect())?; + + Ok(list) +} diff --git a/src/projects/v2.rs b/src/projects/v2.rs new file mode 100644 index 0000000..b44f96f --- /dev/null +++ b/src/projects/v2.rs @@ -0,0 +1,62 @@ +use std::io; +use std::path::{Path, PathBuf}; + +use anyhow::Result; +use tracing::trace; + +use super::v1; + + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Project { + pub path: PathBuf, + pub custom_name: Option, +} + +impl Project { + pub fn path(&self) -> &PathBuf { + &self.path + } + + pub fn name(&self) -> String { + match &self.custom_name { + Some(name) => name.clone(), + None => { + let name = self.path.file_name().unwrap(); + name.to_string_lossy().to_string() + } + } + } +} + +impl From for Project { + fn from(path: PathBuf) -> Self { + Self { + path, + custom_name: None, + } + } +} + +impl From for Project { + fn from(value: v1::Project) -> Self { + Self { + path: value.path, + custom_name: None, + } + } +} + + +#[tracing::instrument(skip_all)] +pub fn deserialize_project_list

(path: P) -> Result> +where + P: AsRef +{ + let file = std::fs::File::open(path)?; + + trace!("Deserializing V2 project list"); + let list = bincode::deserialize_from::<_, Vec>(file)?; + + Ok(list) +} diff --git a/src/tui.rs b/src/tui.rs index 6eef17d..f317568 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,4 +1,6 @@ use std::collections::VecDeque; +use std::fs; +use std::io::Write; use std::sync::mpsc; use anyhow::Result; @@ -11,9 +13,8 @@ use ratatui::Frame; use tracing::trace; use tui_input::{Input, InputRequest}; -use crate::projects::{ - clear_selected_project_file, write_selected_project_file, Project, Projects, -}; +use crate::dirs; +use crate::projects::{Project, Projects}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -129,10 +130,14 @@ fn handle_key_event(state: &mut State, tx: &mut mpsc::Sender, event: Ke state.mode = Mode::Search; Message::Noop } - (Mode::Rename, KeyModifiers::NONE, KeyCode::Char(c)) => { + (Mode::Rename, KeyModifiers::NONE | KeyModifiers::SHIFT, KeyCode::Char(c)) => { state.rename.handle(InputRequest::InsertChar(c)); Message::Noop } + (Mode::Rename, KeyModifiers::NONE, KeyCode::Backspace) => { + state.rename.handle(InputRequest::DeletePrevChar); + Message::Noop + } (Mode::Rename, KeyModifiers::NONE, KeyCode::Enter) => Message::RenameEntry, (_, _, KeyCode::Enter) => Message::Confirm, @@ -168,16 +173,28 @@ fn handle_messages(state: &mut State, rx: &mut mpsc::Receiver) -> Resul } Message::Confirm => { if let Some(selected) = state.project_table.selected() { - if let Some(project_path) = state.filtered_projects.get(selected) { + if let Some(project) = state.filtered_projects.get(selected) { state.should_exit = true; - state.selected_project = Some(project_path.clone()); + state.selected_project = Some(project.clone()); }; } } Message::SearchUpdate => { state.project_table.select_first(); } - Message::RenameEntry => {} + Message::RenameEntry => { + if let Some(selected) = state.project_table.selected() { + if let Some(project) = state.filtered_projects.get(selected) { + if let Some(pos) = state.projects.iter().position(|p| p == project) { + if let Some(project) = state.projects.get_mut(pos) { + project.custom_name = Some(state.rename.value().to_string()); + }; + } + } + } + state.mode = Mode::Search; + state.projects.write_file()?; + } _ => (), } @@ -308,6 +325,32 @@ fn fuzzy_search(search: &str, text: &str) -> Option> { } +pub fn clear_selected_project_file() -> Result<()> { + let cache_file = dirs::selected_project_file(); + + if fs::exists(cache_file)? { + fs::remove_file(cache_file)?; + } + + Ok(()) +} + + +pub fn write_selected_project_file(project: &Project) -> Result<()> { + let cache_file = dirs::selected_project_file(); + let path = project.path(); + + let mut file = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(cache_file)?; + write!(file, "{}", path.display())?; + + Ok(()) +} + + #[cfg(test)] mod test { use super::*;