Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for specifying name/path of .env #941

Merged
merged 13 commits into from
Aug 9, 2021
10 changes: 9 additions & 1 deletion completions/just.bash
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ _just() {

case "${cmd}" in
just)
opts=" -q -u -v -e -l -h -V -f -d -c -s --dry-run --highlight --no-dotenv --no-highlight --quiet --shell-command --clear-shell-args --unsorted --unstable --verbose --changelog --choose --dump --edit --evaluate --fmt --init --list --summary --variables --help --version --chooser --color --list-heading --list-prefix --justfile --set --shell --shell-arg --working-directory --command --completions --show <ARGUMENTS>... "
opts=" -q -u -v -e -l -h -V -f -d -c -s --dry-run --highlight --no-dotenv --no-highlight --quiet --shell-command --clear-shell-args --unsorted --unstable --verbose --changelog --choose --dump --edit --evaluate --fmt --init --list --summary --variables --help --version --chooser --color --list-heading --list-prefix --justfile --set --shell --shell-arg --working-directory --command --completions --show --dotenv-filename --dotenv-path <ARGUMENTS>... "
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -97,6 +97,14 @@ _just() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--dotenv-filename)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--dotenv-path)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
Expand Down
2 changes: 2 additions & 0 deletions completions/just.elvish
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ edit:completion:arg-completer[just] = [@words]{
cand --completions 'Print shell completion script for <SHELL>'
cand -s 'Show information about <RECIPE>'
cand --show 'Show information about <RECIPE>'
cand --dotenv-filename 'Search for environment file named <DOTENV-FILENAME> instead of `.env`'
cand --dotenv-path 'Load environment file at <DOTENV-PATH> instead of searching for one'
cand --dry-run 'Print what just would do without doing it'
cand --highlight 'Highlight echoed recipe lines in bold'
cand --no-dotenv 'Don''t load `.env` file'
Expand Down
2 changes: 2 additions & 0 deletions completions/just.fish
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ complete -c just -n "__fish_use_subcommand" -s d -l working-directory -d 'Use <W
complete -c just -n "__fish_use_subcommand" -s c -l command -d 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set'
complete -c just -n "__fish_use_subcommand" -l completions -d 'Print shell completion script for <SHELL>' -r -f -a "zsh bash fish powershell elvish"
complete -c just -n "__fish_use_subcommand" -s s -l show -d 'Show information about <RECIPE>'
complete -c just -n "__fish_use_subcommand" -l dotenv-filename -d 'Search for environment file named <DOTENV-FILENAME> instead of `.env`'
complete -c just -n "__fish_use_subcommand" -l dotenv-path -d 'Load environment file at <DOTENV-PATH> instead of searching for one'
complete -c just -n "__fish_use_subcommand" -l dry-run -d 'Print what just would do without doing it'
complete -c just -n "__fish_use_subcommand" -l highlight -d 'Highlight echoed recipe lines in bold'
complete -c just -n "__fish_use_subcommand" -l no-dotenv -d 'Don\'t load `.env` file'
Expand Down
2 changes: 2 additions & 0 deletions completions/just.powershell
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
[CompletionResult]::new('--completions', 'completions', [CompletionResultType]::ParameterName, 'Print shell completion script for <SHELL>')
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Show information about <RECIPE>')
[CompletionResult]::new('--show', 'show', [CompletionResultType]::ParameterName, 'Show information about <RECIPE>')
[CompletionResult]::new('--dotenv-filename', 'dotenv-filename', [CompletionResultType]::ParameterName, 'Search for environment file named <DOTENV-FILENAME> instead of `.env`')
[CompletionResult]::new('--dotenv-path', 'dotenv-path', [CompletionResultType]::ParameterName, 'Load environment file at <DOTENV-PATH> instead of searching for one')
[CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'Print what just would do without doing it')
[CompletionResult]::new('--highlight', 'highlight', [CompletionResultType]::ParameterName, 'Highlight echoed recipe lines in bold')
[CompletionResult]::new('--no-dotenv', 'no-dotenv', [CompletionResultType]::ParameterName, 'Don''t load `.env` file')
Expand Down
2 changes: 2 additions & 0 deletions completions/just.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ _just() {
'--completions=[Print shell completion script for <SHELL>]: :(zsh bash fish powershell elvish)' \
'-s+[Show information about <RECIPE>]: :_just_commands' \
'--show=[Show information about <RECIPE>]: :_just_commands' \
'(--dotenv-path)--dotenv-filename=[Search for environment file named <DOTENV-FILENAME> instead of `.env`]' \
'--dotenv-path=[Load environment file at <DOTENV-PATH> instead of searching for one]' \
'(-q --quiet)--dry-run[Print what just would do without doing it]' \
'--highlight[Highlight echoed recipe lines in bold]' \
'--no-dotenv[Don'\''t load `.env` file]' \
Expand Down
30 changes: 30 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub(crate) struct Config {
pub(crate) subcommand: Subcommand,
pub(crate) unsorted: bool,
pub(crate) unstable: bool,
pub(crate) dotenv_filename: Option<String>,
pub(crate) dotenv_path: Option<PathBuf>,
pub(crate) verbosity: Verbosity,
}

Expand Down Expand Up @@ -96,6 +98,8 @@ mod arg {
pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND";
pub(crate) const UNSORTED: &str = "UNSORTED";
pub(crate) const UNSTABLE: &str = "UNSTABLE";
pub(crate) const DOTENV_FILENAME: &str = "DOTENV_FILENAME";
pub(crate) const DOTENV_PATH: &str = "DOTENV_PATH";
pub(crate) const VERBOSE: &str = "VERBOSE";
pub(crate) const WORKING_DIRECTORY: &str = "WORKING-DIRECTORY";

Expand Down Expand Up @@ -317,6 +321,19 @@ impl Config {
.long("variables")
.help("List names of variables"),
)
.arg(
Arg::with_name(arg::DOTENV_FILENAME)
.long("dotenv-filename")
.takes_value(true)
.help("Search for environment file named <DOTENV-FILENAME> instead of `.env`")
.conflicts_with(arg::DOTENV_PATH),
)
.arg(
Arg::with_name(arg::DOTENV_PATH)
.long("dotenv-path")
.help("Load environment file at <DOTENV-PATH> instead of searching for one")
.takes_value(true),
)
.group(ArgGroup::with_name("SUBCOMMAND").args(cmd::ALL))
.arg(
Arg::with_name(arg::ARGUMENTS)
Expand Down Expand Up @@ -537,6 +554,8 @@ impl Config {
shell_args,
shell_present,
subcommand,
dotenv_filename: matches.value_of(arg::DOTENV_FILENAME).map(str::to_owned),
dotenv_path: matches.value_of(arg::DOTENV_PATH).map(PathBuf::from),
verbosity,
})
}
Expand Down Expand Up @@ -616,6 +635,12 @@ OPTIONS:
--completions <SHELL>
Print shell completion script for <SHELL> [possible values: zsh,
bash, fish, powershell, elvish]
--dotenv-filename <DOTENV_FILENAME>
Search for environment file named <DOTENV-FILENAME> instead of
`.env`
--dotenv-path <DOTENV_PATH>
Load environment file at <DOTENV-PATH> instead of searching for one

-f, --justfile <JUSTFILE> Use <JUSTFILE> as justfile
--list-heading <TEXT> Print <TEXT> before list
--list-prefix <TEXT>
Expand Down Expand Up @@ -883,6 +908,11 @@ ARGS:
verbosity: Verbosity::Quiet,
}

error! {
name: dotenv_both_filename_and_path,
args: ["--dotenv-filename", "foo", "--dotenv-path", "bar"],
}

test! {
name: set_default,
args: [],
Expand Down
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ impl<'src> ColorDisplay for Error<'src> {
)?;
},
Dotenv { dotenv_error } => {
write!(f, "Failed to load .env: {}", dotenv_error)?;
write!(f, "Failed to load environment file: {}", dotenv_error)?;
},
EditorInvoke { editor, io_error } => {
write!(
Expand Down
75 changes: 48 additions & 27 deletions src/load_dotenv.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,66 @@
use crate::common::*;

const DEFAULT_DOTENV_FILENAME: &str = ".env";

pub(crate) fn load_dotenv(
config: &Config,
settings: &Settings,
working_directory: &Path,
) -> 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.unwrap_or(true) {
Celeo marked this conversation as resolved.
Show resolved Hide resolved
return Ok(BTreeMap::new());
}

for directory in working_directory.ancestors() {
let path = directory.join(".env");
if let Some(path) = &config.dotenv_path {
return load_from_file(config, settings, &path);
}

let filename = config
.dotenv_filename
.as_deref()
.unwrap_or(DEFAULT_DOTENV_FILENAME)
.to_owned();

for directory in working_directory.ancestors() {
let path = directory.join(&filename);
if path.is_file() {
if settings.dotenv_load.is_none()
&& config.verbosity.loud()
&& !std::env::var_os("JUST_SUPPRESS_DOTENV_LOAD_WARNING")
.map(|val| val.as_os_str().to_str() == Some("1"))
.unwrap_or(false)
{
eprintln!(
"{}",
Warning::DotenvLoad.color_display(config.color.stderr())
);
}

let iter = dotenv::from_path_iter(&path)?;
let mut dotenv = BTreeMap::new();
for result in iter {
let (key, value) = result?;
if env::var_os(&key).is_none() {
dotenv.insert(key, value);
}
}
return Ok(dotenv);
return load_from_file(config, settings, &path);
}
}

Ok(BTreeMap::new())
}

fn load_from_file(
Celeo marked this conversation as resolved.
Show resolved Hide resolved
config: &Config,
settings: &Settings,
path: &Path,
) -> 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 config.verbosity.loud()
&& settings.dotenv_load.is_none()
&& config.dotenv_filename.is_none()
&& config.dotenv_path.is_none()
&& !std::env::var_os("JUST_SUPPRESS_DOTENV_LOAD_WARNING")
.map(|val| val.as_os_str().to_str() == Some("1"))
.unwrap_or(false)
{
eprintln!(
"{}",
Warning::DotenvLoad.color_display(config.color.stderr())
);
}

let iter = dotenv::from_path_iter(&path)?;
let mut dotenv = BTreeMap::new();
for result in iter {
let (key, value) = result?;
if env::var_os(&key).is_none() {
dotenv.insert(key, value);
}
}
Ok(dotenv)
}
60 changes: 60 additions & 0 deletions tests/dotenv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,63 @@ echo $DOTENV_KEY
.suppress_dotenv_load_warning(false)
.run();
}

#[test]
fn path_not_found() {
Test::new()
.justfile(
"
foo:
echo $NAME
",
)
.args(&["--dotenv-path", ".env.prod"])
.stderr(if cfg!(windows) {
"error: Failed to load environment file: The system cannot find the file specified. (os \
error 2)\n"
} else {
"error: Failed to load environment file: No such file or directory (os error 2)\n"
})
.status(EXIT_FAILURE)
.run();
}

#[test]
fn path_resolves() {
Test::new()
.justfile(
"
foo:
#!/bin/bash
echo $NAME
",
)
.tree(tree! {
subdir: {
".env": "NAME=bar"
}
})
.args(&["--dotenv-path", "subdir/.env"])
.stdout("bar\n")
.status(EXIT_SUCCESS)
.run();
}

#[test]
fn filename_resolves() {
Test::new()
.justfile(
"
foo:
#!/bin/bash
echo $NAME
",
)
.tree(tree! {
".env.special": "NAME=bar"
})
.args(&["--dotenv-filename", ".env.special"])
.stdout("bar\n")
.status(EXIT_SUCCESS)
.run();
}