diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..31aa6ae --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,132 @@ +use std::path::{self, Path, PathBuf}; + +use anyhow::{ensure, Result}; +use clap::Subcommand; + +use crate::state::write_projects_file; +use crate::{dirs, Projects}; + + +#[derive(Debug, Clone, clap::ValueEnum)] +pub enum ShellType { + Nu, +} + +impl ShellType { + pub fn integration(&self) -> String { + let template = match self { + Self::Nu => include_str!("./integrations/nushell.nu"), + }; + + let file_path = dirs::selected_project_file().to_str().unwrap(); + let current_exe = std::env::current_exe().unwrap(); + let current_exe = current_exe.to_str().unwrap(); + + template + .replace("{%SELECTED_PROJECT_FILE%}", file_path) + .replace("{%PROJ_EXE%}", current_exe) + } +} + + +#[derive(Debug, Subcommand)] +pub enum Command { + /// Add a project + Add { path: PathBuf }, + + /// Remove a project + Remove { path: PathBuf }, + + /// List existing projects + List, + + /// Initialize shell integration + Init { shell: ShellType }, +} + +impl Command { + pub fn execute(&self, mut projects: Projects) -> Result<()> { + match self { + Command::Add { path } => { + add_project(&mut projects, path)?; + } + Command::Remove { path } => { + remove_project(&mut projects, path)?; + } + Command::List => { + list_projects(&projects)?; + } + Command::Init { shell } => { + print_shell_integration(shell); + } + } + + Ok(()) + } +} + + +fn print_shell_integration(shell: &ShellType) { + println!("{}", shell.integration()); +} + + +fn add_project

(projects: &mut Projects, path: P) -> Result<()> +where + P: AsRef, +{ + let path = path::absolute(path)?; + + ensure!(path.is_dir(), "Project path does not exists"); + ensure!( + !projects.list.contains(&path.to_path_buf()), + "Project path already registered" + ); + + projects.list.push(path); + write_projects_file(projects)?; + println!("Added {}", projects.list.last().unwrap().display()); + + Ok(()) +} + + +fn remove_project

(projects: &mut Projects, path: P) -> Result<()> +where + P: AsRef, +{ + let path = path::absolute(path)?; + + ensure!( + projects.list.contains(&path.to_path_buf()), + "Project path not in registry" + ); + + let idx = + projects.list.iter().enumerate().find_map( + |(idx, elem)| { + if elem == &path { + Some(idx) + } else { + None + } + }, + ); + + if let Some(idx) = idx { + let proj = projects.list.remove(idx); + write_projects_file(projects)?; + println!("Removed {}", proj.display()); + } + + Ok(()) +} + + +fn list_projects(projects: &Projects) -> Result<()> { + for project in &projects.list { + println!("{project:?}") + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 50dbaa2..9bd7e99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,62 +1,27 @@ +mod cli; mod dirs; +mod state; mod tui; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::{fs, path}; +use std::path::PathBuf; -use anyhow::{ensure, Result}; -use clap::{Parser, Subcommand}; - - -#[derive(Debug, Clone, clap::ValueEnum)] -enum ShellType { - Nu, -} - -impl ShellType { - pub fn integration(&self) -> String { - let template = match self { - Self::Nu => include_str!("./integrations/nushell.nu"), - }; - - let file_path = dbg!(dirs::selected_project_file().to_str().unwrap()); - let current_exe = std::env::current_exe().unwrap(); - let current_exe = current_exe.to_str().unwrap(); - - template - .replace("{%SELECTED_PROJECT_FILE%}", file_path) - .replace("{%PROJ_EXE%}", current_exe) - } -} +use anyhow::Result; +use clap::Parser; +use cli::Command; +use state::read_projects_file; #[derive(Debug, Parser)] -pub struct Args { +struct Args { #[command(subcommand)] - cmd: Option, -} - -#[derive(Debug, Subcommand)] -enum Command { - /// Add a project - Add { path: PathBuf }, - - /// Remove a project - Remove { path: PathBuf }, - - /// List existing projects - List, - - /// Initialize shell integration - Init { shell: ShellType }, + pub cmd: Option, } #[derive(Debug, Default, serde::Serialize, serde::Deserialize)] struct Projects { - list: Vec, + pub list: Vec, } @@ -65,164 +30,10 @@ fn main() -> Result<()> { let projects = read_projects_file()?; - let result = match &args.cmd { - Some(cmd) => handle_subcommands(projects, cmd), - None => run_tui(projects), + match &args.cmd { + Some(cmd) => cmd.execute(projects)?, + None => tui::run(projects)?, }; - if let Err(err) = result { - eprintln!("{err}"); - std::process::exit(1); - } - Ok(()) } - - -fn run_tui(projects: Projects) -> Result<()> { - clear_selected_project_file()?; - let selected_project = tui::run(projects)?; - - if let Some(selected_project) = selected_project { - write_selected_project_file(selected_project)? - } - - Ok(()) -} - - -fn handle_subcommands(mut projects: Projects, cmd: &Command) -> Result<()> { - match cmd { - Command::Add { path } => { - add_project(&mut projects, path)?; - } - Command::Remove { path } => { - remove_project(&mut projects, path)?; - } - Command::List => { - list_projects(&projects)?; - } - Command::Init { shell } => { - print_shell_integration(shell); - } - } - - Ok(()) -} - - -fn print_shell_integration(shell: &ShellType) { - println!("{}", shell.integration()); -} - - -fn add_project

(projects: &mut Projects, path: P) -> Result<()> -where - P: AsRef, -{ - let path = path::absolute(path)?; - - ensure!(path.is_dir(), "Project path does not exists"); - ensure!( - !projects.list.contains(&path.to_path_buf()), - "Project path already registered" - ); - - projects.list.push(path); - write_projects_file(projects)?; - println!("Added {}", projects.list.last().unwrap().display()); - - Ok(()) -} - - -fn remove_project

(projects: &mut Projects, path: P) -> Result<()> -where - P: AsRef, -{ - let path = path::absolute(path)?; - - ensure!( - projects.list.contains(&path.to_path_buf()), - "Project path not in registry" - ); - - let idx = - projects.list.iter().enumerate().find_map( - |(idx, elem)| { - if elem == &path { - Some(idx) - } else { - None - } - }, - ); - - if let Some(idx) = idx { - let proj = projects.list.remove(idx); - write_projects_file(projects)?; - println!("Removed {}", proj.display()); - } - - Ok(()) -} - - -fn list_projects(projects: &Projects) -> Result<()> { - for project in &projects.list { - println!("{project:?}") - } - - Ok(()) -} - -fn clear_selected_project_file() -> Result<()> { - let cache_file = dirs::selected_project_file(); - - if fs::exists(cache_file)? { - fs::remove_file(cache_file)?; - } - - Ok(()) -} - -fn write_selected_project_file(path: PathBuf) -> Result<()> { - let cache_file = dirs::selected_project_file(); - - let mut file = fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(cache_file)?; - write!(file, "{}", path.display())?; - - Ok(()) -} - -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(()) -} - - -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/state.rs b/src/state.rs new file mode 100644 index 0000000..7826943 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,59 @@ +use std::fs; +use std::io::Write; +use std::path::PathBuf; + +use anyhow::Result; + +use crate::{dirs, Projects}; + + +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(path: PathBuf) -> Result<()> { + let cache_file = dirs::selected_project_file(); + + let mut file = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(cache_file)?; + write!(file, "{}", path.display())?; + + 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/tui.rs b/src/tui.rs index f2ea3ce..f2b0a50 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -9,6 +9,7 @@ use ratatui::style::{Color, Style}; use ratatui::widgets::{Block, Borders, Cell, Row, Table, TableState}; use ratatui::Frame; +use crate::state::{clear_selected_project_file, write_selected_project_file}; use crate::Projects; @@ -43,7 +44,9 @@ impl State { } -pub fn run(projects: Projects) -> Result> { +pub fn run(projects: Projects) -> Result<()> { + clear_selected_project_file()?; + let mut term = ratatui::init(); let mut state = State::new(projects); @@ -69,7 +72,11 @@ pub fn run(projects: Projects) -> Result> { ratatui::restore(); - Ok(state.selected_project) + if let Some(selected_project) = state.selected_project { + write_selected_project_file(selected_project)? + } + + Ok(()) }