refactor(cli): clean up code

cleared out main
moved subcommand handling to a method on each arg struct
moved from many, many expects to returning errors
This commit is contained in:
2026-09-01 18:42:50 -05:00
parent 95347aebb6
commit 15013dd8b5
5 changed files with 167 additions and 136 deletions
+100 -1
View File
@@ -1,5 +1,11 @@
use std::error::Error;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use crate::config;
use crate::config::Profile;
use crate::git;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(version, about = "a cli to change git users")] #[command(version, about = "a cli to change git users")]
pub struct Cli { pub struct Cli {
@@ -9,13 +15,47 @@ pub struct Cli {
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum Commands { pub enum Commands {
List, List(ListArgs),
Use(UseArgs), Use(UseArgs),
Add(AddArgs), Add(AddArgs),
Remove(RemoveArgs), Remove(RemoveArgs),
Current(CurrentArgs), Current(CurrentArgs),
} }
impl Commands {
pub fn execute(self) -> Result<(), Box<dyn Error>> {
match self {
Commands::List(args) => args.execute(),
Commands::Use(args) => args.execute(),
Commands::Add(args) => args.execute(),
Commands::Remove(args) => args.execute(),
Commands::Current(args) => args.execute(),
}
}
}
#[derive(Debug, Args)]
pub struct ListArgs;
impl ListArgs {
fn execute(self) -> Result<(), Box<dyn Error>> {
let profiles = config::get_profile_list()?;
if profiles.is_empty() {
println!("no profiles found");
return Ok(());
}
println!("{:<15} {:<20} {:<25}", "profile", "name", "email");
for profile in profiles {
println!(
"{:<15} {:<20} {:<25}",
profile.name, profile.username, profile.email
);
}
Ok(())
}
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct UseArgs { pub struct UseArgs {
pub profile_name: String, pub profile_name: String,
@@ -23,6 +63,23 @@ pub struct UseArgs {
pub global: bool, pub global: bool,
} }
impl UseArgs {
fn execute(self) -> Result<(), Box<dyn Error>> {
let profiles = config::get_profile_list()?;
let profile = profiles.iter().find(|p| p.name == self.profile_name);
if profile.is_none() {
println!("profile not found");
return Ok(());
}
let profile = profile.unwrap();
git::set_git_config("user.name", &profile.username, self.global)?;
git::set_git_config("user.email", &profile.email, self.global)?;
Ok(())
}
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct AddArgs { pub struct AddArgs {
pub profile_name: String, pub profile_name: String,
@@ -32,13 +89,55 @@ pub struct AddArgs {
pub email: String, pub email: String,
} }
impl AddArgs {
fn execute(self) -> Result<(), Box<dyn Error>> {
let mut profiles = config::get_profile_list()?;
let existing_profile = profiles.iter().position(|p| p.name == self.profile_name);
if existing_profile.is_some() {
println!("profile already exists");
return Ok(());
}
profiles.push(Profile {
name: self.profile_name,
username: self.name,
email: self.email,
});
config::write_profile_list(profiles)?;
println!("profile added");
Ok(())
}
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct RemoveArgs { pub struct RemoveArgs {
pub profile_name: String, pub profile_name: String,
} }
impl RemoveArgs {
fn execute(self) -> Result<(), Box<dyn Error>> {
let mut profiles = config::get_profile_list()?;
let index = profiles
.iter()
.position(|p| p.name == self.profile_name)
.expect("unable to find profile");
profiles.remove(index);
config::write_profile_list(profiles)?;
println!("profile removed");
Ok(())
}
}
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct CurrentArgs { pub struct CurrentArgs {
#[arg(short, long)] #[arg(short, long)]
pub global: bool, pub global: bool,
} }
impl CurrentArgs {
fn execute(self) -> Result<(), Box<dyn Error>> {
let name = git::get_git_config("user.name", self.global)?;
let email = git::get_git_config("user.email", self.global)?;
println!("current user: {} ({})", name, email);
Ok(())
}
}
+20 -22
View File
@@ -1,6 +1,7 @@
use std::{ use std::{
error::Error,
fs::{self, File}, fs::{self, File},
io::{BufReader, BufWriter, Error}, io::{BufReader, BufWriter},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@@ -13,39 +14,36 @@ pub struct Profile {
pub email: String, pub email: String,
} }
fn get_config_dir() -> PathBuf { fn get_config_dir() -> Result<PathBuf, Box<dyn Error>> {
let mut config_dir = dirs::config_dir().expect("unable to find config dir"); let mut config_dir = dirs::config_dir().ok_or("unable to get config dir")?;
config_dir.push("git-su"); config_dir.push("git-su");
config_dir Ok(config_dir)
} }
fn create_if_missing(file: &Path, content: &str) -> Result<(), Error> { fn create_if_missing(file: &Path, content: &str) -> Result<(), Box<dyn Error + 'static>> {
if !file.exists() { if !file.exists() {
fs::write(file, content) fs::write(file, content)?;
} else { }
Ok(()) Ok(())
} }
}
pub fn get_profile_list() -> Result<Vec<Profile>, String> { pub fn get_profile_list() -> Result<Vec<Profile>, Box<dyn Error>> {
let config_dir = get_config_dir(); let config_dir = get_config_dir()?;
fs::create_dir_all(&config_dir).expect("unable to create config directory"); fs::create_dir_all(&config_dir)?;
let profile_file = config_dir.join("profiles.json"); let profile_file = config_dir.join("profiles.json");
create_if_missing(profile_file.as_path(), "[]").expect("unable to create profile file"); create_if_missing(profile_file.as_path(), "[]")?;
let profile_file = File::open(&profile_file).expect("unable to open profile file"); let profile_file = File::open(&profile_file)?;
let reader = BufReader::new(profile_file); let reader = BufReader::new(profile_file);
Ok( let profiles = serde_json::from_reader::<BufReader<File>, Vec<Profile>>(reader)?;
serde_json::from_reader::<BufReader<File>, Vec<Profile>>(reader) Ok(profiles)
.expect("unable to parse json"),
)
} }
pub fn write_profile_list(profiles: Vec<Profile>) -> Result<(), String> { pub fn write_profile_list(profiles: Vec<Profile>) -> Result<(), Box<dyn Error>> {
let config_dir = get_config_dir(); let config_dir = get_config_dir()?;
fs::create_dir_all(&config_dir).expect("unable to create config directory"); fs::create_dir_all(&config_dir)?;
let profile_file = config_dir.join("profiles.json"); let profile_file = config_dir.join("profiles.json");
let profile_file = File::create(&profile_file).expect("unable to create profile file"); let profile_file = File::create(&profile_file)?;
let writer = BufWriter::new(profile_file); let writer = BufWriter::new(profile_file);
serde_json::to_writer_pretty(writer, &profiles).expect("unable to write json"); serde_json::to_writer_pretty(writer, &profiles)?;
Ok(()) Ok(())
} }
+35
View File
@@ -0,0 +1,35 @@
use std::{error::Error, process::Command};
pub fn get_git_config(key: &str, global: bool) -> Result<String, Box<dyn Error>> {
let args = if global {
vec!["config", "--global", key]
} else {
vec!["config", key]
};
let result = run_command("git", &args)?;
Ok(result.trim().to_string())
}
pub fn set_git_config(key: &str, value: &str, global: bool) -> Result<(), Box<dyn Error>> {
let args = if global {
vec!["config", "--global", key, value]
} else {
vec!["config", key, value]
};
let _ = run_command("git", &args)?;
Ok(())
}
fn run_command(command: &str, args: &[&str]) -> Result<String, Box<dyn Error>> {
let child = Command::new(command).args(args).output()?;
if child.status.success() {
let stdout = String::from_utf8(child.stdout)?;
Ok(stdout.to_owned())
} else {
let stderr = String::from_utf8(child.stderr)?;
Ok(stderr.to_owned())
}
}
+4 -89
View File
@@ -1,99 +1,14 @@
use clap::Parser; use clap::Parser;
use crate::cli::Commands;
use crate::config::Profile;
mod cli; mod cli;
mod config; mod config;
mod utils; mod git;
fn main() { fn main() {
let args = cli::Cli::parse(); let args = cli::Cli::parse();
match args.command { if let Err(why) = args.command.execute() {
Commands::List => { eprintln!("error: {}", why);
let profiles = config::get_profile_list().expect("unable to get list of profiles"); std::process::exit(1);
if profiles.is_empty() {
println!("no profiles found");
return;
}
println!("{:<15} {:<20} {:<25}", "profile", "name", "email");
for profile in profiles {
println!(
"{:<15} {:<20} {:<25}",
profile.name, profile.username, profile.email
);
} }
} }
Commands::Use(args) => {
let profiles = config::get_profile_list().expect("unable to get list of profiles");
let profile = profiles.iter().find(|p| p.name == args.profile_name);
if profile.is_none() {
println!("profile not found");
return;
}
let profile = profile.unwrap();
utils::run_command(
"git",
&add_global_if(&["config", "user.name", &profile.username], args.global),
)
.expect("command failed");
utils::run_command(
"git",
&add_global_if(&["config", "user.email", &profile.email], args.global),
)
.expect("command failed");
}
Commands::Add(args) => {
let mut profiles = config::get_profile_list().expect("unable to get list of profiles");
let existing_profile = profiles.iter().position(|p| p.name == args.profile_name);
if existing_profile.is_some() {
println!("profile already exists");
return;
}
profiles.push(Profile {
name: args.profile_name,
username: args.name,
email: args.email,
});
config::write_profile_list(profiles).expect("unable to write profile file");
println!("profile added")
}
Commands::Remove(args) => {
let mut profiles = config::get_profile_list().expect("unable to get list of profiles");
let index = profiles
.iter()
.position(|p| p.name == args.profile_name)
.expect("unable to find profile");
profiles.remove(index);
config::write_profile_list(profiles).expect("unable to write profile file");
println!("profile removed")
}
Commands::Current(args) => {
let mut name =
utils::run_command("git", &add_global_if(&["config", "user.name"], args.global))
.expect("command failed");
let mut email = utils::run_command(
"git",
&add_global_if(&["config", "user.email"], args.global),
)
.expect("command failed");
name.pop();
email.pop();
println!("current user: {} ({})", name, email);
}
}
}
fn add_global_if<'a>(args: &[&'a str], global: bool) -> Vec<&'a str> {
let mut result = args.to_vec();
if global {
result.insert(1, "--global");
}
result
}
-16
View File
@@ -1,16 +0,0 @@
use std::process::Command;
pub fn run_command(command: &str, args: &[&str]) -> Result<String, String> {
let child = Command::new(command)
.args(args)
.output()
.expect("failed to execute process");
if child.status.success() {
let stdout = String::from_utf8(child.stdout).unwrap();
Ok(stdout.to_owned())
} else {
let stderr = String::from_utf8(child.stderr).unwrap();
Err(stderr)
}
}