Skip to main content

zoi_project/
environment.rs

1use super::{config, executor};
2use anyhow::{Result, anyhow};
3use clap_complete::Shell;
4use colored::*;
5use dialoguer::{Select, theme::ColorfulTheme};
6use std::collections::HashMap;
7use std::io::{self, Write};
8use std::process::Stdio;
9use zoi_core::utils;
10
11pub fn setup(env_alias: Option<&str>, config: &config::ProjectConfig) -> Result<()> {
12    if config.environments.is_empty() {
13        return Err(anyhow!("No environments defined in zoi.yaml"));
14    }
15
16    let env_to_setup = match env_alias {
17        Some(alias) => config
18            .environments
19            .iter()
20            .find(|e| e.cmd == alias)
21            .ok_or_else(|| anyhow!("Environment '{alias}' not found in zoi.yaml"))?
22            .clone(),
23        None => {
24            let selections: Vec<&str> = config
25                .environments
26                .iter()
27                .map(|e| e.name.as_str())
28                .collect();
29            let selection = Select::with_theme(&ColorfulTheme::default())
30                .with_prompt("Choose an environment to set up")
31                .items(&selections)
32                .default(0)
33                .interact_opt()?
34                .ok_or(anyhow!("No environment chosen."))?;
35
36            config.environments[selection].clone()
37        }
38    };
39
40    println!(
41        "\n{} Setting up environment: {}...",
42        "::".bold().blue(),
43        env_to_setup.name.bold()
44    );
45
46    check_packages(config)?;
47
48    let platform = utils::get_platform()?;
49
50    let run_cmds = match &env_to_setup.run {
51        config::PlatformOrStringVec::StringVec(v) => v.clone(),
52        config::PlatformOrStringVec::Platform(p) => p
53            .get(&platform)
54            .or_else(|| p.get("default"))
55            .cloned()
56            .ok_or_else(|| {
57                anyhow!(
58                    "No commands found for platform '{}' and no default specified",
59                    platform
60                )
61            })?,
62    };
63
64    let env_vars = match &env_to_setup.env {
65        config::PlatformOrEnvMap::EnvMap(m) => m.clone(),
66        config::PlatformOrEnvMap::Platform(p) => p
67            .get(&platform)
68            .or_else(|| p.get("default"))
69            .cloned()
70            .unwrap_or_default(),
71    };
72
73    for cmd_str in &run_cmds {
74        executor::run_shell_command(cmd_str, &env_vars)?;
75    }
76
77    Ok(())
78}
79
80fn check_packages(config: &config::ProjectConfig) -> Result<()> {
81    if config.packages.is_empty() {
82        return Ok(());
83    }
84    println!("\nChecking required packages...");
85    let mut all_ok = true;
86    for package in &config.packages {
87        print!("- Checking for '{}': ", package.name.cyan());
88        let _ = io::stdout().flush();
89
90        let status = executor::get_shell_command(&package.check)
91            .stdout(Stdio::null())
92            .stderr(Stdio::null())
93            .status()?;
94
95        if status.success() {
96            println!("{}", "OK".green());
97        } else {
98            println!("{}", "MISSING".red());
99            all_ok = false;
100        }
101    }
102    if !all_ok {
103        return Err(anyhow!("One or more required packages are missing."));
104    }
105    Ok(())
106}
107
108pub fn export_shell(
109    env_alias: Option<&str>,
110    config: &config::ProjectConfig,
111    shell: Shell,
112) -> Result<()> {
113    let platform = utils::get_platform()?;
114    let mut env_vars = HashMap::new();
115
116    if let Some(alias) = env_alias {
117        let env_spec = config
118            .environments
119            .iter()
120            .find(|e| e.cmd == alias)
121            .ok_or_else(|| anyhow!("Environment '{alias}' not found"))?;
122
123        let extra_env = match &env_spec.env {
124            config::PlatformOrEnvMap::EnvMap(m) => m.clone(),
125            config::PlatformOrEnvMap::Platform(p) => p
126                .get(&platform)
127                .or_else(|| p.get("default"))
128                .cloned()
129                .unwrap_or_default(),
130        };
131        env_vars.extend(extra_env);
132    }
133
134    if config.config.local {
135        let bin_dir = std::env::current_dir()?
136            .join(".zoi")
137            .join("pkgs")
138            .join("bin");
139        if bin_dir.exists() {
140            let mut path = bin_dir.to_string_lossy().to_string();
141            if let Ok(old_path) = std::env::var("PATH") {
142                path = format!(
143                    "{}{}{}",
144                    path,
145                    if cfg!(windows) { ";" } else { ":" },
146                    old_path
147                );
148            }
149            env_vars.insert("PATH".to_string(), path);
150        }
151    }
152
153    for (k, v) in env_vars {
154        match shell {
155            Shell::Bash | Shell::Zsh => {
156                println!("export {}=\"{}\"", k, v);
157            }
158            Shell::Fish => {
159                println!("set -gx {} \"{}\"", k, v);
160            }
161            Shell::PowerShell => {
162                println!("$env:{} = \"{}\"", k, v);
163            }
164            Shell::Elvish => {
165                println!("set E:{} = \"{}\"", k, v);
166            }
167            _ => {}
168        }
169    }
170
171    Ok(())
172}