Add flags for specifying name and path environment file (#941)

This commit is contained in:
Matt Boulanger 2021-08-08 22:37:35 -07:00 committed by GitHub
parent f5689617f4
commit e72e7dd569
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 200 additions and 30 deletions

View File

@ -20,7 +20,7 @@ _just() {
case "${cmd}" in case "${cmd}" in
just) 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 if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -97,6 +97,14 @@ _just() {
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
;; ;;
--dotenv-filename)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--dotenv-path)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*) *)
COMPREPLY=() COMPREPLY=()
;; ;;

View File

@ -30,6 +30,8 @@ edit:completion:arg-completer[just] = [@words]{
cand --completions 'Print shell completion script for <SHELL>' cand --completions 'Print shell completion script for <SHELL>'
cand -s 'Show information about <RECIPE>' cand -s 'Show information about <RECIPE>'
cand --show '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 --dry-run 'Print what just would do without doing it'
cand --highlight 'Highlight echoed recipe lines in bold' cand --highlight 'Highlight echoed recipe lines in bold'
cand --no-dotenv 'Don''t load `.env` file' cand --no-dotenv 'Don''t load `.env` file'

View File

@ -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" -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" -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" -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 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 highlight -d 'Highlight echoed recipe lines in bold'
complete -c just -n "__fish_use_subcommand" -l no-dotenv -d 'Don\'t load `.env` file' complete -c just -n "__fish_use_subcommand" -l no-dotenv -d 'Don\'t load `.env` file'

View File

@ -35,6 +35,8 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
[CompletionResult]::new('--completions', 'completions', [CompletionResultType]::ParameterName, 'Print shell completion script for <SHELL>') [CompletionResult]::new('--completions', 'completions', [CompletionResultType]::ParameterName, 'Print shell completion script for <SHELL>')
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Show information about <RECIPE>') [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Show information about <RECIPE>')
[CompletionResult]::new('--show', 'show', [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('--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('--highlight', 'highlight', [CompletionResultType]::ParameterName, 'Highlight echoed recipe lines in bold')
[CompletionResult]::new('--no-dotenv', 'no-dotenv', [CompletionResultType]::ParameterName, 'Don''t load `.env` file') [CompletionResult]::new('--no-dotenv', 'no-dotenv', [CompletionResultType]::ParameterName, 'Don''t load `.env` file')

View File

@ -31,6 +31,8 @@ _just() {
'--completions=[Print shell completion script for <SHELL>]: :(zsh bash fish powershell elvish)' \ '--completions=[Print shell completion script for <SHELL>]: :(zsh bash fish powershell elvish)' \
'-s+[Show information about <RECIPE>]: :_just_commands' \ '-s+[Show information about <RECIPE>]: :_just_commands' \
'--show=[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]' \ '(-q --quiet)--dry-run[Print what just would do without doing it]' \
'--highlight[Highlight echoed recipe lines in bold]' \ '--highlight[Highlight echoed recipe lines in bold]' \
'--no-dotenv[Don'\''t load `.env` file]' \ '--no-dotenv[Don'\''t load `.env` file]' \

View File

@ -29,6 +29,8 @@ pub(crate) struct Config {
pub(crate) subcommand: Subcommand, pub(crate) subcommand: Subcommand,
pub(crate) unsorted: bool, pub(crate) unsorted: bool,
pub(crate) unstable: bool, pub(crate) unstable: bool,
pub(crate) dotenv_filename: Option<String>,
pub(crate) dotenv_path: Option<PathBuf>,
pub(crate) verbosity: Verbosity, pub(crate) verbosity: Verbosity,
} }
@ -96,6 +98,8 @@ mod arg {
pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND"; pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND";
pub(crate) const UNSORTED: &str = "UNSORTED"; pub(crate) const UNSORTED: &str = "UNSORTED";
pub(crate) const UNSTABLE: &str = "UNSTABLE"; 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 VERBOSE: &str = "VERBOSE";
pub(crate) const WORKING_DIRECTORY: &str = "WORKING-DIRECTORY"; pub(crate) const WORKING_DIRECTORY: &str = "WORKING-DIRECTORY";
@ -317,6 +321,19 @@ impl Config {
.long("variables") .long("variables")
.help("List names of 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)) .group(ArgGroup::with_name("SUBCOMMAND").args(cmd::ALL))
.arg( .arg(
Arg::with_name(arg::ARGUMENTS) Arg::with_name(arg::ARGUMENTS)
@ -537,6 +554,8 @@ impl Config {
shell_args, shell_args,
shell_present, shell_present,
subcommand, 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, verbosity,
}) })
} }
@ -616,6 +635,12 @@ OPTIONS:
--completions <SHELL> --completions <SHELL>
Print shell completion script for <SHELL> [possible values: zsh, Print shell completion script for <SHELL> [possible values: zsh,
bash, fish, powershell, elvish] 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 -f, --justfile <JUSTFILE> Use <JUSTFILE> as justfile
--list-heading <TEXT> Print <TEXT> before list --list-heading <TEXT> Print <TEXT> before list
--list-prefix <TEXT> --list-prefix <TEXT>
@ -883,6 +908,11 @@ ARGS:
verbosity: Verbosity::Quiet, verbosity: Verbosity::Quiet,
} }
error! {
name: dotenv_both_filename_and_path,
args: ["--dotenv-filename", "foo", "--dotenv-path", "bar"],
}
test! { test! {
name: set_default, name: set_default,
args: [], args: [],

View File

@ -426,7 +426,7 @@ impl<'src> ColorDisplay for Error<'src> {
)?; )?;
}, },
Dotenv { dotenv_error } => { Dotenv { dotenv_error } => {
write!(f, "Failed to load .env: {}", dotenv_error)?; write!(f, "Failed to load environment file: {}", dotenv_error)?;
}, },
EditorInvoke { editor, io_error } => { EditorInvoke { editor, io_error } => {
write!( write!(

View File

@ -1,45 +1,69 @@
use crate::common::*; use crate::common::*;
const DEFAULT_DOTENV_FILENAME: &str = ".env";
pub(crate) fn load_dotenv( pub(crate) fn load_dotenv(
config: &Config, config: &Config,
settings: &Settings, settings: &Settings,
working_directory: &Path, working_directory: &Path,
) -> RunResult<'static, BTreeMap<String, String>> { ) -> RunResult<'static, BTreeMap<String, String>> {
// `dotenv::from_path_iter` should eventually be un-deprecated, see: if !settings.dotenv_load.unwrap_or(true)
// https://github.com/dotenv-rs/dotenv/issues/13 && config.dotenv_filename.is_none()
#![allow(deprecated)] && config.dotenv_path.is_none()
{
if !settings.dotenv_load.unwrap_or(true) {
return Ok(BTreeMap::new()); return Ok(BTreeMap::new());
} }
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() { for directory in working_directory.ancestors() {
let path = directory.join(".env"); let path = directory.join(&filename);
if path.is_file() { if path.is_file() {
if settings.dotenv_load.is_none() return load_from_file(config, settings, &path);
&& 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);
} }
} }
Ok(BTreeMap::new()) Ok(BTreeMap::new())
} }
fn load_from_file(
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)
}

View File

@ -101,3 +101,103 @@ echo $DOTENV_KEY
.suppress_dotenv_load_warning(false) .suppress_dotenv_load_warning(false)
.run(); .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:
@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:
@echo $NAME
",
)
.tree(tree! {
".env.special": "NAME=bar"
})
.args(&["--dotenv-filename", ".env.special"])
.stdout("bar\n")
.status(EXIT_SUCCESS)
.run();
}
#[test]
fn filename_flag_overwrites_no_load() {
Test::new()
.justfile(
"
set dotenv-load := false
foo:
@echo $NAME
",
)
.tree(tree! {
".env.special": "NAME=bar"
})
.args(&["--dotenv-filename", ".env.special"])
.stdout("bar\n")
.status(EXIT_SUCCESS)
.run();
}
#[test]
fn path_flag_overwrites_no_load() {
Test::new()
.justfile(
"
set dotenv-load := false
foo:
@echo $NAME
",
)
.tree(tree! {
subdir: {
".env": "NAME=bar"
}
})
.args(&["--dotenv-path", "subdir/.env"])
.stdout("bar\n")
.status(EXIT_SUCCESS)
.run();
}