nvy/
init.rs

1use anyhow::Result;
2use glob::glob;
3use std::collections::BTreeMap;
4use std::io::{self};
5use std::path::PathBuf;
6
7use crate::nvy_config::{does_config_exist, is_target_shell, load_config, save_config, Config, Profile, DEFAULT_TARGET};
8use crate::log::{message, wrap_yellow};
9use crate::{success, warn};
10
11pub fn run_init() -> Result<()> {
12    let mut target = String::from(DEFAULT_TARGET);
13    let mut ignore = vec![".env.example".to_string()];
14
15    if does_config_exist() {
16        if !prompt_reinit()? {
17            warn!("Initialization cancelled.");
18            return Ok(());
19        }
20
21        let config = load_config()?;
22        if !is_target_shell(&config) {
23            target = config.target.clone();
24            ignore.push(target.clone().to_string());
25        }
26    }
27
28    let env_files = discover_env_files(ignore)?;
29    init_config(&target, env_files)?;
30    Ok(())
31}
32
33fn prompt_reinit() -> Result<bool> {
34    let prompt = "Do you want to reinitialize? [Y/n]";
35    let formatted: String = wrap_yellow(prompt);
36
37    message(vec![
38        "An existing nv configuration file was found in the current directory.",
39        &formatted,
40    ]);
41
42    let mut input = String::new();
43    io::stdin().read_line(&mut input)?;
44    let input = input.trim().to_lowercase();
45
46    Ok(input != "n" && input != "no")
47}
48
49fn discover_env_files(ignore: Vec<String>) -> Result<Vec<PathBuf>> {
50    Ok(glob(".env*")?
51        .filter_map(|result| {
52            result.ok().and_then(|path| {
53                let path_str = path.to_string_lossy();
54                if ignore.iter().any(|ignored| path_str.contains(ignored)) {
55                    None
56                } else {
57                    Some(path)
58                }
59            })
60        })
61        .collect())
62}
63
64/// Map an env file path to its corresponding profile name
65fn get_profile_name(file_name: &str) -> Option<String> {
66    if file_name == ".env" {
67        Some("default".to_string())
68    } else if let Some(suffix) = file_name.strip_prefix(".env.") {
69        if !suffix.is_empty() {
70            Some(suffix.to_string())
71        } else {
72            None
73        }
74    } else {
75        None
76    }
77}
78
79fn init_config(target: &str, env_files: Vec<PathBuf>) -> Result<()> {
80    let mut profiles = BTreeMap::new();
81
82    profiles.insert(
83        "default".to_string(),
84        vec![Profile {
85            path: ".env".to_string(),
86        }],
87    );
88
89    for file in env_files {
90        let file_name = file.to_string_lossy();
91        if let Some(profile_name) = get_profile_name(&file_name) {
92            if profile_name != "default" { 
93                profiles.insert(
94                    profile_name.to_string(),
95                    vec![Profile {
96                        path: file_name.into_owned(),
97                    }],
98                );
99            }
100        }
101    }
102
103    let config = Config { target: target.to_string(), profiles, current_profiles: vec![] };
104    let res = save_config(&config);
105    match res {
106        Ok(()) => {
107            success!("Initialized nvy.yaml in file mode, pointing to {}; run `nvy target set <file>` to change the target.", DEFAULT_TARGET);
108            Ok(())
109        },
110        Err(e) => Err(anyhow::anyhow!(e)),
111    }    
112}
113
114#[cfg(test)]
115mod tests {
116    use std::fs;
117    use crate::nvy_config::TARGET_SHELL;
118
119    use super::*;
120
121    #[test]
122    fn test_get_profile_name() {
123        assert_eq!(get_profile_name(".env"), Some("default".to_string()));
124        assert_eq!(get_profile_name(".env.local"), Some("local".to_string()));
125        assert_eq!(get_profile_name(".env.prod"), Some("prod".to_string()));
126        assert_eq!(get_profile_name(".env.staging"), Some("staging".to_string()));
127        assert_eq!(get_profile_name(".env."), None);
128        assert_eq!(get_profile_name("env"), None);
129        assert_eq!(get_profile_name(".environment"), None);
130    }
131
132    #[test]
133    fn test_init_config_empty_dir() -> Result<()> {
134        let empty_files = Vec::new();
135        init_config(TARGET_SHELL,empty_files)?;
136
137        let content = fs::read_to_string("nvy.yaml")?;
138        assert!(content.contains("default:"));
139        assert!(content.contains("path: .env"));
140        
141        fs::remove_file("nvy.yaml")?;
142        Ok(())
143    }
144}