ccsync_core/config/
discovery.rs1use std::path::{Path, PathBuf};
4
5use crate::error::Result;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ConfigFiles {
10 pub cli: Option<PathBuf>,
12 pub local: Option<PathBuf>,
14 pub project: Option<PathBuf>,
16 pub global: Option<PathBuf>,
18}
19
20pub struct ConfigDiscovery;
22
23impl Default for ConfigDiscovery {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl ConfigDiscovery {
30 #[must_use]
32 pub const fn new() -> Self {
33 Self
34 }
35
36 pub fn discover(cli_path: Option<&Path>) -> Result<ConfigFiles> {
44 let cli = if let Some(p) = cli_path {
45 if !p.exists() {
46 anyhow::bail!(
47 "Config file specified via CLI does not exist: {}",
48 p.display()
49 );
50 }
51 Some(p.to_path_buf())
52 } else {
53 None
54 };
55
56 let local = Self::find_file(".ccsync.local");
57 let project = Self::find_file(".ccsync");
58 let global = Self::find_global_config();
59
60 Ok(ConfigFiles {
61 cli,
62 local,
63 project,
64 global,
65 })
66 }
67
68 fn find_file(name: &str) -> Option<PathBuf> {
72 let mut current = std::env::current_dir().ok()?;
73
74 loop {
75 let candidate = current.join(name);
76
77 if let Ok(metadata) = candidate.symlink_metadata()
79 && metadata.is_file()
80 {
81 return Some(candidate);
82 }
83
84 if !current.pop() {
86 break;
87 }
88 }
89
90 None
91 }
92
93 fn find_global_config() -> Option<PathBuf> {
97 let config_dir = dirs::config_dir()?;
98 let global_config = config_dir.join("ccsync").join("config.toml");
99
100 if let Ok(metadata) = global_config.symlink_metadata()
102 && metadata.is_file()
103 {
104 return Some(global_config);
105 }
106
107 None
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use std::fs;
115 use tempfile::TempDir;
116
117 #[test]
118 fn test_discover_no_configs() {
119 let _discovery = ConfigDiscovery::new();
120 let files = ConfigDiscovery::discover(None).unwrap();
121
122 assert!(files.cli.is_none());
123 }
125
126 #[test]
127 fn test_discover_cli_config() {
128 let tmp = TempDir::new().unwrap();
129 let cli_config = tmp.path().join("custom.toml");
130 fs::write(&cli_config, "# config").unwrap();
131
132 let _discovery = ConfigDiscovery::new();
133 let files = ConfigDiscovery::discover(Some(&cli_config)).unwrap();
134
135 assert_eq!(files.cli, Some(cli_config));
136 }
137
138 #[test]
139 fn test_discover_cli_config_nonexistent() {
140 let tmp = TempDir::new().unwrap();
141 let cli_config = tmp.path().join("nonexistent.toml");
142
143 let _discovery = ConfigDiscovery::new();
144 let result = ConfigDiscovery::discover(Some(&cli_config));
145
146 assert!(result.is_err());
148 assert!(result.unwrap_err().to_string().contains("does not exist"));
149 }
150
151 }