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