Skip to main content

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}