nu_protocol/plugin/registry_file/
mod.rs1use std::{
2 io::{Read, Write},
3 path::PathBuf,
4};
5
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 PluginIdentity, PluginMetadata, PluginSignature, ShellError, Span,
10 shell_error::generic::GenericError,
11};
12
13const BUFFER_SIZE: usize = 65536;
15
16const COMPRESSION_QUALITY: u32 = 3; const WIN_SIZE: u32 = 20; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21pub struct PluginRegistryFile {
22 pub nushell_version: String,
24
25 pub plugins: Vec<PluginRegistryItem>,
27}
28
29impl Default for PluginRegistryFile {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl PluginRegistryFile {
36 pub fn new() -> PluginRegistryFile {
38 PluginRegistryFile {
39 nushell_version: env!("CARGO_PKG_VERSION").to_owned(),
40 plugins: vec![],
41 }
42 }
43
44 pub fn read_from(
46 reader: impl Read,
47 error_span: Option<Span>,
48 ) -> Result<PluginRegistryFile, ShellError> {
49 let load_error = |err: String| {
50 let error = format!("Failed to load plugin file: {err}");
51 let msg = "plugin file load attempted here";
52 let help = "it may be corrupt. Try deleting it and registering your plugins again";
53 match error_span {
54 Some(span) => {
55 ShellError::Generic(GenericError::new(error, msg, span).with_help(help))
56 }
57 None => ShellError::Generic(GenericError::new_internal(error, msg).with_help(help)),
58 }
59 };
60
61 let mut bytes = Vec::new();
63 brotli::Decompressor::new(reader, BUFFER_SIZE)
64 .read_to_end(&mut bytes)
65 .map_err(|err| load_error(err.to_string()))?;
66
67 match rmp_serde::from_slice::<StrictPluginRegistryFile>(&bytes) {
74 Ok(file) => Ok(file.into()),
75 Err(_) => rmp_serde::from_slice(&bytes).map_err(|err| load_error(err.to_string())),
76 }
77 }
78
79 pub fn write_to(
83 &mut self,
84 writer: impl Write,
85 error_span: Option<Span>,
86 ) -> Result<(), ShellError> {
87 env!("CARGO_PKG_VERSION").clone_into(&mut self.nushell_version);
89
90 let mut brotli_writer =
92 brotli::CompressorWriter::new(writer, BUFFER_SIZE, COMPRESSION_QUALITY, WIN_SIZE);
93
94 rmp_serde::encode::write_named(&mut brotli_writer, self)
95 .map_err(|err| err.to_string())
96 .and_then(|_| brotli_writer.flush().map_err(|err| err.to_string()))
97 .map_err(|err| {
98 let error = "Failed to save plugin file";
99 let msg = "plugin file save attempted here";
100 match error_span {
101 Some(span) => {
102 ShellError::Generic(GenericError::new(error, msg, span).with_help(err))
103 }
104 None => {
105 ShellError::Generic(GenericError::new_internal(error, msg).with_help(err))
106 }
107 }
108 })
109 }
110
111 pub fn upsert_plugin(&mut self, item: PluginRegistryItem) {
113 if let Some(existing_item) = self.plugins.iter_mut().find(|p| p.name == item.name) {
114 *existing_item = item;
115 } else {
116 self.plugins.push(item);
117
118 self.plugins
120 .sort_by(|item1, item2| item1.name.cmp(&item2.name));
121 }
122 }
123}
124
125#[derive(Deserialize)]
129struct StrictPluginRegistryFile {
130 nushell_version: String,
131 plugins: Vec<StrictPluginRegistryItem>,
132}
133
134#[derive(Deserialize)]
136struct StrictPluginRegistryItem {
137 name: String,
138 filename: PathBuf,
139 shell: Option<PathBuf>,
140 #[serde(default)]
141 metadata: PluginMetadata,
142 commands: Vec<PluginSignature>,
143}
144
145impl From<StrictPluginRegistryFile> for PluginRegistryFile {
146 fn from(file: StrictPluginRegistryFile) -> Self {
147 PluginRegistryFile {
148 nushell_version: file.nushell_version,
149 plugins: file
150 .plugins
151 .into_iter()
152 .map(|item| PluginRegistryItem {
153 name: item.name,
154 filename: item.filename,
155 shell: item.shell,
156 data: PluginRegistryItemData::Valid {
157 metadata: item.metadata,
158 commands: item.commands,
159 },
160 })
161 .collect(),
162 }
163 }
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
171pub struct PluginRegistryItem {
172 pub name: String,
175
176 pub filename: PathBuf,
178
179 pub shell: Option<PathBuf>,
181
182 #[serde(flatten)]
185 pub data: PluginRegistryItemData,
186}
187
188impl PluginRegistryItem {
189 pub fn new(
191 identity: &PluginIdentity,
192 metadata: PluginMetadata,
193 mut commands: Vec<PluginSignature>,
194 ) -> PluginRegistryItem {
195 commands.sort_by(|cmd1, cmd2| cmd1.sig.name.cmp(&cmd2.sig.name));
197
198 PluginRegistryItem {
199 name: identity.name().to_owned(),
200 filename: identity.filename().to_owned(),
201 shell: identity.shell().map(|p| p.to_owned()),
202 data: PluginRegistryItemData::Valid { metadata, commands },
203 }
204 }
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
210#[serde(untagged)]
211pub enum PluginRegistryItemData {
212 Valid {
213 #[serde(default)]
215 metadata: PluginMetadata,
216 commands: Vec<PluginSignature>,
218 },
219 #[serde(
220 serialize_with = "serialize_invalid",
221 deserialize_with = "deserialize_invalid"
222 )]
223 Invalid,
224}
225
226fn serialize_invalid<S>(serializer: S) -> Result<S::Ok, S::Error>
227where
228 S: serde::Serializer,
229{
230 ().serialize(serializer)
231}
232
233fn deserialize_invalid<'de, D>(deserializer: D) -> Result<(), D::Error>
234where
235 D: serde::Deserializer<'de>,
236{
237 serde::de::IgnoredAny::deserialize(deserializer)?;
238 Ok(())
239}
240
241#[cfg(test)]
242mod tests;