use std::{
ffi::OsString,
fs,
io::{ErrorKind, IsTerminal, Write},
};
use anyhow::{anyhow, Result};
use clap::{Subcommand, ValueHint};
use copy_dir::copy_dir;
use owo_colors::OwoColorize;
use crate::{config::CONFIG, update_cfg, utils::init_msg};
#[derive(Subcommand)]
pub enum ProfileCommands {
#[clap(alias = "s", alias = "choose", alias = "activate")]
Select {
#[clap(value_hint = ValueHint::Other)]
name: String,
},
Ignore {
#[clap(value_hint = ValueHint::Other)]
name: String,
},
Unignore {
#[clap(value_hint = ValueHint::Other)]
name: String,
},
#[clap(alias("ls"))]
List,
#[clap(alias("n"))]
New {
#[clap(value_hint = ValueHint::Other)]
name: OsString,
#[arg(long, short)]
force: bool,
},
#[clap(alias = "dupe", alias = "cp", alias = "copy")]
Clone {
#[clap(value_hint = ValueHint::Other)]
source: String,
#[clap(value_hint = ValueHint::Other)]
new: Option<String>,
#[arg(long, short)]
force: bool,
},
}
pub fn handle(command: &ProfileCommands) -> Result<()> {
match command {
ProfileCommands::List => list_profiles(),
ProfileCommands::New { name, force } => new_profile(name, *force),
ProfileCommands::Clone { source, new, force } => clone_profile(source, new, *force),
ProfileCommands::Select { name } => activate_profile(name),
ProfileCommands::Ignore { name } => {
update_cfg!(ignore(name))?;
println!("Added {} to ignore list", name.bright_cyan());
Ok(())
}
ProfileCommands::Unignore { name } => {
update_cfg!(unignore(name))?;
println!("Removed {} from ignore list", name.bright_cyan());
Ok(())
}
}
}
fn activate_profile(name: &String) -> Result<()> {
let Some(dir) = CONFIG.game_dir() else {
return init_msg();
};
if CONFIG.is_ignored(name) {
println!(
"Directory {} is on the ignore list. Please run '{}' and try again.",
name.bright_red(),
format!("papa profile unignore {}", name).bright_cyan()
);
return Err(anyhow!("Profile was ignored"));
}
let real = dir.join(name);
if !real.try_exists()? {
println!("Profile {} doesn't exist", name.bright_cyan());
return Err(anyhow!("Profile not found"));
}
update_cfg!(profile(name))?;
println!("Made {} the active profile", name.bright_cyan());
Ok(())
}
fn list_profiles() -> Result<()> {
let Some(dir) = CONFIG.game_dir() else {
return init_msg();
};
let mut profiles = vec![];
for candidate in dir.read_dir()? {
let candidate = candidate?;
if !candidate.file_type()?.is_dir()
|| CONFIG.is_ignored(
candidate
.file_name()
.to_str()
.expect("Unable to convert from OsString"),
)
{
continue;
}
let path = candidate.path();
profiles.push(path);
}
if !std::io::stdout().is_terminal() {
let out = std::io::stdout();
for p in profiles {
if let Some(name) = p.file_name().and_then(|os| os.to_str()) {
if let Err(e) = writeln!(out.lock(), "{name}") {
if e.kind() != ErrorKind::BrokenPipe {
return Err(e.into());
}
}
}
}
return Ok(());
}
if profiles.is_empty() {
println!("No profiles found");
return Ok(());
}
println!("Available profiles:");
for name in profiles
.iter()
.filter_map(|v| v.file_name().and_then(|os| os.to_str()))
{
println!(
"{:<4}{}",
if name == CONFIG.current_profile() {
"-->"
} else {
""
}
.bright_green(),
name.bright_cyan()
);
}
Ok(())
}
fn new_profile(name: &OsString, force: bool) -> Result<()> {
let Some(dir) = CONFIG.game_dir() else {
return init_msg();
};
let prof = dir.join(name);
if prof.try_exists()? {
if force {
fs::remove_dir_all(&prof)?;
} else {
println!("A folder of that name already exists, remove it first");
return Ok(());
}
}
fs::create_dir(&prof)?;
println!("Created profile {:?}", name.bright_cyan());
Ok(())
}
fn clone_profile(source: &String, new: &Option<String>, force: bool) -> Result<()> {
let Some(game) = CONFIG.game_dir() else {
return init_msg();
};
let source_dir = game.join(source);
let target_dir = if let Some(target) = new {
game.join(target)
} else {
game.join(format!("{}-copy", source))
};
let target_name = target_dir
.file_name()
.expect("Missing file name?")
.to_string_lossy();
if target_dir.try_exists()? {
if force {
fs::remove_dir_all(&target_dir)?;
} else {
println!("Profile '{}' already exists", target_name.bright_green());
}
}
copy_dir(source_dir, &target_dir)?;
println!(
"Cloned profile '{}' to '{}'",
source.bright_green(),
target_name.bright_green()
);
Ok(())
}