1use crate::util::eval_source;
2#[cfg(feature = "plugin")]
3use nu_path::canonicalize_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) = canonicalize_with(path_dir, &cwd) {
174 let path = path_dir.join(path.file_name().unwrap_or(path.as_os_str()));
177 let path = canonicalize_with(&path, &cwd).unwrap_or(path);
178 engine_state.plugin_path = Some(path)
179 } else {
180 report_parse_error(
182 None,
183 &StateWorkingSet::new(engine_state),
184 &ParseError::FileNotFound(
185 path_dir.to_string_lossy().into_owned(),
186 plugin_file.span,
187 ),
188 );
189 }
190 } else if let Some(plugin_path) = nu_path::nu_config_dir() {
191 let mut plugin_path =
193 canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path.into());
194 plugin_path.push(PLUGIN_FILE);
195 let plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
196 engine_state.plugin_path = Some(plugin_path);
197 }
198 }
199}
200
201pub fn eval_config_contents(
202 config_path: PathBuf,
203 engine_state: &mut EngineState,
204 stack: &mut Stack,
205 strict_mode: bool,
206) {
207 if config_path.exists() & config_path.is_file() {
208 let config_filename = config_path.to_string_lossy();
209
210 if let Ok(contents) = std::fs::read(&config_path) {
211 let prev_file = engine_state.file.take();
213 engine_state.file = Some(config_path.clone());
214
215 let exit_code = eval_source(
217 engine_state,
218 stack,
219 &contents,
220 &config_filename,
221 PipelineData::empty(),
222 false,
223 );
224 if exit_code != 0 && strict_mode {
225 std::process::exit(exit_code)
226 }
227
228 engine_state.file = prev_file;
230
231 if let Err(e) = engine_state.merge_env(stack) {
233 report_shell_error(Some(stack), engine_state, &e);
234 }
235 }
236 }
237}
238
239#[cfg(feature = "plugin")]
240pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
241 use nu_protocol::{
242 PluginExample, PluginIdentity, PluginRegistryItem, PluginRegistryItemData, PluginSignature,
243 ShellError, shell_error::io::IoError,
244 };
245 use std::collections::BTreeMap;
246
247 let start_time = std::time::Instant::now();
248
249 let Ok(cwd) = engine_state.cwd_as_string(None) else {
250 return false;
251 };
252
253 let Some(config_dir) =
254 nu_path::nu_config_dir().and_then(|dir| nu_path::canonicalize_with(dir, &cwd).ok())
255 else {
256 return false;
257 };
258
259 let Ok(old_plugin_file_path) = nu_path::canonicalize_with(OLD_PLUGIN_FILE, &config_dir) else {
260 return false;
261 };
262
263 let old_contents = match std::fs::read(&old_plugin_file_path) {
264 Ok(old_contents) => old_contents,
265 Err(err) => {
266 report_shell_error(
267 None,
268 engine_state,
269 &ShellError::GenericError {
270 error: "Can't read old plugin file to migrate".into(),
271 msg: "".into(),
272 span: None,
273 help: Some(err.to_string()),
274 inner: vec![],
275 },
276 );
277 return false;
278 }
279 };
280
281 let mut engine_state = engine_state.clone();
283 let mut stack = Stack::new();
284
285 if eval_source(
286 &mut engine_state,
287 &mut stack,
288 &old_contents,
289 &old_plugin_file_path.to_string_lossy(),
290 PipelineData::empty(),
291 false,
292 ) != 0
293 {
294 return false;
295 }
296
297 let mut contents = PluginRegistryFile::new();
299
300 let mut groups = BTreeMap::<PluginIdentity, Vec<PluginSignature>>::new();
301
302 for decl in engine_state.plugin_decls() {
303 if let Some(identity) = decl.plugin_identity() {
304 groups
305 .entry(identity.clone())
306 .or_default()
307 .push(PluginSignature {
308 sig: decl.signature(),
309 examples: decl
310 .examples()
311 .into_iter()
312 .map(PluginExample::from)
313 .collect(),
314 })
315 }
316 }
317
318 for (identity, commands) in groups {
319 contents.upsert_plugin(PluginRegistryItem {
320 name: identity.name().to_owned(),
321 filename: identity.filename().to_owned(),
322 shell: identity.shell().map(|p| p.to_owned()),
323 data: PluginRegistryItemData::Valid {
324 metadata: Default::default(),
325 commands,
326 },
327 });
328 }
329
330 let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
332 if let Err(err) = std::fs::File::create(&new_plugin_file_path)
333 .map_err(|err| {
334 IoError::new_internal_with_path(
335 err,
336 "Could not create new plugin file",
337 nu_protocol::location!(),
338 new_plugin_file_path.clone(),
339 )
340 })
341 .map_err(ShellError::from)
342 .and_then(|file| contents.write_to(file, None))
343 {
344 report_shell_error(
345 None,
346 &engine_state,
347 &ShellError::GenericError {
348 error: "Failed to save migrated plugin file".into(),
349 msg: "".into(),
350 span: None,
351 help: Some("ensure `$nu.plugin-path` is writable".into()),
352 inner: vec![err],
353 },
354 );
355 return false;
356 }
357
358 if engine_state.is_interactive {
359 eprintln!(
360 "Your old plugin.nu file has been migrated to the new format: {}",
361 new_plugin_file_path.display()
362 );
363 eprintln!(
364 "The plugin.nu file has not been removed. If `plugin list` looks okay, \
365 you may do so manually."
366 );
367 }
368
369 perf!(
370 "migrate old plugin file",
371 start_time,
372 engine_state
373 .get_config()
374 .use_ansi_coloring
375 .get(&engine_state)
376 );
377 true
378}