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