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