Skip to content

Commit

Permalink
Add let-with-metadata and set-metadata to allow persisting metadata
Browse files Browse the repository at this point in the history
This should help work around issues like nushell#7368 without having to
propagate metadata everywhere (which would be a much larger refactor).
  • Loading branch information
KoviRobi committed Feb 2, 2023
1 parent ad14b76 commit 8f4e025
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 53 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -65,6 +65,7 @@ rayon = "1.6.1"
is_executable = "1.0.1"
simplelog = "0.12.0"
time = "0.3.12"
strum = { version = "0.24.1", features = ["derive"] }

[target.'cfg(not(target_os = "windows"))'.dependencies]
# Our dependencies don't use OpenSSL on Windows
Expand Down
131 changes: 131 additions & 0 deletions crates/nu-command/src/core_commands/let_with_metadata.rs
@@ -0,0 +1,131 @@
use nu_engine::eval_expression_with_input;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Type};

#[derive(Clone)]
pub struct LetWithMetadata;

impl Command for LetWithMetadata {
fn name(&self) -> &str {
"let-with-metadata"
}

fn usage(&self) -> &str {
"Create a variable and give it a value."
}

fn signature(&self) -> nu_protocol::Signature {
Signature::build("let-with-metadata")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
.required(
"metadata_var_name",
SyntaxShape::VarWithOptType,
"metadata variable name",
)
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
"equals sign followed by value",
)
.category(Category::Core)
}

fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"#
}

fn is_parser_keyword(&self) -> bool {
true
}

fn search_terms(&self) -> Vec<&str> {
vec!["set", "const"]
}

fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let var_id = call
.positional_nth(0)
.expect("checked through parser")
.as_var()
.expect("internal error: missing variable");

let metadata_var_id = call
.positional_nth(1)
.expect("checked through parser")
.as_var()
.expect("internal error: missing variable");

let keyword_expr = call
.positional_nth(2)
.expect("checked through parser")
.as_keyword()
.expect("internal error: missing keyword");

let rhs = eval_expression_with_input(
engine_state,
stack,
keyword_expr,
input,
call.redirect_stdout,
call.redirect_stderr,
)?
.0;

let metadata = rhs.metadata();
let val = rhs.into_value(call.head.clone());
stack.add_var(var_id, val.clone());
stack.add_var(
metadata_var_id,
crate::metadata::build_metadata_record(val.span().ok(), &metadata, call.head),
);

Ok(PipelineData::empty())
}

fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Set a variable to a value",
example: "let-with-metadata x md = $in",
result: None,
},
Example {
description: "Set a variable to the result of an expression",
example: "let-with-metadata x md = ls",
result: None,
},
]
}
}

#[cfg(test)]
mod test {
use nu_protocol::engine::CommandType;

use super::*;

#[test]
fn test_examples() {
use crate::test_examples;

test_examples(LetWithMetadata {})
}

#[test]
fn test_command_type() {
assert!(matches!(
LetWithMetadata.command_type(),
CommandType::Keyword
));
}
}
78 changes: 27 additions & 51 deletions crates/nu-command/src/core_commands/metadata.rs
Expand Up @@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata, Signature,
Span, SyntaxShape, Type, Value,
Category, Example, IntoPipelineData, PipelineData, PipelineMetadata, Signature, Span,
SyntaxShape, Type, Value,
};

#[derive(Clone)]
Expand Down Expand Up @@ -54,51 +54,35 @@ impl Command for Metadata {
} => {
let origin = stack.get_var_with_origin(*var_id, *span)?;

Ok(build_metadata_record(&origin, &input.metadata(), head)
.into_pipeline_data())
Ok(
build_metadata_record(origin.span().ok(), &input.metadata(), head)
.into_pipeline_data(),
)
}
_ => {
let val: Value = call.req(engine_state, stack, 0)?;
Ok(build_metadata_record(&val, &input.metadata(), head)
.into_pipeline_data())
Ok(
build_metadata_record(val.span().ok(), &input.metadata(), head)
.into_pipeline_data(),
)
}
}
} else {
let val: Value = call.req(engine_state, stack, 0)?;
Ok(build_metadata_record(&val, &input.metadata(), head).into_pipeline_data())
Ok(
build_metadata_record(val.span().ok(), &input.metadata(), head)
.into_pipeline_data(),
)
}
}
Some(_) => {
let val: Value = call.req(engine_state, stack, 0)?;
Ok(build_metadata_record(&val, &input.metadata(), head).into_pipeline_data())
}
None => {
let mut cols = vec![];
let mut vals = vec![];
if let Some(x) = &input.metadata() {
match x {
PipelineMetadata {
data_source: DataSource::Ls,
} => {
cols.push("source".into());
vals.push(Value::string("ls", head))
}
PipelineMetadata {
data_source: DataSource::HtmlThemes,
} => {
cols.push("source".into());
vals.push(Value::string("into html --list", head))
}
}
}

Ok(Value::Record {
cols,
vals,
span: head,
}
.into_pipeline_data())
Ok(
build_metadata_record(val.span().ok(), &input.metadata(), head)
.into_pipeline_data(),
)
}
None => Ok(build_metadata_record(None, &input.metadata(), head).into_pipeline_data()),
}
}

Expand All @@ -118,11 +102,15 @@ impl Command for Metadata {
}
}

fn build_metadata_record(arg: &Value, metadata: &Option<PipelineMetadata>, head: Span) -> Value {
pub fn build_metadata_record(
arg_span: Option<Span>,
metadata: &Option<PipelineMetadata>,
head: Span,
) -> Value {
let mut cols = vec![];
let mut vals = vec![];

if let Ok(span) = arg.span() {
if let Some(span) = arg_span {
cols.push("span".into());
vals.push(Value::Record {
cols: vec!["start".into(), "end".into()],
Expand All @@ -141,20 +129,8 @@ fn build_metadata_record(arg: &Value, metadata: &Option<PipelineMetadata>, head:
}

if let Some(x) = &metadata {
match x {
PipelineMetadata {
data_source: DataSource::Ls,
} => {
cols.push("source".into());
vals.push(Value::string("ls", head))
}
PipelineMetadata {
data_source: DataSource::HtmlThemes,
} => {
cols.push("source".into());
vals.push(Value::string("into html --list", head))
}
}
cols.push("source".into());
vals.push(Value::string(format!("{}", x), head))
}

Value::Record {
Expand Down
6 changes: 5 additions & 1 deletion crates/nu-command/src/core_commands/mod.rs
Expand Up @@ -29,12 +29,14 @@ mod hide_env;
mod if_;
mod ignore;
mod let_;
mod let_with_metadata;
mod loop_;
mod metadata;
pub(crate) mod metadata;
mod module;
mod mut_;
pub(crate) mod overlay;
mod return_;
mod set_metadata;
mod try_;
mod use_;
mod version;
Expand Down Expand Up @@ -71,12 +73,14 @@ pub use hide_env::HideEnv;
pub use if_::If;
pub use ignore::Ignore;
pub use let_::Let;
pub use let_with_metadata::LetWithMetadata;
pub use loop_::Loop;
pub use metadata::Metadata;
pub use module::Module;
pub use mut_::Mut;
pub use overlay::*;
pub use return_::Return;
pub use set_metadata::SetMetadata;
pub use try_::Try;
pub use use_::Use;
pub use version::Version;
Expand Down
75 changes: 75 additions & 0 deletions crates/nu-command/src/core_commands/set_metadata.rs
@@ -0,0 +1,75 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, PipelineMetadata, Signature, SyntaxShape, Type, Value,
};

#[derive(Clone)]
pub struct SetMetadata;

impl Command for SetMetadata {
fn name(&self) -> &str {
"set-metadata"
}

fn usage(&self) -> &str {
"Assigns the metadata from the metadata argument into the stream"
}

fn signature(&self) -> nu_protocol::Signature {
Signature::build("set-metadata")
.input_output_types(vec![(Type::Any, Type::Any)])
.allow_variants_without_examples(true)
.required(
"metadata_var_name",
SyntaxShape::Record,
"metadata variable name",
)
.category(Category::Core)
}

fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let val: Value = call.req(engine_state, stack, 0)?;
let metadata = get_source(val);
Ok(input.set_metadata(metadata))
}

fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Set the metadata of a variable",
example: "let-with-metadata a md = ls; $a | set-metadata $md",
result: None,
}]
}
}

pub fn get_source(arg: Value) -> Option<PipelineMetadata> {
let source_val = match arg {
Value::Record { cols, vals, .. } => cols
.iter()
.zip(vals)
.find_map(|(col, val)| (col == "source").then_some(val)),
_ => return None,
}?;

Some(source_val.as_string().ok()?.parse().ok()?)
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_examples() {
use crate::test_examples;

test_examples(SetMetadata {})
}
}
2 changes: 2 additions & 0 deletions crates/nu-command/src/default_context.rs
Expand Up @@ -64,11 +64,13 @@ pub fn create_default_context() -> EngineState {
OverlayNew,
OverlayHide,
Let,
LetWithMetadata,
Loop,
Metadata,
Module,
Mut,
Return,
SetMetadata,
Try,
Use,
Version,
Expand Down

0 comments on commit 8f4e025

Please sign in to comment.