Skip to content

Commit

Permalink
Implement unsetting variables
Browse files Browse the repository at this point in the history
  • Loading branch information
neunenak committed May 28, 2024
1 parent a045cf0 commit e3137c8
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 15 deletions.
6 changes: 6 additions & 0 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ impl<'src> Analyzer<'src> {

let mut modules: Table<Justfile> = Table::new();

let mut unsets: HashSet<String> = HashSet::new();

let mut definitions: HashMap<&str, (&'static str, Name)> = HashMap::new();

let mut define = |name: Name<'src>,
Expand Down Expand Up @@ -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());
}
}
}

Expand Down Expand Up @@ -166,6 +171,7 @@ impl<'src> Analyzer<'src> {
name,
recipes,
settings,
unsets,
warnings,
})
}
Expand Down
28 changes: 22 additions & 6 deletions src/command_ext.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
use super::*;

pub(crate) trait CommandExt {
fn export(&mut self, settings: &Settings, dotenv: &BTreeMap<String, String>, scope: &Scope);
fn export(
&mut self,
settings: &Settings,
dotenv: &BTreeMap<String, String>,
scope: &Scope,
unsets: &HashSet<String>,
);

fn export_scope(&mut self, settings: &Settings, scope: &Scope);
fn export_scope(&mut self, settings: &Settings, scope: &Scope, unsets: &HashSet<String>);
}

impl CommandExt for Command {
fn export(&mut self, settings: &Settings, dotenv: &BTreeMap<String, String>, scope: &Scope) {
fn export(
&mut self,
settings: &Settings,
dotenv: &BTreeMap<String, String>,
scope: &Scope,
unsets: &HashSet<String>,
) {
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<String>) {
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() {
Expand Down
9 changes: 8 additions & 1 deletion src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub(crate) struct Evaluator<'src: 'run, 'run> {
pub(crate) scope: Scope<'src, 'run>,
pub(crate) settings: &'run Settings<'run>,
pub(crate) search: &'run Search,
unsets: &'run HashSet<String>,
}

impl<'src, 'run> Evaluator<'src, 'run> {
Expand All @@ -17,6 +18,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
overrides: Scope<'src, 'run>,
settings: &'run Settings<'run>,
search: &'run Search,
unsets: &'run HashSet<String>,
) -> RunResult<'src, Scope<'src, 'run>> {
let mut evaluator = Self {
scope: overrides,
Expand All @@ -25,6 +27,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
dotenv,
settings,
search,
unsets,
};

for assignment in assignments.values() {
Expand Down Expand Up @@ -214,7 +217,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()
Expand Down Expand Up @@ -257,6 +260,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
scope: &'run Scope<'src, 'run>,
settings: &'run Settings,
search: &'run Search,
unsets: &'run HashSet<String>,
) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> {
let mut evaluator = Self {
assignments: None,
Expand All @@ -265,6 +269,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
settings,
dotenv,
config,
unsets,
};

let mut scope = scope.child();
Expand Down Expand Up @@ -310,6 +315,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
scope: &'run Scope<'src, 'run>,
settings: &'run Settings,
search: &'run Search,
unsets: &'run HashSet<String>,
) -> Evaluator<'src, 'run> {
Self {
assignments: None,
Expand All @@ -318,6 +324,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
settings,
dotenv,
config,
unsets,
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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}"),
}
}
}
16 changes: 13 additions & 3 deletions src/justfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub(crate) struct Justfile<'src> {
pub(crate) name: Option<Name<'src>>,
pub(crate) recipes: Table<'src, Rc<Recipe<'src>>>,
pub(crate) settings: Settings<'src>,
pub(crate) unsets: HashSet<String>,
pub(crate) warnings: Vec<Warning>,
}

Expand Down Expand Up @@ -109,6 +110,7 @@ impl<'src> Justfile<'src> {
scope,
&self.settings,
search,
&self.unsets,
)
}

Expand Down Expand Up @@ -159,7 +161,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 {
Expand Down Expand Up @@ -280,6 +282,7 @@ impl<'src> Justfile<'src> {
config,
scope: invocation.scope,
search,
unsets: &self.unsets,
};

Self::run_recipe(
Expand Down Expand Up @@ -433,12 +436,19 @@ impl<'src> Justfile<'src> {
context.scope,
context.settings,
search,
context.unsets,
)?;

let scope = outer.child();

let mut evaluator =
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
let mut evaluator = Evaluator::recipe_evaluator(
context.config,
dotenv,
&scope,
context.settings,
search,
context.unsets,
);

if !context.config.no_dependencies {
for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) {
Expand Down
1 change: 1 addition & 0 deletions src/keyword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub(crate) enum Keyword {
Shell,
Tempdir,
True,
Unset,
WindowsPowershell,
WindowsShell,
X,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,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},
Expand Down
5 changes: 5 additions & 0 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
14 changes: 10 additions & 4 deletions src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,14 @@ impl<'src, D> Recipe<'src, D> {
);
}

let evaluator =
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
let evaluator = Evaluator::recipe_evaluator(
context.config,
dotenv,
&scope,
context.settings,
search,
context.unsets,
);

if self.shebang {
self.run_shebang(context, dotenv, &scope, positional, config, evaluator)
Expand Down Expand Up @@ -264,7 +270,7 @@ impl<'src, D> Recipe<'src, D> {
cmd.stdout(Stdio::null());
}

cmd.export(context.settings, dotenv, scope);
cmd.export(context.settings, dotenv, scope, context.unsets);

match InterruptHandler::guard(|| cmd.status()) {
Ok(exit_status) => {
Expand Down Expand Up @@ -411,7 +417,7 @@ impl<'src, D> Recipe<'src, D> {
command.args(positional);
}

command.export(context.settings, dotenv, scope);
command.export(context.settings, dotenv, scope, context.unsets);

// run it!
match InterruptHandler::guard(|| command.status()) {
Expand Down
1 change: 1 addition & 0 deletions src/recipe_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,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<String>,
}
37 changes: 37 additions & 0 deletions tests/export.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use super::*;

test! {
name: success,
justfile: r#"
Expand Down Expand Up @@ -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();
}
Loading

0 comments on commit e3137c8

Please sign in to comment.