mirror of
https://github.com/nushell/nushell
synced 2024-12-27 21:43:09 +00: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:
parent
aa7226d5f6
commit
88988dc9f4
18 changed files with 215 additions and 27 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -948,6 +948,7 @@ dependencies = [
|
||||||
"nu-path",
|
"nu-path",
|
||||||
"nu-plugin",
|
"nu-plugin",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -978,6 +979,7 @@ dependencies = [
|
||||||
"im",
|
"im",
|
||||||
"miette",
|
"miette",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ ctrlc = "3.2.1"
|
||||||
# mimalloc = { version = "*", default-features = false }
|
# mimalloc = { version = "*", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin"]
|
plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin"]
|
||||||
default = ["plugin"]
|
default = ["plugin"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -114,6 +114,12 @@ fn help(
|
||||||
span: head,
|
span: head,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cols.push("is_plugin".into());
|
||||||
|
vals.push(Value::Bool {
|
||||||
|
val: cmd.2,
|
||||||
|
span: head,
|
||||||
|
});
|
||||||
|
|
||||||
cols.push("usage".into());
|
cols.push("usage".into());
|
||||||
vals.push(Value::String { val: c, span: head });
|
vals.push(Value::String { val: c, span: head });
|
||||||
|
|
||||||
|
@ -157,6 +163,12 @@ fn help(
|
||||||
span: head,
|
span: head,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cols.push("is_plugin".into());
|
||||||
|
vals.push(Value::Bool {
|
||||||
|
val: cmd.2,
|
||||||
|
span: head,
|
||||||
|
});
|
||||||
|
|
||||||
cols.push("usage".into());
|
cols.push("usage".into());
|
||||||
vals.push(Value::String { val: c, span: head });
|
vals.push(Value::String { val: c, span: head });
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ impl Command for Register {
|
||||||
SyntaxShape::Filepath,
|
SyntaxShape::Filepath,
|
||||||
"location of bin for plugin",
|
"location of bin for plugin",
|
||||||
)
|
)
|
||||||
|
.optional("signature", SyntaxShape::Any, "plugin signature")
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ pub fn create_default_context() -> EngineState {
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
|
|
||||||
engine_state.merge_delta(delta);
|
let _ = engine_state.merge_delta(delta);
|
||||||
|
|
||||||
engine_state
|
engine_state
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub fn test_examples(cmd: impl Command + 'static) {
|
||||||
working_set.render()
|
working_set.render()
|
||||||
};
|
};
|
||||||
|
|
||||||
engine_state.merge_delta(delta);
|
let _ = engine_state.merge_delta(delta);
|
||||||
|
|
||||||
for example in examples {
|
for example in examples {
|
||||||
// Skip tests that don't have results to compare to
|
// Skip tests that don't have results to compare to
|
||||||
|
@ -53,7 +53,7 @@ pub fn test_examples(cmd: impl Command + 'static) {
|
||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
engine_state.merge_delta(delta);
|
let _ = engine_state.merge_delta(delta);
|
||||||
|
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
|
|
|
@ -585,7 +585,7 @@ pub fn eval_variable(
|
||||||
|
|
||||||
cols.push("is_plugin".to_string());
|
cols.push("is_plugin".to_string());
|
||||||
vals.push(Value::Bool {
|
vals.push(Value::Bool {
|
||||||
val: decl.is_plugin(),
|
val: decl.is_plugin().is_some(),
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ miette = "3.0.0"
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.29"
|
||||||
nu-protocol = { path = "../nu-protocol"}
|
nu-protocol = { path = "../nu-protocol"}
|
||||||
nu-plugin = { path = "../nu-plugin", optional=true}
|
nu-plugin = { path = "../nu-plugin", optional=true}
|
||||||
|
serde_json = "1.0"
|
||||||
nu-path = {path = "../nu-path"}
|
nu-path = {path = "../nu-path"}
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -192,6 +192,6 @@ pub enum ParseError {
|
||||||
FileNotFound(String),
|
FileNotFound(String),
|
||||||
|
|
||||||
#[error("Plugin error")]
|
#[error("Plugin error")]
|
||||||
#[diagnostic(code(nu::parser::export_not_found), url(docsrs))]
|
#[diagnostic(code(nu::parser::plugin_error), url(docsrs))]
|
||||||
PluginError(String),
|
PluginError(String),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1085,6 +1085,8 @@ pub fn parse_plugin(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
spans: &[Span],
|
spans: &[Span],
|
||||||
) -> (Statement, Option<ParseError>) {
|
) -> (Statement, Option<ParseError>) {
|
||||||
|
use nu_protocol::Signature;
|
||||||
|
|
||||||
let name = working_set.get_span_contents(spans[0]);
|
let name = working_set.get_span_contents(spans[0]);
|
||||||
|
|
||||||
if name != b"register" {
|
if name != b"register" {
|
||||||
|
@ -1122,7 +1124,8 @@ pub fn parse_plugin(
|
||||||
// store declaration in working set
|
// store declaration in working set
|
||||||
let plugin_decl =
|
let plugin_decl =
|
||||||
PluginDeclaration::new(filename.clone(), signature);
|
PluginDeclaration::new(filename.clone(), signature);
|
||||||
working_set.add_decl(Box::new(plugin_decl));
|
|
||||||
|
working_set.add_plugin_decl(Box::new(plugin_decl));
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
@ -1135,8 +1138,27 @@ pub fn parse_plugin(
|
||||||
Some(ParseError::NonUtf8(spans[1]))
|
Some(ParseError::NonUtf8(spans[1]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3 => {
|
||||||
|
let filename = working_set.get_span_contents(spans[1]);
|
||||||
|
let signature = working_set.get_span_contents(spans[2]);
|
||||||
|
|
||||||
|
if let Ok(filename) = String::from_utf8(filename.to_vec()) {
|
||||||
|
if let Ok(signature) = serde_json::from_slice::<Signature>(signature) {
|
||||||
|
let plugin_decl = PluginDeclaration::new(filename, signature);
|
||||||
|
working_set.add_plugin_decl(Box::new(plugin_decl));
|
||||||
|
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ParseError::PluginError(
|
||||||
|
"unable to deserialize signature".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(ParseError::NonUtf8(spans[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let span = spans[2..].iter().fold(spans[2], |acc, next| Span {
|
let span = spans[3..].iter().fold(spans[3], |acc, next| Span {
|
||||||
start: acc.start,
|
start: acc.start,
|
||||||
end: next.end,
|
end: next.end,
|
||||||
});
|
});
|
||||||
|
|
|
@ -215,8 +215,8 @@ impl Command for PluginDeclaration {
|
||||||
Ok(pipeline_data)
|
Ok(pipeline_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_plugin(&self) -> bool {
|
fn is_plugin(&self) -> Option<&str> {
|
||||||
true
|
Some(self.filename.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,3 +13,10 @@ chrono = { version="0.4.19", features=["serde"] }
|
||||||
chrono-humanize = "0.2.1"
|
chrono-humanize = "0.2.1"
|
||||||
byte-unit = "4.0.9"
|
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
|
// Is a plugin command
|
||||||
fn is_plugin(&self) -> bool {
|
fn is_plugin(&self) -> Option<&str> {
|
||||||
false
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// If command is a block i.e. def blah [] { }, get the block id
|
// If command is a block i.e. def blah [] { }, get the block id
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::Command;
|
use super::Command;
|
||||||
use crate::{
|
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 core::panic;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -8,6 +9,8 @@ use std::{
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
use std::path::PathBuf;
|
||||||
// Tells whether a decl etc. is visible or not
|
// Tells whether a decl etc. is visible or not
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Visibility {
|
struct Visibility {
|
||||||
|
@ -136,6 +139,8 @@ pub struct EngineState {
|
||||||
overlays: im::Vector<Overlay>,
|
overlays: im::Vector<Overlay>,
|
||||||
pub scope: im::Vector<ScopeFrame>,
|
pub scope: im::Vector<ScopeFrame>,
|
||||||
pub ctrlc: Option<Arc<AtomicBool>>,
|
pub ctrlc: Option<Arc<AtomicBool>>,
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
pub plugin_signatures: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const NU_VARIABLE_ID: usize = 0;
|
pub const NU_VARIABLE_ID: usize = 0;
|
||||||
|
@ -154,6 +159,8 @@ impl EngineState {
|
||||||
overlays: im::vector![],
|
overlays: im::vector![],
|
||||||
scope: im::vector![ScopeFrame::new()],
|
scope: im::vector![ScopeFrame::new()],
|
||||||
ctrlc: None,
|
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
|
/// 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.
|
/// 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
|
// Take the mutable reference and extend the permanent state from the working set
|
||||||
self.files.extend(delta.files);
|
self.files.extend(delta.files);
|
||||||
self.file_contents.extend(delta.file_contents);
|
self.file_contents.extend(delta.file_contents);
|
||||||
|
@ -188,6 +195,53 @@ impl EngineState {
|
||||||
last.overlays.insert(item.0, item.1);
|
last.overlays.insert(item.0, item.1);
|
||||||
}
|
}
|
||||||
last.visibility.merge_with(first.visibility);
|
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
|
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> {
|
pub fn find_overlay(&self, name: &[u8]) -> Option<OverlayId> {
|
||||||
for scope in self.scope.iter().rev() {
|
for scope in self.scope.iter().rev() {
|
||||||
if let Some(overlay_id) = scope.overlays.get(name) {
|
if let Some(overlay_id) = scope.overlays.get(name) {
|
||||||
|
@ -314,7 +372,7 @@ impl EngineState {
|
||||||
output
|
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![];
|
let mut output = vec![];
|
||||||
for decl in self.decls.iter() {
|
for decl in self.decls.iter() {
|
||||||
if decl.get_block_id().is_none() {
|
if decl.get_block_id().is_none() {
|
||||||
|
@ -322,7 +380,7 @@ impl EngineState {
|
||||||
signature.usage = decl.usage().to_string();
|
signature.usage = decl.usage().to_string();
|
||||||
signature.extra_usage = decl.extra_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)>,
|
pub(crate) file_contents: Vec<(Vec<u8>, usize, usize)>,
|
||||||
vars: Vec<Type>, // indexed by VarId
|
vars: Vec<Type>, // indexed by VarId
|
||||||
decls: Vec<Box<dyn Command>>, // indexed by DeclId
|
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
|
blocks: Vec<Block>, // indexed by BlockId
|
||||||
overlays: Vec<Overlay>, // indexed by OverlayId
|
overlays: Vec<Overlay>, // indexed by OverlayId
|
||||||
pub scope: Vec<ScopeFrame>,
|
pub scope: Vec<ScopeFrame>,
|
||||||
|
@ -457,6 +517,8 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
file_contents: vec![],
|
file_contents: vec![],
|
||||||
vars: vec![],
|
vars: vec![],
|
||||||
decls: vec![],
|
decls: vec![],
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
plugin_decls: vec![],
|
||||||
blocks: vec![],
|
blocks: vec![],
|
||||||
overlays: vec![],
|
overlays: vec![],
|
||||||
scope: vec![ScopeFrame::new()],
|
scope: vec![ScopeFrame::new()],
|
||||||
|
@ -527,6 +589,11 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
scope_frame.predecls.insert(name, decl_id)
|
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> {
|
pub fn merge_predecl(&mut self, name: &[u8]) -> Option<DeclId> {
|
||||||
let scope_frame = self
|
let scope_frame = self
|
||||||
.delta
|
.delta
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::ast::Call;
|
use crate::ast::Call;
|
||||||
use crate::engine::Command;
|
use crate::engine::Command;
|
||||||
use crate::engine::EngineState;
|
use crate::engine::EngineState;
|
||||||
|
@ -7,7 +10,7 @@ use crate::PipelineData;
|
||||||
use crate::SyntaxShape;
|
use crate::SyntaxShape;
|
||||||
use crate::VarId;
|
use crate::VarId;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Flag {
|
pub struct Flag {
|
||||||
pub long: String,
|
pub long: String,
|
||||||
pub short: Option<char>,
|
pub short: Option<char>,
|
||||||
|
@ -18,7 +21,7 @@ pub struct Flag {
|
||||||
pub var_id: Option<VarId>,
|
pub var_id: Option<VarId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct PositionalArg {
|
pub struct PositionalArg {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub desc: String,
|
pub desc: String,
|
||||||
|
@ -27,7 +30,7 @@ pub struct PositionalArg {
|
||||||
pub var_id: Option<VarId>,
|
pub var_id: Option<VarId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum Category {
|
pub enum Category {
|
||||||
Default,
|
Default,
|
||||||
Conversions,
|
Conversions,
|
||||||
|
@ -66,7 +69,7 @@ impl std::fmt::Display for Category {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Signature {
|
pub struct Signature {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub usage: String,
|
pub usage: String,
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::Type;
|
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.
|
/// 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 {
|
pub enum SyntaxShape {
|
||||||
/// A specific match to a word or symbol
|
/// A specific match to a word or symbol
|
||||||
Keyword(Vec<u8>, Box<SyntaxShape>),
|
Keyword(Vec<u8>, Box<SyntaxShape>),
|
||||||
|
|
|
@ -120,3 +120,51 @@ fn test_signature_same_name() {
|
||||||
)
|
)
|
||||||
.named("name", SyntaxShape::String, "named description", Some('n'));
|
.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,);
|
||||||
|
}
|
||||||
|
|
33
src/main.rs
33
src/main.rs
|
@ -118,7 +118,10 @@ fn main() -> Result<()> {
|
||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
engine_state.merge_delta(delta);
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
|
let working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
}
|
||||||
|
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
let mut stack = nu_protocol::engine::Stack::new();
|
||||||
|
|
||||||
|
@ -185,10 +188,9 @@ fn main() -> Result<()> {
|
||||||
config_path.push("nushell");
|
config_path.push("nushell");
|
||||||
config_path.push("config.nu");
|
config_path.push("config.nu");
|
||||||
|
|
||||||
// FIXME: remove this message when we're ready
|
|
||||||
println!("Loading config from: {:?}", config_path);
|
|
||||||
|
|
||||||
if config_path.exists() {
|
if config_path.exists() {
|
||||||
|
// FIXME: remove this message when we're ready
|
||||||
|
println!("Loading config from: {:?}", config_path);
|
||||||
let config_filename = config_path.to_string_lossy().to_owned();
|
let config_filename = config_path.to_string_lossy().to_owned();
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read_to_string(&config_path) {
|
if let Ok(contents) = std::fs::read_to_string(&config_path) {
|
||||||
|
@ -206,6 +208,24 @@ fn main() -> Result<()> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
{
|
||||||
|
// Reading signatures from signature file
|
||||||
|
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||||
|
if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||||
|
// Path to store plugins signatures
|
||||||
|
plugin_path.push("nushell");
|
||||||
|
plugin_path.push("plugin.nu");
|
||||||
|
engine_state.plugin_signatures = Some(plugin_path.clone());
|
||||||
|
|
||||||
|
let plugin_filename = plugin_path.to_string_lossy().to_owned();
|
||||||
|
|
||||||
|
if let Ok(contents) = std::fs::read_to_string(&plugin_path) {
|
||||||
|
eval_source(&mut engine_state, &mut stack, &contents, &plugin_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
//Reset the ctrl-c handler
|
//Reset the ctrl-c handler
|
||||||
ctrlc.store(false, Ordering::SeqCst);
|
ctrlc.store(false, Ordering::SeqCst);
|
||||||
|
@ -387,7 +407,10 @@ fn eval_source(
|
||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
engine_state.merge_delta(delta);
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
}
|
||||||
|
|
||||||
match eval_block(
|
match eval_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
|
|
Loading…
Reference in a new issue