refactor: Split CLI and state into submodules

This commit is contained in:
Patrick Auernig 2024-11-27 16:29:36 +01:00
parent b59d47a769
commit 456111acae
4 changed files with 213 additions and 204 deletions

132
src/cli.rs Normal file
View File

@ -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<P>(projects: &mut Projects, path: P) -> Result<()>
where
P: AsRef<Path>,
{
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<P>(projects: &mut Projects, path: P) -> Result<()>
where
P: AsRef<Path>,
{
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(())
}

View File

@ -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<Command>,
}
#[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<Command>,
}
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
struct Projects {
list: Vec<PathBuf>,
pub list: Vec<PathBuf>,
}
@ -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<P>(projects: &mut Projects, path: P) -> Result<()>
where
P: AsRef<Path>,
{
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<P>(projects: &mut Projects, path: P) -> Result<()>
where
P: AsRef<Path>,
{
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<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)
}

59
src/state.rs Normal file
View File

@ -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<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)
}

View File

@ -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<Option<PathBuf>> {
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<Option<PathBuf>> {
ratatui::restore();
Ok(state.selected_project)
if let Some(selected_project) = state.selected_project {
write_selected_project_file(selected_project)?
}
Ok(())
}