Add dotenv-load
setting (#778)
The `dotenv-load` setting controls whether or not a `.env` file will be loaded if present. It currently defaults to true.
This commit is contained in:
parent
2e8c58e1cd
commit
18b9799e8d
@ -55,9 +55,12 @@ assignment : NAME ':=' expression eol
|
||||
|
||||
export : 'export' assignment
|
||||
|
||||
setting : 'set' 'export'
|
||||
setting : 'set' 'dotenv-load' boolean?
|
||||
| 'set' 'export' boolean?
|
||||
| 'set' 'shell' ':=' '[' string (',' string)* ','? ']'
|
||||
|
||||
boolean : ':=' ('true' | 'false')
|
||||
|
||||
expression : 'if' condition '{' expression '}' else '{' expression '}'
|
||||
| value '+' expression
|
||||
| value
|
||||
|
47
README.adoc
47
README.adoc
@ -388,29 +388,30 @@ foo:
|
||||
[options="header"]
|
||||
|=================
|
||||
| Name | Value | Description
|
||||
| `export` | | Export all variables as environment variables
|
||||
| `dotenv-load` | `true` or `false` | Load a `.env` file, if present.
|
||||
| `export` | `true` or `false` | Export all variables as environment variables.
|
||||
|`shell` | `[COMMAND, ARGS...]` | Set the command used to invoke recipes and evaluate backticks.
|
||||
|=================
|
||||
|
||||
==== Shell
|
||||
Boolean settings can be written as:
|
||||
|
||||
The `shell` setting controls the command used to invoke recipe lines and backticks. Shebang recipes are unaffected.
|
||||
|
||||
```make
|
||||
# use python3 to execute recipe lines and backticks
|
||||
set shell := ["python3", "-c"]
|
||||
|
||||
# use print to capture result of evaluation
|
||||
foos := `print("foo" * 4)`
|
||||
|
||||
foo:
|
||||
print("Snake snake snake snake.")
|
||||
print("{{foos}}")
|
||||
```
|
||||
set NAME
|
||||
```
|
||||
|
||||
Which is equivalent to:
|
||||
|
||||
```
|
||||
set NAME := true
|
||||
```
|
||||
|
||||
==== Dotenv Load
|
||||
|
||||
If `dotenv-load` is `true`, a `.env` file will be loaded if present. Defaults to `true`.
|
||||
|
||||
==== Export
|
||||
|
||||
The `export` setting causes all Just variables to be exported as environment variables.
|
||||
The `export` setting causes all Just variables to be exported as environment variables. Defaults to `false`.
|
||||
|
||||
```make
|
||||
set export
|
||||
@ -428,6 +429,22 @@ hello
|
||||
goodbye
|
||||
```
|
||||
|
||||
==== Shell
|
||||
|
||||
The `shell` setting controls the command used to invoke recipe lines and backticks. Shebang recipes are unaffected.
|
||||
|
||||
```make
|
||||
# use python3 to execute recipe lines and backticks
|
||||
set shell := ["python3", "-c"]
|
||||
|
||||
# use print to capture result of evaluation
|
||||
foos := `print("foo" * 4)`
|
||||
|
||||
foo:
|
||||
print("Snake snake snake snake.")
|
||||
print("{{foos}}")
|
||||
```
|
||||
|
||||
=== Documentation Comments
|
||||
|
||||
Comments immediately preceding a recipe will appear in `just --list`:
|
||||
|
@ -65,13 +65,16 @@ impl<'src> Analyzer<'src> {
|
||||
|
||||
for (_, set) in self.sets {
|
||||
match set.value {
|
||||
Setting::DotenvLoad(dotenv_load) => {
|
||||
settings.dotenv_load = dotenv_load;
|
||||
},
|
||||
Setting::Export(export) => {
|
||||
settings.export = export;
|
||||
},
|
||||
Setting::Shell(shell) => {
|
||||
assert!(settings.shell.is_none());
|
||||
settings.shell = Some(shell);
|
||||
},
|
||||
Setting::Export => {
|
||||
settings.export = true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ impl Display for CompilationError<'_> {
|
||||
|
||||
write!(f, "{}", message.prefix())?;
|
||||
|
||||
match self.kind {
|
||||
match &self.kind {
|
||||
AliasShadowsRecipe { alias, recipe_line } => {
|
||||
writeln!(
|
||||
f,
|
||||
@ -116,22 +116,23 @@ impl Display for CompilationError<'_> {
|
||||
"Dependency `{}` got {} {} but takes ",
|
||||
dependency,
|
||||
found,
|
||||
Count("argument", found),
|
||||
Count("argument", *found),
|
||||
)?;
|
||||
|
||||
if min == max {
|
||||
let expected = min;
|
||||
writeln!(f, "{} {}", expected, Count("argument", expected))?;
|
||||
writeln!(f, "{} {}", expected, Count("argument", *expected))?;
|
||||
} else if found < min {
|
||||
writeln!(f, "at least {} {}", min, Count("argument", min))?;
|
||||
writeln!(f, "at least {} {}", min, Count("argument", *min))?;
|
||||
} else {
|
||||
writeln!(f, "at most {} {}", max, Count("argument", max))?;
|
||||
writeln!(f, "at most {} {}", max, Count("argument", *max))?;
|
||||
}
|
||||
},
|
||||
ExpectedKeyword { expected, found } => writeln!(
|
||||
f,
|
||||
"Expected keyword `{}` but found identifier `{}`",
|
||||
expected, found
|
||||
"Expected keyword {} but found identifier `{}`",
|
||||
List::or_ticked(expected),
|
||||
found
|
||||
)?,
|
||||
ParameterShadowsVariable { parameter } => {
|
||||
writeln!(
|
||||
@ -171,7 +172,7 @@ impl Display for CompilationError<'_> {
|
||||
"Function `{}` called with {} {} but takes {}",
|
||||
function,
|
||||
found,
|
||||
Count("argument", found),
|
||||
Count("argument", *found),
|
||||
expected
|
||||
)?;
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ pub(crate) enum CompilationErrorKind<'src> {
|
||||
first: usize,
|
||||
},
|
||||
ExpectedKeyword {
|
||||
expected: Keyword,
|
||||
expected: Vec<Keyword>,
|
||||
found: &'src str,
|
||||
},
|
||||
ExtraLeadingWhitespace,
|
||||
|
@ -91,7 +91,7 @@ impl<'src> Justfile<'src> {
|
||||
}
|
||||
|
||||
let dotenv = if config.load_dotenv {
|
||||
load_dotenv(&search.working_directory)?
|
||||
load_dotenv(&search.working_directory, &self.settings)?
|
||||
} else {
|
||||
BTreeMap::new()
|
||||
};
|
||||
|
@ -6,6 +6,9 @@ pub(crate) enum Keyword {
|
||||
Alias,
|
||||
Else,
|
||||
Export,
|
||||
DotenvLoad,
|
||||
True,
|
||||
False,
|
||||
If,
|
||||
Set,
|
||||
Shell,
|
||||
|
@ -2,10 +2,16 @@ use crate::common::*;
|
||||
|
||||
pub(crate) fn load_dotenv(
|
||||
working_directory: &Path,
|
||||
settings: &Settings,
|
||||
) -> RunResult<'static, BTreeMap<String, String>> {
|
||||
// `dotenv::from_path_iter` should eventually be un-deprecated, see:
|
||||
// https://github.com/dotenv-rs/dotenv/issues/13
|
||||
#![allow(deprecated)]
|
||||
|
||||
if !settings.dotenv_load {
|
||||
return Ok(BTreeMap::new());
|
||||
}
|
||||
|
||||
for directory in working_directory.ancestors() {
|
||||
let path = directory.join(".env");
|
||||
|
||||
|
@ -187,17 +187,17 @@ impl<'src> Node<'src> for Set<'src> {
|
||||
fn tree(&self) -> Tree<'src> {
|
||||
let mut set = Tree::atom(Keyword::Set.lexeme());
|
||||
|
||||
set.push_mut(self.name.lexeme());
|
||||
set.push_mut(self.name.lexeme().replace('-', "_"));
|
||||
|
||||
use Setting::*;
|
||||
match &self.value {
|
||||
DotenvLoad(value) | Export(value) => set.push_mut(value.to_string()),
|
||||
Shell(setting::Shell { command, arguments }) => {
|
||||
set.push_mut(Tree::string(&command.cooked));
|
||||
for argument in arguments {
|
||||
set.push_mut(Tree::string(&argument.cooked));
|
||||
}
|
||||
},
|
||||
Export => {},
|
||||
}
|
||||
|
||||
set
|
||||
|
@ -179,7 +179,10 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
if expected == found {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(identifier.error(CompilationErrorKind::ExpectedKeyword { expected, found }))
|
||||
Err(identifier.error(CompilationErrorKind::ExpectedKeyword {
|
||||
expected: vec![expected],
|
||||
found,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,6 +350,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
Some(Keyword::Set) =>
|
||||
if self.next_are(&[Identifier, Identifier, ColonEquals])
|
||||
|| self.next_are(&[Identifier, Identifier, Eol])
|
||||
|| self.next_are(&[Identifier, Identifier, Eof])
|
||||
{
|
||||
items.push(Item::Set(self.parse_set()?));
|
||||
} else {
|
||||
@ -678,19 +682,50 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
/// Parse a boolean setting value
|
||||
fn parse_set_bool(&mut self) -> CompilationResult<'src, bool> {
|
||||
if !self.accepted(ColonEquals)? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let identifier = self.expect(Identifier)?;
|
||||
|
||||
let value = if Keyword::True == identifier.lexeme() {
|
||||
true
|
||||
} else if Keyword::False == identifier.lexeme() {
|
||||
false
|
||||
} else {
|
||||
return Err(identifier.error(CompilationErrorKind::ExpectedKeyword {
|
||||
expected: vec![Keyword::True, Keyword::False],
|
||||
found: identifier.lexeme(),
|
||||
}));
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Parse a setting
|
||||
fn parse_set(&mut self) -> CompilationResult<'src, Set<'src>> {
|
||||
self.presume_keyword(Keyword::Set)?;
|
||||
let name = Name::from_identifier(self.presume(Identifier)?);
|
||||
let lexeme = name.lexeme();
|
||||
|
||||
if name.lexeme() == Keyword::Export.lexeme() {
|
||||
if Keyword::DotenvLoad == lexeme {
|
||||
let value = self.parse_set_bool()?;
|
||||
return Ok(Set {
|
||||
value: Setting::Export,
|
||||
value: Setting::DotenvLoad(value),
|
||||
name,
|
||||
});
|
||||
} else if Keyword::Export == lexeme {
|
||||
let value = self.parse_set_bool()?;
|
||||
return Ok(Set {
|
||||
value: Setting::Export(value),
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
self.presume(ColonEquals)?;
|
||||
self.expect(ColonEquals)?;
|
||||
|
||||
if name.lexeme() == Keyword::Shell.lexeme() {
|
||||
self.expect(BracketL)?;
|
||||
|
||||
@ -1541,6 +1576,42 @@ mod tests {
|
||||
tree: (justfile (recipe a (body ("foo"))) (recipe b)),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_export_implicit,
|
||||
text: "set export",
|
||||
tree: (justfile (set export true)),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_export_true,
|
||||
text: "set export := true",
|
||||
tree: (justfile (set export true)),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_export_false,
|
||||
text: "set export := false",
|
||||
tree: (justfile (set export false)),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_dotenv_load_implicit,
|
||||
text: "set dotenv-load",
|
||||
tree: (justfile (set dotenv_load true)),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_dotenv_load_true,
|
||||
text: "set dotenv-load := true",
|
||||
tree: (justfile (set dotenv_load true)),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_dotenv_load_false,
|
||||
text: "set dotenv-load := false",
|
||||
tree: (justfile (set dotenv_load false)),
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_shell_no_arguments,
|
||||
text: "set shell := ['tclsh']",
|
||||
|
@ -3,7 +3,8 @@ use crate::common::*;
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Setting<'src> {
|
||||
Shell(Shell<'src>),
|
||||
Export,
|
||||
Export(bool),
|
||||
DotenvLoad(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -2,15 +2,17 @@ use crate::common::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct Settings<'src> {
|
||||
pub(crate) shell: Option<setting::Shell<'src>>,
|
||||
pub(crate) dotenv_load: bool,
|
||||
pub(crate) export: bool,
|
||||
pub(crate) shell: Option<setting::Shell<'src>>,
|
||||
}
|
||||
|
||||
impl<'src> Settings<'src> {
|
||||
pub(crate) fn new() -> Settings<'src> {
|
||||
Settings {
|
||||
shell: None,
|
||||
dotenv_load: true,
|
||||
export: false,
|
||||
shell: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ impl<'src> Token<'src> {
|
||||
space_column,
|
||||
color.prefix(),
|
||||
"",
|
||||
space_width,
|
||||
space_width.max(1),
|
||||
color.suffix()
|
||||
)?;
|
||||
},
|
||||
|
@ -3,8 +3,8 @@ use crate::common::*;
|
||||
use std::mem;
|
||||
|
||||
/// Construct a `Tree` from a symbolic expression literal. This macro, and the
|
||||
/// Tree type, are only used in the Parser unit tests, as a concise notation
|
||||
/// representing the expected results of parsing a given string.
|
||||
/// Tree type, are only used in the Parser unit tests, providing a concise
|
||||
/// notation for representing the expected results of parsing a given string.
|
||||
macro_rules! tree {
|
||||
{
|
||||
($($child:tt)*)
|
||||
|
@ -26,3 +26,39 @@ fn dotenv() {
|
||||
let stdout = str::from_utf8(&output.stdout).unwrap();
|
||||
assert_eq!(stdout, "KEY=SUB\n");
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_false,
|
||||
justfile: r#"
|
||||
set dotenv-load := false
|
||||
|
||||
foo:
|
||||
if [ -n "${DOTENV_KEY+1}" ]; then echo defined; else echo undefined; fi
|
||||
"#,
|
||||
stdout: "undefined\n",
|
||||
stderr: "if [ -n \"${DOTENV_KEY+1}\" ]; then echo defined; else echo undefined; fi\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_implicit,
|
||||
justfile: r#"
|
||||
set dotenv-load
|
||||
|
||||
foo:
|
||||
echo $DOTENV_KEY
|
||||
"#,
|
||||
stdout: "dotenv-value\n",
|
||||
stderr: "echo $DOTENV_KEY\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: set_true,
|
||||
justfile: r#"
|
||||
set dotenv-load := true
|
||||
|
||||
foo:
|
||||
echo $DOTENV_KEY
|
||||
"#,
|
||||
stdout: "dotenv-value\n",
|
||||
stderr: "echo $DOTENV_KEY\n",
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ recipe:
|
||||
}
|
||||
|
||||
test! {
|
||||
name: setting,
|
||||
name: setting_implicit,
|
||||
justfile: "
|
||||
set export
|
||||
|
||||
@ -97,6 +97,37 @@ test! {
|
||||
stderr: "echo $A\necho $B\necho $C\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: setting_true,
|
||||
justfile: "
|
||||
set export := true
|
||||
|
||||
A := 'hello'
|
||||
|
||||
foo B C=`echo $A`:
|
||||
echo $A
|
||||
echo $B
|
||||
echo $C
|
||||
",
|
||||
args: ("foo", "goodbye"),
|
||||
stdout: "hello\ngoodbye\nhello\n",
|
||||
stderr: "echo $A\necho $B\necho $C\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: setting_false,
|
||||
justfile: r#"
|
||||
set export := false
|
||||
|
||||
A := 'hello'
|
||||
|
||||
foo:
|
||||
if [ -n "${A+1}" ]; then echo defined; else echo undefined; fi
|
||||
"#,
|
||||
stdout: "undefined\n",
|
||||
stderr: "if [ -n \"${A+1}\" ]; then echo defined; else echo undefined; fi\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: setting_shebang,
|
||||
justfile: "
|
||||
|
@ -65,6 +65,20 @@ test! {
|
||||
stderr: "echo bar\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: bad_setting,
|
||||
justfile: "
|
||||
set foo
|
||||
",
|
||||
stderr: "
|
||||
error: Expected ':=', but found end of line
|
||||
|
|
||||
1 | set foo
|
||||
| ^
|
||||
",
|
||||
status: EXIT_FAILURE,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: alias_with_dependencies,
|
||||
justfile: "foo:\n echo foo\nbar: foo\nalias b := bar",
|
||||
@ -2214,82 +2228,6 @@ test! {
|
||||
",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: shell_args,
|
||||
justfile: "
|
||||
default:
|
||||
echo A${foo}A
|
||||
",
|
||||
args: ("--shell-arg", "-c"),
|
||||
stdout: "AA\n",
|
||||
stderr: "echo A${foo}A\n",
|
||||
shell: false,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: shell_override,
|
||||
justfile: "
|
||||
set shell := ['foo-bar-baz']
|
||||
|
||||
default:
|
||||
echo hello
|
||||
",
|
||||
args: ("--shell", "bash"),
|
||||
stdout: "hello\n",
|
||||
stderr: "echo hello\n",
|
||||
shell: false,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: shell_arg_override,
|
||||
justfile: "
|
||||
set shell := ['foo-bar-baz']
|
||||
|
||||
default:
|
||||
echo hello
|
||||
",
|
||||
args: ("--shell-arg", "-cu"),
|
||||
stdout: "hello\n",
|
||||
stderr: "echo hello\n",
|
||||
shell: false,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
test! {
|
||||
name: set_shell,
|
||||
justfile: "
|
||||
set shell := ['echo', '-n']
|
||||
|
||||
x := `bar`
|
||||
|
||||
foo:
|
||||
echo {{x}}
|
||||
echo foo
|
||||
",
|
||||
args: (),
|
||||
stdout: "echo barecho foo",
|
||||
stderr: "echo bar\necho foo\n",
|
||||
shell: false,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
test! {
|
||||
name: set_shell,
|
||||
justfile: "
|
||||
set shell := ['echo', '-n']
|
||||
|
||||
x := `bar`
|
||||
|
||||
foo:
|
||||
echo {{x}}
|
||||
echo foo
|
||||
",
|
||||
args: (),
|
||||
stdout: "-n echo -n bar\r\r\n-n echo foo\r\n",
|
||||
stderr: "echo -n bar\r\necho foo\n",
|
||||
shell: false,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: dependency_argument_string,
|
||||
justfile: "
|
||||
|
@ -98,3 +98,79 @@ fn powershell() {
|
||||
|
||||
assert_stdout(&output, stdout);
|
||||
}
|
||||
|
||||
test! {
|
||||
name: shell_args,
|
||||
justfile: "
|
||||
default:
|
||||
echo A${foo}A
|
||||
",
|
||||
args: ("--shell-arg", "-c"),
|
||||
stdout: "AA\n",
|
||||
stderr: "echo A${foo}A\n",
|
||||
shell: false,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: shell_override,
|
||||
justfile: "
|
||||
set shell := ['foo-bar-baz']
|
||||
|
||||
default:
|
||||
echo hello
|
||||
",
|
||||
args: ("--shell", "bash"),
|
||||
stdout: "hello\n",
|
||||
stderr: "echo hello\n",
|
||||
shell: false,
|
||||
}
|
||||
|
||||
test! {
|
||||
name: shell_arg_override,
|
||||
justfile: "
|
||||
set shell := ['foo-bar-baz']
|
||||
|
||||
default:
|
||||
echo hello
|
||||
",
|
||||
args: ("--shell-arg", "-cu"),
|
||||
stdout: "hello\n",
|
||||
stderr: "echo hello\n",
|
||||
shell: false,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
test! {
|
||||
name: set_shell,
|
||||
justfile: "
|
||||
set shell := ['echo', '-n']
|
||||
|
||||
x := `bar`
|
||||
|
||||
foo:
|
||||
echo {{x}}
|
||||
echo foo
|
||||
",
|
||||
args: (),
|
||||
stdout: "echo barecho foo",
|
||||
stderr: "echo bar\necho foo\n",
|
||||
shell: false,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
test! {
|
||||
name: set_shell,
|
||||
justfile: "
|
||||
set shell := ['echo', '-n']
|
||||
|
||||
x := `bar`
|
||||
|
||||
foo:
|
||||
echo {{x}}
|
||||
echo foo
|
||||
",
|
||||
args: (),
|
||||
stdout: "-n echo -n bar\r\r\n-n echo foo\r\n",
|
||||
stderr: "echo -n bar\r\necho foo\n",
|
||||
shell: false,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user