返回 CodeWhale
paths.rs
根目录 / crates / tui / src / config / paths.rs
1 //! Filesystem path resolution helpers for config/cache/workspace locations.
2 //!
3 //! Pure path-building helpers extracted verbatim from `config.rs`. They depend
4 //! only on `std`, `codewhale-paths`, and `shellexpand` plus one another, so they
5 //! form a clean leaf. `config.rs` pulls them back in (`use paths::{...}`) for the
6 //! workspace-trust and config-loading logic that stays there, and re-exports
7 //! the two `pub(crate)` entry points (`effective_home_dir`, `expand_path`) so
8 //! external `crate::config::` callers resolve unchanged (#3311).
9 //!
10 //! Visibility note: helpers that were file-private `fn` in `config.rs` are
11 //! `pub(crate)` here purely so the parent module can name them; none are
12 //! re-exported publicly, so the crate's external surface is unchanged.
13
14 use std::path::{Path, PathBuf};
15
16 /// Re-exported so `config::effective_home_dir` and every `use paths::{...}`
17 /// caller resolve unchanged. It lives in `home.rs` because this module is not
18 /// includable from an integration test binary — see that file's header.
19 pub(crate) use super::home::effective_home_dir;
20
21 pub(crate) fn default_config_path() -> anyhow::Result<PathBuf> {
22 try_default_config_path()
23 }
24
25 pub(crate) fn try_default_config_path() -> anyhow::Result<PathBuf> {
26 #[cfg(test)]
27 {
28 let honor_guarded_environment = crate::test_support::current_thread_holds_test_env_lock();
29 crate::test_support::with_test_env_lock(|| {
30 if honor_guarded_environment {
31 try_default_config_path_from_environment()
32 } else {
33 Ok(crate::test_support::isolated_test_state_root()
34 .join(codewhale_config::CONFIG_FILE_NAME))
35 }
36 })
37 }
38
39 #[cfg(not(test))]
40 try_default_config_path_from_environment()
41 }
42
43 fn try_default_config_path_from_environment() -> anyhow::Result<PathBuf> {
44 codewhale_config::resolve_config_path(None)
45 }
46
47 pub(crate) fn codewhale_home_dir() -> Result<Option<PathBuf>, codewhale_paths::PathOverrideError> {
48 codewhale_paths::codewhale_home_override()
49 }
50
51 /// The user-global config document: `$CODEWHALE_HOME/config.toml` when an
52 /// explicit home is set, otherwise `~/.codewhale/config.toml` (falling back to
53 /// the legacy `~/.deepseek/config.toml` only when that file already exists).
54 ///
55 /// Credential writes are rerouted here when the ambient config path resolves
56 /// to a workspace-scoped document (#5045, #5193); non-credential settings keep
57 /// the ambient scoping.
58 pub(crate) fn home_config_path() -> Option<PathBuf> {
59 match codewhale_home_dir() {
60 Ok(Some(home)) => return Some(home.join(codewhale_config::CONFIG_FILE_NAME)),
61 Ok(None) => {}
62 Err(error) => {
63 tracing::error!(
64 error = %error,
65 "invalid Codewhale home override; refusing to substitute a different config path"
66 );
67 return None;
68 }
69 }
70
71 effective_home_dir().map(|home| {
72 let primary = home.join(".codewhale").join("config.toml");
73 if primary.exists() {
74 return primary;
75 }
76 let legacy = home.join(".deepseek").join("config.toml");
77 if legacy.exists() {
78 return legacy;
79 }
80 primary
81 })
82 }
83
84 pub(crate) fn workspace_config_key(workspace: &Path) -> String {
85 canonicalize_or_keep(workspace)
86 .to_string_lossy()
87 .into_owned()
88 }
89
90 pub(crate) fn canonicalize_or_keep(path: &Path) -> PathBuf {
91 path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
92 }
93
94 pub(crate) fn env_config_path() -> Result<Option<PathBuf>, codewhale_paths::PathOverrideError> {
95 #[cfg(test)]
96 {
97 crate::test_support::with_test_env_lock(env_config_path_unlocked)
98 }
99 #[cfg(not(test))]
100 {
101 env_config_path_unlocked()
102 }
103 }
104
105 fn env_config_path_unlocked() -> Result<Option<PathBuf>, codewhale_paths::PathOverrideError> {
106 codewhale_paths::config_path_override()
107 }
108
109 pub(crate) fn expand_pathbuf(path: PathBuf) -> PathBuf {
110 if let Some(raw) = path.to_str() {
111 return expand_path(raw);
112 }
113 path
114 }
115
116 pub(crate) fn default_managed_config_path() -> Option<PathBuf> {
117 #[cfg(unix)]
118 {
119 Some(PathBuf::from("/etc/deepseek/managed_config.toml"))
120 }
121 #[cfg(not(unix))]
122 {
123 effective_home_dir().map(|home| {
124 let primary = home.join(".codewhale").join("managed_config.toml");
125 if primary.exists() {
126 return primary;
127 }
128 home.join(".deepseek").join("managed_config.toml")
129 })
130 }
131 }
132
133 pub(crate) fn default_requirements_path() -> Option<PathBuf> {
134 #[cfg(unix)]
135 {
136 Some(PathBuf::from("/etc/deepseek/requirements.toml"))
137 }
138 #[cfg(not(unix))]
139 {
140 effective_home_dir().map(|home| {
141 let primary = home.join(".codewhale").join("requirements.toml");
142 if primary.exists() {
143 return primary;
144 }
145 home.join(".deepseek").join("requirements.toml")
146 })
147 }
148 }
149
150 pub(crate) fn expand_path(path: &str) -> PathBuf {
151 if let Some(stripped) = path.strip_prefix('~')
152 && (stripped.is_empty() || stripped.starts_with('/') || stripped.starts_with('\\'))
153 && let Some(mut home) = effective_home_dir()
154 {
155 let suffix = stripped.trim_start_matches(['/', '\\']);
156 if !suffix.is_empty() {
157 home.push(suffix);
158 }
159 return home;
160 }
161
162 let expanded = shellexpand::tilde(path);
163 PathBuf::from(expanded.as_ref())
164 }
165
166 pub(crate) fn default_skills_dir() -> Option<PathBuf> {
167 default_user_state_path("skills")
168 }
169
170 pub(crate) fn default_mcp_config_path() -> Option<PathBuf> {
171 default_user_state_path("mcp.json")
172 }
173
174 pub(crate) fn default_notes_path() -> Option<PathBuf> {
175 default_user_state_path("notes.txt")
176 }
177
178 pub(crate) fn default_memory_path() -> Option<PathBuf> {
179 default_user_state_path("memory.md")
180 }
181
182 fn default_user_state_path(name: &str) -> Option<PathBuf> {
183 match codewhale_home_dir() {
184 Ok(Some(home)) => return Some(home.join(name)),
185 Ok(None) => {}
186 Err(error) => {
187 tracing::error!(
188 error = %error,
189 "invalid Codewhale home override; refusing to substitute a different state root"
190 );
191 return None;
192 }
193 }
194 effective_home_dir().map(|home| {
195 let primary = home.join(".codewhale").join(name);
196 if primary.exists() {
197 return primary;
198 }
199 let legacy = home.join(".deepseek").join(name);
200 if legacy.exists() {
201 return legacy;
202 }
203 primary
204 })
205 }
206
206 lines RUST