|
|
@@ -0,0 +1,106 @@
|
|
|
+//! Global config management: path resolution, `config path` display,
|
|
|
+//! `config init-global` scaffold.
|
|
|
+
|
|
|
+use crate::config::default_config_dir;
|
|
|
+use std::path::PathBuf;
|
|
|
+
|
|
|
+/// Global config template written by `config init-global`.
|
|
|
+const GLOBAL_TEMPLATE: &str = r#"# Global sm121 config (created by `sm121 config init-global`).
|
|
|
+# Used when ./config.toml is absent, so `sm121` works from any cwd.
|
|
|
+# `ssh_user` / `ssh_key_path` may instead be provided by a cluster source.
|
|
|
+# `~` is expanded.
|
|
|
+#
|
|
|
+# recipe_sources = ["/path/to/recipes"] # extra recipe dirs
|
|
|
+# cluster_sources = ["/path/to/clusters.toml"] # extra cluster def files
|
|
|
+"#;
|
|
|
+
|
|
|
+/// Resolve the path to the config file.
|
|
|
+///
|
|
|
+/// Search order:
|
|
|
+/// 1. `--config <path>` from CLI (always wins; error if missing)
|
|
|
+/// 2. `./config.toml` in cwd
|
|
|
+/// 3. `<default_config_dir>/config.toml`
|
|
|
+///
|
|
|
+/// Returns the explicit path if nothing is found; the loader reports a
|
|
|
+/// clean error.
|
|
|
+pub fn resolve_config_path(cli_config: &str) -> PathBuf {
|
|
|
+ let explicit = PathBuf::from(cli_config);
|
|
|
+ // User passed a non-default path: use it as-is.
|
|
|
+ if cli_config != "config.toml" {
|
|
|
+ return explicit;
|
|
|
+ }
|
|
|
+ // Default value (or no flag): try cwd first, then default folder.
|
|
|
+ if explicit.exists() {
|
|
|
+ return explicit;
|
|
|
+ }
|
|
|
+ if let Some(dir) = default_config_dir() {
|
|
|
+ let candidate = dir.join("config.toml");
|
|
|
+ if candidate.exists() {
|
|
|
+ return candidate;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ explicit
|
|
|
+}
|
|
|
+
|
|
|
+/// `sm121 config path`: print the active config and the global config
|
|
|
+/// location (the part that is not transparent — it lives in the
|
|
|
+/// platform config dir, e.g. `~/Library/Application Support/sm121` on macOS).
|
|
|
+pub fn show_paths(cli_config: &str) {
|
|
|
+ let active = resolve_config_path(cli_config);
|
|
|
+ let origin = if cli_config != "config.toml" {
|
|
|
+ "--config"
|
|
|
+ } else if std::path::Path::new("config.toml").exists() {
|
|
|
+ "./config.toml (cwd)"
|
|
|
+ } else {
|
|
|
+ "global config dir"
|
|
|
+ };
|
|
|
+ println!("active config: {} [{}]", active.display(), origin);
|
|
|
+ if let Some(dir) = default_config_dir() {
|
|
|
+ let global = dir.join("config.toml");
|
|
|
+ let state = if global.exists() {
|
|
|
+ "exists"
|
|
|
+ } else {
|
|
|
+ "missing — `sm121 config init-global` creates a template"
|
|
|
+ };
|
|
|
+ println!("global config: {} [{}]", global.display(), state);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// `sm121 config init-global`: create the global config (template) if
|
|
|
+/// missing. Never overwrites an existing file.
|
|
|
+pub fn init_global(dry_run: bool) -> anyhow::Result<()> {
|
|
|
+ let dir = default_config_dir().ok_or_else(|| {
|
|
|
+ anyhow::anyhow!("cannot determine the platform config dir (dirs::config_dir)")
|
|
|
+ })?;
|
|
|
+ let global = dir.join("config.toml");
|
|
|
+ if global.exists() {
|
|
|
+ println!("global config already exists: {}", global.display());
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
+ if dry_run {
|
|
|
+ println!("would write: {}", global.display());
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
+ std::fs::create_dir_all(&dir)?;
|
|
|
+ std::fs::write(&global, GLOBAL_TEMPLATE)?;
|
|
|
+ println!("created global config template: {}", global.display());
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use super::resolve_config_path;
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn explicit_config_path_wins() {
|
|
|
+ assert_eq!(
|
|
|
+ resolve_config_path("/tmp/explicit-sm121.toml"),
|
|
|
+ std::path::PathBuf::from("/tmp/explicit-sm121.toml")
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn default_config_dir_is_some() {
|
|
|
+ assert!(crate::config::default_config_dir().is_some());
|
|
|
+ }
|
|
|
+}
|