nu_protocol/plugin/
identity.rs1use std::path::{Path, PathBuf};
2
3use crate::{ParseError, ShellError, Spanned};
4
5#[derive(Debug, Clone)]
7pub struct InvalidPluginFilename(PathBuf);
8
9impl std::fmt::Display for InvalidPluginFilename {
10 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11 f.write_str("invalid plugin filename")
12 }
13}
14
15impl From<Spanned<InvalidPluginFilename>> for ParseError {
16 fn from(error: Spanned<InvalidPluginFilename>) -> ParseError {
17 ParseError::LabeledError(
18 "Invalid plugin filename".into(),
19 "must start with `nu_plugin_`".into(),
20 error.span,
21 )
22 }
23}
24
25impl From<Spanned<InvalidPluginFilename>> for ShellError {
26 fn from(error: Spanned<InvalidPluginFilename>) -> ShellError {
27 ShellError::GenericError {
28 error: format!("Invalid plugin filename: {}", error.item.0.display()),
29 msg: "not a valid plugin filename".into(),
30 span: Some(error.span),
31 help: Some("valid Nushell plugin filenames must start with `nu_plugin_`".into()),
32 inner: vec![],
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct PluginIdentity {
39 filename: PathBuf,
41 shell: Option<PathBuf>,
43 name: String,
45}
46
47impl PluginIdentity {
48 pub fn new(
53 filename: impl Into<PathBuf>,
54 shell: Option<PathBuf>,
55 ) -> Result<PluginIdentity, InvalidPluginFilename> {
56 let filename: PathBuf = filename.into();
57
58 if filename.is_relative() {
60 return Err(InvalidPluginFilename(filename));
61 }
62
63 let name = filename
64 .file_stem()
65 .map(|stem| stem.to_string_lossy().into_owned())
66 .and_then(|stem| stem.strip_prefix("nu_plugin_").map(|s| s.to_owned()))
67 .ok_or_else(|| InvalidPluginFilename(filename.clone()))?;
68
69 Ok(PluginIdentity {
70 filename,
71 shell,
72 name,
73 })
74 }
75
76 pub fn filename(&self) -> &Path {
78 &self.filename
79 }
80
81 pub fn shell(&self) -> Option<&Path> {
83 self.shell.as_deref()
84 }
85
86 pub fn name(&self) -> &str {
92 &self.name
93 }
94
95 #[cfg(windows)]
97 #[doc(hidden)]
98 pub fn new_fake(name: &str) -> PluginIdentity {
99 PluginIdentity::new(format!(r"C:\fake\path\nu_plugin_{name}.exe"), None)
100 .expect("fake plugin identity path is invalid")
101 }
102
103 #[cfg(not(windows))]
105 #[doc(hidden)]
106 pub fn new_fake(name: &str) -> PluginIdentity {
107 PluginIdentity::new(format!(r"/fake/path/nu_plugin_{name}"), None)
108 .expect("fake plugin identity path is invalid")
109 }
110
111 pub fn add_command(&self) -> String {
113 if let Some(shell) = self.shell() {
114 format!(
115 "plugin add --shell '{}' '{}'",
116 shell.display(),
117 self.filename().display(),
118 )
119 } else {
120 format!("plugin add '{}'", self.filename().display())
121 }
122 }
123
124 pub fn use_command(&self) -> String {
126 format!("plugin use '{}'", self.name())
127 }
128}
129
130#[test]
131fn parses_name_from_path() {
132 assert_eq!("test", PluginIdentity::new_fake("test").name());
133 assert_eq!("test_2", PluginIdentity::new_fake("test_2").name());
134 let absolute_path = if cfg!(windows) {
135 r"C:\path\to\nu_plugin_foo.sh"
136 } else {
137 "/path/to/nu_plugin_foo.sh"
138 };
139 assert_eq!(
140 "foo",
141 PluginIdentity::new(absolute_path, Some("sh".into()))
142 .expect("should be valid")
143 .name()
144 );
145 PluginIdentity::new("nu_plugin_foo.sh", Some("sh".into())).expect_err("should be invalid");
147 PluginIdentity::new("other", None).expect_err("should be invalid");
148 PluginIdentity::new("", None).expect_err("should be invalid");
149}