gconfig.rs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. //! Global config management: path resolution, `config path` display,
  2. //! `config init-global` scaffold.
  3. use crate::config::default_config_dir;
  4. use std::path::PathBuf;
  5. /// Global config template written by `config init-global`.
  6. const GLOBAL_TEMPLATE: &str = r#"# Global sm121 config (created by `sm121 config init-global`).
  7. # Used when ./config.toml is absent, so `sm121` works from any cwd.
  8. # `ssh_user` / `ssh_key_path` may instead be provided by a cluster source.
  9. # `~` is expanded.
  10. #
  11. # recipe_sources = ["/path/to/recipes"] # extra recipe dirs
  12. # cluster_sources = ["/path/to/clusters.toml"] # extra cluster def files
  13. "#;
  14. /// Resolve the path to the config file.
  15. ///
  16. /// Search order:
  17. /// 1. `--config <path>` from CLI (always wins; error if missing)
  18. /// 2. `./config.toml` in cwd
  19. /// 3. `<default_config_dir>/config.toml`
  20. ///
  21. /// Returns the explicit path if nothing is found; the loader reports a
  22. /// clean error.
  23. pub fn resolve_config_path(cli_config: &str) -> PathBuf {
  24. let explicit = PathBuf::from(cli_config);
  25. // User passed a non-default path: use it as-is.
  26. if cli_config != "config.toml" {
  27. return explicit;
  28. }
  29. // Default value (or no flag): try cwd first, then default folder.
  30. if explicit.exists() {
  31. return explicit;
  32. }
  33. if let Some(dir) = default_config_dir() {
  34. let candidate = dir.join("config.toml");
  35. if candidate.exists() {
  36. return candidate;
  37. }
  38. }
  39. explicit
  40. }
  41. /// `sm121 config path`: print the active config and the global config
  42. /// location (the part that is not transparent — it lives in the
  43. /// platform config dir, e.g. `~/Library/Application Support/sm121` on macOS).
  44. pub fn show_paths(cli_config: &str) {
  45. let active = resolve_config_path(cli_config);
  46. let origin = if cli_config != "config.toml" {
  47. "--config"
  48. } else if std::path::Path::new("config.toml").exists() {
  49. "./config.toml (cwd)"
  50. } else {
  51. "global config dir"
  52. };
  53. println!("active config: {} [{}]", active.display(), origin);
  54. if let Some(dir) = default_config_dir() {
  55. let global = dir.join("config.toml");
  56. let state = if global.exists() {
  57. "exists"
  58. } else {
  59. "missing — `sm121 config init-global` creates a template"
  60. };
  61. println!("global config: {} [{}]", global.display(), state);
  62. }
  63. }
  64. /// `sm121 config init-global`: create the global config (template) if
  65. /// missing. Never overwrites an existing file.
  66. pub fn init_global(dry_run: bool) -> anyhow::Result<()> {
  67. let dir = default_config_dir().ok_or_else(|| {
  68. anyhow::anyhow!("cannot determine the platform config dir (dirs::config_dir)")
  69. })?;
  70. let global = dir.join("config.toml");
  71. if global.exists() {
  72. println!("global config already exists: {}", global.display());
  73. return Ok(());
  74. }
  75. if dry_run {
  76. println!("would write: {}", global.display());
  77. return Ok(());
  78. }
  79. std::fs::create_dir_all(&dir)?;
  80. std::fs::write(&global, GLOBAL_TEMPLATE)?;
  81. println!("created global config template: {}", global.display());
  82. Ok(())
  83. }
  84. #[cfg(test)]
  85. mod tests {
  86. use super::resolve_config_path;
  87. #[test]
  88. fn explicit_config_path_wins() {
  89. assert_eq!(
  90. resolve_config_path("/tmp/explicit-sm121.toml"),
  91. std::path::PathBuf::from("/tmp/explicit-sm121.toml")
  92. );
  93. }
  94. #[test]
  95. fn default_config_dir_is_some() {
  96. assert!(crate::config::default_config_dir().is_some());
  97. }
  98. }