refactor: Split CLI and state into submodules
This commit is contained in:
parent
b59d47a769
commit
456111acae
132
src/cli.rs
Normal file
132
src/cli.rs
Normal 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(())
|
||||||
|
}
|
215
src/main.rs
215
src/main.rs
@ -1,62 +1,27 @@
|
|||||||
|
mod cli;
|
||||||
mod dirs;
|
mod dirs;
|
||||||
|
mod state;
|
||||||
mod tui;
|
mod tui;
|
||||||
|
|
||||||
|
|
||||||
use std::io::Write;
|
use std::path::PathBuf;
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::{fs, path};
|
|
||||||
|
|
||||||
use anyhow::{ensure, Result};
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::Parser;
|
||||||
|
use cli::Command;
|
||||||
|
use state::read_projects_file;
|
||||||
#[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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct Args {
|
struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
cmd: Option<Command>,
|
pub 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 },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||||
struct Projects {
|
struct Projects {
|
||||||
list: Vec<PathBuf>,
|
pub list: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -65,164 +30,10 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let projects = read_projects_file()?;
|
let projects = read_projects_file()?;
|
||||||
|
|
||||||
let result = match &args.cmd {
|
match &args.cmd {
|
||||||
Some(cmd) => handle_subcommands(projects, cmd),
|
Some(cmd) => cmd.execute(projects)?,
|
||||||
None => run_tui(projects),
|
None => tui::run(projects)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = result {
|
|
||||||
eprintln!("{err}");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
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
59
src/state.rs
Normal 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)
|
||||||
|
}
|
11
src/tui.rs
11
src/tui.rs
@ -9,6 +9,7 @@ use ratatui::style::{Color, Style};
|
|||||||
use ratatui::widgets::{Block, Borders, Cell, Row, Table, TableState};
|
use ratatui::widgets::{Block, Borders, Cell, Row, Table, TableState};
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
|
|
||||||
|
use crate::state::{clear_selected_project_file, write_selected_project_file};
|
||||||
use crate::Projects;
|
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 term = ratatui::init();
|
||||||
let mut state = State::new(projects);
|
let mut state = State::new(projects);
|
||||||
|
|
||||||
@ -69,7 +72,11 @@ pub fn run(projects: Projects) -> Result<Option<PathBuf>> {
|
|||||||
|
|
||||||
ratatui::restore();
|
ratatui::restore();
|
||||||
|
|
||||||
Ok(state.selected_project)
|
if let Some(selected_project) = state.selected_project {
|
||||||
|
write_selected_project_file(selected_project)?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user