From 73028a694ee120e1fbbb0039562e484f6af9e35b Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Sun, 26 May 2024 02:01:44 -0700 Subject: [PATCH 01/14] Implement unsetting variables --- src/analyzer.rs | 6 ++++++ src/command_ext.rs | 28 ++++++++++++++++++++++------ src/evaluator.rs | 13 ++++++++++++- src/item.rs | 4 ++++ src/justfile.rs | 7 ++++++- src/keyword.rs | 1 + src/lib.rs | 2 +- src/node.rs | 5 +++++ src/parser.rs | 5 +++++ src/recipe.rs | 13 +++++++------ src/recipe_context.rs | 1 + tests/export.rs | 37 +++++++++++++++++++++++++++++++++++++ tests/json.rs | 19 +++++++++++++++++++ 13 files changed, 126 insertions(+), 15 deletions(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index ed9fb37ba1..9064699826 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -37,6 +37,8 @@ impl<'src> Analyzer<'src> { let mut modules: Table = Table::new(); + let mut unsets: HashSet = HashSet::new(); + let mut definitions: HashMap<&str, (&'static str, Name)> = HashMap::new(); let mut define = |name: Name<'src>, @@ -98,6 +100,9 @@ impl<'src> Analyzer<'src> { self.analyze_set(set)?; self.sets.insert(set.clone()); } + Item::Unset { name } => { + unsets.insert(name.lexeme().to_string()); + } } } @@ -167,6 +172,7 @@ impl<'src> Analyzer<'src> { recipes, settings, source: root.into(), + unsets, warnings, }) } diff --git a/src/command_ext.rs b/src/command_ext.rs index a317e6fcae..fbd6d256a6 100644 --- a/src/command_ext.rs +++ b/src/command_ext.rs @@ -1,25 +1,41 @@ use super::*; pub(crate) trait CommandExt { - fn export(&mut self, settings: &Settings, dotenv: &BTreeMap, scope: &Scope); + fn export( + &mut self, + settings: &Settings, + dotenv: &BTreeMap, + scope: &Scope, + unsets: &HashSet, + ); - fn export_scope(&mut self, settings: &Settings, scope: &Scope); + fn export_scope(&mut self, settings: &Settings, scope: &Scope, unsets: &HashSet); } impl CommandExt for Command { - fn export(&mut self, settings: &Settings, dotenv: &BTreeMap, scope: &Scope) { + fn export( + &mut self, + settings: &Settings, + dotenv: &BTreeMap, + scope: &Scope, + unsets: &HashSet, + ) { for (name, value) in dotenv { self.env(name, value); } if let Some(parent) = scope.parent() { - self.export_scope(settings, parent); + self.export_scope(settings, parent, unsets); } } - fn export_scope(&mut self, settings: &Settings, scope: &Scope) { + fn export_scope(&mut self, settings: &Settings, scope: &Scope, unsets: &HashSet) { if let Some(parent) = scope.parent() { - self.export_scope(settings, parent); + self.export_scope(settings, parent, unsets); + } + + for unset in unsets { + self.env_remove(unset); } for binding in scope.bindings() { diff --git a/src/evaluator.rs b/src/evaluator.rs index 1e029c79e5..03ba69222c 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -8,6 +8,7 @@ pub(crate) struct Evaluator<'src: 'run, 'run> { pub(crate) scope: Scope<'src, 'run>, pub(crate) search: &'run Search, pub(crate) settings: &'run Settings<'run>, + unsets: &'run HashSet, } impl<'src, 'run> Evaluator<'src, 'run> { @@ -19,6 +20,7 @@ impl<'src, 'run> Evaluator<'src, 'run> { scope: Scope<'src, 'run>, search: &'run Search, settings: &'run Settings<'run>, + unsets: &'run HashSet, ) -> RunResult<'src, Scope<'src, 'run>> { let mut evaluator = Self { assignments: Some(assignments), @@ -28,6 +30,7 @@ impl<'src, 'run> Evaluator<'src, 'run> { scope, search, settings, + unsets, }; for assignment in assignments.values() { @@ -217,7 +220,7 @@ impl<'src, 'run> Evaluator<'src, 'run> { cmd.arg(command); cmd.args(args); cmd.current_dir(&self.search.working_directory); - cmd.export(self.settings, self.dotenv, &self.scope); + cmd.export(self.settings, self.dotenv, &self.scope, self.unsets); cmd.stdin(Stdio::inherit()); cmd.stderr(if self.config.verbosity.quiet() { Stdio::null() @@ -261,6 +264,7 @@ impl<'src, 'run> Evaluator<'src, 'run> { scope: &'run Scope<'src, 'run>, search: &'run Search, settings: &'run Settings, + unsets: &'run HashSet, ) -> RunResult<'src, (Scope<'src, 'run>, Vec)> { let mut evaluator = Self { assignments: None, @@ -270,6 +274,9 @@ impl<'src, 'run> Evaluator<'src, 'run> { scope: scope.child(), search, settings, + dotenv, + config, + unsets, }; let mut scope = scope.child(); @@ -316,6 +323,7 @@ impl<'src, 'run> Evaluator<'src, 'run> { scope: &'run Scope<'src, 'run>, search: &'run Search, settings: &'run Settings, + unsets: &'run HashSet, ) -> Self { Self { assignments: None, @@ -325,6 +333,9 @@ impl<'src, 'run> Evaluator<'src, 'run> { scope: Scope::child(scope), search, settings, + dotenv, + config, + unsets, } } } diff --git a/src/item.rs b/src/item.rs index a39b5d2d0c..84c0b3b1bd 100644 --- a/src/item.rs +++ b/src/item.rs @@ -20,6 +20,9 @@ pub(crate) enum Item<'src> { }, Recipe(UnresolvedRecipe<'src>), Set(Set<'src>), + Unset { + name: Name<'src>, + }, } impl<'src> Display for Item<'src> { @@ -61,6 +64,7 @@ impl<'src> Display for Item<'src> { } Self::Recipe(recipe) => write!(f, "{}", recipe.color_display(Color::never())), Self::Set(set) => write!(f, "{set}"), + Self::Unset { name } => write!(f, "unset {name}"), } } } diff --git a/src/justfile.rs b/src/justfile.rs index e86f6c88a3..5eae7fa1c7 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -24,6 +24,7 @@ pub(crate) struct Justfile<'src> { pub(crate) settings: Settings<'src>, #[serde(skip)] pub(crate) source: PathBuf, + pub(crate) unsets: HashSet, pub(crate) warnings: Vec, } @@ -113,6 +114,7 @@ impl<'src> Justfile<'src> { scope, search, &self.settings, + &self.unsets, ) } @@ -163,7 +165,7 @@ impl<'src> Justfile<'src> { let scope = scope.child(); - command.export(&self.settings, &dotenv, &scope); + command.export(&self.settings, &dotenv, &scope, &self.unsets); let status = InterruptHandler::guard(|| command.status()).map_err(|io_error| { Error::CommandInvoke { @@ -286,6 +288,7 @@ impl<'src> Justfile<'src> { scope: invocation.scope, search, settings: invocation.settings, + unsets: &self.unsets, }; Self::run_recipe( @@ -441,6 +444,7 @@ impl<'src> Justfile<'src> { context.scope, search, context.settings, + context.unsets, )?; let scope = outer.child(); @@ -452,6 +456,7 @@ impl<'src> Justfile<'src> { &scope, search, context.settings, + context.unsets, ); if !context.config.no_dependencies { diff --git a/src/keyword.rs b/src/keyword.rs index d5f268a963..dc9db2b88b 100644 --- a/src/keyword.rs +++ b/src/keyword.rs @@ -25,6 +25,7 @@ pub(crate) enum Keyword { Shell, Tempdir, True, + Unset, WindowsPowershell, WindowsShell, X, diff --git a/src/lib.rs b/src/lib.rs index 10cd39144e..1d0817d9b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ pub(crate) use { std::{ borrow::Cow, cmp, - collections::{BTreeMap, BTreeSet, HashMap}, + collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, ffi::OsString, fmt::{self, Debug, Display, Formatter}, diff --git a/src/node.rs b/src/node.rs index e4c4d03906..e30d959a28 100644 --- a/src/node.rs +++ b/src/node.rs @@ -54,6 +54,11 @@ impl<'src> Node<'src> for Item<'src> { } Self::Recipe(recipe) => recipe.tree(), Self::Set(set) => set.tree(), + Self::Unset { name } => { + let mut unset = Tree::atom(Keyword::Unset.lexeme()); + unset.push_mut(name.lexeme().replace('-', "_")); + unset + } } } } diff --git a/src/parser.rs b/src/parser.rs index 9f7e883b28..4bb5343eb5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -340,6 +340,11 @@ impl<'run, 'src> Parser<'run, 'src> { self.presume_keyword(Keyword::Export)?; items.push(Item::Assignment(self.parse_assignment(true)?)); } + Some(Keyword::Unset) => { + self.presume_keyword(Keyword::Unset)?; + let name = self.parse_name()?; + items.push(Item::Unset { name }); + } Some(Keyword::Import) if self.next_are(&[Identifier, StringToken]) || self.next_are(&[Identifier, Identifier, StringToken]) diff --git a/src/recipe.rs b/src/recipe.rs index 97dc84765f..118539fbc0 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -164,11 +164,12 @@ impl<'src, D> Recipe<'src, D> { let evaluator = Evaluator::recipe_evaluator( context.config, - context.dotenv, - context.module_source, - scope, - context.search, + dotenv, + &scope, context.settings, + search, + context.settings, + context.unsets, ); if self.shebang { @@ -279,7 +280,7 @@ impl<'src, D> Recipe<'src, D> { cmd.stdout(Stdio::null()); } - cmd.export(context.settings, context.dotenv, scope); + cmd.export(context.settings, context.dotenv, scope, context.unsets); match InterruptHandler::guard(|| cmd.status()) { Ok(exit_status) => { @@ -425,7 +426,7 @@ impl<'src, D> Recipe<'src, D> { command.args(positional); } - command.export(context.settings, context.dotenv, scope); + command.export(context.settings, context.dotenv, scope, context.unsets); // run it! match InterruptHandler::guard(|| command.status()) { diff --git a/src/recipe_context.rs b/src/recipe_context.rs index f1ed5c6787..e129d865d5 100644 --- a/src/recipe_context.rs +++ b/src/recipe_context.rs @@ -7,4 +7,5 @@ pub(crate) struct RecipeContext<'src: 'run, 'run> { pub(crate) scope: &'run Scope<'src, 'run>, pub(crate) search: &'run Search, pub(crate) settings: &'run Settings<'src>, + pub(crate) unsets: &'run HashSet, } diff --git a/tests/export.rs b/tests/export.rs index 9852f973d9..d4cc53486b 100644 --- a/tests/export.rs +++ b/tests/export.rs @@ -1,3 +1,5 @@ +use super::*; + test! { name: success, justfile: r#" @@ -175,3 +177,38 @@ test! { stdout: "undefined\n", stderr: "echo $B\n", } + +#[test] +fn unset_environment_variable_linewise() { + Test::new() + .justfile( + " + unset JUST_TEST_VARIABLE + + recipe: + echo $JUST_TEST_VARIABLE + ", + ) + .env("JUST_TEST_VARIABLE", "foo") + .stderr("echo $JUST_TEST_VARIABLE\nbash: line 1: JUST_TEST_VARIABLE: unbound variable\nerror: Recipe `recipe` failed on line 4 with exit code 127\n") + .status(127) + .run(); +} + +#[test] +fn unset_environment_variable_shebang() { + Test::new() + .justfile( + " + unset JUST_TEST_VARIABLE + + recipe: + #!/usr/bin/env bash + echo \"variable: $JUST_TEST_VARIABLE\" + ", + ) + .env("JUST_TEST_VARIABLE", "foo") + .stdout("variable: \n") + .status(0) + .run(); +} diff --git a/tests/json.rs b/tests/json.rs index 218b62565a..f537b9be45 100644 --- a/tests/json.rs +++ b/tests/json.rs @@ -59,6 +59,7 @@ fn alias() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -98,6 +99,7 @@ fn assignment() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -151,6 +153,7 @@ fn body() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -216,6 +219,7 @@ fn dependencies() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -319,6 +323,7 @@ fn dependency_argument() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -384,6 +389,7 @@ fn duplicate_recipes() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -427,6 +433,7 @@ fn duplicate_variables() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -473,6 +480,7 @@ fn doc_comment() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -505,6 +513,7 @@ fn empty_justfile() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -658,6 +667,7 @@ fn parameters() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -744,6 +754,7 @@ fn priors() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -790,6 +801,7 @@ fn private() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -836,6 +848,7 @@ fn quiet() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -897,6 +910,7 @@ fn settings() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -946,6 +960,7 @@ fn shebang() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -992,6 +1007,7 @@ fn simple() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -1041,6 +1057,7 @@ fn attribute() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }), ); @@ -1103,6 +1120,7 @@ fn module() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], }, }, @@ -1124,6 +1142,7 @@ fn module() { "windows_powershell": false, "windows_shell": null, }, + "unsets": [], "warnings": [], })) .unwrap() From 0bee973d1e1e2cdabcc3692e0bf924e1749ff147 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Tue, 28 May 2024 02:04:15 -0700 Subject: [PATCH 02/14] Fix mac test --- tests/export.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/export.rs b/tests/export.rs index d4cc53486b..cc1fa75134 100644 --- a/tests/export.rs +++ b/tests/export.rs @@ -180,6 +180,11 @@ test! { #[test] fn unset_environment_variable_linewise() { + #[cfg(not(target_os = "macos"))] + let stderr_msg = "echo $JUST_TEST_VARIABLE\nbash: line 1: JUST_TEST_VARIABLE: unbound variable\nerror: Recipe `recipe` failed on line 4 with exit code 127\n"; + #[cfg(target_os="macos")] + let stderr_msg = "echo $JUST_TEST_VARIABLE\nbash: JUST_TEST_VARIABLE: unbound variable\nerror: Recipe `recipe` failed on line 4 with exit code 127\n"; + Test::new() .justfile( " @@ -190,7 +195,7 @@ fn unset_environment_variable_linewise() { ", ) .env("JUST_TEST_VARIABLE", "foo") - .stderr("echo $JUST_TEST_VARIABLE\nbash: line 1: JUST_TEST_VARIABLE: unbound variable\nerror: Recipe `recipe` failed on line 4 with exit code 127\n") + .stderr(stderr_msg) .status(127) .run(); } From c99b68873e64b51bc03c43b9a4e499ec922fe925 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Sun, 2 Jun 2024 00:16:33 -0700 Subject: [PATCH 03/14] Rebase fixes --- src/evaluator.rs | 4 ---- src/recipe.rs | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/evaluator.rs b/src/evaluator.rs index 03ba69222c..1c6e6ae452 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -274,8 +274,6 @@ impl<'src, 'run> Evaluator<'src, 'run> { scope: scope.child(), search, settings, - dotenv, - config, unsets, }; @@ -333,8 +331,6 @@ impl<'src, 'run> Evaluator<'src, 'run> { scope: Scope::child(scope), search, settings, - dotenv, - config, unsets, } } diff --git a/src/recipe.rs b/src/recipe.rs index 118539fbc0..573d764ef8 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -164,10 +164,10 @@ impl<'src, D> Recipe<'src, D> { let evaluator = Evaluator::recipe_evaluator( context.config, - dotenv, - &scope, - context.settings, - search, + context.dotenv, + context.module_source, + scope, + context.search, context.settings, context.unsets, ); From a1b2de5d7187349334fcef148c32b2d38dd654f5 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Sun, 2 Jun 2024 00:21:31 -0700 Subject: [PATCH 04/14] rename unset to unexport --- src/analyzer.rs | 8 ++++---- src/item.rs | 4 ++-- src/justfile.rs | 12 ++++++------ src/keyword.rs | 2 +- src/node.rs | 8 ++++---- src/parser.rs | 6 +++--- src/recipe.rs | 6 +++--- src/recipe_context.rs | 2 +- tests/export.rs | 8 ++++---- tests/json.rs | 38 +++++++++++++++++++------------------- 10 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index 9064699826..e2f955ff40 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -37,7 +37,7 @@ impl<'src> Analyzer<'src> { let mut modules: Table = Table::new(); - let mut unsets: HashSet = HashSet::new(); + let mut unexports: HashSet = HashSet::new(); let mut definitions: HashMap<&str, (&'static str, Name)> = HashMap::new(); @@ -100,8 +100,8 @@ impl<'src> Analyzer<'src> { self.analyze_set(set)?; self.sets.insert(set.clone()); } - Item::Unset { name } => { - unsets.insert(name.lexeme().to_string()); + Item::Unexport { name } => { + unexports.insert(name.lexeme().to_string()); } } } @@ -172,7 +172,7 @@ impl<'src> Analyzer<'src> { recipes, settings, source: root.into(), - unsets, + unexports, warnings, }) } diff --git a/src/item.rs b/src/item.rs index 84c0b3b1bd..b72ec8dcec 100644 --- a/src/item.rs +++ b/src/item.rs @@ -20,7 +20,7 @@ pub(crate) enum Item<'src> { }, Recipe(UnresolvedRecipe<'src>), Set(Set<'src>), - Unset { + Unexport { name: Name<'src>, }, } @@ -64,7 +64,7 @@ impl<'src> Display for Item<'src> { } Self::Recipe(recipe) => write!(f, "{}", recipe.color_display(Color::never())), Self::Set(set) => write!(f, "{set}"), - Self::Unset { name } => write!(f, "unset {name}"), + Self::Unexport { name } => write!(f, "unexport {name}"), } } } diff --git a/src/justfile.rs b/src/justfile.rs index 5eae7fa1c7..150a40597e 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -24,7 +24,7 @@ pub(crate) struct Justfile<'src> { pub(crate) settings: Settings<'src>, #[serde(skip)] pub(crate) source: PathBuf, - pub(crate) unsets: HashSet, + pub(crate) unexports: HashSet, pub(crate) warnings: Vec, } @@ -114,7 +114,7 @@ impl<'src> Justfile<'src> { scope, search, &self.settings, - &self.unsets, + &self.unexports, ) } @@ -165,7 +165,7 @@ impl<'src> Justfile<'src> { let scope = scope.child(); - command.export(&self.settings, &dotenv, &scope, &self.unsets); + command.export(&self.settings, &dotenv, &scope, &self.unexports); let status = InterruptHandler::guard(|| command.status()).map_err(|io_error| { Error::CommandInvoke { @@ -288,7 +288,7 @@ impl<'src> Justfile<'src> { scope: invocation.scope, search, settings: invocation.settings, - unsets: &self.unsets, + unexports: &self.unexports, }; Self::run_recipe( @@ -444,7 +444,7 @@ impl<'src> Justfile<'src> { context.scope, search, context.settings, - context.unsets, + context.unexports, )?; let scope = outer.child(); @@ -456,7 +456,7 @@ impl<'src> Justfile<'src> { &scope, search, context.settings, - context.unsets, + context.unexports, ); if !context.config.no_dependencies { diff --git a/src/keyword.rs b/src/keyword.rs index dc9db2b88b..d57032ae40 100644 --- a/src/keyword.rs +++ b/src/keyword.rs @@ -25,7 +25,7 @@ pub(crate) enum Keyword { Shell, Tempdir, True, - Unset, + Unexport, WindowsPowershell, WindowsShell, X, diff --git a/src/node.rs b/src/node.rs index e30d959a28..2fd73e303b 100644 --- a/src/node.rs +++ b/src/node.rs @@ -54,10 +54,10 @@ impl<'src> Node<'src> for Item<'src> { } Self::Recipe(recipe) => recipe.tree(), Self::Set(set) => set.tree(), - Self::Unset { name } => { - let mut unset = Tree::atom(Keyword::Unset.lexeme()); - unset.push_mut(name.lexeme().replace('-', "_")); - unset + Self::Unexport { name } => { + let mut unexport = Tree::atom(Keyword::Unexport.lexeme()); + unexport.push_mut(name.lexeme().replace('-', "_")); + unexport } } } diff --git a/src/parser.rs b/src/parser.rs index 4bb5343eb5..857284da87 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -340,10 +340,10 @@ impl<'run, 'src> Parser<'run, 'src> { self.presume_keyword(Keyword::Export)?; items.push(Item::Assignment(self.parse_assignment(true)?)); } - Some(Keyword::Unset) => { - self.presume_keyword(Keyword::Unset)?; + Some(Keyword::Unexport) => { + self.presume_keyword(Keyword::Unexport)?; let name = self.parse_name()?; - items.push(Item::Unset { name }); + items.push(Item::Unexport { name }); } Some(Keyword::Import) if self.next_are(&[Identifier, StringToken]) diff --git a/src/recipe.rs b/src/recipe.rs index 573d764ef8..ebd5ab0734 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -169,7 +169,7 @@ impl<'src, D> Recipe<'src, D> { scope, context.search, context.settings, - context.unsets, + context.unexports, ); if self.shebang { @@ -280,7 +280,7 @@ impl<'src, D> Recipe<'src, D> { cmd.stdout(Stdio::null()); } - cmd.export(context.settings, context.dotenv, scope, context.unsets); + cmd.export(context.settings, context.dotenv, scope, context.unexports); match InterruptHandler::guard(|| cmd.status()) { Ok(exit_status) => { @@ -426,7 +426,7 @@ impl<'src, D> Recipe<'src, D> { command.args(positional); } - command.export(context.settings, context.dotenv, scope, context.unsets); + command.export(context.settings, context.dotenv, scope, context.unexports); // run it! match InterruptHandler::guard(|| command.status()) { diff --git a/src/recipe_context.rs b/src/recipe_context.rs index e129d865d5..a1e5434928 100644 --- a/src/recipe_context.rs +++ b/src/recipe_context.rs @@ -7,5 +7,5 @@ pub(crate) struct RecipeContext<'src: 'run, 'run> { pub(crate) scope: &'run Scope<'src, 'run>, pub(crate) search: &'run Search, pub(crate) settings: &'run Settings<'src>, - pub(crate) unsets: &'run HashSet, + pub(crate) unexports: &'run HashSet, } diff --git a/tests/export.rs b/tests/export.rs index cc1fa75134..13d8c71c8e 100644 --- a/tests/export.rs +++ b/tests/export.rs @@ -179,7 +179,7 @@ test! { } #[test] -fn unset_environment_variable_linewise() { +fn unexport_environment_variable_linewise() { #[cfg(not(target_os = "macos"))] let stderr_msg = "echo $JUST_TEST_VARIABLE\nbash: line 1: JUST_TEST_VARIABLE: unbound variable\nerror: Recipe `recipe` failed on line 4 with exit code 127\n"; #[cfg(target_os="macos")] @@ -188,7 +188,7 @@ fn unset_environment_variable_linewise() { Test::new() .justfile( " - unset JUST_TEST_VARIABLE + unexport JUST_TEST_VARIABLE recipe: echo $JUST_TEST_VARIABLE @@ -201,11 +201,11 @@ fn unset_environment_variable_linewise() { } #[test] -fn unset_environment_variable_shebang() { +fn unexport_environment_variable_shebang() { Test::new() .justfile( " - unset JUST_TEST_VARIABLE + unexport JUST_TEST_VARIABLE recipe: #!/usr/bin/env bash diff --git a/tests/json.rs b/tests/json.rs index f537b9be45..d44e0da01a 100644 --- a/tests/json.rs +++ b/tests/json.rs @@ -59,7 +59,7 @@ fn alias() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -99,7 +99,7 @@ fn assignment() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -153,7 +153,7 @@ fn body() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -219,7 +219,7 @@ fn dependencies() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -323,7 +323,7 @@ fn dependency_argument() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -389,7 +389,7 @@ fn duplicate_recipes() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -433,7 +433,7 @@ fn duplicate_variables() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -480,7 +480,7 @@ fn doc_comment() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -513,7 +513,7 @@ fn empty_justfile() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -667,7 +667,7 @@ fn parameters() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -754,7 +754,7 @@ fn priors() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -801,7 +801,7 @@ fn private() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -848,7 +848,7 @@ fn quiet() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -910,7 +910,7 @@ fn settings() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -960,7 +960,7 @@ fn shebang() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -1007,7 +1007,7 @@ fn simple() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -1057,7 +1057,7 @@ fn attribute() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }), ); @@ -1120,7 +1120,7 @@ fn module() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], }, }, @@ -1142,7 +1142,7 @@ fn module() { "windows_powershell": false, "windows_shell": null, }, - "unsets": [], + "unexports": [], "warnings": [], })) .unwrap() From a7b2cc2b257601d7b84fc70d67d1898633ce7281 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Sun, 2 Jun 2024 00:31:27 -0700 Subject: [PATCH 05/14] Duplicate unexport --- src/analyzer.rs | 6 +++++- src/compile_error.rs | 3 +++ src/compile_error_kind.rs | 3 +++ tests/export.rs | 20 ++++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index e2f955ff40..1b6a2fd011 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -101,7 +101,11 @@ impl<'src> Analyzer<'src> { self.sets.insert(set.clone()); } Item::Unexport { name } => { - unexports.insert(name.lexeme().to_string()); + if !unexports.insert(name.lexeme().to_string()) { + return Err(name.token.error(DuplicateUnexport { + variable: name.lexeme(), + })); + } } } } diff --git a/src/compile_error.rs b/src/compile_error.rs index e2e5781bad..7c0815c1d1 100644 --- a/src/compile_error.rs +++ b/src/compile_error.rs @@ -131,6 +131,9 @@ impl Display for CompileError<'_> { DuplicateVariable { variable } => { write!(f, "Variable `{variable}` has multiple definitions") } + DuplicateUnexport { variable } => { + write!(f, "Variable `{variable}` is unexported multiple times") + } ExpectedKeyword { expected, found } => { let expected = List::or_ticked(expected); if found.kind == TokenKind::Identifier { diff --git a/src/compile_error_kind.rs b/src/compile_error_kind.rs index 5a9b7a41ba..709d806a68 100644 --- a/src/compile_error_kind.rs +++ b/src/compile_error_kind.rs @@ -52,6 +52,9 @@ pub(crate) enum CompileErrorKind<'src> { DuplicateVariable { variable: &'src str, }, + DuplicateUnexport { + variable: &'src str, + }, ExpectedKeyword { expected: Vec, found: Token<'src>, diff --git a/tests/export.rs b/tests/export.rs index 13d8c71c8e..161659fa64 100644 --- a/tests/export.rs +++ b/tests/export.rs @@ -217,3 +217,23 @@ fn unexport_environment_variable_shebang() { .status(0) .run(); } + +#[test] +fn duplicate_unexport_fails() { + Test::new() + .justfile( + " + unexport JUST_TEST_VARIABLE + + recipe: + #!/usr/bin/env bash + echo \"variable: $JUST_TEST_VARIABLE\" + + unexport JUST_TEST_VARIABLE + ", + ) + .env("JUST_TEST_VARIABLE", "foo") + .stderr("error: Variable `JUST_TEST_VARIABLE` is unexported multiple times\n ——▶ justfile:7:10\n │\n7 │ unexport JUST_TEST_VARIABLE\n │ ^^^^^^^^^^^^^^^^^^\n") + .status(1) + .run(); +} From 6f3d1ec168aa2bc70ab20ab6f9f040eb31430a86 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Sun, 2 Jun 2024 02:33:54 -0700 Subject: [PATCH 06/14] Export unexport conflict --- src/analyzer.rs | 14 ++++++++++---- src/compile_error.rs | 3 +++ src/compile_error_kind.rs | 3 +++ tests/export.rs | 19 +++++++++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index 1b6a2fd011..bfed41c60b 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -118,9 +118,9 @@ impl<'src> Analyzer<'src> { let mut recipe_table: Table<'src, UnresolvedRecipe<'src>> = Table::default(); for assignment in assignments { - if !settings.allow_duplicate_variables - && self.assignments.contains_key(assignment.name.lexeme()) - { + let lexeme = assignment.name.lexeme(); + + if !settings.allow_duplicate_variables && self.assignments.contains_key(lexeme) { return Err(assignment.name.token.error(DuplicateVariable { variable: assignment.name.lexeme(), })); @@ -128,11 +128,17 @@ impl<'src> Analyzer<'src> { if self .assignments - .get(assignment.name.lexeme()) + .get(lexeme) .map_or(true, |original| assignment.depth <= original.depth) { self.assignments.insert(assignment.clone()); } + + if let Some(unexport) = unexports.get(lexeme) { + return Err(assignment.name.token.error(ExportUnexportConflict { + variable: unexport.to_string(), + })); + } } AssignmentResolver::resolve_assignments(&self.assignments)?; diff --git a/src/compile_error.rs b/src/compile_error.rs index 7c0815c1d1..d63b14e372 100644 --- a/src/compile_error.rs +++ b/src/compile_error.rs @@ -146,6 +146,9 @@ impl Display for CompileError<'_> { write!(f, "Expected keyword {expected} but found `{}`", found.kind) } } + ExportUnexportConflict { variable } => { + write!(f, "Variable {variable} is both exported and unexported") + } ExtraLeadingWhitespace => write!(f, "Recipe line has extra leading whitespace"), FunctionArgumentCountMismatch { function, diff --git a/src/compile_error_kind.rs b/src/compile_error_kind.rs index 709d806a68..b8c7074cd9 100644 --- a/src/compile_error_kind.rs +++ b/src/compile_error_kind.rs @@ -59,6 +59,9 @@ pub(crate) enum CompileErrorKind<'src> { expected: Vec, found: Token<'src>, }, + ExportUnexportConflict { + variable: String, + }, ExtraLeadingWhitespace, FunctionArgumentCountMismatch { function: &'src str, diff --git a/tests/export.rs b/tests/export.rs index 161659fa64..634bf5268f 100644 --- a/tests/export.rs +++ b/tests/export.rs @@ -237,3 +237,22 @@ fn duplicate_unexport_fails() { .status(1) .run(); } + +#[test] +fn export_unexport_conflict() { + Test::new() + .justfile( + " + unexport JUST_TEST_VARIABLE + + recipe: + #!/usr/bin/env bash + echo \"variable: $JUST_TEST_VARIABLE\" + + export JUST_TEST_VARIABLE := 'foo' + ", + ) + .stderr("error: Variable JUST_TEST_VARIABLE is both exported and unexported\n ——▶ justfile:7:8\n │\n7 │ export JUST_TEST_VARIABLE := 'foo'\n │ ^^^^^^^^^^^^^^^^^^\n") + .status(1) + .run(); +} From a4624d352cb48ea49c8eca86191e8cabf8b08d7d Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Sun, 2 Jun 2024 11:35:45 -0700 Subject: [PATCH 07/14] unexport override test --- tests/export.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/export.rs b/tests/export.rs index 634bf5268f..1f3687f6b1 100644 --- a/tests/export.rs +++ b/tests/export.rs @@ -226,14 +226,13 @@ fn duplicate_unexport_fails() { unexport JUST_TEST_VARIABLE recipe: - #!/usr/bin/env bash echo \"variable: $JUST_TEST_VARIABLE\" unexport JUST_TEST_VARIABLE ", ) .env("JUST_TEST_VARIABLE", "foo") - .stderr("error: Variable `JUST_TEST_VARIABLE` is unexported multiple times\n ——▶ justfile:7:10\n │\n7 │ unexport JUST_TEST_VARIABLE\n │ ^^^^^^^^^^^^^^^^^^\n") + .stderr("error: Variable `JUST_TEST_VARIABLE` is unexported multiple times\n ——▶ justfile:6:10\n │\n6 │ unexport JUST_TEST_VARIABLE\n │ ^^^^^^^^^^^^^^^^^^\n") .status(1) .run(); } @@ -246,13 +245,29 @@ fn export_unexport_conflict() { unexport JUST_TEST_VARIABLE recipe: - #!/usr/bin/env bash echo \"variable: $JUST_TEST_VARIABLE\" export JUST_TEST_VARIABLE := 'foo' ", ) - .stderr("error: Variable JUST_TEST_VARIABLE is both exported and unexported\n ——▶ justfile:7:8\n │\n7 │ export JUST_TEST_VARIABLE := 'foo'\n │ ^^^^^^^^^^^^^^^^^^\n") + .stderr("error: Variable JUST_TEST_VARIABLE is both exported and unexported\n ——▶ justfile:6:8\n │\n6 │ export JUST_TEST_VARIABLE := 'foo'\n │ ^^^^^^^^^^^^^^^^^^\n") .status(1) .run(); } + +#[test] +fn unexport_doesnt_override_local_recipe_export() { + Test::new() + .justfile( + " + unexport JUST_TEST_VARIABLE + + recipe $JUST_TEST_VARIABLE: + @echo \"variable: $JUST_TEST_VARIABLE\" + ", + ) + .args(["recipe", "value"]) + .stdout("variable: value\n") + .status(0) + .run(); +} From 144bf5707dbed3aa041c2f96c60d36ece8716b41 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 5 Jun 2024 21:58:55 +0200 Subject: [PATCH 08/14] lexeme -> variable so we can use shorthand syntax --- src/analyzer.rs | 21 +++++++++++---------- src/compile_error_kind.rs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index bfed41c60b..4adf5f019c 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -118,26 +118,27 @@ impl<'src> Analyzer<'src> { let mut recipe_table: Table<'src, UnresolvedRecipe<'src>> = Table::default(); for assignment in assignments { - let lexeme = assignment.name.lexeme(); + let variable = assignment.name.lexeme(); - if !settings.allow_duplicate_variables && self.assignments.contains_key(lexeme) { - return Err(assignment.name.token.error(DuplicateVariable { - variable: assignment.name.lexeme(), - })); + if !settings.allow_duplicate_variables && self.assignments.contains_key(variable) { + return Err(assignment.name.token.error(DuplicateVariable { variable })); } if self .assignments - .get(lexeme) + .get(variable) .map_or(true, |original| assignment.depth <= original.depth) { self.assignments.insert(assignment.clone()); } - if let Some(unexport) = unexports.get(lexeme) { - return Err(assignment.name.token.error(ExportUnexportConflict { - variable: unexport.to_string(), - })); + if let Some(unexport) = unexports.get(variable) { + return Err( + assignment + .name + .token + .error(ExportUnexportConflict { variable }), + ); } } diff --git a/src/compile_error_kind.rs b/src/compile_error_kind.rs index b8c7074cd9..d1348e527b 100644 --- a/src/compile_error_kind.rs +++ b/src/compile_error_kind.rs @@ -60,7 +60,7 @@ pub(crate) enum CompileErrorKind<'src> { found: Token<'src>, }, ExportUnexportConflict { - variable: String, + variable: &'src str, }, ExtraLeadingWhitespace, FunctionArgumentCountMismatch { From 0e4c84967ab97268022f708f9534f7a012f484cd Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 5 Jun 2024 22:01:24 +0200 Subject: [PATCH 09/14] Change name to uneexports in commandext --- src/command_ext.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/command_ext.rs b/src/command_ext.rs index fbd6d256a6..6bd7208d3c 100644 --- a/src/command_ext.rs +++ b/src/command_ext.rs @@ -6,10 +6,10 @@ pub(crate) trait CommandExt { settings: &Settings, dotenv: &BTreeMap, scope: &Scope, - unsets: &HashSet, + unexports: &HashSet, ); - fn export_scope(&mut self, settings: &Settings, scope: &Scope, unsets: &HashSet); + fn export_scope(&mut self, settings: &Settings, scope: &Scope, unexports: &HashSet); } impl CommandExt for Command { @@ -18,24 +18,24 @@ impl CommandExt for Command { settings: &Settings, dotenv: &BTreeMap, scope: &Scope, - unsets: &HashSet, + unexports: &HashSet, ) { for (name, value) in dotenv { self.env(name, value); } if let Some(parent) = scope.parent() { - self.export_scope(settings, parent, unsets); + self.export_scope(settings, parent, unexports); } } - fn export_scope(&mut self, settings: &Settings, scope: &Scope, unsets: &HashSet) { + fn export_scope(&mut self, settings: &Settings, scope: &Scope, unexports: &HashSet) { if let Some(parent) = scope.parent() { - self.export_scope(settings, parent, unsets); + self.export_scope(settings, parent, unexports); } - for unset in unsets { - self.env_remove(unset); + for unexport in unexports { + self.env_remove(unexport); } for binding in scope.bindings() { From 923d1346ada7ee1d54aae563cae81514545790a5 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 5 Jun 2024 22:01:31 +0200 Subject: [PATCH 10/14] Suppress output from completions test --- tests/completions.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/completions.rs b/tests/completions.rs index 6ba3e49329..96d35cafdc 100644 --- a/tests/completions.rs +++ b/tests/completions.rs @@ -29,10 +29,10 @@ fn bash() { #[test] fn replacements() { for shell in ["bash", "elvish", "fish", "powershell", "zsh"] { - let status = Command::new(executable_path("just")) + let output = Command::new(executable_path("just")) .args(["--completions", shell]) - .status() + .output() .unwrap(); - assert!(status.success()); + assert!(output.status.success()); } } From bb36a72ced0a3052b20b842a5a97162bbff4c98e Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 5 Jun 2024 22:04:45 +0200 Subject: [PATCH 11/14] Tweak error name --- src/analyzer.rs | 9 ++------- src/compile_error.rs | 2 +- src/compile_error_kind.rs | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/analyzer.rs b/src/analyzer.rs index 4adf5f019c..f2e0b6540b 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -132,13 +132,8 @@ impl<'src> Analyzer<'src> { self.assignments.insert(assignment.clone()); } - if let Some(unexport) = unexports.get(variable) { - return Err( - assignment - .name - .token - .error(ExportUnexportConflict { variable }), - ); + if unexports.contains(variable) { + return Err(assignment.name.token.error(ExportUnexported { variable })); } } diff --git a/src/compile_error.rs b/src/compile_error.rs index d63b14e372..b60835ccde 100644 --- a/src/compile_error.rs +++ b/src/compile_error.rs @@ -146,7 +146,7 @@ impl Display for CompileError<'_> { write!(f, "Expected keyword {expected} but found `{}`", found.kind) } } - ExportUnexportConflict { variable } => { + ExportUnexported { variable } => { write!(f, "Variable {variable} is both exported and unexported") } ExtraLeadingWhitespace => write!(f, "Recipe line has extra leading whitespace"), diff --git a/src/compile_error_kind.rs b/src/compile_error_kind.rs index d1348e527b..49b5624d0d 100644 --- a/src/compile_error_kind.rs +++ b/src/compile_error_kind.rs @@ -59,7 +59,7 @@ pub(crate) enum CompileErrorKind<'src> { expected: Vec, found: Token<'src>, }, - ExportUnexportConflict { + ExportUnexported { variable: &'src str, }, ExtraLeadingWhitespace, From 00ddc48220343d72eed9bb9fa3995efa051a2cbf Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 5 Jun 2024 22:10:13 +0200 Subject: [PATCH 12/14] Move tests to unexport module --- tests/export.rs | 96 ------------------------------------------ tests/lib.rs | 1 + tests/unexport.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 96 deletions(-) create mode 100644 tests/unexport.rs diff --git a/tests/export.rs b/tests/export.rs index 1f3687f6b1..9852f973d9 100644 --- a/tests/export.rs +++ b/tests/export.rs @@ -1,5 +1,3 @@ -use super::*; - test! { name: success, justfile: r#" @@ -177,97 +175,3 @@ test! { stdout: "undefined\n", stderr: "echo $B\n", } - -#[test] -fn unexport_environment_variable_linewise() { - #[cfg(not(target_os = "macos"))] - let stderr_msg = "echo $JUST_TEST_VARIABLE\nbash: line 1: JUST_TEST_VARIABLE: unbound variable\nerror: Recipe `recipe` failed on line 4 with exit code 127\n"; - #[cfg(target_os="macos")] - let stderr_msg = "echo $JUST_TEST_VARIABLE\nbash: JUST_TEST_VARIABLE: unbound variable\nerror: Recipe `recipe` failed on line 4 with exit code 127\n"; - - Test::new() - .justfile( - " - unexport JUST_TEST_VARIABLE - - recipe: - echo $JUST_TEST_VARIABLE - ", - ) - .env("JUST_TEST_VARIABLE", "foo") - .stderr(stderr_msg) - .status(127) - .run(); -} - -#[test] -fn unexport_environment_variable_shebang() { - Test::new() - .justfile( - " - unexport JUST_TEST_VARIABLE - - recipe: - #!/usr/bin/env bash - echo \"variable: $JUST_TEST_VARIABLE\" - ", - ) - .env("JUST_TEST_VARIABLE", "foo") - .stdout("variable: \n") - .status(0) - .run(); -} - -#[test] -fn duplicate_unexport_fails() { - Test::new() - .justfile( - " - unexport JUST_TEST_VARIABLE - - recipe: - echo \"variable: $JUST_TEST_VARIABLE\" - - unexport JUST_TEST_VARIABLE - ", - ) - .env("JUST_TEST_VARIABLE", "foo") - .stderr("error: Variable `JUST_TEST_VARIABLE` is unexported multiple times\n ——▶ justfile:6:10\n │\n6 │ unexport JUST_TEST_VARIABLE\n │ ^^^^^^^^^^^^^^^^^^\n") - .status(1) - .run(); -} - -#[test] -fn export_unexport_conflict() { - Test::new() - .justfile( - " - unexport JUST_TEST_VARIABLE - - recipe: - echo \"variable: $JUST_TEST_VARIABLE\" - - export JUST_TEST_VARIABLE := 'foo' - ", - ) - .stderr("error: Variable JUST_TEST_VARIABLE is both exported and unexported\n ——▶ justfile:6:8\n │\n6 │ export JUST_TEST_VARIABLE := 'foo'\n │ ^^^^^^^^^^^^^^^^^^\n") - .status(1) - .run(); -} - -#[test] -fn unexport_doesnt_override_local_recipe_export() { - Test::new() - .justfile( - " - unexport JUST_TEST_VARIABLE - - recipe $JUST_TEST_VARIABLE: - @echo \"variable: $JUST_TEST_VARIABLE\" - ", - ) - .args(["recipe", "value"]) - .stdout("variable: value\n") - .status(0) - .run(); -} diff --git a/tests/lib.rs b/tests/lib.rs index cceda57b23..18da62d64a 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -104,6 +104,7 @@ mod summary; mod tempdir; mod timestamps; mod undefined_variables; +mod unexport; mod unstable; #[cfg(target_family = "windows")] mod windows_shell; diff --git a/tests/unexport.rs b/tests/unexport.rs new file mode 100644 index 0000000000..9ca93e87e9 --- /dev/null +++ b/tests/unexport.rs @@ -0,0 +1,104 @@ +use super::*; + +#[test] +fn unexport_environment_variable_linewise() { + Test::new() + .justfile( + " + unexport JUST_TEST_VARIABLE + + @recipe: + echo ${JUST_TEST_VARIABLE:-unset} + ", + ) + .env("JUST_TEST_VARIABLE", "foo") + .stdout("unset\n") + .run(); +} + +#[test] +fn unexport_environment_variable_shebang() { + Test::new() + .justfile( + " + unexport JUST_TEST_VARIABLE + + recipe: + #!/usr/bin/env bash + echo ${JUST_TEST_VARIABLE:-unset} + ", + ) + .env("JUST_TEST_VARIABLE", "foo") + .stdout("unset\n") + .run(); +} + +#[test] +fn duplicate_unexport_fails() { + Test::new() + .justfile( + " + unexport JUST_TEST_VARIABLE + + recipe: + echo \"variable: $JUST_TEST_VARIABLE\" + + unexport JUST_TEST_VARIABLE + ", + ) + .env("JUST_TEST_VARIABLE", "foo") + .stderr( + " + error: Variable `JUST_TEST_VARIABLE` is unexported multiple times + ——▶ justfile:6:10 + │ + 6 │ unexport JUST_TEST_VARIABLE + │ ^^^^^^^^^^^^^^^^^^ + ", + ) + .status(1) + .run(); +} + +#[test] +fn export_unexport_conflict() { + Test::new() + .justfile( + " + unexport JUST_TEST_VARIABLE + + recipe: + echo variable: $JUST_TEST_VARIABLE + + export JUST_TEST_VARIABLE := 'foo' + ", + ) + .stderr( + " + error: Variable JUST_TEST_VARIABLE is both exported and unexported + ——▶ justfile:6:8 + │ + 6 │ export JUST_TEST_VARIABLE := 'foo' + │ ^^^^^^^^^^^^^^^^^^ + ", + ) + .status(1) + .run(); +} + +#[test] +fn unexport_doesnt_override_local_recipe_export() { + Test::new() + .justfile( + " + unexport JUST_TEST_VARIABLE + + recipe $JUST_TEST_VARIABLE: + @echo \"variable: $JUST_TEST_VARIABLE\" + ", + ) + .args(["recipe", "value"]) + .stdout("variable: value\n") + .status(0) + .run(); +} From d842f8b9c82ebdbcd8768b83e1a84790b5380973 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 5 Jun 2024 22:13:51 +0200 Subject: [PATCH 13/14] Update readme --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 6b05577ab6..00c78263ce 100644 --- a/README.md +++ b/README.md @@ -2053,6 +2053,23 @@ a $A $B=`echo $A`: When [export](#export) is set, all `just` variables are exported as environment variables. +#### Unexporting Environment Variables + +Environment variables can be unexported with the `unexport keyword`: + +```just +unexport FOO + +@foo: + echo $FOO +``` + +``` +$ export FOO=bar +$ just foo +sh: FOO: unbound variable +``` + #### Getting Environment Variables from the environment Environment variables from the environment are passed automatically to the From 4a570826b82491284fa6e6c278bba225b7b4f1c3 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 5 Jun 2024 22:14:16 +0200 Subject: [PATCH 14/14] Note version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00c78263ce..9efa80a677 100644 --- a/README.md +++ b/README.md @@ -2053,7 +2053,7 @@ a $A $B=`echo $A`: When [export](#export) is set, all `just` variables are exported as environment variables. -#### Unexporting Environment Variables +#### Unexporting Environment Variablesmaster Environment variables can be unexported with the `unexport keyword`: