mirror of
https://github.com/nushell/nushell.git
synced 2025-08-19 08:52:27 +02:00
Plugins signature load (#349)
* saving signatures to file * loading plugin signature from file * is_plugin column for help command
This commit is contained in:
@@ -12,4 +12,11 @@ serde = {version = "1.0.130", features = ["derive"]}
|
||||
chrono = { version="0.4.19", features=["serde"] }
|
||||
chrono-humanize = "0.2.1"
|
||||
byte-unit = "4.0.9"
|
||||
im = "15.0.0"
|
||||
im = "15.0.0"
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
|
||||
[features]
|
||||
plugin = ["serde_json"]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0"
|
||||
|
@@ -47,8 +47,8 @@ pub trait Command: Send + Sync + CommandClone {
|
||||
}
|
||||
|
||||
// Is a plugin command
|
||||
fn is_plugin(&self) -> bool {
|
||||
false
|
||||
fn is_plugin(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
// If command is a block i.e. def blah [] { }, get the block id
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use super::Command;
|
||||
use crate::{
|
||||
ast::Block, BlockId, DeclId, Example, Overlay, OverlayId, Signature, Span, Type, VarId,
|
||||
ast::Block, BlockId, DeclId, Example, Overlay, OverlayId, ShellError, Signature, Span, Type,
|
||||
VarId,
|
||||
};
|
||||
use core::panic;
|
||||
use std::{
|
||||
@@ -8,6 +9,8 @@ use std::{
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
use std::path::PathBuf;
|
||||
// Tells whether a decl etc. is visible or not
|
||||
#[derive(Debug, Clone)]
|
||||
struct Visibility {
|
||||
@@ -136,6 +139,8 @@ pub struct EngineState {
|
||||
overlays: im::Vector<Overlay>,
|
||||
pub scope: im::Vector<ScopeFrame>,
|
||||
pub ctrlc: Option<Arc<AtomicBool>>,
|
||||
#[cfg(feature = "plugin")]
|
||||
pub plugin_signatures: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub const NU_VARIABLE_ID: usize = 0;
|
||||
@@ -154,6 +159,8 @@ impl EngineState {
|
||||
overlays: im::vector![],
|
||||
scope: im::vector![ScopeFrame::new()],
|
||||
ctrlc: None,
|
||||
#[cfg(feature = "plugin")]
|
||||
plugin_signatures: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +171,7 @@ impl EngineState {
|
||||
///
|
||||
/// When we want to preserve what the parser has created, we can take its output (the `StateDelta`) and
|
||||
/// use this function to merge it into the global state.
|
||||
pub fn merge_delta(&mut self, mut delta: StateDelta) {
|
||||
pub fn merge_delta(&mut self, mut delta: StateDelta) -> Result<(), ShellError> {
|
||||
// Take the mutable reference and extend the permanent state from the working set
|
||||
self.files.extend(delta.files);
|
||||
self.file_contents.extend(delta.file_contents);
|
||||
@@ -188,6 +195,53 @@ impl EngineState {
|
||||
last.overlays.insert(item.0, item.1);
|
||||
}
|
||||
last.visibility.merge_with(first.visibility);
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
if !delta.plugin_decls.is_empty() {
|
||||
for decl in delta.plugin_decls {
|
||||
let name = decl.name().as_bytes().to_vec();
|
||||
self.decls.push_back(decl);
|
||||
let decl_id = self.decls.len() - 1;
|
||||
|
||||
last.decls.insert(name, decl_id);
|
||||
last.visibility.use_decl_id(&decl_id);
|
||||
}
|
||||
|
||||
return self.update_plugin_file();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn update_plugin_file(&self) -> Result<(), ShellError> {
|
||||
use std::io::Write;
|
||||
|
||||
// Updating the signatures plugin file with the added signatures
|
||||
if let Some(plugin_path) = &self.plugin_signatures {
|
||||
// Always creating the file which will erase previous signatures
|
||||
let mut plugin_file = std::fs::File::create(plugin_path.as_path())
|
||||
.map_err(|err| ShellError::PluginError(err.to_string()))?;
|
||||
|
||||
// Plugin definitions with parsed signature
|
||||
for decl in self.plugin_decls() {
|
||||
// A successful plugin registration already includes the plugin filename
|
||||
// No need to check the None option
|
||||
let file_name = decl.is_plugin().expect("plugin should have file name");
|
||||
|
||||
let line = serde_json::to_string_pretty(&decl.signature())
|
||||
.map(|signature| format!("register {} {}\n", file_name, signature))
|
||||
.map_err(|err| ShellError::PluginError(err.to_string()))?;
|
||||
|
||||
plugin_file
|
||||
.write_all(line.as_bytes())
|
||||
.map_err(|err| ShellError::PluginError(err.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ShellError::PluginError("Plugin file not found".into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,6 +306,10 @@ impl EngineState {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn plugin_decls(&self) -> impl Iterator<Item = &Box<dyn Command + 'static>> {
|
||||
self.decls.iter().filter(|decl| decl.is_plugin().is_some())
|
||||
}
|
||||
|
||||
pub fn find_overlay(&self, name: &[u8]) -> Option<OverlayId> {
|
||||
for scope in self.scope.iter().rev() {
|
||||
if let Some(overlay_id) = scope.overlays.get(name) {
|
||||
@@ -314,7 +372,7 @@ impl EngineState {
|
||||
output
|
||||
}
|
||||
|
||||
pub fn get_signatures_with_examples(&self) -> Vec<(Signature, Vec<Example>)> {
|
||||
pub fn get_signatures_with_examples(&self) -> Vec<(Signature, Vec<Example>, bool)> {
|
||||
let mut output = vec![];
|
||||
for decl in self.decls.iter() {
|
||||
if decl.get_block_id().is_none() {
|
||||
@@ -322,7 +380,7 @@ impl EngineState {
|
||||
signature.usage = decl.usage().to_string();
|
||||
signature.extra_usage = decl.extra_usage().to_string();
|
||||
|
||||
output.push((signature, decl.examples()));
|
||||
output.push((signature, decl.examples(), decl.is_plugin().is_some()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,6 +476,8 @@ pub struct StateDelta {
|
||||
pub(crate) file_contents: Vec<(Vec<u8>, usize, usize)>,
|
||||
vars: Vec<Type>, // indexed by VarId
|
||||
decls: Vec<Box<dyn Command>>, // indexed by DeclId
|
||||
#[cfg(feature = "plugin")]
|
||||
plugin_decls: Vec<Box<dyn Command>>, // indexed by DeclId
|
||||
blocks: Vec<Block>, // indexed by BlockId
|
||||
overlays: Vec<Overlay>, // indexed by OverlayId
|
||||
pub scope: Vec<ScopeFrame>,
|
||||
@@ -457,6 +517,8 @@ impl<'a> StateWorkingSet<'a> {
|
||||
file_contents: vec![],
|
||||
vars: vec![],
|
||||
decls: vec![],
|
||||
#[cfg(feature = "plugin")]
|
||||
plugin_decls: vec![],
|
||||
blocks: vec![],
|
||||
overlays: vec![],
|
||||
scope: vec![ScopeFrame::new()],
|
||||
@@ -527,6 +589,11 @@ impl<'a> StateWorkingSet<'a> {
|
||||
scope_frame.predecls.insert(name, decl_id)
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn add_plugin_decl(&mut self, decl: Box<dyn Command>) {
|
||||
self.delta.plugin_decls.push(decl);
|
||||
}
|
||||
|
||||
pub fn merge_predecl(&mut self, name: &[u8]) -> Option<DeclId> {
|
||||
let scope_frame = self
|
||||
.delta
|
||||
|
@@ -1,3 +1,6 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ast::Call;
|
||||
use crate::engine::Command;
|
||||
use crate::engine::EngineState;
|
||||
@@ -7,7 +10,7 @@ use crate::PipelineData;
|
||||
use crate::SyntaxShape;
|
||||
use crate::VarId;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Flag {
|
||||
pub long: String,
|
||||
pub short: Option<char>,
|
||||
@@ -18,7 +21,7 @@ pub struct Flag {
|
||||
pub var_id: Option<VarId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PositionalArg {
|
||||
pub name: String,
|
||||
pub desc: String,
|
||||
@@ -27,7 +30,7 @@ pub struct PositionalArg {
|
||||
pub var_id: Option<VarId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Category {
|
||||
Default,
|
||||
Conversions,
|
||||
@@ -66,7 +69,7 @@ impl std::fmt::Display for Category {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Signature {
|
||||
pub name: String,
|
||||
pub usage: String,
|
||||
|
@@ -1,7 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Type;
|
||||
|
||||
/// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SyntaxShape {
|
||||
/// A specific match to a word or symbol
|
||||
Keyword(Vec<u8>, Box<SyntaxShape>),
|
||||
|
@@ -120,3 +120,51 @@ fn test_signature_same_name() {
|
||||
)
|
||||
.named("name", SyntaxShape::String, "named description", Some('n'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signature_round_trip() {
|
||||
let signature = Signature::new("new_signature")
|
||||
.desc("description")
|
||||
.required("first", SyntaxShape::String, "first required")
|
||||
.required("second", SyntaxShape::Int, "second required")
|
||||
.optional("optional", SyntaxShape::String, "optional description")
|
||||
.required_named(
|
||||
"req_named",
|
||||
SyntaxShape::String,
|
||||
"required named description",
|
||||
Some('r'),
|
||||
)
|
||||
.named("named", SyntaxShape::String, "named description", Some('n'))
|
||||
.switch("switch", "switch description", None)
|
||||
.rest("rest", SyntaxShape::String, "rest description")
|
||||
.category(nu_protocol::Category::Conversions);
|
||||
|
||||
let string = serde_json::to_string_pretty(&signature).unwrap();
|
||||
let returned: Signature = serde_json::from_str(&string).unwrap();
|
||||
|
||||
assert_eq!(signature.name, returned.name);
|
||||
assert_eq!(signature.usage, returned.usage);
|
||||
assert_eq!(signature.extra_usage, returned.extra_usage);
|
||||
assert_eq!(signature.is_filter, returned.is_filter);
|
||||
assert_eq!(signature.category, returned.category);
|
||||
|
||||
signature
|
||||
.required_positional
|
||||
.iter()
|
||||
.zip(returned.required_positional.iter())
|
||||
.for_each(|(lhs, rhs)| assert_eq!(lhs, rhs));
|
||||
|
||||
signature
|
||||
.optional_positional
|
||||
.iter()
|
||||
.zip(returned.optional_positional.iter())
|
||||
.for_each(|(lhs, rhs)| assert_eq!(lhs, rhs));
|
||||
|
||||
signature
|
||||
.named
|
||||
.iter()
|
||||
.zip(returned.named.iter())
|
||||
.for_each(|(lhs, rhs)| assert_eq!(lhs, rhs));
|
||||
|
||||
assert_eq!(signature.rest_positional, returned.rest_positional,);
|
||||
}
|
||||
|
Reference in New Issue
Block a user