diff --git a/src/cli.rs b/src/cli.rs index f309a37..549777e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3,8 +3,8 @@ use std::path::{self, Path, PathBuf}; use anyhow::{ensure, Result}; use clap::Subcommand; -use crate::state::write_projects_file; -use crate::{dirs, Project, Projects}; +use crate::dirs; +use crate::projects::{write_projects_file, Project, Projects}; #[derive(Debug, Clone, clap::ValueEnum)] @@ -81,13 +81,13 @@ where let project = Project::from(path); ensure!( - !projects.list.contains(&project), + !projects.list().contains(&project), "Project path already registered" ); - projects.list.push(project); + projects.add(project); write_projects_file(projects)?; - println!("Added {}", projects.list.last().unwrap().path().display()); + println!("Added {}", projects.list().last().unwrap().path().display()); Ok(()) } @@ -101,23 +101,22 @@ where let project = Project::from(path); ensure!( - projects.list.contains(&project), + projects.list().contains(&project), "Project path not in registry" ); - let idx = - projects.list.iter().enumerate().find_map( - |(idx, elem)| { - if elem == &project { - Some(idx) - } else { - None - } - }, - ); + let idx = projects.iter().enumerate().find_map( + |(idx, elem)| { + if elem == &project { + Some(idx) + } else { + None + } + }, + ); if let Some(idx) = idx { - let proj = projects.list.remove(idx); + let proj = projects.remove(idx); write_projects_file(projects)?; println!("Removed {}", proj.path().display()); } @@ -127,8 +126,13 @@ where fn list_projects(projects: &Projects) -> Result<()> { - for (idx, project) in projects.list.iter().enumerate() { - println!("{}: {} ({})", idx + 1, project.name(), project.path().display()) + for (idx, project) in projects.iter().enumerate() { + println!( + "{}: {} ({})", + idx + 1, + project.name(), + project.path().display() + ) } Ok(()) diff --git a/src/main.rs b/src/main.rs index 8c68847..10504b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,13 @@ mod cli; mod dirs; -mod state; +mod projects; mod tui; -use std::path::PathBuf; - use anyhow::Result; use clap::Parser; use cli::Command; -use state::read_projects_file; +use projects::read_projects_file; #[derive(Debug, Parser)] @@ -19,35 +17,6 @@ struct Args { } -#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] -struct Projects { - pub list: Vec, -} - - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -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() - } -} - -impl From for Project { - fn from(path: PathBuf) -> Self { - Self { path } - } -} - - fn main() -> Result<()> { init_tracing()?; diff --git a/src/state.rs b/src/projects.rs similarity index 63% rename from src/state.rs rename to src/projects.rs index 93cae53..a176d40 100644 --- a/src/state.rs +++ b/src/projects.rs @@ -1,9 +1,75 @@ +mod v1; + use std::fs; use std::io::Write; use anyhow::Result; +pub use v1::Project; -use crate::{dirs, Project, Projects}; +use crate::dirs; + + +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct Projects { + list: Vec, +} + +impl Projects { + pub fn iter(&self) -> std::slice::Iter<'_, Project> { + self.list.iter() + } + + pub fn list(&self) -> &[Project] { + &self.list + } + + pub fn remove(&mut self, idx: usize) -> Project { + self.list.remove(idx) + } + + pub fn add(&mut self, project: Project) { + self.list.push(project); + } +} + + +impl<'a> IntoIterator for &'a Projects { + type IntoIter = std::slice::Iter<'a, Project>; + type Item = &'a Project; + + fn into_iter(self) -> Self::IntoIter { + 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<()> { @@ -29,31 +95,3 @@ pub fn write_selected_project_file(project: &Project) -> Result<()> { Ok(()) } - -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 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) -} diff --git a/src/projects/v1.rs b/src/projects/v1.rs new file mode 100644 index 0000000..7cc8ec4 --- /dev/null +++ b/src/projects/v1.rs @@ -0,0 +1,24 @@ +use std::path::PathBuf; + + +#[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() + } +} + +impl From for Project { + fn from(path: PathBuf) -> Self { + Self { path } + } +} diff --git a/src/tui.rs b/src/tui.rs index 17ba011..6eef17d 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -11,8 +11,9 @@ use ratatui::Frame; use tracing::trace; use tui_input::{Input, InputRequest}; -use crate::state::{clear_selected_project_file, write_selected_project_file}; -use crate::{Project, Projects}; +use crate::projects::{ + clear_selected_project_file, write_selected_project_file, Project, Projects, +}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -219,14 +220,14 @@ fn draw_status(state: &mut State, frame: &mut Frame, area: Rect) { fn draw_list(state: &mut State, frame: &mut Frame, area: Rect) { let search_value = state.search.value(); - let projects = &state.projects.list; + let projects = &state.projects; state.filtered_projects = if search_value.is_empty() { - projects.clone() + projects.list().to_vec() } else { let mut filtered = VecDeque::new(); - for project in projects.iter() { + for project in projects { match fuzzy_search(search_value, &project.name()) { Some(indices) => { trace!(?search_value, ?project, ?indices);