diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 2412af0b6..e35c40882 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1211,6 +1211,87 @@ pub fn parse_export_in_module( ) } +pub fn parse_export_env( + working_set: &mut StateWorkingSet, + spans: &[Span], + expand_aliases_denylist: &[usize], +) -> (Pipeline, Option) { + // Just used to be allowed inside modules, otherwise does nothing in the parser. + + if !spans.is_empty() && working_set.get_span_contents(spans[0]) != b"export-env" { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Wrong call name for 'export-env' command".into(), + span(spans), + )), + ); + } + + if spans.len() < 2 { + return ( + garbage_pipeline(spans), + Some(ParseError::MissingPositional( + "block".into(), + span(spans), + "export-env ".into(), + )), + ); + } + + let call = match working_set.find_decl(b"export-env", &Type::Any) { + Some(decl_id) => { + let ParsedInternalCall { + call, + error: mut err, + output, + } = parse_internal_call( + working_set, + spans[0], + &[spans[1]], + decl_id, + expand_aliases_denylist, + ); + let decl = working_set.get_decl(decl_id); + + let call_span = span(spans); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: output, + custom_completion: None, + }]), + err, + ); + } + + call + } + None => { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: 'export-env' declaration not found".into(), + span(spans), + )), + ) + } + }; + + let pipeline = Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Any, + custom_completion: None, + }]); + + (pipeline, None) +} + pub fn parse_module_block( working_set: &mut StateWorkingSet, span: Span, @@ -1282,14 +1363,6 @@ pub fn parse_module_block( (pipeline, err) } - // TODO: Currently, it is not possible to define a private env var. - // TODO: Exported env vars are usable iside the module only if correctly - // exported by the user. For example: - // - // > module foo { export env a { "2" }; export def b [] { $env.a } } - // - // will work only if you call `use foo *; b` but not with `use foo; foo b` - // since in the second case, the name of the env var would be $env."foo a". b"export" => { let (pipe, exportables, err) = parse_export_in_module( working_set, @@ -1315,6 +1388,11 @@ pub fn parse_module_block( (pipe, err) } + b"export-env" => parse_export_env( + working_set, + &pipeline.commands[0].parts, + expand_aliases_denylist, + ), _ => ( garbage_pipeline(&pipeline.commands[0].parts), Some(ParseError::ExpectedKeyword( diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 26840917f..784454c53 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1,8 +1,6 @@ use crate::{ lex, lite_parse, lite_parse::LiteCommand, - parse_export_in_block, - parse_keywords::{parse_extern, parse_for, parse_source}, type_check::{math_result_type, type_compatible}, LiteBlock, ParseError, Token, TokenContents, }; @@ -19,8 +17,8 @@ use nu_protocol::{ }; use crate::parse_keywords::{ - parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_overlay, - parse_use, + parse_alias, parse_def, parse_def_predecl, parse_export_in_block, parse_extern, parse_for, + parse_hide, parse_let, parse_module, parse_overlay, parse_source, parse_use, }; use itertools::Itertools; diff --git a/tests/modules/mod.rs b/tests/modules/mod.rs index e65c07e98..b8d629ba6 100644 --- a/tests/modules/mod.rs +++ b/tests/modules/mod.rs @@ -345,3 +345,21 @@ fn module_nested_imports_in_dirs_prefixed() { assert_eq!(actual.out, "bar"); }) } + +#[test] +fn module_eval_export_env() { + Playground::setup("module_eval_export_env", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "spam.nu", + r#" + export-env { let-env FOO = 'foo' } + "#, + )]); + + let inp = &[r#"source spam.nu"#, r#"$env.FOO"#]; + + let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; "))); + + assert_eq!(actual.out, "foo"); + }) +} diff --git a/tests/parsing/mod.rs b/tests/parsing/mod.rs index 474a3012b..0cafc1a6c 100644 --- a/tests/parsing/mod.rs +++ b/tests/parsing/mod.rs @@ -151,3 +151,23 @@ fn parse_file_relative_to_parsed_file_dont_use_cwd_2() { assert!(actual.err.contains("File not found")); }) } + +#[test] +fn parse_export_env_in_module() { + let actual = nu!(cwd: "tests/parsing/samples", + r#" + module spam { export-env { } } + "#); + + assert!(actual.err.is_empty()); +} + +#[test] +fn parse_export_env_missing_block() { + let actual = nu!(cwd: "tests/parsing/samples", + r#" + module spam { export-env } + "#); + + assert!(actual.err.contains("missing block")); +}