|
1 | 1 | use crate::csmrc::Config; |
2 | 2 |
|
| 3 | +use log::{debug, error}; |
| 4 | +use serde::Deserialize; |
| 5 | +use std::io::{Error, ErrorKind}; |
| 6 | +use std::path::Component; |
| 7 | +use std::process; |
| 8 | + |
3 | 9 | #[derive(Debug, clap::Subcommand)] |
4 | 10 | pub enum Subcommand { |
5 | 11 | /// Create an environment |
@@ -29,7 +35,153 @@ pub struct CreateArgs { |
29 | 35 | name: Option<String>, |
30 | 36 | } |
31 | 37 |
|
| 38 | +/// Contains the fields we need from a parsed `robotmk-env.yml` file. |
| 39 | +#[derive(Deserialize)] |
| 40 | +struct RobotmkEnv { |
| 41 | + /// The name of the environment |
| 42 | + name: Option<String>, |
| 43 | +} |
| 44 | + |
| 45 | +/// Attempt to parse a robotmk-env.yaml in the current directory. |
| 46 | +fn parse_robotmk_env_yaml() -> Result<RobotmkEnv, std::io::Error> { |
| 47 | + // TODO: Should we handle .yml too? |
| 48 | + let contents = std::fs::read_to_string("robotmk-env.yaml")?; |
| 49 | + serde_yaml_ng::from_str(&contents).map_err(|e| Error::new(ErrorKind::InvalidData, e)) |
| 50 | +} |
| 51 | + |
| 52 | +pub fn determine_env_name(args: CreateArgs) -> Option<String> { |
| 53 | + // If someone gave an explicit --name, use that first. |
| 54 | + if let Some(name) = args.name { |
| 55 | + debug!("Using '{}' as env name, given by CLI argument", name); |
| 56 | + return Some(name); |
| 57 | + } |
| 58 | + |
| 59 | + // Fallback 1: Look for a name key in robotmk-env.yaml |
| 60 | + // We ignore errors from parse_robotmk_env_yaml() here, we'll fall back |
| 61 | + // below if we can't parse it for some reason |
| 62 | + if let Ok(env) = parse_robotmk_env_yaml() |
| 63 | + && let Some(name) = env.name |
| 64 | + { |
| 65 | + debug!("Using '{}' as env name, found in robotmk-env.yaml", name); |
| 66 | + return Some(name); |
| 67 | + } |
| 68 | + |
| 69 | + // Fallback 2: Current directory name |
| 70 | + match std::env::current_dir() { |
| 71 | + Err(e) => { |
| 72 | + debug!("Could not determine current directory: {}", e); |
| 73 | + None |
| 74 | + } |
| 75 | + Ok(pathbuf) => match pathbuf.components().next_back() { |
| 76 | + Some(Component::Normal(s)) => match s.to_str().map(String::from) { |
| 77 | + Some(name) => { |
| 78 | + debug!( |
| 79 | + "Using '{}' as env name, taken from current directory name", |
| 80 | + name |
| 81 | + ); |
| 82 | + Some(name) |
| 83 | + } |
| 84 | + _ => None, // Likely could not convert path name to utf-8 |
| 85 | + }, |
| 86 | + _ => None, // In theory, I think this should never happen |
| 87 | + }, |
| 88 | + } |
| 89 | +} |
| 90 | + |
32 | 91 | pub fn run(config: Config, subcommand: Subcommand) { |
33 | | - println!("{:?}", config); |
34 | | - println!("{:?}", subcommand); |
| 92 | + match subcommand { |
| 93 | + Subcommand::Create(args) => { |
| 94 | + let Some(env_name) = determine_env_name(args) else { |
| 95 | + error!("No environment name could be determined. You can specify one with --name"); |
| 96 | + process::exit(1); // TODO: Probably better to return Result and let main() do this. |
| 97 | + }; |
| 98 | + println!("env: {}", env_name); |
| 99 | + } |
| 100 | + _ => { |
| 101 | + println!("{:?}", config); |
| 102 | + println!("{:?}", subcommand); |
| 103 | + } |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +#[cfg(test)] |
| 108 | +mod tests { |
| 109 | + use super::*; |
| 110 | + use std::env; |
| 111 | + use std::fs; |
| 112 | + |
| 113 | + /// Run a test in a temporary directory with an optional robotmk-env.yaml in it |
| 114 | + fn run_in_temp_dir<F>(dir_name: &str, yaml_content: Option<&str>, test_fn: F) |
| 115 | + where |
| 116 | + F: FnOnce(), |
| 117 | + { |
| 118 | + let temp_dir = env::temp_dir().join(dir_name); |
| 119 | + fs::create_dir_all(&temp_dir).unwrap(); |
| 120 | + |
| 121 | + if let Some(content) = yaml_content { |
| 122 | + let yaml_path = temp_dir.join("robotmk-env.yaml"); |
| 123 | + fs::write(&yaml_path, content).unwrap(); |
| 124 | + } |
| 125 | + |
| 126 | + let original_dir = env::current_dir().unwrap(); |
| 127 | + env::set_current_dir(&temp_dir).unwrap(); |
| 128 | + |
| 129 | + test_fn(); |
| 130 | + |
| 131 | + env::set_current_dir(original_dir).unwrap(); |
| 132 | + fs::remove_dir_all(&temp_dir).unwrap(); |
| 133 | + } |
| 134 | + |
| 135 | + #[test] |
| 136 | + fn test_determine_env_name_with_cli_arg() { |
| 137 | + let args = CreateArgs { |
| 138 | + name: Some("test-env".to_string()), |
| 139 | + }; |
| 140 | + |
| 141 | + let result = determine_env_name(args); |
| 142 | + assert_eq!(result, Some("test-env".to_string())); |
| 143 | + } |
| 144 | + |
| 145 | + #[test] |
| 146 | + fn test_determine_env_name_cli_arg_overrides_yaml() { |
| 147 | + run_in_temp_dir("csm_test_override", Some("name: yaml-env-name"), || { |
| 148 | + let args = CreateArgs { |
| 149 | + name: Some("cli-override".to_string()), |
| 150 | + }; |
| 151 | + |
| 152 | + let result = determine_env_name(args); |
| 153 | + assert_eq!(result, Some("cli-override".to_string())); |
| 154 | + }); |
| 155 | + } |
| 156 | + |
| 157 | + #[test] |
| 158 | + fn test_determine_env_name_robotmk_env_yaml() { |
| 159 | + // (dir_name, yaml, expected) |
| 160 | + let test_cases = vec![ |
| 161 | + ( |
| 162 | + "valid_yaml", |
| 163 | + Some("name: yaml-env-name\nother_field: value"), |
| 164 | + "yaml-env-name", |
| 165 | + ), |
| 166 | + ( |
| 167 | + "yaml_no_name", |
| 168 | + Some("other_field: value\nyet_another: field"), |
| 169 | + "yaml_no_name", |
| 170 | + ), |
| 171 | + ( |
| 172 | + "invalid_yaml", |
| 173 | + Some("invalid: yaml: content: \"unclosed"), |
| 174 | + "invalid_yaml", |
| 175 | + ), |
| 176 | + ("no_yaml", None, "no_yaml"), |
| 177 | + ]; |
| 178 | + |
| 179 | + for (dir_name, yaml, expected) in test_cases { |
| 180 | + run_in_temp_dir(dir_name, yaml, || { |
| 181 | + let args = CreateArgs { name: None }; |
| 182 | + let result = determine_env_name(args); |
| 183 | + assert_eq!(result.unwrap(), expected, "Failed case: {}", dir_name); |
| 184 | + }); |
| 185 | + } |
| 186 | + } |
35 | 187 | } |
0 commit comments