nu_config/paths.rs
1//! Resolved config directory layout and path types.
2//!
3//! [`NushellConfigDirs`] is the single source of truth after
4//! [`crate::resolve_paths`] runs. File paths that may be CLI-overridden use
5//! [`ConfigPath`] so origin (default vs override) stays in the type system.
6
7use std::path::{Path, PathBuf};
8
9/// A resolved config-related file path that records whether it came from a
10/// CLI override or from the default location under `config_home`.
11///
12/// Origin is part of the type so loaders can decide whether missing files
13/// should error (override) or be scaffolded (default) without a parallel bool.
14///
15/// # Examples
16///
17/// ```
18/// use nu_config::ConfigPath;
19/// use std::path::PathBuf;
20///
21/// let default = ConfigPath::Default(PathBuf::from("/home/me/.config/nushell/config.nu"));
22/// assert!(!default.is_override());
23///
24/// let r#override = ConfigPath::Override(PathBuf::from("/tmp/custom.nu"));
25/// assert!(r#override.is_override());
26/// assert_eq!(r#override.as_path(), PathBuf::from("/tmp/custom.nu"));
27/// ```
28#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
29#[display("{}", self.as_path().display())]
30pub enum ConfigPath {
31 /// Path derived from the resolved config home (e.g. `config_home/config.nu`).
32 /// First-run scaffolding is allowed when the file is missing.
33 Default(PathBuf),
34 /// Path supplied by a CLI flag (`--config`, `--env-config`, `--plugin-config`).
35 /// A missing file is an error; do not scaffold.
36 Override(PathBuf),
37}
38
39impl ConfigPath {
40 /// The concrete filesystem path.
41 pub fn as_path(&self) -> &Path {
42 match self {
43 Self::Default(path) | Self::Override(path) => path,
44 }
45 }
46
47 /// Owned copy of the concrete filesystem path.
48 pub fn to_path_buf(&self) -> PathBuf {
49 self.as_path().to_path_buf()
50 }
51
52 /// Consume and return the concrete filesystem path.
53 pub fn into_path_buf(self) -> PathBuf {
54 match self {
55 Self::Default(path) | Self::Override(path) => path,
56 }
57 }
58
59 /// Whether this path came from a CLI override.
60 pub fn is_override(&self) -> bool {
61 matches!(self, Self::Override(_))
62 }
63
64 /// Empty default path — used only for inert pre-resolve state
65 /// ([`NushellConfigDirs::empty`]).
66 pub fn empty_default() -> Self {
67 Self::Default(PathBuf::new())
68 }
69}
70
71impl AsRef<Path> for ConfigPath {
72 fn as_ref(&self) -> &Path {
73 self.as_path()
74 }
75}
76
77/// All resolved configuration directories and file paths for Nushell.
78///
79/// Every path here is the *final* answer after applying the full resolution
80/// chain: CLI overrides → XDG env vars → platform defaults.
81///
82/// This is the **single source of truth** for where config lives. Downstream
83/// code (config-file loading, `$nu` constant generation, history backends,
84/// plugin registry, etc.) must read from this struct instead of re-resolving.
85///
86/// # `$nu` constant
87///
88/// Most fields map to `$nu.*` members. See field docs for the mapping.
89/// `create_nu_constant()` in `nu-protocol` reads from `engine_state.config_dirs`.
90///
91/// # Empty state
92///
93/// [`NushellConfigDirs::empty`] is used only before `resolve_paths` runs (or
94/// when resolution fails). Check [`Self::is_resolved`] before treating paths
95/// as meaningful.
96#[derive(Debug, Clone)]
97pub struct NushellConfigDirs {
98 /// The nushell config directory (e.g. `~/.config/nushell`).
99 /// Maps to `$nu.default-config-dir`.
100 pub config_home: PathBuf,
101
102 /// Path to `config.nu` — either a CLI override (`--config`) or
103 /// `config_home/config.nu`. Maps to `$nu.config-path`.
104 pub config_file: ConfigPath,
105
106 /// Path to `env.nu` — either a CLI override (`--env-config`) or
107 /// `config_home/env.nu`. Maps to `$nu.env-path`.
108 pub env_file: ConfigPath,
109
110 /// The nushell data directory (e.g. `~/.local/share/nushell`).
111 /// Maps to `$nu.data-dir`.
112 pub data_home: PathBuf,
113
114 /// The nushell cache directory (e.g. `~/.cache/nushell`).
115 /// Maps to `$nu.cache-dir`.
116 pub cache_home: PathBuf,
117
118 /// The user's home directory. Maps to `$nu.home-dir`.
119 pub home_dir: PathBuf,
120
121 /// Vendor autoload directories — directories from which Nushell
122 /// automatically loads `.nu` files at startup. These come from
123 /// `XDG_DATA_DIRS`, platform-specific paths, and `$NU_VENDOR_AUTOLOAD_DIR`.
124 /// Maps to `$nu.vendor-autoload-dirs`.
125 ///
126 /// Order matters: files are evaluated in list order, so later entries
127 /// override earlier ones. On Unix, `XDG_DATA_DIRS` is reversed so that
128 /// earlier entries in the env var win (XDG precedence).
129 pub vendor_autoload_dirs: Vec<PathBuf>,
130
131 /// User autoload directories — `config_home/autoload`.
132 /// Maps to `$nu.user-autoload-dirs`.
133 pub user_autoload_dirs: Vec<PathBuf>,
134
135 /// Path to the plugin registry file — either a CLI override
136 /// (`--plugin-config`) or `config_home/plugin.msgpackz`.
137 /// Maps to `$nu.plugin-path`.
138 #[cfg(feature = "plugin")]
139 pub plugin_file: ConfigPath,
140}
141
142impl NushellConfigDirs {
143 /// Create an empty/inert instance for use before `resolve_paths()` has
144 /// been called (e.g. in `EngineState::new()`).
145 ///
146 /// All paths are empty. Call `resolve_paths()` before accessing `$nu`.
147 pub fn empty() -> Self {
148 Self {
149 config_home: PathBuf::new(),
150 config_file: ConfigPath::empty_default(),
151 env_file: ConfigPath::empty_default(),
152 data_home: PathBuf::new(),
153 cache_home: PathBuf::new(),
154 home_dir: PathBuf::new(),
155 vendor_autoload_dirs: Vec::new(),
156 user_autoload_dirs: Vec::new(),
157 #[cfg(feature = "plugin")]
158 plugin_file: ConfigPath::empty_default(),
159 }
160 }
161
162 /// Whether resolution produced usable paths (config home is non-empty).
163 pub fn is_resolved(&self) -> bool {
164 !self.config_home.as_os_str().is_empty()
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn config_path_default_is_not_override() {
174 let path = ConfigPath::Default(PathBuf::from("/cfg/config.nu"));
175 assert!(!path.is_override());
176 assert_eq!(path.as_path(), Path::new("/cfg/config.nu"));
177 assert_eq!(path.to_path_buf(), PathBuf::from("/cfg/config.nu"));
178 }
179
180 #[test]
181 fn config_path_override_is_override() {
182 let path = ConfigPath::Override(PathBuf::from("/tmp/x.nu"));
183 assert!(path.is_override());
184 assert_eq!(path.to_string(), "/tmp/x.nu");
185 }
186
187 #[test]
188 fn empty_dirs_are_unresolved() {
189 assert!(!NushellConfigDirs::empty().is_resolved());
190 }
191
192 #[test]
193 fn into_path_buf_consumes_either_variant() {
194 assert_eq!(
195 ConfigPath::Default(PathBuf::from("a")).into_path_buf(),
196 PathBuf::from("a")
197 );
198 assert_eq!(
199 ConfigPath::Override(PathBuf::from("b")).into_path_buf(),
200 PathBuf::from("b")
201 );
202 }
203}