feat(core): Add basic versioning for projects file
This commit is contained in:
parent
005b80fc58
commit
77378d3939
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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)?,
|
||||
|
@ -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<Projects> {
|
||||
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<Projects> {
|
||||
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(())
|
||||
}
|
||||
|
@ -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<PathBuf> for Project {
|
||||
@ -22,3 +14,18 @@ impl From<PathBuf> for Project {
|
||||
Self { path }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn deserialize_project_list<P>(path: P) -> Result<Vec<super::Project>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let file = std::fs::File::open(path)?;
|
||||
trace!("Deserializing and migrating V1 project list");
|
||||
|
||||
let list = bincode::deserialize_from::<_, Vec<Project>>(file)
|
||||
.map(|projects| projects.into_iter().map(From::from).collect())?;
|
||||
|
||||
Ok(list)
|
||||
}
|
||||
|
62
src/projects/v2.rs
Normal file
62
src/projects/v2.rs
Normal file
@ -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<String>,
|
||||
}
|
||||
|
||||
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<PathBuf> for Project {
|
||||
fn from(path: PathBuf) -> Self {
|
||||
Self {
|
||||
path,
|
||||
custom_name: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v1::Project> for Project {
|
||||
fn from(value: v1::Project) -> Self {
|
||||
Self {
|
||||
path: value.path,
|
||||
custom_name: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn deserialize_project_list<P>(path: P) -> Result<Vec<super::Project>>
|
||||
where
|
||||
P: AsRef<Path>
|
||||
{
|
||||
let file = std::fs::File::open(path)?;
|
||||
|
||||
trace!("Deserializing V2 project list");
|
||||
let list = bincode::deserialize_from::<_, Vec<super::Project>>(file)?;
|
||||
|
||||
Ok(list)
|
||||
}
|
57
src/tui.rs
57
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<Message>, 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<Message>) -> 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<Vec<usize>> {
|
||||
}
|
||||
|
||||
|
||||
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::*;
|
||||
|
Loading…
Reference in New Issue
Block a user