1use crate::util::eval_source;
2#[cfg(feature = "plugin")]
3use nu_path::absolute_with;
4#[cfg(feature = "plugin")]
5use nu_protocol::shell_error::generic::GenericError;
6#[cfg(feature = "plugin")]
7use nu_protocol::{ParseError, PluginRegistryFile, Spanned, engine::StateWorkingSet};
8use nu_protocol::{
9 PipelineData,
10 engine::{EngineState, Stack},
11 report_shell_error,
12};
13#[cfg(feature = "plugin")]
14use nu_utils::perf;
15#[cfg(feature = "plugin")]
16use nu_utils::time::Instant;
17use std::path::PathBuf;
18
19#[cfg(feature = "plugin")]
20const PLUGIN_FILE: &str = "plugin.msgpackz";
21#[cfg(feature = "plugin")]
22const OLD_PLUGIN_FILE: &str = "plugin.nu";
23
24#[cfg(feature = "plugin")]
25pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
26 use nu_protocol::{ShellError, shell_error::io::IoError};
27 use std::path::Path;
28
29 let span = plugin_file.as_ref().map(|s| s.span);
30
31 if plugin_file
33 .as_ref()
34 .and_then(|p| Path::new(&p.item).extension())
35 .is_some_and(|ext| ext == "nu")
36 {
37 let error = "Wrong plugin file format";
38 let msg = ".nu plugin files are no longer supported";
39 report_shell_error(
40 None,
41 engine_state,
42 &ShellError::Generic(
43 match span {
44 Some(span) => GenericError::new(error, msg, span),
45 None => GenericError::new_internal(error, msg),
46 }
47 .with_help("please recreate this file in the new .msgpackz format"),
48 ),
49 );
50 return;
51 }
52
53 let mut start_time = Instant::now();
54 add_plugin_file(engine_state, plugin_file.clone());
57 perf!(
58 "add plugin file to engine_state",
59 start_time,
60 engine_state
61 .get_config()
62 .use_ansi_coloring
63 .get(engine_state)
64 );
65
66 start_time = Instant::now();
67 let plugin_path = engine_state.plugin_path.clone();
68 if let Some(plugin_path) = plugin_path {
69 let mut file = match std::fs::File::open(&plugin_path) {
71 Ok(file) => file,
72 Err(err) => {
73 if err.kind() == std::io::ErrorKind::NotFound {
74 log::warn!("Plugin file not found: {}", plugin_path.display());
75
76 if plugin_file.is_none() && migrate_old_plugin_file(engine_state) {
78 let Ok(file) = std::fs::File::open(&plugin_path) else {
79 log::warn!("Failed to load newly migrated plugin file");
80 return;
81 };
82 file
83 } else {
84 return;
85 }
86 } else {
87 report_shell_error(
88 None,
89 engine_state,
90 &ShellError::Io(IoError::new_internal_with_path(
91 err,
92 "Could not open plugin registry file",
93 plugin_path,
94 )),
95 );
96 return;
97 }
98 }
99 };
100
101 if file.metadata().is_ok_and(|m| m.len() == 0) {
103 log::warn!(
104 "Not reading plugin file because it's empty: {}",
105 plugin_path.display()
106 );
107 return;
108 }
109
110 let contents = match PluginRegistryFile::read_from(&mut file, span) {
112 Ok(contents) => contents,
113 Err(err) => {
114 log::warn!("Failed to read plugin registry file: {err:?}");
115 let error = format!(
116 "Error while reading plugin registry file: {}",
117 plugin_path.display()
118 );
119 let msg = "plugin path defined here";
120 report_shell_error(
121 None,
122 engine_state,
123 &ShellError::Generic(
124 match span {
125 Some(span) => GenericError::new(error, msg, span),
126 None => GenericError::new_internal(error, msg),
127 }
128 .with_help(
129 "you might try deleting the file and registering all of your plugins again",
130 ),
131 ),
132 );
133 return;
134 }
135 };
136
137 perf!(
138 &format!("read plugin file {}", plugin_path.display()),
139 start_time,
140 engine_state
141 .get_config()
142 .use_ansi_coloring
143 .get(engine_state)
144 );
145 start_time = Instant::now();
146
147 let mut working_set = StateWorkingSet::new(engine_state);
148
149 let plugin_load_errors =
150 nu_plugin_engine::load_plugin_file(&mut working_set, &contents, span);
151
152 if plugin_load_errors > 0 {
153 let error = format!(
154 "Failed to load {plugin_load_errors} plugin entr{} from {}",
155 if plugin_load_errors == 1 { "y" } else { "ies" },
156 plugin_path.display(),
157 );
158 let msg = "plugins with incompatible or invalid registry data were skipped";
159 let help = "run `plugin list` and re-add outdated plugins with `plugin add`";
160 let generic_error = match span {
161 Some(span) => GenericError::new(error, msg, span),
162 None => GenericError::new_internal(error, msg),
163 };
164 report_shell_error(
165 None,
166 engine_state,
167 &ShellError::Generic(generic_error.with_help(help)),
168 );
169 }
170
171 if let Err(err) = engine_state.merge_delta(working_set.render()) {
172 report_shell_error(None, engine_state, &err);
173 return;
174 }
175
176 perf!(
177 &format!("load plugin file {}", plugin_path.display()),
178 start_time,
179 engine_state
180 .get_config()
181 .use_ansi_coloring
182 .get(engine_state)
183 );
184 }
185}
186
187#[cfg(feature = "plugin")]
188pub fn add_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
189 use std::path::Path;
190
191 use nu_protocol::report_parse_error;
192
193 if let Ok(cwd) = engine_state.cwd_as_string(None) {
194 if let Some(plugin_file) = plugin_file {
195 let path = Path::new(&plugin_file.item);
196 let path_dir = path.parent().unwrap_or(path);
197 if let Ok(path_dir) = absolute_with(path_dir, &cwd)
199 && path_dir.exists()
200 {
201 let path = path_dir.join(path.file_name().unwrap_or(path.as_os_str()));
204 let path = absolute_with(&path, &cwd).unwrap_or(path);
205 engine_state.plugin_path = Some(path)
206 } else {
207 report_parse_error(
209 None,
210 &StateWorkingSet::new(engine_state),
211 &ParseError::FileNotFound(
212 path_dir.to_string_lossy().into_owned(),
213 plugin_file.span,
214 ),
215 );
216 }
217 } else if let Some(plugin_path) = nu_path::nu_config_dir() {
218 let mut plugin_path = absolute_with(&plugin_path, &cwd).unwrap_or(plugin_path.into());
220 plugin_path.push(PLUGIN_FILE);
221 let plugin_path = absolute_with(&plugin_path, &cwd).unwrap_or(plugin_path);
222 engine_state.plugin_path = Some(plugin_path);
223 }
224 }
225}
226
227pub fn eval_config_contents(
228 config_path: PathBuf,
229 engine_state: &mut EngineState,
230 stack: &mut Stack,
231 strict_mode: bool,
232) {
233 if config_path.exists() & config_path.is_file() {
234 let config_filename = config_path.to_string_lossy();
235
236 if let Ok(contents) = std::fs::read(&config_path) {
237 let prev_file = engine_state.file.take();
239 engine_state.file = Some(config_path.clone());
240
241 let exit_code = eval_source(
243 engine_state,
244 stack,
245 &contents,
246 &config_filename,
247 PipelineData::empty(),
248 false,
249 );
250 if exit_code != 0 && strict_mode {
251 std::process::exit(exit_code)
252 }
253
254 engine_state.file = prev_file;
256
257 if let Err(e) = engine_state.merge_env(stack) {
259 report_shell_error(Some(stack), engine_state, &e);
260 }
261 }
262 }
263}
264
265#[cfg(feature = "plugin")]
266pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
267 use nu_protocol::{
268 PluginExample, PluginIdentity, PluginRegistryItem, PluginRegistryItemData, PluginSignature,
269 ShellError, shell_error::io::IoError,
270 };
271 use std::collections::BTreeMap;
272
273 let start_time = Instant::now();
274
275 let Ok(cwd) = engine_state.cwd_as_string(None) else {
276 return false;
277 };
278
279 let Some(config_dir) =
280 nu_path::nu_config_dir().and_then(|dir| nu_path::absolute_with(dir, &cwd).ok())
281 else {
282 return false;
283 };
284
285 let Ok(old_plugin_file_path) = nu_path::absolute_with(OLD_PLUGIN_FILE, &config_dir) else {
286 return false;
287 };
288
289 if !config_dir.exists() || !old_plugin_file_path.exists() {
290 return false;
291 }
292
293 let old_contents = match std::fs::read(&old_plugin_file_path) {
294 Ok(old_contents) => old_contents,
295 Err(err) => {
296 report_shell_error(
297 None,
298 engine_state,
299 &ShellError::Generic(
300 GenericError::new_internal("Can't read old plugin file to migrate", "")
301 .with_help(err.to_string()),
302 ),
303 );
304 return false;
305 }
306 };
307
308 let mut engine_state = engine_state.clone();
310 let mut stack = Stack::new();
311
312 if eval_source(
313 &mut engine_state,
314 &mut stack,
315 &old_contents,
316 &old_plugin_file_path.to_string_lossy(),
317 PipelineData::empty(),
318 false,
319 ) != 0
320 {
321 return false;
322 }
323
324 let mut contents = PluginRegistryFile::new();
326
327 let mut groups = BTreeMap::<PluginIdentity, Vec<PluginSignature>>::new();
328
329 for decl in engine_state.plugin_decls() {
330 if let Some(identity) = decl.plugin_identity() {
331 groups
332 .entry(identity.clone())
333 .or_default()
334 .push(PluginSignature {
335 sig: decl.signature(),
336 examples: decl
337 .examples()
338 .into_iter()
339 .map(PluginExample::from)
340 .collect(),
341 })
342 }
343 }
344
345 for (identity, commands) in groups {
346 contents.upsert_plugin(PluginRegistryItem {
347 name: identity.name().to_owned(),
348 filename: identity.filename().to_owned(),
349 shell: identity.shell().map(|p| p.to_owned()),
350 data: PluginRegistryItemData::Valid {
351 metadata: Default::default(),
352 commands,
353 },
354 });
355 }
356
357 let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
359 if let Err(err) = std::fs::File::create(&new_plugin_file_path)
360 .map_err(|err| {
361 IoError::new_internal_with_path(
362 err,
363 "Could not create new plugin file",
364 new_plugin_file_path.clone(),
365 )
366 })
367 .map_err(ShellError::from)
368 .and_then(|file| contents.write_to(file, None))
369 {
370 report_shell_error(
371 None,
372 &engine_state,
373 &ShellError::Generic(
374 GenericError::new_internal("Failed to save migrated plugin file", "")
375 .with_help("ensure `$nu.plugin-path` is writable")
376 .with_inner([err]),
377 ),
378 );
379 return false;
380 }
381
382 if engine_state.is_interactive {
383 eprintln!(
384 "Your old plugin.nu file has been migrated to the new format: {}",
385 new_plugin_file_path.display()
386 );
387 eprintln!(
388 "The plugin.nu file has not been removed. If `plugin list` looks okay, \
389 you may do so manually."
390 );
391 }
392
393 perf!(
394 "migrate old plugin file",
395 start_time,
396 engine_state
397 .get_config()
398 .use_ansi_coloring
399 .get(&engine_state)
400 );
401 true
402}