use std::env::join_paths;
use std::ops::Deref;
use std::path::PathBuf;
use color_eyre::eyre::Result;
use itertools::Itertools;
use crate::cli::command::Command;
use crate::config::Config;
use crate::config::MissingRuntimeBehavior::{Prompt, Warn};
use crate::direnv::DirenvDiff;
use crate::env_diff::{EnvDiff, EnvDiffOperation, EnvDiffPatches};
use crate::output::Output;
use crate::shell::{get_shell, ShellType};
use crate::{dirs, env, hook_env};
#[derive(Debug, clap::Args)]
#[clap(hide = true)]
pub struct HookEnv {
#[clap(long, short)]
shell: Option<ShellType>,
}
impl Command for HookEnv {
fn run(self, mut config: Config, out: &mut Output) -> Result<()> {
if config.settings.missing_runtime_behavior == Prompt {
config.settings.missing_runtime_behavior = Warn;
}
config.ensure_installed()?;
self.clear_old_env(out);
let env = config.env()?;
let diff = EnvDiff::new(&env::PRISTINE_ENV, env);
let mut patches = diff.to_patches();
patches.extend(self.build_path_operations(&config)?);
patches.push(self.build_diff_operation(&diff)?);
patches.push(self.build_watch_operation(&config)?);
let output = self.build_env_commands(&patches);
out.stdout.write(output);
self.display_status(&config, out);
Ok(())
}
}
impl HookEnv {
fn build_env_commands(&self, patches: &EnvDiffPatches) -> String {
let shell = get_shell(self.shell);
let mut output = String::new();
for patch in patches.iter() {
match patch {
EnvDiffOperation::Add(k, v) | EnvDiffOperation::Change(k, v) => {
output.push_str(&shell.set_env(k, v));
}
EnvDiffOperation::Remove(k) => {
output.push_str(&shell.unset_env(k));
}
}
}
output
}
fn clear_old_env(&self, out: &mut Output) {
let patches = env::__RTX_DIFF.reverse().to_patches();
let output = self.build_env_commands(&patches);
out.stdout.write(output);
}
fn display_status(&self, config: &Config, out: &mut Output) {
let installed_versions = config
.ts
.list_current_installed_versions()
.into_iter()
.map(|v| v.to_string())
.collect_vec();
if !installed_versions.is_empty() && !*env::RTX_QUIET {
rtxstatusln!(out, "{}", installed_versions.join(" "));
}
}
fn build_path_operations(&self, config: &Config) -> Result<Vec<EnvDiffOperation>> {
let installs = config.list_paths()?;
let other = env::PATH
.clone()
.into_iter()
.filter(|p| !p.starts_with(dirs::INSTALLS.deref()))
.collect_vec();
let new_path = join_paths([installs.clone(), other].concat())?
.to_string_lossy()
.to_string();
let mut ops = vec![EnvDiffOperation::Add("PATH".into(), new_path)];
if let Some(input) = env::DIRENV_DIFF.deref() {
match self.update_direnv_diff(input, &installs) {
Ok(op) => {
ops.push(op);
}
Err(err) => warn!("failed to update DIRENV_DIFF: {}", err),
}
}
Ok(ops)
}
fn update_direnv_diff(&self, input: &str, installs: &Vec<PathBuf>) -> Result<EnvDiffOperation> {
let mut diff = DirenvDiff::parse(input)?;
for install in installs {
diff.add_path_to_old_and_new(install)?;
}
Ok(EnvDiffOperation::Change("DIRENV_DIFF".into(), diff.dump()?))
}
fn build_diff_operation(&self, diff: &EnvDiff) -> Result<EnvDiffOperation> {
Ok(EnvDiffOperation::Add(
"__RTX_DIFF".into(),
diff.serialize()?,
))
}
fn build_watch_operation(&self, config: &Config) -> Result<EnvDiffOperation> {
Ok(EnvDiffOperation::Add(
"__RTX_WATCH".into(),
hook_env::serialize_watches(&hook_env::build_watches(config)?)?,
))
}
}
#[cfg(test)]
mod test {
use crate::assert_cli;
#[test]
fn test_hook_env() {
assert_cli!("hook-env", "-s", "fish");
}
}