Skip to main content

nu_cli/
startup_context.rs

1//! Context for files loaded during Nushell startup (env/config/login/autoload).
2//!
3//! # Error reporting design
4//!
5//! Parse/compile/shell diagnostics use the normal miette reporters. Path and
6//! labels come from the source file name passed into `parse` / spans on the
7//! error — not from a custom preface or continue banner (those duplicated what
8//! miette already shows).
9//!
10//! [`StartupLoadContext`] identifies *which* startup file is being loaded so
11//! call sites can attach path/role to path-level failures (read errors, missing
12//! override files) where there is no useful parse span.
13
14use std::path::PathBuf;
15
16use nu_protocol::{
17    ParseError, Span,
18    engine::{EngineState, StateWorkingSet},
19    report_parse_error,
20};
21
22/// Which kind of startup file is being loaded.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum StartupFileKind {
25    Env,
26    Config,
27    Login,
28    Autoload,
29    DefaultEnv,
30    DefaultConfig,
31}
32
33impl StartupFileKind {
34    pub fn display_name(self) -> &'static str {
35        match self {
36            Self::Env => "env.nu",
37            Self::Config => "config.nu",
38            Self::Login => "login.nu",
39            Self::Autoload => "autoload",
40            Self::DefaultEnv => "default_env.nu",
41            Self::DefaultConfig => "default_config.nu",
42        }
43    }
44}
45
46/// Identifies a startup load (path and role).
47///
48/// Used when reporting path-level failures (missing/unreadable files).
49/// Parse/compile/shell errors go through the standard reporters; their location
50/// comes from miette spans and the evaluated source name.
51#[derive(Debug, Clone)]
52pub struct StartupLoadContext {
53    pub kind: StartupFileKind,
54    pub path: PathBuf,
55}
56
57impl StartupLoadContext {
58    pub fn new(kind: StartupFileKind, path: impl Into<PathBuf>) -> Self {
59        Self {
60            kind,
61            path: path.into(),
62        }
63    }
64}
65
66fn writeln_stderr(msg: &str) -> std::io::Result<()> {
67    use std::io::Write;
68    let mut err = std::io::stderr().lock();
69    writeln!(err, "{msg}")
70}
71
72fn writeln_stdout(msg: &str) -> std::io::Result<()> {
73    use std::io::Write;
74    let mut out = std::io::stdout().lock();
75    writeln!(out, "{msg}")
76}
77
78/// Report a missing/unreadable startup path without blaming Host Environment Variables.
79pub fn report_startup_file_not_found(
80    engine_state: &EngineState,
81    path_display: &str,
82    cli_span: Option<Span>,
83    startup: Option<&StartupLoadContext>,
84) {
85    match cli_span {
86        Some(span) if span != Span::unknown() => {
87            let working_set = StateWorkingSet::new(engine_state);
88            report_parse_error(
89                None,
90                &working_set,
91                &ParseError::FileNotFound(path_display.to_string(), span),
92            );
93        }
94        _ => {
95            // No real CLI span — avoid Span::unknown() (Host Environment Variables) and
96            // new_internal (Rust source location). Plain message is clearest here.
97            let role = startup
98                .map(|s| s.kind.display_name())
99                .unwrap_or("startup file");
100            let msg = format!(
101                "Error: File not found: {path_display} ({role})\n  help: Check the path passed to --config / --env-config, or create the file under your config directory."
102            );
103            if writeln_stderr(&msg).is_err() {
104                let _ = writeln_stdout(&msg);
105            }
106        }
107    }
108}