use std::ffi::OsString;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::{fs, io};
use crate::errors::{ConfGuardError, ConfGuardResult, ConfGuardContext};
use clap::{Command, CommandFactory, Parser};
use clap_complete::{generate, Generator};
use colored::Colorize;
use tracing::{debug, info};
use crate::cli::args::{Cli, Commands};
use crate::core::config::get_confguard_config_path;
use crate::core::get_settings;
use crate::core::ConfGuard;
use crate::sops::manager::SopsManager;
use crate::util::copy_file_from_resources;
use crate::util::helper::path::to_absolute_path;
use crate::util::link::replace_link_with_target;
pub fn print_completions<G: Generator>(gen: G, cmd: &mut Command) {
generate(gen, cmd, cmd.get_name().to_string(), &mut io::stdout());
}
pub fn execute_command(cli: &Cli) -> ConfGuardResult<()> {
if let Some(generator) = cli.generator {
let mut cmd = Cli::command();
eprintln!("Generating completion file for {generator:?}...");
print_completions(generator, &mut cmd);
}
if cli.info {
print_info()?
}
match &cli.command {
Some(Commands::Show { source_dir }) => {
let abs_dir = to_absolute_path(source_dir)?;
show(&abs_dir)
}
Some(Commands::Guard {
source_dir,
absolute,
}) => {
let abs_dir = to_absolute_path(source_dir)?;
guard(&abs_dir, *absolute)
}
Some(Commands::Unguard { source_dir }) => {
let abs_dir = to_absolute_path(source_dir)?;
unguard(&abs_dir)
}
Some(Commands::GuardOne {
source_dir,
source_path,
}) => {
let abs_dir = to_absolute_path(source_dir)?;
let abs_path = to_absolute_path(source_path)?;
guard_one(&abs_dir, &abs_path)
}
Some(Commands::Relink { envrc_path }) => {
let abs_path = to_absolute_path(envrc_path)?;
relink(&abs_path)
}
Some(Commands::ReplaceLink { link }) => {
let path: &Path = link.as_ref();
let abs_link = if path.is_absolute() {
path.to_path_buf()
} else {
let current_dir =
std::env::current_dir().context("Failed to get current directory")?;
current_dir.join(path)
};
replace_link(&abs_link)
}
Some(Commands::FixRunConfig { source_dir }) => {
let abs_dir = to_absolute_path(source_dir)?;
fix_run_config(&abs_dir)
}
Some(Commands::Init {
source_dir,
template_path,
}) => {
let abs_dir = to_absolute_path(source_dir)?;
let abs_template = template_path
.as_ref()
.map(|p| to_absolute_path(p))
.transpose()?;
init_envrc(&abs_dir, abs_template.as_deref())
}
Some(Commands::Settings) => print_settings(),
Some(Commands::SopsEnc { dir }) => {
let conguard_config_path = get_confguard_config_path(None as Option<&Path>)?;
let manager = SopsManager::new(conguard_config_path.as_path())?;
let base_dir = dir.as_ref().map(|d| to_absolute_path(d)).transpose()?;
manager.encrypt_files(base_dir.as_deref())
}
Some(Commands::SopsDec { dir }) => {
let conguard_config_path = get_confguard_config_path(None as Option<&Path>)?;
let manager = SopsManager::new(conguard_config_path.as_path())?;
let base_dir = dir.as_ref().map(|d| to_absolute_path(d)).transpose()?;
manager.decrypt_files(base_dir.as_deref())
}
Some(Commands::SopsClean { dir }) => {
let conguard_config_path = get_confguard_config_path(None as Option<&Path>)?;
let manager = SopsManager::new(conguard_config_path.as_path())?;
let base_dir = dir.as_ref().map(|d| to_absolute_path(d)).transpose()?;
manager.clean_files(base_dir.as_deref())
}
Some(Commands::SopsInit { template_path }) => {
let abs_template = template_path
.as_ref()
.map(|p| to_absolute_path(p))
.transpose()?;
sops_init(abs_template)
}
None => Ok(()),
}
}
fn print_info() -> ConfGuardResult<()> {
println!("CONFGUARD_BASE_DIR: {}", get_settings().confguard_base_dir);
use clap::CommandFactory;
Cli::command()
.get_author()
.map(|a| println!("AUTHOR: {}", a));
Cli::command()
.get_version()
.map(|v| println!("VERSION: {}", v));
Ok(())
}
fn print_settings() -> ConfGuardResult<()> {
let settings = get_settings();
println!("Settings:");
println!(" Base: {}", settings.confguard_base_dir);
println!(" Version: {}", settings.version);
Ok(())
}
fn init_envrc(source_dir: &Path, template_path: Option<&Path>) -> ConfGuardResult<()> {
debug!(
"source_dir: {:?}, template_path: {:?}",
source_dir, template_path
);
let path = PathBuf::from(source_dir).join(".envrc");
if path.exists() {
return Err(ConfGuardError::FileAlreadyExists(path));
}
if path.exists() {
if let Some(_) = ConfGuard::from_envrc(&path)? {
return Err(ConfGuardError::AlreadyGuarded(source_dir.to_path_buf()));
}
}
match template_path {
Some(template) => {
fs::copy(template, &path)
.with_context(|| format!("Failed to copy template: {}", template.display()))?;
println!(".envrc created from template: {}", template.display());
}
None => {
copy_file_from_resources("dot.envrc", path.to_str().unwrap(), false)
.context("Could not copy resources/dot.envrc -> .envrc")?;
println!(".envrc created from default template");
}
}
println!("\nCreated: {}", path.to_string_lossy());
Ok(())
}
fn fix_run_config(source_dir: &Path) -> ConfGuardResult<()> {
debug!("source_dir: {:?}", source_dir);
let guard = ConfGuard::from_guarded_project(Path::new(source_dir))?;
let run_config_path = guard.source_dir.join(".idea/runConfigurations/rsenv.sh");
copy_file_from_resources("rsenv.sh", run_config_path.to_str().unwrap(), true)
.context("Could not copy rsenv.sh")?;
println!(
"Environment file created:\n{}\n",
run_config_path.to_string_lossy()
);
Ok(())
}
fn show(source_dir: &Path) -> ConfGuardResult<()> {
debug!("source_dir: {:?}", source_dir);
match ConfGuard::from_guarded_project(Path::new(source_dir)) {
Ok(guard) => {
println!("{:#?}", guard);
Ok(())
}
Err(e) => {
println!("Error: {}", e);
Ok(())
}
}
}
fn guard(source_dir: &Path, absolute: bool) -> ConfGuardResult<()> {
debug!("source_dir: {:?}, absolute: {}", source_dir, absolute);
let source_path = PathBuf::from(source_dir);
let mut guard = ConfGuard::new_for_guarding(source_path, !absolute)?;
guard.guard(absolute)
}
fn unguard(source_dir: &Path) -> ConfGuardResult<()> {
debug!("source_dir: {:?}", source_dir);
let mut guard = ConfGuard::from_guarded_project(Path::new(source_dir))?;
guard.unguard()
}
fn guard_one(source_dir: &Path, source_path: &Path) -> ConfGuardResult<()> {
debug!(
"source_dir: {:?}, source_path: {:?}",
source_dir, source_path
);
let guard = ConfGuard::from_guarded_project(Path::new(source_dir))?;
let source = PathBuf::from(source_path)
.canonicalize()
.context("Failed to resolve source path")?;
if !source.exists() {
return Err(ConfGuardError::SourceNotFound(source));
}
if !source.starts_with(&guard.source_dir) {
return Err(ConfGuardError::SourceNotInProject(source));
}
let relative = source
.strip_prefix(&guard.source_dir)
.context("Failed to calculate relative path")?;
let target_dir = guard
.target_dir
.as_ref()
.ok_or_else(|| ConfGuardError::TargetDirectoryNotSet)?;
let target = target_dir.join(relative);
if target.exists() {
return Err(ConfGuardError::TargetAlreadyExists(target));
}
if let Some(parent) = target.parent() {
fs::create_dir_all(parent)?;
}
fs::rename(&source, &target)?;
std::os::unix::fs::symlink(&target, &source)?;
println!("Guarded: {} -> {}", source.display(), target.display());
Ok(())
}
fn relink(envrc_path: &Path) -> ConfGuardResult<()> {
debug!("envrc_path: {:?}", envrc_path);
match ConfGuard::relink(Path::new(envrc_path)) {
Ok(_) => Ok(()),
Err(e) => {
if e.to_string().contains("Link already exists")
|| e.to_string().contains("Link exists but points elsewhere")
{
println!("{}", e);
Ok(())
} else {
Err(e)
}
}
}
}
fn replace_link(link: &Path) -> ConfGuardResult<()> {
debug!("link: {:?}", link);
let path = Path::new(link);
if !path.exists() {
return Err(ConfGuardError::LinkNotFound(path.to_path_buf()));
}
if !path.symlink_metadata()?.file_type().is_symlink() {
return Err(ConfGuardError::NotSymbolicLink(path.to_path_buf()));
}
replace_link_with_target(path)?;
println!("Replaced link with target: {:?}", path);
Ok(())
}
fn sops_init(template_path: Option<PathBuf>) -> ConfGuardResult<()> {
debug!("template_path: {:?}", template_path);
let settings = get_settings();
let config_path = PathBuf::from(&settings.confguard_base_dir).join("confguard.toml");
if config_path.exists() {
return Err(ConfGuardError::ConfigFileAlreadyExists(config_path));
}
fs::create_dir_all(&settings.confguard_base_dir).context("Failed to create base directory")?;
match template_path {
Some(template) => {
fs::copy(template.clone(), &config_path).with_context({
let template_display = template.display().to_string();
move || format!("Failed to copy template: {}", template_display)
})?;
println!("Configuration copied from template: {}", template.display());
}
None => {
copy_file_from_resources("confguard.toml", config_path.to_str().unwrap(), false)
.context("Could not copy default confguard.toml template")?;
println!("Default configuration created");
}
}
println!("\nCreated: {}", config_path.to_string_lossy());
Ok(())
}